create-tinybase 0.2.5 → 0.3.1
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 +7 -4
- package/screenshots/chat.png +0 -0
- package/screenshots/drawing.png +0 -0
- package/screenshots/todos.png +0 -0
- package/templates/README.md.hbs +130 -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.ts.hbs +43 -0
- package/templates/server/package.json.hbs +6 -7
- package/templates/server/wrangler.toml.hbs +1 -1
- package/templates/server/index-do.ts.hbs +0 -22
- package/templates/server/index-node.ts.hbs +0 -8
- /package/templates/client/{.prettierrc.hbs → .prettierrc.json.hbs} +0 -0
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
{{includeFile template="client/src/drawing/colorPicker.css.hbs" output="client/src/colorPicker.css"}}
|
|
2
|
-
import './colorPicker.css';
|
|
3
|
-
import {
|
|
2
|
+
{{addImport "import './colorPicker.css';"}}
|
|
3
|
+
{{addImport "import {useValueState, STORE_ID} from './SettingsStore';"}}
|
|
4
4
|
|
|
5
5
|
export const ColorPicker = () => {
|
|
6
6
|
const colors = ['#d81b60', '#1976d2', '#388e3c', '#f57c00', '#7b1fa2', '#fff'];
|
|
7
|
-
const currentColor =
|
|
8
|
-
|
|
9
|
-
const setColor = useSetValueCallback(
|
|
10
|
-
'brushColor',
|
|
11
|
-
(color: string) => color,
|
|
12
|
-
[],
|
|
13
|
-
STORE_ID,
|
|
14
|
-
);
|
|
7
|
+
const [currentColor, setColor] = useValueState('brushColor', STORE_ID);
|
|
15
8
|
|
|
16
9
|
return (
|
|
17
10
|
<div id="colorPicker">
|
|
@@ -21,4 +14,4 @@ return (
|
|
|
21
14
|
))}
|
|
22
15
|
</div>
|
|
23
16
|
);
|
|
24
|
-
};
|
|
17
|
+
};
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{{includeFile template="client/src/drawing/drawingControls.css.hbs" output="client/src/drawingControls.css"}}
|
|
2
|
-
import './drawingControls.css';
|
|
3
|
-
import {useDelTableCallback, STORE_ID} from './CanvasStore';
|
|
2
|
+
{{addImport "import './drawingControls.css';"}}
|
|
3
|
+
{{addImport "import {useDelTableCallback, STORE_ID} from './CanvasStore';"}}
|
|
4
4
|
|
|
5
5
|
{{includeFile template="client/src/shared/Button.tsx.hbs" output="client/src/Button.{{ext}}"}}
|
|
6
|
-
import {Button} from './Button';
|
|
6
|
+
{{addImport "import {Button} from './Button';"}}
|
|
7
7
|
|
|
8
8
|
{{includeFile template="client/src/drawing/ColorPicker.tsx.hbs" output="client/src/ColorPicker.{{ext}}"}}
|
|
9
|
-
import {ColorPicker} from './ColorPicker';
|
|
9
|
+
{{addImport "import {ColorPicker} from './ColorPicker';"}}
|
|
10
10
|
|
|
11
11
|
{{includeFile template="client/src/drawing/BrushSize.tsx.hbs" output="client/src/BrushSize.{{ext}}"}}
|
|
12
|
-
import {BrushSize} from './BrushSize';
|
|
12
|
+
{{addImport "import {BrushSize} from './BrushSize';"}}
|
|
13
13
|
|
|
14
14
|
export const DrawingControls = () => {
|
|
15
|
-
const clearStrokes = useDelTableCallback('strokes',
|
|
15
|
+
const clearStrokes = useDelTableCallback('strokes', STORE_ID);
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
18
|
<div id="drawingControls">
|
|
@@ -21,4 +21,4 @@ return (
|
|
|
21
21
|
<Button onClick={clearStrokes} variant="primary">Clear</Button>
|
|
22
22
|
</div>
|
|
23
23
|
);
|
|
24
|
-
};
|
|
24
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{{#if schemas}}
|
|
2
|
-
import {createStore} from 'tinybase/with-schemas';
|
|
3
|
-
import * as UiReact from 'tinybase/ui-react/with-schemas';
|
|
4
|
-
import {type NoTablesSchema} from 'tinybase/with-schemas';
|
|
2
|
+
{{addImport "import {createStore} from 'tinybase/with-schemas';"}}
|
|
3
|
+
{{addImport "import * as UiReact from 'tinybase/ui-react/with-schemas';"}}
|
|
4
|
+
{{addImport "import {type NoTablesSchema} from 'tinybase/with-schemas';"}}
|
|
5
5
|
{{else}}
|
|
6
|
-
import {createStore} from 'tinybase';
|
|
7
|
-
import {useCreateStore, useProvideStore,
|
|
6
|
+
{{addImport "import {createStore} from 'tinybase';"}}
|
|
7
|
+
{{addImport "import {useCreateStore, useProvideStore, useValueState, useValues} from 'tinybase/ui-react';"}}
|
|
8
8
|
{{/if}}
|
|
9
9
|
|
|
10
10
|
export const STORE_ID = 'settings';
|
|
@@ -17,12 +17,12 @@ export const STORE_ID = 'settings';
|
|
|
17
17
|
|
|
18
18
|
type Schemas = [NoTablesSchema, typeof VALUES_SCHEMA];
|
|
19
19
|
|
|
20
|
-
const {useCreateStore,
|
|
20
|
+
const {useCreateStore, {{#if persist}}useCreatePersister, {{/if}}useProvideStore, useValues, useValueState} = UiReact as UiReact.WithSchemas<Schemas>;
|
|
21
21
|
{{/if}}
|
|
22
22
|
|
|
23
|
-
export {
|
|
23
|
+
export {useValueState, useValues};
|
|
24
24
|
|
|
25
|
-
export const SettingsStore = () => {
|
|
25
|
+
export const SettingsStore = ({onReady}: {onReady?: () => void}) => {
|
|
26
26
|
const store = useCreateStore(() =>
|
|
27
27
|
createStore(){{#if schemas}}
|
|
28
28
|
.setValuesSchema(VALUES_SCHEMA){{/if}}
|
|
@@ -32,5 +32,78 @@ createStore(){{#if schemas}}
|
|
|
32
32
|
|
|
33
33
|
useProvideStore(STORE_ID, store);
|
|
34
34
|
|
|
35
|
+
{{#if persist}}
|
|
36
|
+
{{#if persistLocalStorage}}
|
|
37
|
+
{{#if schemas}}
|
|
38
|
+
{{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser/with-schemas';"}}
|
|
39
|
+
{{else}}
|
|
40
|
+
{{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser';"}}
|
|
41
|
+
{{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
|
|
42
|
+
{{/if}}
|
|
43
|
+
|
|
44
|
+
useCreatePersister(
|
|
45
|
+
store,
|
|
46
|
+
(store) => createLocalPersister(store, STORE_ID),
|
|
47
|
+
[],
|
|
48
|
+
async (persister) => {
|
|
49
|
+
await persister.load();
|
|
50
|
+
await persister.startAutoSave();
|
|
51
|
+
onReady?.();
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
{{/if}}
|
|
55
|
+
{{#if persistSqlite}}
|
|
56
|
+
{{#if schemas}}
|
|
57
|
+
{{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm/with-schemas';"}}
|
|
58
|
+
{{else}}
|
|
59
|
+
{{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';"}}
|
|
60
|
+
{{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
|
|
61
|
+
{{/if}}
|
|
62
|
+
{{includeFile template="client/src/shared/sqlite.ts.hbs" output="client/src/sqlite.{{ext}}"}}
|
|
63
|
+
{{addImport "import {getDb} from './sqlite';"}}
|
|
64
|
+
useCreatePersister(
|
|
65
|
+
store,
|
|
66
|
+
async (store) => {
|
|
67
|
+
const {sqlite3, db} = await getDb();
|
|
68
|
+
return createSqliteWasmPersister(store, sqlite3, db, STORE_ID);
|
|
69
|
+
},
|
|
70
|
+
[],
|
|
71
|
+
async (persister) => {
|
|
72
|
+
await persister.load();
|
|
73
|
+
await persister.startAutoSave();
|
|
74
|
+
onReady?.();
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
{{/if}}
|
|
78
|
+
{{#if persistPglite}}
|
|
79
|
+
{{#if schemas}}
|
|
80
|
+
{{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite/with-schemas';"}}
|
|
81
|
+
{{else}}
|
|
82
|
+
{{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite';"}}
|
|
83
|
+
{{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
|
|
84
|
+
{{/if}}
|
|
85
|
+
{{includeFile template="client/src/shared/pglite.ts.hbs" output="client/src/pglite.{{ext}}"}}
|
|
86
|
+
{{addImport "import {getPgLite} from './pglite';"}}
|
|
87
|
+
|
|
88
|
+
useCreatePersister(
|
|
89
|
+
store,
|
|
90
|
+
async (store) => {
|
|
91
|
+
const pgLite = await getPgLite();
|
|
92
|
+
return await createPglitePersister(store, pgLite, STORE_ID);
|
|
93
|
+
},
|
|
94
|
+
[],
|
|
95
|
+
async (persister) => {
|
|
96
|
+
await persister.load();
|
|
97
|
+
await persister.startAutoSave();
|
|
98
|
+
onReady?.();
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
{{/if}}
|
|
102
|
+
{{/if}}
|
|
103
|
+
|
|
104
|
+
{{#unless persist}}{{#unless sync}}
|
|
105
|
+
onReady?.();
|
|
106
|
+
{{/unless}}{{/unless}}
|
|
107
|
+
|
|
35
108
|
return null;
|
|
36
109
|
};
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
{{includeFile template="client/src/drawing/settingsStore.ts.hbs" output="client/src/settingsStore.ts"}}
|
|
2
|
-
import {settingsStore} from './settingsStore';
|
|
2
|
+
{{addImport "import {settingsStore, settingsStoreReady} from './settingsStore';"}}
|
|
3
3
|
|
|
4
4
|
{{includeFile template="client/src/drawing/canvasStore.ts.hbs" output="client/src/canvasStore.ts"}}
|
|
5
|
-
import {canvasStore} from './canvasStore';
|
|
5
|
+
{{addImport "import {canvasStore, canvasStoreReady} from './canvasStore';"}}
|
|
6
|
+
|
|
7
|
+
{{includeFile template="client/src/shared/loading.ts.hbs" output="client/src/loading.{{ext}}"}}
|
|
8
|
+
{{addImport "import {showLoading, hideLoading} from './loading';"}}
|
|
6
9
|
|
|
7
10
|
{{includeFile template="client/src/drawing/drawingControls.ts.hbs" output="client/src/drawingControls.{{ext}}"}}
|
|
8
|
-
import {createDrawingControls} from './drawingControls';
|
|
11
|
+
{{addImport "import {createDrawingControls} from './drawingControls';"}}
|
|
9
12
|
|
|
10
13
|
{{includeFile template="client/src/drawing/canvas.ts.hbs" output="client/src/canvas.{{ext}}"}}
|
|
11
|
-
import {createCanvas} from './canvas';
|
|
14
|
+
{{addImport "import {createCanvas} from './canvas';"}}
|
|
12
15
|
|
|
13
|
-
const app = () => {
|
|
16
|
+
export const app = async () => {
|
|
14
17
|
const appContainer = document.getElementById('app')!;
|
|
15
18
|
|
|
19
|
+
// Show loading spinner
|
|
20
|
+
const loadingDiv = showLoading(appContainer);
|
|
21
|
+
|
|
22
|
+
// Wait for stores to be ready
|
|
23
|
+
await Promise.all([settingsStoreReady, canvasStoreReady]);
|
|
24
|
+
|
|
25
|
+
// Remove loading spinner
|
|
26
|
+
hideLoading(loadingDiv);
|
|
27
|
+
|
|
16
28
|
appContainer.appendChild(createDrawingControls(settingsStore, canvasStore));
|
|
17
29
|
appContainer.appendChild(createCanvas(settingsStore, canvasStore));
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export {app};
|
|
30
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{{includeFile template="client/src/drawing/brushSize.css.hbs" output="client/src/brushSize.css"}}
|
|
2
|
-
import './brushSize.css';
|
|
3
|
-
import type {
|
|
2
|
+
{{addImport "import './brushSize.css';"}}
|
|
3
|
+
{{addImport "import type {SettingsStore} from './settingsStore';"}}
|
|
4
4
|
|
|
5
5
|
export const createBrushSize = (store: SettingsStore): HTMLDivElement => {
|
|
6
6
|
const container = document.createElement('div');
|
|
@@ -13,11 +13,15 @@ const sizeSlider = document.createElement('input');
|
|
|
13
13
|
sizeSlider.type = 'range';
|
|
14
14
|
sizeSlider.min = '1';
|
|
15
15
|
sizeSlider.max = '50';
|
|
16
|
-
sizeSlider.value = '5';
|
|
17
16
|
|
|
18
17
|
const sizeLabel = document.createElement('span');
|
|
19
18
|
sizeLabel.id = 'brushSizeValue';
|
|
20
|
-
|
|
19
|
+
|
|
20
|
+
const updateSize = () => {
|
|
21
|
+
const size = store.getValue('brushSize') ?? 5;
|
|
22
|
+
sizeSlider.value = `${size}`;
|
|
23
|
+
sizeLabel.textContent = `${size}`;
|
|
24
|
+
};
|
|
21
25
|
|
|
22
26
|
sizeSlider.addEventListener('input', () => {
|
|
23
27
|
const size = parseInt(sizeSlider.value);
|
|
@@ -25,9 +29,12 @@ store.setValue('brushSize', size);
|
|
|
25
29
|
sizeLabel.textContent = `${size}`;
|
|
26
30
|
});
|
|
27
31
|
|
|
32
|
+
store.addValueListener('brushSize', updateSize);
|
|
33
|
+
updateSize();
|
|
34
|
+
|
|
28
35
|
container.appendChild(label);
|
|
29
36
|
container.appendChild(sizeSlider);
|
|
30
37
|
container.appendChild(sizeLabel);
|
|
31
38
|
|
|
32
39
|
return container;
|
|
33
|
-
};
|
|
40
|
+
};
|
|
@@ -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
|
+
};
|