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.
Files changed (81) hide show
  1. package/README.md +62 -18
  2. package/cli.js +2 -2
  3. package/package.json +7 -4
  4. package/screenshots/chat.png +0 -0
  5. package/screenshots/drawing.png +0 -0
  6. package/screenshots/todos.png +0 -0
  7. package/templates/README.md.hbs +130 -12
  8. package/templates/client/eslint.config.js.hbs +7 -0
  9. package/templates/client/package.json.hbs +28 -8
  10. package/templates/client/src/chat/App.tsx.hbs +28 -22
  11. package/templates/client/src/chat/ChatStore.tsx.hbs +108 -27
  12. package/templates/client/src/chat/Message.tsx.hbs +8 -9
  13. package/templates/client/src/chat/MessageInput.tsx.hbs +7 -7
  14. package/templates/client/src/chat/Messages.tsx.hbs +5 -15
  15. package/templates/client/src/chat/SettingsStore.tsx.hbs +86 -9
  16. package/templates/client/src/chat/UsernameInput.tsx.hbs +6 -11
  17. package/templates/client/src/chat/app.ts.hbs +18 -18
  18. package/templates/client/src/chat/chatStore.ts.hbs +97 -35
  19. package/templates/client/src/chat/message.ts.hbs +4 -3
  20. package/templates/client/src/chat/messageInput.css.hbs +1 -1
  21. package/templates/client/src/chat/messageInput.ts.hbs +7 -8
  22. package/templates/client/src/chat/messages.ts.hbs +7 -12
  23. package/templates/client/src/chat/settingsStore.ts.hbs +65 -6
  24. package/templates/client/src/chat/usernameInput.css.hbs +1 -1
  25. package/templates/client/src/chat/usernameInput.ts.hbs +6 -6
  26. package/templates/client/src/chat/utils.ts.hbs +26 -0
  27. package/templates/client/src/drawing/App.tsx.hbs +26 -20
  28. package/templates/client/src/drawing/BrushSize.tsx.hbs +8 -11
  29. package/templates/client/src/drawing/Canvas.tsx.hbs +65 -73
  30. package/templates/client/src/drawing/CanvasStore.tsx.hbs +104 -18
  31. package/templates/client/src/drawing/ColorPicker.tsx.hbs +4 -11
  32. package/templates/client/src/drawing/DrawingControls.tsx.hbs +7 -7
  33. package/templates/client/src/drawing/SettingsStore.tsx.hbs +81 -8
  34. package/templates/client/src/drawing/app.ts.hbs +18 -8
  35. package/templates/client/src/drawing/brushSize.ts.hbs +12 -5
  36. package/templates/client/src/drawing/canvas.ts.hbs +84 -86
  37. package/templates/client/src/drawing/canvasStore.ts.hbs +93 -26
  38. package/templates/client/src/drawing/colorPicker.ts.hbs +3 -3
  39. package/templates/client/src/drawing/drawingControls.ts.hbs +7 -7
  40. package/templates/client/src/drawing/settingsStore.ts.hbs +63 -8
  41. package/templates/client/src/game/App.tsx.hbs +20 -16
  42. package/templates/client/src/game/Board.tsx.hbs +8 -8
  43. package/templates/client/src/game/Game.tsx.hbs +14 -21
  44. package/templates/client/src/game/GameStatus.tsx.hbs +5 -5
  45. package/templates/client/src/game/Square.tsx.hbs +6 -11
  46. package/templates/client/src/game/Store.tsx.hbs +106 -16
  47. package/templates/client/src/game/app.ts.hbs +17 -6
  48. package/templates/client/src/game/board.ts.hbs +7 -7
  49. package/templates/client/src/game/game.ts.hbs +12 -18
  50. package/templates/client/src/game/gameStatus.ts.hbs +3 -3
  51. package/templates/client/src/game/square.ts.hbs +4 -4
  52. package/templates/client/src/game/store.ts.hbs +95 -23
  53. package/templates/client/src/index.tsx.hbs +5 -7
  54. package/templates/client/src/shared/Button.tsx.hbs +3 -3
  55. package/templates/client/src/shared/Input.tsx.hbs +2 -2
  56. package/templates/client/src/shared/Loading.tsx.hbs +5 -0
  57. package/templates/client/src/shared/button.ts.hbs +2 -2
  58. package/templates/client/src/shared/config.ts.hbs +4 -6
  59. package/templates/client/src/shared/input.ts.hbs +2 -2
  60. package/templates/client/src/shared/loading.css.hbs +21 -0
  61. package/templates/client/src/shared/loading.ts.hbs +13 -0
  62. package/templates/client/src/shared/pglite.ts.hbs +10 -0
  63. package/templates/client/src/shared/sqlite.ts.hbs +17 -0
  64. package/templates/client/src/todos/App.tsx.hbs +22 -22
  65. package/templates/client/src/todos/Store.tsx.hbs +106 -23
  66. package/templates/client/src/todos/TodoInput.tsx.hbs +6 -8
  67. package/templates/client/src/todos/TodoItem.tsx.hbs +5 -6
  68. package/templates/client/src/todos/TodoList.tsx.hbs +5 -6
  69. package/templates/client/src/todos/app.ts.hbs +16 -10
  70. package/templates/client/src/todos/store.ts.hbs +94 -30
  71. package/templates/client/src/todos/todoInput.ts.hbs +5 -8
  72. package/templates/client/src/todos/todoItem.ts.hbs +3 -4
  73. package/templates/client/src/todos/todoList.ts.hbs +6 -8
  74. package/templates/client/vite-env.d.ts.hbs +4 -0
  75. package/templates/client/vite.config.js.hbs +53 -3
  76. package/templates/server/index.ts.hbs +43 -0
  77. package/templates/server/package.json.hbs +6 -7
  78. package/templates/server/wrangler.toml.hbs +1 -1
  79. package/templates/server/index-do.ts.hbs +0 -22
  80. package/templates/server/index-node.ts.hbs +0 -8
  81. /package/templates/client/{.prettierrc.hbs → .prettierrc.json.hbs} +0 -0
@@ -1,56 +1,130 @@
1
1
  {{#if schemas}}
2
- import {createMergeableStore} from 'tinybase/with-schemas';
3
- import * as UiReact from 'tinybase/ui-react/with-schemas';
4
- import {type NoValuesSchema} from 'tinybase/with-schemas';
2
+ {{addImport "import {createMergeableStore, type Row} from 'tinybase/with-schemas';"}}
3
+ {{addImport "import * as UiReact from 'tinybase/ui-react/with-schemas';"}}
4
+ {{addImport "import {type NoValuesSchema} from 'tinybase/with-schemas';"}}
5
5
  {{else}}
6
- import {createMergeableStore} from 'tinybase';
7
- import {useCreateStore, useProvideStore, useAddRowCallback, useRow, useRowIds, useStore} from 'tinybase/ui-react';
6
+ {{addImport "import {createMergeableStore} from 'tinybase';"}}
7
+ {{addImport "import {useCreateMergeableStore, useProvideStore, useAddRowCallback, useRow, useSortedRowIds, useStore} from 'tinybase/ui-react';"}}
8
8
  {{/if}}
9
9
 
10
+ {{includeFile template="client/src/chat/utils.ts.hbs" output="client/src/utils.{{ext}}"}}
11
+ {{addImport "import {getDefaultContent} from './utils';"}}
12
+
10
13
  export const STORE_ID = 'chat';
11
14
 
12
15
  {{#if schemas}}
13
16
  const TABLES_SCHEMA = {
14
17
  messages: {
15
- username: {type: 'string'},
16
- text: {type: 'string'},
17
- timestamp: {type: 'number'},
18
+ username: {type: 'string', default: ''},
19
+ text: {type: 'string', default: ''},
20
+ timestamp: {type: 'number', default: 0},
18
21
  },
19
22
  } as const;
20
23
 
21
24
  type Schemas = [typeof TABLES_SCHEMA, NoValuesSchema];
22
25
 
23
- const {useCreateStore, useProvideStore, useAddRowCallback, useRow, useRowIds, useStore} = UiReact as UiReact.WithSchemas<Schemas>;
26
+ const {useAddRowCallback, useCreateMergeableStore, {{#if persist}}useCreatePersister, {{/if}}useProvideStore, useRow, useSortedRowIds, useStore} = UiReact as UiReact.WithSchemas<Schemas>;
24
27
  {{/if}}
25
28
 
26
- export {useAddRowCallback, useRow, useRowIds, useStore};
29
+ export type MessageRow = {{#if schemas}}Row<typeof TABLES_SCHEMA, 'messages'>{{else}}{username: string; text: string; timestamp: number}{{/if}};
30
+
31
+ export {useAddRowCallback, useRow, useSortedRowIds, useStore};
27
32
 
28
- export const ChatStore = () => {
29
- const store = useCreateStore(() =>
30
- createMergeableStore(STORE_ID){{#if schemas}}
33
+ export const ChatStore = ({onReady}: {onReady?: () => void}) => {
34
+ const store = useCreateMergeableStore(() =>
35
+ createMergeableStore(){{#if schemas}}
31
36
  .setTablesSchema(TABLES_SCHEMA){{/if}}
32
- .setDefaultContent([
33
- {
34
- messages: {
35
- '1': {username: 'Alice', text: 'Hello!', timestamp: Date.now() - 60000},
36
- '2': {username: 'Bob', text: 'Hi there!', timestamp: Date.now() - 30000},
37
- },
38
- },
39
- {},
40
- ]),
37
+ .setDefaultContent(getDefaultContent()),
41
38
  );
42
39
 
43
40
  useProvideStore(STORE_ID, store);
44
41
 
42
+ {{#if persist}}
43
+ {{#if persistLocalStorage}}
44
+ {{#if schemas}}
45
+ {{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser/with-schemas';"}}
46
+ {{else}}
47
+ {{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser';"}}
48
+ {{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
49
+ {{/if}}
50
+
51
+ useCreatePersister(
52
+ store,
53
+ (store) => createLocalPersister(store, STORE_ID),
54
+ [],
55
+ async (persister) => {
56
+ await persister.load();
57
+ await persister.startAutoSave();
58
+ onReady?.();
59
+ },
60
+ );
61
+ {{/if}}
62
+ {{#if persistSqlite}}
63
+ {{#if schemas}}
64
+ {{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm/with-schemas';"}}
65
+ {{else}}
66
+ {{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';"}}
67
+ {{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
68
+ {{/if}}
69
+ {{includeFile template="client/src/shared/sqlite.ts.hbs" output="client/src/sqlite.{{ext}}"}}
70
+ {{addImport "import {getDb} from './sqlite';"}}
71
+
72
+ useCreatePersister(
73
+ store,
74
+ async (store) => {
75
+ const {sqlite3, db} = await getDb();
76
+ return createSqliteWasmPersister(store, sqlite3, db, STORE_ID);
77
+ },
78
+ [],
79
+ async (persister) => {
80
+ await persister.load();
81
+ await persister.startAutoSave();
82
+ onReady?.();
83
+ },
84
+ );
85
+ {{/if}}
86
+ {{#if persistPglite}}
87
+ {{#if schemas}}
88
+ {{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite/with-schemas';"}}
89
+ {{else}}
90
+ {{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite';"}}
91
+ {{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
92
+ {{/if}}
93
+ {{includeFile template="client/src/shared/pglite.ts.hbs" output="client/src/pglite.{{ext}}"}}
94
+ {{addImport "import {getPgLite} from './pglite';"}}
95
+
96
+ useCreatePersister(
97
+ store,
98
+ async (store) => {
99
+ const pgLite = await getPgLite();
100
+ return await createPglitePersister(store, pgLite, 'chat');
101
+ },
102
+ [],
103
+ async (persister) => {
104
+ await persister.load();
105
+ await persister.startAutoSave();
106
+ onReady?.();
107
+ },
108
+ );
109
+ {{/if}}
110
+ {{/if}}
111
+
45
112
  {{#if sync}}
46
113
  {{includeFile template="client/src/shared/config.ts.hbs" output="client/src/config.ts"}}
47
114
  {{addImport "import {SERVER} from './config';"}}
48
115
  {{addImport "import ReconnectingWebSocket from 'reconnecting-websocket';"}}
49
- {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
50
- {{addImport "import {useCreateSynchronizer} from 'tinybase/ui-react';"}}
51
- {{addImport "import type {MergeableStore} from 'tinybase';"}}
116
+ {{#if schemas}}
117
+ {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client/with-schemas';"}}
118
+ {{else}}
119
+ {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
120
+ {{/if}}
121
+ {{#if schemas}}
122
+ {{addImport "import {useCreateSynchronizer} from 'tinybase/ui-react/with-schemas';"}}
123
+ {{else}}
124
+ {{addImport "import {useCreateSynchronizer} from 'tinybase/ui-react';"}}
125
+ {{/if}}
52
126
 
53
- useCreateSynchronizer(store, async (store: MergeableStore) => {
127
+ useCreateSynchronizer(store, async (store) => {
54
128
  const serverPathId = location.pathname;
55
129
  const synchronizer = await createWsSynchronizer(
56
130
  store,
@@ -59,12 +133,19 @@ useProvideStore(STORE_ID, store);
59
133
  await synchronizer.startSync();
60
134
 
61
135
  synchronizer.getWebSocket().addEventListener('open', () => {
62
- synchronizer.load().then(() => synchronizer.save());
136
+ synchronizer.load().then(() => {
137
+ synchronizer.save();
138
+ {{#unless persist}}onReady?.();{{/unless}}
139
+ });
63
140
  });
64
141
 
65
142
  return synchronizer;
66
143
  });
67
144
  {{/if}}
68
145
 
146
+ {{#unless persist}}{{#unless sync}}
147
+ onReady?.();
148
+ {{/unless}}{{/unless}}
149
+
69
150
  return null;
70
151
  };
@@ -1,21 +1,20 @@
1
- import {useRow, STORE_ID} from './ChatStore';
1
+ {{addImport "import {useRow, type MessageRow, STORE_ID} from './ChatStore';"}}
2
2
 
3
3
  {{includeFile template="client/src/chat/message.css.hbs" output="client/src/message.css"}}
4
- import './message.css';
4
+ {{addImport "import './message.css';"}}
5
5
 
6
6
  interface MessageProps {
7
7
  rowId: string;
8
8
  }
9
9
 
10
10
  export const Message = ({rowId}: MessageProps) => {
11
- const row = useRow('messages', rowId, 'chat');
12
- const time = new Date((row as any).timestamp).toLocaleTimeString();
11
+ const {username, text, timestamp} = useRow('messages', rowId, STORE_ID) as MessageRow;
13
12
 
14
13
  return (
15
14
  <div className="message">
16
- <span className="username">{(row as any).username}:</span>
17
- <span className="text">{(row as any).text}</span>
18
- <span className="time">{time}</span>
15
+ <span className="username">{username}:</span>
16
+ <span className="text">{text}</span>
17
+ <span className="time">{new Date(timestamp).toLocaleTimeString()}</span>
19
18
  </div>
20
- );
21
- };
19
+ )
20
+ };
@@ -1,14 +1,14 @@
1
1
  {{includeFile template="client/src/chat/messageInput.css.hbs" output="client/src/messageInput.css"}}
2
- import {useState} from 'react';
3
- import {useAddRowCallback, STORE_ID as CHAT_STORE_ID} from './ChatStore';
4
- import {useValue, STORE_ID as SETTINGS_STORE_ID} from './SettingsStore';
5
- import './messageInput.css';
2
+ {{addImport "import {useState} from 'react';"}}
3
+ {{addImport "import {useAddRowCallback, STORE_ID as CHAT_STORE_ID} from './ChatStore';"}}
4
+ {{addImport "import {useValue, STORE_ID as SETTINGS_STORE_ID} from './SettingsStore';"}}
5
+ {{addImport "import './messageInput.css';"}}
6
6
 
7
7
  {{includeFile template="client/src/shared/Button.tsx.hbs" output="client/src/Button.{{ext}}"}}
8
- import {Button} from './Button';
8
+ {{addImport "import {Button} from './Button';"}}
9
9
 
10
10
  {{includeFile template="client/src/shared/Input.tsx.hbs" output="client/src/Input.{{ext}}"}}
11
- import {Input} from './Input';
11
+ {{addImport "import {Input} from './Input';"}}
12
12
 
13
13
  export const MessageInput = () => {
14
14
  const [message, setMessage] = useState('');
@@ -39,4 +39,4 @@ return (
39
39
  <Button type="submit" variant="primary">Send</Button>
40
40
  </form>
41
41
  );
42
- };
42
+ };
@@ -1,23 +1,13 @@
1
- import {useMemo} from 'react';
2
- import {useRowIds, useStore, STORE_ID} from './ChatStore';
1
+ {{addImport "import {useSortedRowIds, STORE_ID} from './ChatStore';"}}
3
2
 
4
3
  {{includeFile template="client/src/chat/messages.css.hbs" output="client/src/messages.css"}}
5
- import './messages.css';
4
+ {{addImport "import './messages.css';"}}
6
5
 
7
6
  {{includeFile template="client/src/chat/Message.tsx.hbs" output="client/src/Message.{{ext}}"}}
8
- import {Message} from './Message';
7
+ {{addImport "import {Message} from './Message';"}}
9
8
 
10
9
  export const Messages = () => {
11
- const store = useStore(STORE_ID);
12
- const messageIds = useRowIds('messages', STORE_ID);
13
-
14
- const sortedIds = useMemo(() => {
15
- return [...messageIds].sort((a, b) => {
16
- const rowA = store.getRow('messages', a) as any;
17
- const rowB = store.getRow('messages', b) as any;
18
- return rowA.timestamp - rowB.timestamp;
19
- });
20
- }, [messageIds, store]);
10
+ const sortedIds = useSortedRowIds('messages', 'timestamp', false, 0, undefined, STORE_ID);
21
11
 
22
12
  return (
23
13
  <div id="messages">
@@ -26,4 +16,4 @@ return (
26
16
  ))}
27
17
  </div>
28
18
  );
29
- };
19
+ };
@@ -1,12 +1,15 @@
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, useValue, useSetValueCallback} from 'tinybase/ui-react';
6
+ {{addImport "import {createStore} from 'tinybase';"}}
7
+ {{addImport "import {useCreateStore, useProvideStore, useValue, useValueState} from 'tinybase/ui-react';"}}
8
8
  {{/if}}
9
9
 
10
+ {{includeFile template="client/src/chat/utils.ts.hbs" output="client/src/utils.{{ext}}"}}
11
+ {{addImport "import {getRandomName} from './utils';"}}
12
+
10
13
  export const STORE_ID = 'settings';
11
14
 
12
15
  {{#if schemas}}
@@ -16,19 +19,93 @@ export const STORE_ID = 'settings';
16
19
 
17
20
  type Schemas = [NoTablesSchema, typeof VALUES_SCHEMA];
18
21
 
19
- const {useCreateStore, useProvideStore, useValue, useSetValueCallback} = UiReact as UiReact.WithSchemas<Schemas>;
22
+ const {useCreateStore, {{#if persist}}useCreatePersister, {{/if}}useProvideStore, useValue, useValueState} = UiReact as UiReact.WithSchemas<Schemas>;
20
23
  {{/if}}
21
24
 
22
- export {useValue, useSetValueCallback};
25
+ export {useValue, useValueState};
23
26
 
24
- export const SettingsStore = () => {
27
+ export const SettingsStore = ({onReady}: {onReady?: () => void}) => {
25
28
  const store = useCreateStore(() =>
26
29
  createStore(){{#if schemas}}
27
30
  .setValuesSchema(VALUES_SCHEMA){{/if}}
28
- .setValue('username', 'Carol'),
31
+ .setValue('username', getRandomName()),
29
32
  );
30
33
 
31
34
  useProvideStore(STORE_ID, store);
32
35
 
36
+ {{#if persist}}
37
+ {{#if persistLocalStorage}}
38
+ {{#if schemas}}
39
+ {{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser/with-schemas';"}}
40
+ {{else}}
41
+ {{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser';"}}
42
+ {{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
43
+ {{/if}}
44
+
45
+ useCreatePersister(
46
+ store,
47
+ (store) => createLocalPersister(store, STORE_ID),
48
+ [],
49
+ async (persister) => {
50
+ await persister.load();
51
+ await persister.startAutoSave();
52
+ onReady?.();
53
+ },
54
+ );
55
+ {{/if}}
56
+ {{#if persistSqlite}}
57
+ {{#if schemas}}
58
+ {{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm/with-schemas';"}}
59
+ {{else}}
60
+ {{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';"}}
61
+ {{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
62
+ {{/if}}
63
+ {{includeFile template="client/src/shared/sqlite.ts.hbs" output="client/src/sqlite.{{ext}}"}}
64
+ {{addImport "import {getDb} from './sqlite';"}}
65
+
66
+ useCreatePersister(
67
+ store,
68
+ async (store) => {
69
+ const {sqlite3, db} = await getDb();
70
+ return createSqliteWasmPersister(store, sqlite3, db, STORE_ID);
71
+ },
72
+ [],
73
+ async (persister) => {
74
+ await persister.load();
75
+ await persister.startAutoSave();
76
+ onReady?.();
77
+ },
78
+ );
79
+ {{/if}}
80
+ {{#if persistPglite}}
81
+ {{#if schemas}}
82
+ {{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite/with-schemas';"}}
83
+ {{else}}
84
+ {{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite';"}}
85
+ {{addImport "import {useCreatePersister} from 'tinybase/ui-react';"}}
86
+ {{/if}}
87
+ {{includeFile template="client/src/shared/pglite.ts.hbs" output="client/src/pglite.{{ext}}"}}
88
+ {{addImport "import {getPgLite} from './pglite';"}}
89
+
90
+ useCreatePersister(
91
+ store,
92
+ async (store) => {
93
+ const pgLite = await getPgLite();
94
+ return await createPglitePersister(store, pgLite, STORE_ID);
95
+ },
96
+ [],
97
+ async (persister) => {
98
+ await persister.load();
99
+ await persister.startAutoSave();
100
+ onReady?.();
101
+ },
102
+ );
103
+ {{/if}}
104
+ {{/if}}
105
+
106
+ {{#unless persist}}{{#unless sync}}
107
+ onReady?.();
108
+ {{/unless}}{{/unless}}
109
+
33
110
  return null;
34
111
  };
@@ -1,22 +1,17 @@
1
1
  {{includeFile template="client/src/chat/usernameInput.css.hbs" output="client/src/usernameInput.css"}}
2
- import {useValue, useSetValueCallback, STORE_ID} from './SettingsStore';
3
- import './usernameInput.css';
2
+ {{addImport "import {useValueState, STORE_ID} from './SettingsStore';"}}
3
+ {{addImport "import './usernameInput.css';"}}
4
4
 
5
5
  {{includeFile template="client/src/shared/Input.tsx.hbs" output="client/src/Input.{{ext}}"}}
6
- import {Input} from './Input';
6
+ {{addImport "import {Input} from './Input';"}}
7
7
 
8
8
  export const UsernameInput = () => {
9
- const username = useValue('username', STORE_ID) || '';
10
- const setUsername = useSetValueCallback(
11
- 'username',
12
- (_e) => _e,[],
13
- STORE_ID,
14
- );
9
+ const [username, setUsername] = useValueState('username', STORE_ID);
15
10
 
16
11
  return (
17
12
  <div id="usernameInput">
18
13
  <label>Your name:</label>
19
- <Input value={username} onChange={setUsername} placeholder="Enter your name" />
14
+ <Input value={username + '' } onChange={setUsername} placeholder="Enter your name" />
20
15
  </div>
21
16
  );
22
- };
17
+ };
@@ -1,39 +1,39 @@
1
1
  {{includeFile template="client/src/chat/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/chat/chatStore.ts.hbs" output="client/src/chatStore.ts"}}
5
- import {chatStore} from './chatStore';
5
+ {{addImport "import {chatStore, chatStoreReady} from './chatStore';"}}
6
6
 
7
- {{includeFile template="client/src/shared/button.ts.hbs" output="client/src/button.{{ext}}"}}
8
- import {createButton} from './button';
9
-
10
- {{includeFile template="client/src/shared/input.ts.hbs" output="client/src/input.{{ext}}"}}
11
- import {createInput} from './input';
12
-
13
- {{includeFile template="client/src/chat/message.ts.hbs" output="client/src/message.{{ext}}"}}
14
- import {createMessage} from './message';
7
+ {{includeFile template="client/src/shared/loading.ts.hbs" output="client/src/loading.{{ext}}"}}
8
+ {{addImport "import {showLoading, hideLoading} from './loading';"}}
15
9
 
16
10
  {{includeFile template="client/src/chat/messages.ts.hbs" output="client/src/messages.{{ext}}"}}
17
- import {createMessages} from './messages';
11
+ {{addImport "import {createMessages} from './messages';"}}
18
12
 
19
13
  {{includeFile template="client/src/chat/usernameInput.ts.hbs" output="client/src/usernameInput.{{ext}}"}}
20
- import {createUsernameInput} from './usernameInput';
14
+ {{addImport "import {createUsernameInput} from './usernameInput';"}}
21
15
 
22
16
  {{includeFile template="client/src/chat/messageInput.ts.hbs" output="client/src/messageInput.{{ext}}"}}
23
- import {createMessageInput} from './messageInput';
17
+ {{addImport "import {createMessageInput} from './messageInput';"}}
24
18
 
25
- const app = () => {
19
+ export const app = async () => {
26
20
  const appContainer = document.getElementById('app')!;
27
21
 
22
+ // Show loading spinner
23
+ const loadingDiv = showLoading(appContainer);
24
+
25
+ // Wait for stores to be ready
26
+ await Promise.all([settingsStoreReady, chatStoreReady]);
27
+
28
+ // Remove loading spinner
29
+ hideLoading(loadingDiv);
30
+
28
31
  appContainer.appendChild(createUsernameInput(settingsStore, chatStore));
29
32
  appContainer.appendChild(createMessages(chatStore));
30
33
 
31
34
  const messageInputContainer = createMessageInput(settingsStore, chatStore);
32
35
  appContainer.appendChild(messageInputContainer);
33
36
 
34
- // Focus the input after it's in the DOM
35
37
  const messageInput = messageInputContainer.querySelector('input')!;
36
38
  messageInput.focus();
37
- };
38
-
39
- export {app};
39
+ };
@@ -1,50 +1,112 @@
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
6
 
7
- const STORE_ID = 'chat';
7
+ {{includeFile template="client/src/chat/utils.ts.hbs" output="client/src/utils.ts"}}
8
+ {{addImport "import {getDefaultContent} from './utils';"}}
8
9
 
9
10
  {{#if schemas}}
10
11
  const TABLES_SCHEMA = {
11
12
  messages: {
12
- username: {type: 'string'},
13
- text: {type: 'string'},
14
- timestamp: {type: 'number'},
13
+ username: {type: 'string', default: ''},
14
+ text: {type: 'string', default: ''},
15
+ timestamp: {type: 'number', default: 0},
15
16
  },
16
17
  } as const;
17
18
 
18
19
  {{/if}}
19
- export const chatStore = createMergeableStore(STORE_ID){{#if schemas}}
20
+ export const chatStore = createMergeableStore(){{#if schemas}}
20
21
  .setTablesSchema(TABLES_SCHEMA){{/if}}
21
- .setDefaultContent([
22
- {
23
- messages: {
24
- '1': {username: 'Alice', text: 'Hello!', timestamp: Date.now() - 60000},
25
- '2': {username: 'Bob', text: 'Hi there!', timestamp: Date.now() - 30000},
26
- },
27
- },
28
- {},
29
- ]);
30
-
31
- {{#if sync}}
32
- {{includeFile template="client/src/shared/config.ts.hbs" output="client/src/config.ts"}}
33
- {{addImport "import {SERVER} from './config';"}}
34
- {{addImport "import ReconnectingWebSocket from 'reconnecting-websocket';"}}
35
- {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
36
-
37
- const serverPathId = location.pathname;
38
- createWsSynchronizer(
39
- chatStore,
40
- new ReconnectingWebSocket(SERVER + serverPathId),
41
- ).then(async (synchronizer) => {
42
- await synchronizer.startSync();
43
-
44
- synchronizer.getWebSocket().addEventListener('open', () => {
45
- synchronizer.load().then(() => synchronizer.save());
46
- });
22
+ .setDefaultContent(getDefaultContent());
23
+
24
+ export type MessageRow = {{#if schemas}}Row<typeof TABLES_SCHEMA, 'messages'>{{else}}{username: string; text: string; timestamp: number}{{/if}};
25
+
26
+ let resolveChatReady: () => void;
27
+ export const chatStoreReady = new Promise<void>((resolve) => {
28
+ resolveChatReady = resolve;
47
29
  });
48
- {{/if}}
49
30
 
50
- export type ChatStore = typeof chatStore;
31
+ {{#if persist}}
32
+ {{#if persistLocalStorage}}
33
+ {{#if schemas}}
34
+ {{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser/with-schemas';"}}
35
+ {{else}}
36
+ {{addImport "import {createLocalPersister} from 'tinybase/persisters/persister-browser';"}}
37
+ {{/if}}
38
+
39
+ const chatPersister = createLocalPersister(chatStore, 'chat');
40
+ chatPersister.load().then(() => {
41
+ chatPersister.startAutoSave();
42
+ resolveChatReady();
43
+ });
44
+ {{/if}}
45
+ {{#if persistSqlite}}
46
+ {{#if schemas}}
47
+ {{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm/with-schemas';"}}
48
+ {{else}}
49
+ {{addImport "import {createSqliteWasmPersister} from 'tinybase/persisters/persister-sqlite-wasm';"}}
50
+ {{/if}}
51
+ {{includeFile template="client/src/shared/sqlite.ts.hbs" output="client/src/sqlite.ts"}}
52
+ {{addImport "import {getDb} from './sqlite';"}}
53
+
54
+ getDb().then(({sqlite3, db}) => {
55
+ const chatPersister = createSqliteWasmPersister(chatStore, sqlite3, db, 'chat');
56
+ chatPersister.load().then(() => {
57
+ chatPersister.startAutoSave();
58
+ resolveChatReady();
59
+ });
60
+ });
61
+ {{/if}}
62
+ {{#if persistPglite}}
63
+ {{#if schemas}}
64
+ {{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite/with-schemas';"}}
65
+ {{else}}
66
+ {{addImport "import {createPglitePersister} from 'tinybase/persisters/persister-pglite';"}}
67
+ {{/if}}
68
+ {{includeFile template="client/src/shared/pglite.ts.hbs" output="client/src/pglite.{{ext}}"}}
69
+ {{addImport "import {getPgLite} from './pglite';"}}
70
+
71
+ getPgLite().then((pgLite) => {
72
+ createPglitePersister(chatStore, pgLite, 'chat').then((chatPersister) => {
73
+ chatPersister.load().then(() => {
74
+ chatPersister.startAutoSave();
75
+ resolveChatReady();
76
+ });
77
+ });
78
+ });
79
+ {{/if}}
80
+ {{/if}}
81
+
82
+ {{#if sync}}
83
+ {{includeFile template="client/src/shared/config.ts.hbs" output="client/src/config.ts"}}
84
+ {{addImport "import {SERVER} from './config';"}}
85
+ {{addImport "import ReconnectingWebSocket from 'reconnecting-websocket';"}}
86
+ {{#if schemas}}
87
+ {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client/with-schemas';"}}
88
+ {{else}}
89
+ {{addImport "import {createWsSynchronizer} from 'tinybase/synchronizers/synchronizer-ws-client';"}}
90
+ {{/if}}
91
+
92
+ const serverPathId = location.pathname;
93
+ createWsSynchronizer(
94
+ chatStore,
95
+ new ReconnectingWebSocket(SERVER + serverPathId),
96
+ ).then(async (synchronizer) => {
97
+ await synchronizer.startSync();
98
+
99
+ synchronizer.getWebSocket().addEventListener('open', () => {
100
+ synchronizer.load().then(() => {
101
+ synchronizer.save();
102
+ {{#unless persist}}resolveChatReady();{{/unless}}
103
+ });
104
+ });
105
+ });
106
+ {{/if}}
107
+
108
+ {{#unless persist}}{{#unless sync}}
109
+ resolveChatReady();
110
+ {{/unless}}{{/unless}}
111
+
112
+ export type ChatStore = typeof chatStore;
@@ -1,7 +1,8 @@
1
1
  {{includeFile template="client/src/chat/message.css.hbs" output="client/src/message.css"}}
2
- import './message.css';
2
+ {{addImport "import './message.css';"}}
3
+ {{addImport "import type {MessageRow} from './chatStore';"}}
3
4
 
4
- export const createMessage = (username: string, text: string, timestamp: number): HTMLDivElement => {
5
+ export const createMessage = ({username, text, timestamp}: MessageRow): HTMLDivElement => {
5
6
  const time = new Date(timestamp).toLocaleTimeString();
6
7
 
7
8
  const messageDiv = document.createElement('div');
@@ -24,4 +25,4 @@ messageDiv.appendChild(textSpan);
24
25
  messageDiv.appendChild(timeSpan);
25
26
 
26
27
  return messageDiv;
27
- };
28
+ };
@@ -3,4 +3,4 @@ display: flex;
3
3
  gap: 0.5rem;
4
4
  padding: 1rem;
5
5
  border-top: 1px solid var(--border);
6
- }
6
+ }