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.
Files changed (79) hide show
  1. package/README.md +62 -18
  2. package/cli.js +2 -2
  3. package/package.json +3 -3
  4. package/screenshots/chat.png +0 -0
  5. package/screenshots/todos.png +0 -0
  6. package/templates/README.md.hbs +13 -12
  7. package/templates/client/eslint.config.js.hbs +7 -0
  8. package/templates/client/package.json.hbs +28 -8
  9. package/templates/client/src/chat/App.tsx.hbs +28 -22
  10. package/templates/client/src/chat/ChatStore.tsx.hbs +108 -27
  11. package/templates/client/src/chat/Message.tsx.hbs +8 -9
  12. package/templates/client/src/chat/MessageInput.tsx.hbs +7 -7
  13. package/templates/client/src/chat/Messages.tsx.hbs +5 -15
  14. package/templates/client/src/chat/SettingsStore.tsx.hbs +86 -9
  15. package/templates/client/src/chat/UsernameInput.tsx.hbs +6 -11
  16. package/templates/client/src/chat/app.ts.hbs +18 -18
  17. package/templates/client/src/chat/chatStore.ts.hbs +97 -35
  18. package/templates/client/src/chat/message.ts.hbs +4 -3
  19. package/templates/client/src/chat/messageInput.css.hbs +1 -1
  20. package/templates/client/src/chat/messageInput.ts.hbs +7 -8
  21. package/templates/client/src/chat/messages.ts.hbs +7 -12
  22. package/templates/client/src/chat/settingsStore.ts.hbs +65 -6
  23. package/templates/client/src/chat/usernameInput.css.hbs +1 -1
  24. package/templates/client/src/chat/usernameInput.ts.hbs +6 -6
  25. package/templates/client/src/chat/utils.ts.hbs +26 -0
  26. package/templates/client/src/drawing/App.tsx.hbs +26 -20
  27. package/templates/client/src/drawing/BrushSize.tsx.hbs +8 -11
  28. package/templates/client/src/drawing/Canvas.tsx.hbs +65 -73
  29. package/templates/client/src/drawing/CanvasStore.tsx.hbs +104 -18
  30. package/templates/client/src/drawing/ColorPicker.tsx.hbs +4 -11
  31. package/templates/client/src/drawing/DrawingControls.tsx.hbs +7 -7
  32. package/templates/client/src/drawing/SettingsStore.tsx.hbs +81 -8
  33. package/templates/client/src/drawing/app.ts.hbs +18 -8
  34. package/templates/client/src/drawing/brushSize.ts.hbs +12 -5
  35. package/templates/client/src/drawing/canvas.ts.hbs +84 -86
  36. package/templates/client/src/drawing/canvasStore.ts.hbs +93 -26
  37. package/templates/client/src/drawing/colorPicker.ts.hbs +3 -3
  38. package/templates/client/src/drawing/drawingControls.ts.hbs +7 -7
  39. package/templates/client/src/drawing/settingsStore.ts.hbs +63 -8
  40. package/templates/client/src/game/App.tsx.hbs +20 -16
  41. package/templates/client/src/game/Board.tsx.hbs +8 -8
  42. package/templates/client/src/game/Game.tsx.hbs +14 -21
  43. package/templates/client/src/game/GameStatus.tsx.hbs +5 -5
  44. package/templates/client/src/game/Square.tsx.hbs +6 -11
  45. package/templates/client/src/game/Store.tsx.hbs +106 -16
  46. package/templates/client/src/game/app.ts.hbs +17 -6
  47. package/templates/client/src/game/board.ts.hbs +7 -7
  48. package/templates/client/src/game/game.ts.hbs +12 -18
  49. package/templates/client/src/game/gameStatus.ts.hbs +3 -3
  50. package/templates/client/src/game/square.ts.hbs +4 -4
  51. package/templates/client/src/game/store.ts.hbs +95 -23
  52. package/templates/client/src/index.tsx.hbs +5 -7
  53. package/templates/client/src/shared/Button.tsx.hbs +3 -3
  54. package/templates/client/src/shared/Input.tsx.hbs +2 -2
  55. package/templates/client/src/shared/Loading.tsx.hbs +5 -0
  56. package/templates/client/src/shared/button.ts.hbs +2 -2
  57. package/templates/client/src/shared/config.ts.hbs +4 -6
  58. package/templates/client/src/shared/input.ts.hbs +2 -2
  59. package/templates/client/src/shared/loading.css.hbs +21 -0
  60. package/templates/client/src/shared/loading.ts.hbs +13 -0
  61. package/templates/client/src/shared/pglite.ts.hbs +10 -0
  62. package/templates/client/src/shared/sqlite.ts.hbs +17 -0
  63. package/templates/client/src/todos/App.tsx.hbs +22 -22
  64. package/templates/client/src/todos/Store.tsx.hbs +106 -23
  65. package/templates/client/src/todos/TodoInput.tsx.hbs +6 -8
  66. package/templates/client/src/todos/TodoItem.tsx.hbs +5 -6
  67. package/templates/client/src/todos/TodoList.tsx.hbs +5 -6
  68. package/templates/client/src/todos/app.ts.hbs +16 -10
  69. package/templates/client/src/todos/store.ts.hbs +94 -30
  70. package/templates/client/src/todos/todoInput.ts.hbs +5 -8
  71. package/templates/client/src/todos/todoItem.ts.hbs +3 -4
  72. package/templates/client/src/todos/todoList.ts.hbs +6 -8
  73. package/templates/client/vite-env.d.ts.hbs +4 -0
  74. package/templates/client/vite.config.js.hbs +53 -3
  75. package/templates/server/index-do.ts.hbs +10 -14
  76. package/templates/server/index-node.ts.hbs +3 -3
  77. package/templates/server/package.json.hbs +5 -5
  78. package/templates/server/wrangler.toml.hbs +1 -1
  79. /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 {getUniqueId} from 'tinybase';
4
- import type {Store as SettingsStore} from './settingsStore';
5
- import type {Store as CanvasStore} from './canvasStore';
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 strokes = canvasStore.getTable('strokes');
23
- Object.entries(strokes).forEach(([id, stroke]: [string, any]) => {
24
- const points: Array<{x: number; y: number}> = [];
25
- let i = 0;
26
- while (stroke[`x${i}`] !== undefined && stroke[`y${i}`] !== undefined) {
27
- points.push({x: stroke[`x${i}`], y: stroke[`y${i}`]});
28
- i++;
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
- if (points.length > 0) {
31
- ctx.strokeStyle = stroke.color;
32
- ctx.lineWidth = stroke.size * 2;
33
- ctx.lineCap = 'round';
34
- ctx.lineJoin = 'round';
35
- ctx.beginPath();
36
- ctx.moveTo(points[0].x, points[0].y);
37
- for (let i = 1; i < points.length; i++) { ctx.lineTo(points[i].x, points[i].y); } ctx.stroke(); } }); }; const addPoint=(e: MouseEvent | TouchEvent)=> {
38
- if (!currentStrokeId) return;
39
-
40
- const rect = canvas!.getBoundingClientRect();
41
- const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
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(STORE_ID){{#if schemas}}
17
+ export const canvasStore = createMergeableStore(){{#if schemas}}
20
18
  .setTablesSchema(TABLES_SCHEMA){{/if}}
21
19
  .setDefaultContent([{strokes: {}}, {}]);
22
20
 
23
- {{#if sync}}
24
- {{includeFile template="client/src/shared/config.ts.hbs" output="client/src/config.ts"}}
25
- {{addImport "import {SERVER} from './config';"}}
26
- {{addImport "import ReconnectingWebSocket from 'reconnecting-websocket';"}}
27
- {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
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
- export type CanvasStore = typeof canvasStore;
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 {Store as SettingsStore} from './settingsStore';
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 {Store as SettingsStore} from './settingsStore';
4
- import type {Store as CanvasStore} from './canvasStore';
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
- .setValue('brushColor', '#d81b60')
19
- .setValue('brushSize', 5);
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
- {{#if schemas}}
4
- import {Provider} from 'tinybase/ui-react/with-schemas';
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
- <Game />
22
- <Inspector />
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(',').map(Number));
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 {useStore, useSetValuesCallback, useTableListener, STORE_ID} from './Store';
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], // rows
14
- [0, 3, 6], [1, 4, 7], [2, 5, 8], // columns
15
- [0, 4, 8], [2, 4, 6], // diagonals
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[a]?.value;
33
- const cellB = board[b]?.value;
34
- const cellC = board[c]?.value;
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
- // Check for draw
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>'s turn</>
13
+ <>Player <span className="player">{currentPlayer}</span>&apos;s turn</>
14
14
  )}
15
15
  {gameStatus === 'won' && (
16
16
  <>Player <span className="winner">{winner}</span> wins!</>
17
17
  )}
18
- {gameStatus === 'draw' && <>It's a draw!</>}
18
+ {gameStatus === 'draw' && <>It&apos;s a draw!</>}
19
19
  </div>
20
20
  );
21
- };
21
+ };