create-tinybase 0.2.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -18
- package/cli.js +2 -2
- package/package.json +3 -3
- package/screenshots/chat.png +0 -0
- package/screenshots/todos.png +0 -0
- package/templates/README.md.hbs +13 -12
- package/templates/client/eslint.config.js.hbs +7 -0
- package/templates/client/package.json.hbs +28 -8
- package/templates/client/src/chat/App.tsx.hbs +28 -22
- package/templates/client/src/chat/ChatStore.tsx.hbs +108 -27
- package/templates/client/src/chat/Message.tsx.hbs +8 -9
- package/templates/client/src/chat/MessageInput.tsx.hbs +7 -7
- package/templates/client/src/chat/Messages.tsx.hbs +5 -15
- package/templates/client/src/chat/SettingsStore.tsx.hbs +86 -9
- package/templates/client/src/chat/UsernameInput.tsx.hbs +6 -11
- package/templates/client/src/chat/app.ts.hbs +18 -18
- package/templates/client/src/chat/chatStore.ts.hbs +97 -35
- package/templates/client/src/chat/message.ts.hbs +4 -3
- package/templates/client/src/chat/messageInput.css.hbs +1 -1
- package/templates/client/src/chat/messageInput.ts.hbs +7 -8
- package/templates/client/src/chat/messages.ts.hbs +7 -12
- package/templates/client/src/chat/settingsStore.ts.hbs +65 -6
- package/templates/client/src/chat/usernameInput.css.hbs +1 -1
- package/templates/client/src/chat/usernameInput.ts.hbs +6 -6
- package/templates/client/src/chat/utils.ts.hbs +26 -0
- package/templates/client/src/drawing/App.tsx.hbs +26 -20
- package/templates/client/src/drawing/BrushSize.tsx.hbs +8 -11
- package/templates/client/src/drawing/Canvas.tsx.hbs +65 -73
- package/templates/client/src/drawing/CanvasStore.tsx.hbs +104 -18
- package/templates/client/src/drawing/ColorPicker.tsx.hbs +4 -11
- package/templates/client/src/drawing/DrawingControls.tsx.hbs +7 -7
- package/templates/client/src/drawing/SettingsStore.tsx.hbs +81 -8
- package/templates/client/src/drawing/app.ts.hbs +18 -8
- package/templates/client/src/drawing/brushSize.ts.hbs +12 -5
- package/templates/client/src/drawing/canvas.ts.hbs +84 -86
- package/templates/client/src/drawing/canvasStore.ts.hbs +93 -26
- package/templates/client/src/drawing/colorPicker.ts.hbs +3 -3
- package/templates/client/src/drawing/drawingControls.ts.hbs +7 -7
- package/templates/client/src/drawing/settingsStore.ts.hbs +63 -8
- package/templates/client/src/game/App.tsx.hbs +20 -16
- package/templates/client/src/game/Board.tsx.hbs +8 -8
- package/templates/client/src/game/Game.tsx.hbs +14 -21
- package/templates/client/src/game/GameStatus.tsx.hbs +5 -5
- package/templates/client/src/game/Square.tsx.hbs +6 -11
- package/templates/client/src/game/Store.tsx.hbs +106 -16
- package/templates/client/src/game/app.ts.hbs +17 -6
- package/templates/client/src/game/board.ts.hbs +7 -7
- package/templates/client/src/game/game.ts.hbs +12 -18
- package/templates/client/src/game/gameStatus.ts.hbs +3 -3
- package/templates/client/src/game/square.ts.hbs +4 -4
- package/templates/client/src/game/store.ts.hbs +95 -23
- package/templates/client/src/index.tsx.hbs +5 -7
- package/templates/client/src/shared/Button.tsx.hbs +3 -3
- package/templates/client/src/shared/Input.tsx.hbs +2 -2
- package/templates/client/src/shared/Loading.tsx.hbs +5 -0
- package/templates/client/src/shared/button.ts.hbs +2 -2
- package/templates/client/src/shared/config.ts.hbs +4 -6
- package/templates/client/src/shared/input.ts.hbs +2 -2
- package/templates/client/src/shared/loading.css.hbs +21 -0
- package/templates/client/src/shared/loading.ts.hbs +13 -0
- package/templates/client/src/shared/pglite.ts.hbs +10 -0
- package/templates/client/src/shared/sqlite.ts.hbs +17 -0
- package/templates/client/src/todos/App.tsx.hbs +22 -22
- package/templates/client/src/todos/Store.tsx.hbs +106 -23
- package/templates/client/src/todos/TodoInput.tsx.hbs +6 -8
- package/templates/client/src/todos/TodoItem.tsx.hbs +5 -6
- package/templates/client/src/todos/TodoList.tsx.hbs +5 -6
- package/templates/client/src/todos/app.ts.hbs +16 -10
- package/templates/client/src/todos/store.ts.hbs +94 -30
- package/templates/client/src/todos/todoInput.ts.hbs +5 -8
- package/templates/client/src/todos/todoItem.ts.hbs +3 -4
- package/templates/client/src/todos/todoList.ts.hbs +6 -8
- package/templates/client/vite-env.d.ts.hbs +4 -0
- package/templates/client/vite.config.js.hbs +53 -3
- package/templates/server/index-do.ts.hbs +10 -14
- package/templates/server/index-node.ts.hbs +3 -3
- package/templates/server/package.json.hbs +5 -5
- package/templates/server/wrangler.toml.hbs +1 -1
- /package/templates/client/{.prettierrc.hbs → .prettierrc.json.hbs} +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
{{includeFile template="client/src/drawing/canvas.css.hbs" output="client/src/canvas.css"}}
|
|
2
|
-
import './canvas.css';
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
2
|
+
{{addImport "import './canvas.css';"}}
|
|
3
|
+
{{addImport "import {getHlcFunctions} from 'tinybase';"}}
|
|
4
|
+
{{addImport "import type {SettingsStore} from './settingsStore';"}}
|
|
5
|
+
{{addImport "import type {CanvasStore, StrokeRow} from './canvasStore';"}}
|
|
6
|
+
|
|
7
|
+
const [getNextHlc] = getHlcFunctions();
|
|
6
8
|
|
|
7
9
|
export const createCanvas = (settingsStore: SettingsStore, canvasStore: CanvasStore): HTMLCanvasElement => {
|
|
8
10
|
const canvas = document.createElement('canvas');
|
|
@@ -13,91 +15,87 @@ canvas.height = 400;
|
|
|
13
15
|
const ctx = canvas.getContext('2d')!;
|
|
14
16
|
let isDrawing = false;
|
|
15
17
|
let currentStrokeId: string | null = null;
|
|
16
|
-
let pointIndex = 0;
|
|
17
18
|
|
|
18
19
|
const draw = () => {
|
|
19
20
|
ctx.fillStyle = '#111';
|
|
20
21
|
ctx.fillRect(0, 0, canvas!.width, canvas!.height);
|
|
21
22
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
const sortedIds = canvasStore.getSortedRowIds('strokes');
|
|
24
|
+
sortedIds.forEach((id) => {
|
|
25
|
+
const stroke = canvasStore.getRow('strokes', id) as StrokeRow;
|
|
26
|
+
if (stroke.points) {
|
|
27
|
+
const pointsArray = JSON.parse(stroke.points) as number[];
|
|
28
|
+
if (pointsArray.length >= 2) {
|
|
29
|
+
ctx.strokeStyle = stroke.color;
|
|
30
|
+
ctx.lineWidth = stroke.size * 2;
|
|
31
|
+
ctx.lineCap = 'round';
|
|
32
|
+
ctx.lineJoin = 'round';
|
|
33
|
+
ctx.beginPath();
|
|
34
|
+
ctx.moveTo(pointsArray[0], pointsArray[1]);
|
|
35
|
+
for (let i = 2; i < pointsArray.length; i +=2) { ctx.lineTo(pointsArray[i], pointsArray[i + 1]); } ctx.stroke(); } } }); }; const addPoint=(e: MouseEvent | TouchEvent)=> {
|
|
36
|
+
if (!currentStrokeId) return;
|
|
37
|
+
|
|
38
|
+
const rect = canvas!.getBoundingClientRect();
|
|
39
|
+
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
|
40
|
+
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
41
|
+
|
|
42
|
+
const pointsArray = JSON.parse(canvasStore.getCell('strokes', currentStrokeId, 'points') as string ?? '[]') as number[];
|
|
43
|
+
pointsArray.push(clientX - rect.left, clientY - rect.top);
|
|
44
|
+
canvasStore.setCell('strokes', currentStrokeId, 'points', JSON.stringify(pointsArray));
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const startStroke = (e: MouseEvent | TouchEvent) => {
|
|
48
|
+
const {brushColor, brushSize} = settingsStore.getValues();
|
|
49
|
+
currentStrokeId = getNextHlc();
|
|
50
|
+
|
|
51
|
+
canvasStore.setRow('strokes', currentStrokeId, {
|
|
52
|
+
color: brushColor,
|
|
53
|
+
size: brushSize,
|
|
54
|
+
points: '[]',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
addPoint(e);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
canvas.addEventListener('mousedown', (e) => {
|
|
61
|
+
isDrawing = true;
|
|
62
|
+
startStroke(e);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
canvas.addEventListener('mouseup', () => {
|
|
66
|
+
isDrawing = false;
|
|
67
|
+
currentStrokeId = null;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
canvas.addEventListener('mouseleave', () => {
|
|
71
|
+
isDrawing = false;
|
|
72
|
+
currentStrokeId = null;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
canvas.addEventListener('mousemove', (e) => {
|
|
76
|
+
if (isDrawing) addPoint(e);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
canvas.addEventListener('touchstart', (e) => {
|
|
80
|
+
isDrawing = true;
|
|
81
|
+
startStroke(e);
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
canvas.addEventListener('touchmove', (e) => {
|
|
86
|
+
if (isDrawing) {
|
|
87
|
+
addPoint(e);
|
|
88
|
+
e.preventDefault();
|
|
29
89
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
43
|
-
|
|
44
|
-
canvasStore.setCell('strokes', currentStrokeId, `x${pointIndex}`, clientX - rect.left);
|
|
45
|
-
canvasStore.setCell('strokes', currentStrokeId, `y${pointIndex}`, clientY - rect.top);
|
|
46
|
-
pointIndex++;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const startStroke = (e: MouseEvent | TouchEvent) => {
|
|
50
|
-
const brush = settingsStore.getValues() as any;
|
|
51
|
-
currentStrokeId = getUniqueId();
|
|
52
|
-
pointIndex = 0;
|
|
53
|
-
|
|
54
|
-
canvasStore.setRow('strokes', currentStrokeId, {
|
|
55
|
-
color: brush.brushColor,
|
|
56
|
-
size: brush.brushSize,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
addPoint(e);
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
canvas.addEventListener('mousedown', (e) => {
|
|
63
|
-
isDrawing = true;
|
|
64
|
-
startStroke(e);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
canvas.addEventListener('mouseup', () => {
|
|
68
|
-
isDrawing = false;
|
|
69
|
-
currentStrokeId = null;
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
canvas.addEventListener('mouseleave', () => {
|
|
73
|
-
isDrawing = false;
|
|
74
|
-
currentStrokeId = null;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
canvas.addEventListener('mousemove', (e) => {
|
|
78
|
-
if (isDrawing) addPoint(e);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
canvas.addEventListener('touchstart', (e) => {
|
|
82
|
-
isDrawing = true;
|
|
83
|
-
startStroke(e);
|
|
84
|
-
e.preventDefault();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
canvas.addEventListener('touchmove', (e) => {
|
|
88
|
-
if (isDrawing) {
|
|
89
|
-
addPoint(e);
|
|
90
|
-
e.preventDefault();
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
canvas.addEventListener('touchend', () => {
|
|
95
|
-
isDrawing = false;
|
|
96
|
-
currentStrokeId = null;
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
canvasStore.addTablesListener(draw);
|
|
100
|
-
draw();
|
|
101
|
-
|
|
102
|
-
return canvas;
|
|
103
|
-
};
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
canvas.addEventListener('touchend', () => {
|
|
93
|
+
isDrawing = false;
|
|
94
|
+
currentStrokeId = null;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
canvasStore.addTablesListener(draw);
|
|
98
|
+
draw();
|
|
99
|
+
|
|
100
|
+
return canvas;
|
|
101
|
+
};
|
|
@@ -1,42 +1,109 @@
|
|
|
1
1
|
{{#if schemas}}
|
|
2
|
-
import {createMergeableStore} from 'tinybase/with-schemas';
|
|
2
|
+
{{addImport "import {createMergeableStore, type Row} from 'tinybase/with-schemas';"}}
|
|
3
3
|
{{else}}
|
|
4
|
-
import {createMergeableStore} from 'tinybase';
|
|
4
|
+
{{addImport "import {createMergeableStore} from 'tinybase';"}}
|
|
5
5
|
{{/if}}
|
|
6
|
-
import {getUniqueId} from 'tinybase';
|
|
7
|
-
|
|
8
|
-
const STORE_ID = 'canvas';
|
|
9
6
|
|
|
10
7
|
{{#if schemas}}
|
|
11
8
|
const TABLES_SCHEMA = {
|
|
12
9
|
strokes: {
|
|
13
|
-
color: {type: 'string'},
|
|
14
|
-
size: {type: 'number'},
|
|
10
|
+
color: {type: 'string', default: ''},
|
|
11
|
+
size: {type: 'number', default: 1},
|
|
12
|
+
points: {type: 'string', default: '[]'},
|
|
15
13
|
},
|
|
16
14
|
} as const;
|
|
17
15
|
|
|
18
16
|
{{/if}}
|
|
19
|
-
export const canvasStore = createMergeableStore(
|
|
17
|
+
export const canvasStore = createMergeableStore(){{#if schemas}}
|
|
20
18
|
.setTablesSchema(TABLES_SCHEMA){{/if}}
|
|
21
19
|
.setDefaultContent([{strokes: {}}, {}]);
|
|
22
20
|
|
|
23
|
-
{{#if
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const serverPathId = location.pathname;
|
|
30
|
-
createWsSynchronizer(
|
|
31
|
-
canvasStore,
|
|
32
|
-
new ReconnectingWebSocket(SERVER + serverPathId),
|
|
33
|
-
).then(async (synchronizer) => {
|
|
34
|
-
await synchronizer.startSync();
|
|
35
|
-
|
|
36
|
-
synchronizer.getWebSocket().addEventListener('open', () => {
|
|
37
|
-
synchronizer.load().then(() => synchronizer.save());
|
|
38
|
-
});
|
|
21
|
+
export type StrokeRow = {{#if schemas}}Row<typeof TABLES_SCHEMA, 'strokes'>{{else}}{color: string; size: number; points: string}{{/if}};
|
|
22
|
+
|
|
23
|
+
let resolveCanvasReady: () => void;
|
|
24
|
+
export const canvasStoreReady = new Promise<void>((resolve) => {
|
|
25
|
+
resolveCanvasReady = resolve;
|
|
39
26
|
});
|
|
40
|
-
{{/if}}
|
|
41
27
|
|
|
42
|
-
|
|
28
|
+
{{#if persist}}
|
|
29
|
+
{{#if persistLocalStorage}}
|
|
30
|
+
{{#if schemas}}
|
|
31
|
+
{{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser/with-schemas';"}}
|
|
32
|
+
{{else}}
|
|
33
|
+
{{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser';"}}
|
|
34
|
+
{{/if}}
|
|
35
|
+
|
|
36
|
+
const canvasPersister = createLocalPersister(canvasStore, 'canvas');
|
|
37
|
+
canvasPersister.load().then(() => {
|
|
38
|
+
canvasPersister.startAutoSave();
|
|
39
|
+
resolveCanvasReady();
|
|
40
|
+
});
|
|
41
|
+
{{/if}}
|
|
42
|
+
{{#if persistSqlite}}
|
|
43
|
+
{{#if schemas}}
|
|
44
|
+
{{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm/with-schemas';"}}
|
|
45
|
+
{{else}}
|
|
46
|
+
{{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';"}}
|
|
47
|
+
{{/if}}
|
|
48
|
+
{{includeFile template="client/src/shared/sqlite.ts.hbs" output="client/src/sqlite.ts"}}
|
|
49
|
+
{{addImport "import {getDb} from './sqlite';"}}
|
|
50
|
+
|
|
51
|
+
getDb().then(({sqlite3, db}) => {
|
|
52
|
+
const canvasPersister = createSqliteWasmPersister(canvasStore, sqlite3, db, 'canvas');
|
|
53
|
+
canvasPersister.load().then(() => {
|
|
54
|
+
canvasPersister.startAutoSave();
|
|
55
|
+
resolveCanvasReady();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
{{/if}}
|
|
59
|
+
{{#if persistPglite}}
|
|
60
|
+
{{#if schemas}}
|
|
61
|
+
{{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite/with-schemas';"}}
|
|
62
|
+
{{else}}
|
|
63
|
+
{{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite';"}}
|
|
64
|
+
{{/if}}
|
|
65
|
+
{{includeFile template="client/src/shared/pglite.ts.hbs" output="client/src/pglite.{{ext}}"}}
|
|
66
|
+
{{addImport "import {getPgLite} from './pglite';"}}
|
|
67
|
+
|
|
68
|
+
getPgLite().then((pgLite) => {
|
|
69
|
+
createPglitePersister(canvasStore, pgLite, 'canvas').then((canvasPersister) => {
|
|
70
|
+
canvasPersister.load().then(() => {
|
|
71
|
+
canvasPersister.startAutoSave();
|
|
72
|
+
resolveCanvasReady();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
{{/if}}
|
|
77
|
+
{{/if}}
|
|
78
|
+
|
|
79
|
+
{{#if sync}}
|
|
80
|
+
{{includeFile template="client/src/shared/config.ts.hbs" output="client/src/config.ts"}}
|
|
81
|
+
{{addImport "import {SERVER} from './config';"}}
|
|
82
|
+
{{addImport "import ReconnectingWebSocket from 'reconnecting-websocket';"}}
|
|
83
|
+
{{#if schemas}}
|
|
84
|
+
{{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client/with-schemas';"}}
|
|
85
|
+
{{else}}
|
|
86
|
+
{{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
|
|
87
|
+
{{/if}}
|
|
88
|
+
|
|
89
|
+
const serverPathId = location.pathname;
|
|
90
|
+
createWsSynchronizer(
|
|
91
|
+
canvasStore,
|
|
92
|
+
new ReconnectingWebSocket(SERVER + serverPathId),
|
|
93
|
+
).then(async (synchronizer) => {
|
|
94
|
+
await synchronizer.startSync();
|
|
95
|
+
|
|
96
|
+
synchronizer.getWebSocket().addEventListener('open', () => {
|
|
97
|
+
synchronizer.load().then(() => {
|
|
98
|
+
synchronizer.save();
|
|
99
|
+
{{#unless persist}}resolveCanvasReady();{{/unless}}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
{{/if}}
|
|
104
|
+
|
|
105
|
+
{{#unless persist}}{{#unless sync}}
|
|
106
|
+
resolveCanvasReady();
|
|
107
|
+
{{/unless}}{{/unless}}
|
|
108
|
+
|
|
109
|
+
export type CanvasStore = typeof canvasStore;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{{includeFile template="client/src/drawing/colorPicker.css.hbs" output="client/src/colorPicker.css"}}
|
|
2
|
-
import './colorPicker.css';
|
|
3
|
-
import type {
|
|
2
|
+
{{addImport "import './colorPicker.css';"}}
|
|
3
|
+
{{addImport "import type {SettingsStore} from './settingsStore';"}}
|
|
4
4
|
|
|
5
5
|
export const createColorPicker = (store: SettingsStore): HTMLDivElement => {
|
|
6
6
|
const colors = ['#d81b60', '#1976d2', '#388e3c', '#f57c00', '#7b1fa2', '#fff'];
|
|
@@ -31,4 +31,4 @@ store.addValueListener('brushColor', updateActive);
|
|
|
31
31
|
updateActive();
|
|
32
32
|
|
|
33
33
|
return container;
|
|
34
|
-
};
|
|
34
|
+
};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{{includeFile template="client/src/drawing/drawingControls.css.hbs" output="client/src/drawingControls.css"}}
|
|
2
|
-
import './drawingControls.css';
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
2
|
+
{{addImport "import './drawingControls.css';"}}
|
|
3
|
+
{{addImport "import type {SettingsStore} from './settingsStore';"}}
|
|
4
|
+
{{addImport "import type {CanvasStore} from './canvasStore';"}}
|
|
5
5
|
|
|
6
6
|
{{includeFile template="client/src/shared/button.ts.hbs" output="client/src/button.{{ext}}"}}
|
|
7
|
-
import {createButton} from './button';
|
|
7
|
+
{{addImport "import {createButton} from './button';"}}
|
|
8
8
|
|
|
9
9
|
{{includeFile template="client/src/drawing/colorPicker.ts.hbs" output="client/src/colorPicker.{{ext}}"}}
|
|
10
|
-
import {createColorPicker} from './colorPicker';
|
|
10
|
+
{{addImport "import {createColorPicker} from './colorPicker';"}}
|
|
11
11
|
|
|
12
12
|
{{includeFile template="client/src/drawing/brushSize.ts.hbs" output="client/src/brushSize.{{ext}}"}}
|
|
13
|
-
import {createBrushSize} from './brushSize';
|
|
13
|
+
{{addImport "import {createBrushSize} from './brushSize';"}}
|
|
14
14
|
|
|
15
15
|
export const createDrawingControls = (settingsStore: SettingsStore, canvasStore: CanvasStore): HTMLDivElement => {
|
|
16
16
|
const controls = document.createElement('div');
|
|
@@ -23,4 +23,4 @@ const clearButton = createButton('Clear', () => canvasStore.delTable('strokes'),
|
|
|
23
23
|
controls.appendChild(clearButton);
|
|
24
24
|
|
|
25
25
|
return controls;
|
|
26
|
-
};
|
|
26
|
+
};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{{#if schemas}}
|
|
2
|
-
import {createStore} from 'tinybase/with-schemas';
|
|
2
|
+
{{addImport "import {createStore} from 'tinybase/with-schemas';"}}
|
|
3
3
|
{{else}}
|
|
4
|
-
import {createStore} from 'tinybase';
|
|
4
|
+
{{addImport "import {createStore} from 'tinybase';"}}
|
|
5
5
|
{{/if}}
|
|
6
6
|
|
|
7
|
-
const STORE_ID = 'settings';
|
|
8
|
-
|
|
9
7
|
{{#if schemas}}
|
|
10
8
|
const VALUES_SCHEMA = {
|
|
11
9
|
brushColor: {type: 'string'},
|
|
@@ -14,8 +12,65 @@ const STORE_ID = 'settings';
|
|
|
14
12
|
|
|
15
13
|
{{/if}}
|
|
16
14
|
export const settingsStore = createStore(){{#if schemas}}
|
|
17
|
-
.setValuesSchema(VALUES_SCHEMA){{/if}}
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
.setValuesSchema(VALUES_SCHEMA){{/if}};
|
|
16
|
+
|
|
17
|
+
let resolveSettingsReady: () => void;
|
|
18
|
+
export const settingsStoreReady = new Promise<void>((resolve) => {
|
|
19
|
+
resolveSettingsReady = resolve;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
{{#if persist}}
|
|
23
|
+
{{#if persistLocalStorage}}
|
|
24
|
+
{{#if schemas}}
|
|
25
|
+
{{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser/with-schemas';"}}
|
|
26
|
+
{{else}}
|
|
27
|
+
{{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser';"}}
|
|
28
|
+
{{/if}}
|
|
29
|
+
|
|
30
|
+
const settingsPersister = createLocalPersister(settingsStore, 'settings');
|
|
31
|
+
settingsPersister.load([{}, {brushColor: '#d81b60', brushSize: 5}]).then(() => {
|
|
32
|
+
settingsPersister.startAutoSave();
|
|
33
|
+
resolveSettingsReady();
|
|
34
|
+
});
|
|
35
|
+
{{/if}}
|
|
36
|
+
{{#if persistSqlite}}
|
|
37
|
+
{{#if schemas}}
|
|
38
|
+
{{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm/with-schemas';"}}
|
|
39
|
+
{{else}}
|
|
40
|
+
{{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';"}}
|
|
41
|
+
{{/if}}
|
|
42
|
+
{{includeFile template="client/src/shared/sqlite.ts.hbs" output="client/src/sqlite.ts"}}
|
|
43
|
+
{{addImport "import {getDb} from './sqlite';"}}
|
|
44
|
+
|
|
45
|
+
getDb().then(({sqlite3, db}) => {
|
|
46
|
+
const settingsPersister = createSqliteWasmPersister(settingsStore, sqlite3, db, 'settings');
|
|
47
|
+
settingsPersister.load([{}, {brushColor: '#d81b60', brushSize: 5}]).then(() => {
|
|
48
|
+
settingsPersister.startAutoSave();
|
|
49
|
+
resolveSettingsReady();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
{{/if}}
|
|
53
|
+
{{#if persistPglite}}
|
|
54
|
+
{{#if schemas}}
|
|
55
|
+
{{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite/with-schemas';"}}
|
|
56
|
+
{{else}}
|
|
57
|
+
{{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite';"}}
|
|
58
|
+
{{/if}}
|
|
59
|
+
{{includeFile template="client/src/shared/pglite.ts.hbs" output="client/src/pglite.{{ext}}"}}
|
|
60
|
+
{{addImport "import {getPgLite} from './pglite';"}}
|
|
61
|
+
|
|
62
|
+
getPgLite().then((pgLite) => {
|
|
63
|
+
createPglitePersister(settingsStore, pgLite, 'settings').then((settingsPersister) => {
|
|
64
|
+
settingsPersister.load([{}, {brushColor: '#d81b60', brushSize: 5}]).then(() => {
|
|
65
|
+
settingsPersister.startAutoSave();
|
|
66
|
+
resolveSettingsReady();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
{{/if}}
|
|
71
|
+
{{else}}
|
|
72
|
+
settingsStore.setValue('brushColor', '#d81b60').setValue('brushSize', 5);
|
|
73
|
+
resolveSettingsReady();
|
|
74
|
+
{{/if}}
|
|
20
75
|
|
|
21
|
-
export type SettingsStore = typeof settingsStore;
|
|
76
|
+
export type SettingsStore = typeof settingsStore;
|
|
@@ -1,28 +1,32 @@
|
|
|
1
|
-
import {StrictMode} from 'react';
|
|
1
|
+
{{addImport "import {StrictMode, useState} from 'react';"}}
|
|
2
|
+
{{includeFile template="client/src/shared/Loading.tsx.hbs" output="client/src/Loading.{{ext}}"}}
|
|
3
|
+
{{addImport "import {Loading} from './Loading';"}}
|
|
2
4
|
|
|
3
|
-
{{
|
|
4
|
-
|
|
5
|
-
{{else}}
|
|
6
|
-
import {Provider} from 'tinybase/ui-react';
|
|
7
|
-
{{/if}}
|
|
8
|
-
import {Inspector} from 'tinybase/ui-react-inspector';
|
|
5
|
+
{{addImport "import {Provider} from 'tinybase/ui-react';"}}
|
|
6
|
+
{{addImport "import {Inspector} from 'tinybase/ui-react-inspector';"}}
|
|
9
7
|
|
|
10
8
|
{{includeFile template="client/src/game/Store.tsx.hbs" output="client/src/Store.{{ext}}"}}
|
|
11
|
-
import {Store} from './Store';
|
|
9
|
+
{{addImport "import {Store} from './Store';"}}
|
|
12
10
|
|
|
13
11
|
{{includeFile template="client/src/game/Game.tsx.hbs" output="client/src/Game.{{ext}}"}}
|
|
14
|
-
import {Game} from './Game';
|
|
12
|
+
{{addImport "import {Game} from './Game';"}}
|
|
13
|
+
|
|
14
|
+
export const App = () => {
|
|
15
|
+
const [loading, setLoading] = useState(true);
|
|
15
16
|
|
|
16
|
-
const App = () => {
|
|
17
17
|
return (
|
|
18
18
|
<StrictMode>
|
|
19
19
|
<Provider>
|
|
20
|
-
<Store />
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
<Store onReady={()=> setLoading(false)} />
|
|
21
|
+
{loading ? (
|
|
22
|
+
<Loading />
|
|
23
|
+
) : (
|
|
24
|
+
<>
|
|
25
|
+
<Game />
|
|
26
|
+
<Inspector />
|
|
27
|
+
</>
|
|
28
|
+
)}
|
|
23
29
|
</Provider>
|
|
24
30
|
</StrictMode>
|
|
25
31
|
);
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export {App};
|
|
32
|
+
};
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{{includeFile template="client/src/game/board.css.hbs" output="client/src/board.css"}}
|
|
2
|
-
import './board.css';
|
|
3
|
-
import {useMemo} from 'react';
|
|
4
|
-
import {useValue, STORE_ID} from './Store';
|
|
2
|
+
{{addImport "import './board.css';"}}
|
|
3
|
+
{{addImport "import {useMemo} from 'react';"}}
|
|
4
|
+
{{addImport "import {useValue, STORE_ID} from './Store';"}}
|
|
5
5
|
|
|
6
6
|
{{includeFile template="client/src/game/Square.tsx.hbs" output="client/src/Square.{{ext}}"}}
|
|
7
|
-
import {Square} from './Square';
|
|
7
|
+
{{addImport "import {Square} from './Square';"}}
|
|
8
8
|
|
|
9
9
|
export const Board = () => {
|
|
10
10
|
const gameStatus = useValue('gameStatus', STORE_ID);
|
|
11
11
|
const winningLine = useValue('winningLine', STORE_ID);
|
|
12
12
|
|
|
13
13
|
const winningPositions = useMemo(() => {
|
|
14
|
-
if (!winningLine) return new Set();
|
|
15
|
-
return new Set(winningLine.split(',')
|
|
14
|
+
if (!winningLine || typeof winningLine !== 'string') return new Set();
|
|
15
|
+
return new Set(winningLine.split(','));
|
|
16
16
|
}, [winningLine]);
|
|
17
17
|
|
|
18
18
|
const disabled = gameStatus !== 'playing';
|
|
@@ -20,8 +20,8 @@ const disabled = gameStatus !== 'playing';
|
|
|
20
20
|
return (
|
|
21
21
|
<div id="board">
|
|
22
22
|
{Array.from({length: 9}, (_, i) => (
|
|
23
|
-
<Square key={i} position={i} disabled={disabled} winning={winningPositions.has(i)} />
|
|
23
|
+
<Square key={i} position={i.toString()} disabled={disabled} winning={winningPositions.has(i.toString())} />
|
|
24
24
|
))}
|
|
25
25
|
</div>
|
|
26
26
|
);
|
|
27
|
-
};
|
|
27
|
+
};
|
|
@@ -1,37 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
{{addImport "import {useSetValuesCallback, useTableListener, STORE_ID} from './Store';"}}
|
|
2
2
|
|
|
3
3
|
{{includeFile template="client/src/shared/Button.tsx.hbs" output="client/src/Button.{{ext}}"}}
|
|
4
|
-
import {Button} from './Button';
|
|
4
|
+
{{addImport "import {Button} from './Button';"}}
|
|
5
5
|
|
|
6
6
|
{{includeFile template="client/src/game/Board.tsx.hbs" output="client/src/Board.{{ext}}"}}
|
|
7
|
-
import {Board} from './Board';
|
|
7
|
+
{{addImport "import {Board} from './Board';"}}
|
|
8
8
|
|
|
9
9
|
{{includeFile template="client/src/game/GameStatus.tsx.hbs" output="client/src/GameStatus.{{ext}}"}}
|
|
10
|
-
import {GameStatus} from './GameStatus';
|
|
10
|
+
{{addImport "import {GameStatus} from './GameStatus';"}}
|
|
11
11
|
|
|
12
12
|
const WINNING_LINES = [
|
|
13
|
-
[0, 1, 2], [3, 4, 5], [6, 7, 8],
|
|
14
|
-
[0, 3, 6], [1, 4, 7], [2, 5, 8],
|
|
15
|
-
[0, 4, 8], [2, 4, 6],
|
|
13
|
+
['0', '1', '2'], ['3', '4', '5'], ['6', '7', '8'],
|
|
14
|
+
['0', '3', '6'], ['1', '4', '7'], ['2', '5', '8'],
|
|
15
|
+
['0', '4', '8'], ['2', '4', '6'],
|
|
16
16
|
];
|
|
17
17
|
|
|
18
18
|
export const Game = () => {
|
|
19
|
-
const store = useStore(STORE_ID);
|
|
20
|
-
|
|
21
19
|
useTableListener(
|
|
22
20
|
'board',
|
|
23
|
-
() => {
|
|
21
|
+
(store) => {
|
|
24
22
|
const gameStatus = store.getValue('gameStatus');
|
|
25
23
|
if (gameStatus !== 'playing') return;
|
|
26
24
|
|
|
27
|
-
const board = store.getTable('board');
|
|
28
|
-
|
|
29
|
-
// Check for winner
|
|
30
25
|
for (const line of WINNING_LINES) {
|
|
31
26
|
const [a, b, c] = line;
|
|
32
|
-
const cellA = board
|
|
33
|
-
const cellB = board
|
|
34
|
-
const cellC = board
|
|
27
|
+
const cellA = store.getCell('board', a, 'value');
|
|
28
|
+
const cellB = store.getCell('board', b, 'value');
|
|
29
|
+
const cellC = store.getCell('board', c, 'value');
|
|
35
30
|
|
|
36
31
|
if (cellA && cellA === cellB && cellA === cellC) {
|
|
37
32
|
store.setValue('gameStatus', 'won');
|
|
@@ -41,14 +36,11 @@ return;
|
|
|
41
36
|
}
|
|
42
37
|
}
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
const filledCells = Object.values(board).filter((cell: any) => cell.value).length;
|
|
46
|
-
if (filledCells === 9) {
|
|
39
|
+
if (store.getRowCount('board') === 9) {
|
|
47
40
|
store.setValue('gameStatus', 'draw');
|
|
48
41
|
return;
|
|
49
42
|
}
|
|
50
43
|
|
|
51
|
-
// Switch player after each move
|
|
52
44
|
const currentPlayer = store.getValue('currentPlayer');
|
|
53
45
|
store.setValue('currentPlayer', currentPlayer === 'X' ? 'O' : 'X');
|
|
54
46
|
},
|
|
@@ -57,13 +49,14 @@ true,
|
|
|
57
49
|
STORE_ID,);
|
|
58
50
|
|
|
59
51
|
const resetGame = useSetValuesCallback(
|
|
60
|
-
() => {
|
|
52
|
+
(_, store) => {
|
|
61
53
|
store.delTable('board');
|
|
62
54
|
return {currentPlayer: 'X',
|
|
63
55
|
gameStatus: 'playing',
|
|
64
56
|
};
|
|
65
57
|
},
|
|
66
58
|
[],
|
|
59
|
+
STORE_ID,
|
|
67
60
|
);
|
|
68
61
|
|
|
69
62
|
return (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{{includeFile template="client/src/game/gameStatus.css.hbs" output="client/src/gameStatus.css"}}
|
|
2
|
-
import './gameStatus.css';
|
|
3
|
-
import {useValue, STORE_ID} from './Store';
|
|
2
|
+
{{addImport "import './gameStatus.css';"}}
|
|
3
|
+
{{addImport "import {useValue, STORE_ID} from './Store';"}}
|
|
4
4
|
|
|
5
5
|
export const GameStatus = () => {
|
|
6
6
|
const gameStatus = useValue('gameStatus', STORE_ID);
|
|
@@ -10,12 +10,12 @@ const winner = useValue('winner', STORE_ID);
|
|
|
10
10
|
return (
|
|
11
11
|
<div id="gameStatus">
|
|
12
12
|
{gameStatus === 'playing' && (
|
|
13
|
-
<>Player <span className="player">{currentPlayer}</span
|
|
13
|
+
<>Player <span className="player">{currentPlayer}</span>'s turn</>
|
|
14
14
|
)}
|
|
15
15
|
{gameStatus === 'won' && (
|
|
16
16
|
<>Player <span className="winner">{winner}</span> wins!</>
|
|
17
17
|
)}
|
|
18
|
-
{gameStatus === 'draw' && <>It
|
|
18
|
+
{gameStatus === 'draw' && <>It's a draw!</>}
|
|
19
19
|
</div>
|
|
20
20
|
);
|
|
21
|
-
};
|
|
21
|
+
};
|