Removed UI folder

This commit is contained in:
Will Galebach 2024-01-08 16:21:19 +00:00
parent d4afda7189
commit 6ab2d13a47
21 changed files with 0 additions and 7223 deletions

View File

@ -1,27 +0,0 @@
/* eslint-env node */
module.exports = {
// root: true,
// env: { browser: true, es2020: true },
// extends: [
// 'eslint:recommended',
// 'plugin:@typescript-eslint/recommended',
// 'plugin:@typescript-eslint/recommended-requiring-type-checking',
// 'plugin:react-hooks/recommended',
// ],
// parser: '@typescript-eslint/parser',
// parserOptions: {
// ecmaVersion: 'latest',
// sourceType: 'module',
// project: true,
// tsconfigRootDir: __dirname,
// },
// plugins: ['react-refresh'],
// rules: {
// 'react-refresh/only-export-components': [
// 'warn',
// { allowConstantExport: true },
// ],
// '@typescript-eslint/no-non-null-assertion': 'off',
// },
}

View File

@ -1,26 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.env

View File

@ -1,3 +0,0 @@
# chess-ui
The UI for the Uqbar chess app. This UI is available at /chess:chess:uqbar/ on any Uqbar node.

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,45 +0,0 @@
{
"name": "api-maestro-ui",
"private": true,
"version": "0.1.0",
"scripts": {
"dev": "vite",
"start": "vite",
"build": "tsc && vite build",
"copy": "mkdir -p ../pkg/ui && rm -rf ../pkg/ui/* && cp -r dist/* ../pkg/ui/",
"build:copy": "npm run build && npm run copy",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"@react-oauth/google": "^0.11.0",
"@types/node-forge": "^1.3.5",
"@uqbar/client-encryptor-api": "^0.2.1",
"buffer": "^6.0.3",
"chess.js": "^1.0.0-beta.6",
"moment": "^2.29.4",
"node-forge": "^1.3.1",
"react": "^18.2.0",
"react-chessboard": "^4.2.1",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.1",
"zustand": "^4.3.9"
},
"devDependencies": {
"@types/node": "^20.4.2",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"@typescript-eslint/eslint-plugin": "^5.61.0",
"@typescript-eslint/parser": "^5.61.0",
"@vitejs/plugin-react": "^4.0.1",
"autoprefixer": "^10.4.14",
"eslint": "^8.44.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.1",
"postcss": "^8.4.25",
"tailwindcss": "^3.3.2",
"typescript": "^5.0.2",
"vite": "^4.4.0",
"vite-plugin-mkcert": "^1.16.0"
}
}

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -1,42 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
text-align: center;
width: 100%;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@ -1,239 +0,0 @@
import { FormEvent, useCallback, useEffect, useMemo, useState, MouseEvent } from 'react'
import { Chess } from "chess.js";
import { Chessboard } from "react-chessboard";
import UqbarEncryptorApi from '@uqbar/client-encryptor-api'
import useChessStore, { Game } from './store';
declare global {
var window: Window & typeof globalThis;
var our: { node: string, process: string };
}
import './App.css'
let inited = false
interface SelectedGame extends Game {
game: Chess
}
const isTurn = (game: Game, node: string) => (game.turns || 0) % 2 === 0 ? node === game.white : node === game.black
const BASE_URL = import.meta.env.BASE_URL;
if (window.our) window.our.process = BASE_URL?.replace("/", "");
const PROXY_TARGET = `${(import.meta.env.VITE_NODE_URL || "http://localhost:8080")}${BASE_URL}`;
// This env also has BASE_URL which should match the process + package name
const WEBSOCKET_URL = import.meta.env.DEV
? `${PROXY_TARGET.replace('http', 'ws')}`
: undefined;
function App() {
const { games, handleWsMessage, set } = useChessStore()
const [screen, setScreen] = useState('new')
const [newGame, setNewGame] = useState('')
const game: SelectedGame | undefined = useMemo(() => games[screen] ? ({ ...games[screen], game: new Chess(games[screen].board) }) : undefined, [games, screen])
const currentTurn = useMemo(() => (game?.turns || 0) % 2 === 0 ? `${game?.white} (white)` : `${game?.black} (black)`, [game])
useEffect(() => {
if (!inited) {
inited = true
new UqbarEncryptorApi({
uri: WEBSOCKET_URL,
nodeId: window.our.node,
processId: window.our.process,
onMessage: handleWsMessage
});
}
fetch(`${BASE_URL}/games`).then(res => res.json()).then((games) => {
set({ games })
}).catch(console.error)
}, []) // eslint-disable-line
const startNewGame = useCallback(async (e: FormEvent) => {
e.preventDefault()
try {
const createdGame = await fetch(`${BASE_URL}/games`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: newGame })
}).then(r => {
if (r.status === 409) {
if (games[newGame]) {
setScreen(newGame)
} else {
alert('Game already exists, please refresh the page and select it.')
}
throw new Error('Game already exists')
} else if (r.status === 503) {
alert(`${newGame} may be offline, please confirm it is online and try again.`)
throw new Error('Player offline')
} else if (r.status === 400) {
alert('Please enter a valid player ID')
throw new Error('Invalid player ID')
} else if (r.status > 399) {
alert('There was an error creating the game. Please try again.')
throw new Error('Error creating game')
}
return r.json()
})
const allGames = { ...games }
allGames[createdGame.id] = createdGame
set({ games: allGames })
setScreen(newGame)
setNewGame('')
} catch (err) {
console.error(err)
}
}, [games, newGame, setNewGame, set])
const onDrop = useCallback((sourceSquare: string, targetSquare: string) => {
if (!game || !isTurn(game, window.our.node)) return false
const move = {
from: sourceSquare,
to: targetSquare,
promotion: "q", // always promote to a queen for example simplicity
}
const gameCopy = { ...game };
const result = gameCopy.game.move(move);
if (result === null) {
return false;
}
gameCopy.board = gameCopy.game.fen()
const allGames = { ...games }
allGames[game.id] = gameCopy
set({ games: allGames })
fetch(`${BASE_URL}/games`, {
method: 'PUT',
body: JSON.stringify({ id: game.id, move: sourceSquare + targetSquare })
}).then(r => r.json())
.then((updatedGame) => {
const allGames = { ...games }
allGames[game.id] = updatedGame
set({ games: allGames })
})
.catch((err) => {
console.error(err)
alert('There was an error making your move. Please try again')
// reset the board
const allGames = { ...games }
const gameCopy = { ...game }
gameCopy.game.undo()
allGames[game.id] = gameCopy
set({ games: allGames })
})
return true
}, [game, games, set])
const resignGame = useCallback((e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
if (!game) return
if (!window.confirm('Are you sure you want to resign this game?')) return
fetch(`${BASE_URL}/games?id=${game.id}`, {
method: 'DELETE',
}).then(r => r.json())
.then((updatedGame) => {
const allGames = { ...games }
allGames[game.id] = updatedGame
set({ games: allGames })
})
.catch((err) => {
console.error(err)
alert('There was an error resigning the game. Please try again')
})
}, [game])
const rematchGame = useCallback(async (e: MouseEvent) => {
e.preventDefault()
e.stopPropagation()
if (!game) return
try {
const createdGame = await fetch(`${BASE_URL}/games`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: game.id })
}).then(r => r.json())
const allGames = { ...games }
allGames[createdGame.id] = createdGame
set({ games: allGames })
} catch (err) {
console.error(err)
alert('You could not create the game. Please make sure your current game with this player (if any) has ended and try again.')
}
}, [game])
return (
<div className='flex flex-col justify-center items-center'>
<div className='flex flex-col justify-center' style={{ maxHeight: '100vh', maxWidth: '800px', width: '100%', position: 'relative' }}>
<a href="/" className='absolute top-6 left-0 m-4' style={{ fontSize: 24, color: 'white' }} onClick={e => { e.preventDefault(); window.history.back() }}>
&#x25c0; Back
</a>
<h1 className='m-4'>Chess by Uqbar</h1>
<div className='flex flex-row justify-center items-center h-screen border rounded'>
{Object.keys(games).length > 0 && <div className='flex flex-col border-r' style={{ width: '25%', height: '100%' }}>
<h3 className='m-2'>Games</h3>
<button className='bg-green-600 hover:bg-green-800 text-white font-bold py-2 px-4 m-2 rounded' onClick={() => setScreen('new')}>New</button>
<div className='flex flex-col overflow-scroll'>
{Object.values(games).map(game => (
<div key={game?.id} onClick={() => setScreen(game?.id)}
className={`game-entry m-2 ${screen !== game?.id && isTurn(game, window.our.node) ? 'is-turn' : ''} ${screen === game?.id ? 'selected' : ''} ${game?.ended ? 'ended' : ''}`}
>
{game?.id}
</div>
))}
</div>
</div>}
<div className='flex flex-col justify-center items-center' style={{ width: '75%' }}>
{screen === 'new' || !game ? (
<>
<h2 className='mb-2'>Start New Game</h2>
<h4 className='mb-2'>(game creator will be white)</h4>
<form onSubmit={startNewGame} className='flex flex-col justify-center mb-40' style={{ maxWidth: 400 }}>
<label className='mb-2' style={{ alignSelf: 'flex-start', fontWeight: '600' }}>Player ID</label>
<input className='border rounded p-2 mb-2' style={{ color: 'black' }} type='text' placeholder='Player ID' value={newGame} onChange={e => setNewGame(e.target.value)} />
<button className='bg-green-600 hover:bg-green-800 text-white font-bold py-2 px-4 rounded' type="submit">Start Game</button>
</form>
</>
) : (
<>
<div className='flex flex-row justify-between items-center w-full px-4 pb-2'>
<h3>{screen}</h3>
<h4>{game?.ended ? 'Game Ended' : `Turn: ${currentTurn}`}</h4>
{game?.ended ? (
<button className='bg-green-600 hover:bg-green-800 text-white font-bold py-1 px-4 rounded' onClick={rematchGame}>Rematch</button>
) : (
<button className='bg-green-600 hover:bg-green-800 text-white font-bold py-1 px-4 rounded' onClick={resignGame}>Resign</button>
)}
</div>
<Chessboard position={game?.game.fen()} onPieceDrop={onDrop} boardOrientation={game?.white === window.our.node ? 'white' : 'black'} />
</>
)}
</div>
</div>
</div>
</div>
)
}
export default App

View File

@ -1,152 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
width: 100%;
--uq-vlightpurple: #d9dcfc;
--uq-lightpurple: #c7cafa;
--uq-purple: #727bf2;
--uq-darkpurple: #5761ef;
--midnightpurp: #0a1170;
--forgottenpurp: #45475e;
--uq-lightpink: #f3ced2;
--uq-pink: #dd7c8a;
--uq-darkpink: #cd3c52;
--blush: #d55d6f;
--celeste: #adebe5;
--lturq: #6bdbd0;
--turq: #3acfc0;
--celadon: #21897e;
--deep-jungle: #14524c;
--old-mint: #659792;
--washed-gray: rgba(0, 0, 0, 0.03);
--light-gray: rgba(0, 0, 0, 0.1);
--medium-gray: rgba(0, 0, 0, 0.2);
--dark-gray: rgba(0, 0, 0, 0.5);
--charcoal: #333;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
/* place-items: center; */
min-width: 320px;
min-height: 100vh;
color: white;
background-color: var(--midnightpurp);
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
}
h1 {
font-size: 3em;
}
h2 {
font-size: 2em;
}
h3 {
font-size: 1.5em;
}
h4 {
font-size: 1.25em;
}
h5 {
font-size: 1em;
}
@keyframes colorChange {
0%, 100% {
background-color: var(--uq-purple); /* Start and end color */
}
50% {
background-color: var(--uq-pink); /* 2nd rhythmic change */
}
}
.game-entry {
width: calc(100% - 1em);
cursor: pointer;
color: white;
background-color: var(--uq-purple);
padding: 0.5em;
border-radius: 0.25em;
}
.game-entry.is-turn {
animation: colorChange 3s infinite;
}
.game-entry.selected {
background-color: var(--uq-darkpink);
}
.game-entry.is-ended {
background-color: lightgray;
color: gray;
}
.game-entry:hover {
background-color: var(--uq-pink);
}

View File

@ -1,10 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

View File

@ -1,50 +0,0 @@
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
export interface Game {
id: string,
turns: number,
board: string, // FEN format of the board
white: string,
black: string,
ended: boolean,
}
export interface Games {
[id: string]: Game
}
export interface ChessStore {
games: Games
handleWsMessage: (message: string) => void
set: (partial: ChessStore | Partial<ChessStore>) => void
}
type WsMessage = { kind: 'game_update', data: Game }
const useChessStore = create<ChessStore>()(
persist(
(set, get) => ({
games: {},
handleWsMessage: (json: string) => {
try {
const { kind, data } = JSON.parse(json) as WsMessage
console.log(kind, data)
if (kind === 'game_update') {
set({ games: { ...get().games, [data.id]: data } })
}
} catch (error) {
console.error("Error parsing WebSocket message", error);
}
},
set,
}),
{
name: 'chess', // unique name
storage: createJSONStorage(() => localStorage), // (optional) by default, 'localStorage' is used
}
)
)
export default useChessStore

View File

@ -1,5 +0,0 @@
export const genFetchRoute = (route: string) => {
return window.location.pathname.includes('/http-proxy/serve/') ?
`/http-proxy/serve/${window.location.pathname.split('/')[3]}/${route}`:
route
}

View File

@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@ -1,11 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

View File

@ -1,25 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -1,10 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@ -1,16 +0,0 @@
#!/bin/bash
# Check if there are enough parameters provided.
if [ "$#" -ne 4 ]; then
echo "Usage: $0 <url> <node-id> <vfs-identifier> <destination_process>"
exit 1
fi
URL="$1"
NODE="$2"
IDENTIFIER="$3"
DEST_PROC="$4"
# Generate new vfs capabilities and give "read" to the destination process
curl "$URL/rpc/message" -H 'content-type: application/json' -d "{\"node\": \"$NODE\", \"process\": \"vfs\", \"inherit\": false, \"expects_response\": false, \"ipc\": \"{\\\"New\\\": {\\\"identifier\\\": \\\"$IDENTIFIER\\\"}}\", \"metadata\": null, \"context\": null, \"mime\": null, \"data\": null}"
curl "$URL/rpc/capabilities/transfer" -H 'content-type: application/json' -d "{\"destination_node\": \"$NODE\", \"destination_process\": \"$DEST_PROC\", \"node\": \"$NODE\", \"process\": \"vfs\", \"params\": \"{\\\"identifier\\\": \\\"$IDENTIFIER\\\",\\\"kind\\\":\\\"read\\\"}\"}"

View File

@ -1,30 +0,0 @@
#!/bin/bash
# Check if there are enough parameters provided.
if [ "$#" -ne 5 ]; then
echo "Usage: $0 <url> <node-id> <vfs-identifier> <index.html> <assets directory>"
exit 1
fi
URL="$1"
NODE="$2"
IDENTIFIER="$3"
INDEX_HTML="$4"
DIRECTORY="$5"
# Upload index.html
curl "$URL/rpc/message" -H 'content-type: application/json' -d "{\"node\": \"$NODE\", \"process\": \"vfs\", \"inherit\": false, \"expects_response\": false, \"ipc\": \"{\\\"Add\\\": {\\\"identifier\\\": \\\"$IDENTIFIER\\\", \\\"full_path\\\": \\\"/index.html\\\", \\\"entry_type\\\": {\\\"NewFile\\\": null}}}\", \"metadata\": null, \"context\": null, \"mime\": null, \"data\": \"$(base64 < $INDEX_HTML)\"}"
[[ "$DIRECTORY" != */ ]] && DIRECTORY="$DIRECTORY/"
# Iterate over files in the specified directory
for FILE in "$DIRECTORY"*; do
# Check if it's a regular file before proceeding
if [[ -f "$FILE" ]]; then
# Extract just the file name
FILE_NAME=$(basename "$FILE")
# upload file to Uqbar VFS
curl "$URL/rpc/message" -H 'content-type: application/json' -d "{\"node\": \"$NODE\", \"process\": \"vfs\", \"inherit\": false, \"expects_response\": false, \"ipc\": \"{\\\"Add\\\": {\\\"identifier\\\": \\\"$IDENTIFIER\\\", \\\"full_path\\\": \\\"/$FILE_NAME\\\", \\\"entry_type\\\": {\\\"NewFile\\\": null}}}\", \"metadata\": null, \"context\": null, \"mime\": null, \"data\": \"$(base64 < "$FILE")\"}"
fi
done

View File

@ -1,67 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
/*
If you are developing a UI outside of an Uqbar project,
comment out the following 2 lines:
*/
import manifest from '../pkg/manifest.json'
import metadata from '../pkg/metadata.json'
/*
IMPORTANT:
This must match the process name from pkg/manifest.json + pkg/metadata.json
The format is "/" + "process_name:package_name:publisher_node"
*/
const BASE_URL = `/${manifest[0].process_name}:${metadata.package}:${metadata.publisher}`;
// This is the proxy URL, it must match the node you are developing against
const PROXY_URL = (process.env.VITE_NODE_URL || 'http://127.0.0.1:8080').replace('localhost', '127.0.0.1');
console.log('process.env.VITE_NODE_URL', process.env.VITE_NODE_URL, PROXY_URL);
export default defineConfig({
plugins: [react()],
base: BASE_URL,
build: {
rollupOptions: {
external: ['/our.js']
}
},
server: {
open: true,
proxy: {
'/our': {
target: PROXY_URL,
changeOrigin: true,
},
[`${BASE_URL}/our.js`]: {
target: PROXY_URL,
changeOrigin: true,
rewrite: (path) => path.replace(BASE_URL, ''),
},
// This route will match all other HTTP requests to the backend
[`^${BASE_URL}/(?!(@vite/client|src/.*|node_modules/.*|@react-refresh|$))`]: {
target: PROXY_URL,
changeOrigin: true,
},
// '/example': {
// target: PROXY_URL,
// changeOrigin: true,
// rewrite: (path) => path.replace(BASE_URL, ''),
// // This is only for debugging purposes
// configure: (proxy, _options) => {
// proxy.on('error', (err, _req, _res) => {
// console.log('proxy error', err);
// });
// proxy.on('proxyReq', (proxyReq, req, _res) => {
// console.log('Sending Request to the Target:', req.method, req.url);
// });
// proxy.on('proxyRes', (proxyRes, req, _res) => {
// console.log('Received Response from the Target:', proxyRes.statusCode, req.url);
// });
// },
// },
}
}
});

File diff suppressed because it is too large Load Diff