Skip to content

Commit 59e4f47

Browse files
committed
New apps
1 parent 7b2423d commit 59e4f47

18 files changed

Lines changed: 4385 additions & 622 deletions

src/cli.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@ const config = {
4848
],
4949
initial: 0,
5050
},
51+
{
52+
type: 'select' as const,
53+
name: 'appType',
54+
message: 'App type:',
55+
choices: [
56+
{title: 'Basic demo', value: 'basic'},
57+
{title: 'Chat app', value: 'chat'},
58+
{title: 'Drawing app', value: 'drawing'},
59+
],
60+
initial: 0,
61+
},
5162
{
5263
type: 'confirm' as const,
5364
name: 'prettier',
@@ -63,7 +74,8 @@ const config = {
6374
],
6475

6576
createContext: (answers: Record<string, unknown>) => {
66-
const {projectName, language, framework, prettier, eslint} = answers;
77+
const {projectName, language, framework, appType, prettier, eslint} =
78+
answers;
6779
const typescript = language === 'typescript';
6880
const javascript = !typescript;
6981
const react = framework === 'react';
@@ -73,6 +85,7 @@ const config = {
7385
projectName,
7486
language,
7587
framework,
88+
appType,
7689
prettier,
7790
eslint,
7891
typescript,

templates/index.html.hbs

Lines changed: 69 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,76 @@
1818
{{#if react}}
1919
<div id="app"></div>
2020
{{else}}
21-
<div id="app">
22-
<header>
23-
<h1>
24-
<img src="/favicon.svg" />
25-
TinyBase / {{#if typescript}}TypeScript{{else}}JavaScript{{/if}}
26-
</h1>
27-
</header>
28-
<p>
29-
This is more or less the simplest TinyBase app you could imagine. It
30-
contains a handful of key-values and tables which are mutated with the
31-
buttons below. There's no fancy rendering - just the raw JSON of the
32-
underlying store. See the full set of
33-
<a href="https://tinybase.org/demos/">TinyBase demos</a> for more
34-
interesting examples!
35-
</p>
36-
<div id="buttons">
37-
<button id="countButton">Increment number</button>
38-
<button id="randomButton">Random number</button>
39-
<button id="addPetButton">Add a pet</button>
21+
{{#if (eq appType "basic")}}
22+
<div id="app">
23+
<header>
24+
<h1>
25+
<img src="/favicon.svg" />
26+
TinyBase / {{#if typescript}}TypeScript{{else}}JavaScript{{/if}}
27+
</h1>
28+
</header>
29+
<p>
30+
This is more or less the simplest TinyBase app you could imagine. It
31+
contains a handful of key-values and tables which are mutated with the
32+
buttons below. There's no fancy rendering - just the raw JSON of the
33+
underlying store. See the full set of
34+
<a href="https://tinybase.org/demos/">TinyBase demos</a> for more
35+
interesting examples!
36+
</p>
37+
<div id="buttons">
38+
<button id="countButton">Increment number</button>
39+
<button id="randomButton">Random number</button>
40+
<button id="addPetButton">Add a pet</button>
41+
</div>
42+
<details open>
43+
<summary>Values</summary>
44+
<pre id="valuesJson"></pre>
45+
</details>
46+
<details open>
47+
<summary>Tables</summary>
48+
<pre id="tablesJson"></pre>
49+
</details>
4050
</div>
41-
<details open>
42-
<summary>Values</summary>
43-
<pre id="valuesJson"></pre>
44-
</details>
45-
<details open>
46-
<summary>Tables</summary>
47-
<pre id="tablesJson"></pre>
48-
</details>
49-
</div>
51+
{{/if}}
52+
{{#if (eq appType "chat")}}
53+
<div id="app">
54+
<header>
55+
<h1>
56+
<img src="/favicon.svg" />
57+
TinyBase Chat / {{#if typescript}}TypeScript{{else}}JavaScript{{/if}}
58+
</h1>
59+
</header>
60+
<div class="username-input">
61+
<label>Username:</label>
62+
<input type="text" id="username-input" placeholder="Enter your name" />
63+
</div>
64+
<div id="messages"></div>
65+
<div class="message-input">
66+
<input type="text" id="message-input" placeholder="Type a message..." />
67+
<button id="send-button">Send</button>
68+
</div>
69+
</div>
70+
{{/if}}
71+
{{#if (eq appType "drawing")}}
72+
<div id="app">
73+
<header>
74+
<h1>
75+
<img src="/favicon.svg" />
76+
TinyBase Drawing / {{#if typescript}}TypeScript{{else}}JavaScript{{/if}}
77+
</h1>
78+
</header>
79+
<div class="drawing-controls">
80+
<div class="color-picker" id="color-picker"></div>
81+
<div class="brush-size">
82+
<label>Brush Size:</label>
83+
<input type="range" id="brush-size" min="1" max="50" value="5" />
84+
<span id="brush-size-value">5</span>
85+
</div>
86+
<button id="clear-button" class="clear-btn">Clear Canvas</button>
87+
</div>
88+
<canvas id="drawing-canvas" class="drawing-canvas"></canvas>
89+
</div>
90+
{{/if}}
5091
{{/if}}
5192
{{includeFile template="src/index.tsx.hbs" output="src/index.{{ext}}"}}
5293
<script type="module" src="/src/index.{{ext}}"></script>

templates/src/basic/index.css.hbs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#buttons {
2+
border: solid #666;
3+
border-width: 1px 0;
4+
text-align: center;
5+
}
6+
7+
#buttons button {
8+
border: 1px solid #666;
9+
border-radius: 0.25rem;
10+
padding: 0.5rem 1rem;
11+
background: #222;
12+
color: #fff;
13+
font-family: inherit;
14+
font-weight: 800;
15+
font-size: 0.8rem;
16+
width: 10rem;
17+
margin: 1rem;
18+
}
19+
#buttons button:hover {
20+
border-color: #d81b60;
21+
}
22+
23+
table {
24+
border-collapse: collapse;
25+
font-size: inherit;
26+
line-height: inherit;
27+
margin-top: 0.5rem;
28+
table-layout: fixed;
29+
width: 100%;
30+
margin-bottom: 2rem;
31+
caption-side: bottom;
32+
}
33+
table caption {
34+
text-align: left;
35+
button {
36+
border: 0;
37+
margin-right: 0.25rem;
38+
}
39+
}
40+
table caption button {
41+
line-height: 0.7rem;
42+
margin: 0 0.25rem 0 0;
43+
vertical-align: middle;
44+
}
45+
th,
46+
td {
47+
padding: 0.25rem 0.5rem 0.25rem 0;
48+
text-align: left;
49+
}
50+
thead th,
51+
thead td {
52+
border: solid #999;
53+
border-width: 1px 0;
54+
}
55+
tbody th,
56+
tbody td {
57+
border-bottom: 1px solid #333;
58+
}
59+
60+
table.sortedTable thead th {
61+
cursor: pointer;
62+
}
63+
64+
@media (min-width: 40rem) {
65+
#app {
66+
grid-template-columns: 1fr 1fr;
67+
}
68+
header,
69+
p,
70+
#buttons {
71+
grid-column: span 2;
72+
}
73+
}

templates/src/chat/App.tsx.hbs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {StrictMode, useState} from 'react';
2+
import {createStore} from 'tinybase';
3+
import {Provider, useCreateStore, useAddRowCallback, useRow, useSetValueCallback, useValue} from 'tinybase/ui-react';
4+
import {SortedTableInHtmlTable, useCell} from 'tinybase/ui-react-dom';
5+
import {Inspector} from 'tinybase/ui-react-inspector';
6+
7+
const MessageInput = () => {
8+
const [message, setMessage] = useState('');
9+
const username = useValue('username') || 'Anonymous';
10+
11+
const handleSend = useAddRowCallback(
12+
'messages',
13+
() => ({
14+
username,
15+
text: message,
16+
timestamp: Date.now(),
17+
}),
18+
[message, username],
19+
);
20+
21+
const onSubmit = (e: React.FormEvent) => {
22+
e.preventDefault();
23+
if (message.trim()) {
24+
handleSend();
25+
setMessage('');
26+
}
27+
};
28+
29+
return (
30+
<form onSubmit={onSubmit} className="message-input">
31+
<input type="text" value={message} onChange={(e)=> setMessage(e.target.value)}
32+
placeholder="Type a message..."
33+
autoFocus
34+
/>
35+
<button type="submit">Send</button>
36+
</form>
37+
);
38+
};
39+
40+
const Message = ({rowId}: {rowId: string}) => {
41+
const row = useRow('messages', rowId);
42+
const time = new Date((row as any).timestamp).toLocaleTimeString();
43+
44+
return (
45+
<div className="message">
46+
<span className="username">{(row as any).username}:</span>
47+
<span className="text">{(row as any).text}</span>
48+
<span className="time">{time}</span>
49+
</div>
50+
);
51+
};
52+
53+
const UsernameInput = () => {
54+
const username = useValue('username') || '';
55+
const handleChange = useSetValueCallback(
56+
'username',
57+
(e: React.ChangeEvent<HTMLInputElement>) => e.target.value,[]);
58+
59+
return (
60+
<div className="username-input">
61+
<label>
62+
Your name:
63+
<input type="text" value={username} onChange={handleChange} placeholder="Enter your name" />
64+
</label>
65+
</div>
66+
);
67+
};
68+
69+
export const App = () => {
70+
const store = useCreateStore(() => {
71+
return createStore()
72+
.setValue('username', 'User')
73+
.setTable('messages', {
74+
'1': {username: 'Alice', text: 'Hello!', timestamp: Date.now() - 60000},
75+
'2': {username: 'Bob', text: 'Hi there!', timestamp: Date.now() - 30000},
76+
});
77+
});
78+
79+
return (
80+
<StrictMode>
81+
<Provider store={store}>
82+
<header>
83+
<h1>
84+
<img src="/favicon.svg" />
85+
TinyBase Chat{{#if typescript}} / TypeScript{{else}} / JavaScript{{/if}} + React
86+
</h1>
87+
</header>
88+
<p>
89+
A simple chat app built with TinyBase. Messages are stored in a Table,
90+
and you can change your username to see how data flows through the app.
91+
</p>
92+
<UsernameInput />
93+
<div className="chat-container">
94+
<div className="messages">
95+
<SortedTableInHtmlTable tableId="messages" cellId="timestamp" descending={false} customCell={(tableId, rowId)=>
96+
<Message rowId={rowId} />}
97+
/>
98+
</div>
99+
<MessageInput />
100+
</div>
101+
<Inspector />
102+
</Provider>
103+
</StrictMode>
104+
);
105+
};

templates/src/chat/app.ts.hbs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {createStore} from 'tinybase';
2+
3+
const updateMessages = (store: any) => {
4+
const messages = document.getElementById('messages')!;
5+
const messageRows = store.getTable('messages');
6+
const sortedIds = Object.keys(messageRows).sort(
7+
(a, b) => messageRows[a].timestamp - messageRows[b].timestamp,
8+
);
9+
10+
messages.innerHTML = sortedIds
11+
.map((id) => {
12+
const msg = messageRows[id];
13+
const time = new Date(msg.timestamp).toLocaleTimeString();
14+
return `
15+
<div class="message">
16+
<span class="username">${msg.username}:</span>
17+
<span class="text">${msg.text}</span>
18+
<span class="time">${time}</span>
19+
</div>
20+
`;
21+
})
22+
.join('');
23+
};
24+
25+
{{#if typescript}}
26+
export const app = () => {
27+
{{else}}
28+
const app = () => {
29+
{{/if}}
30+
const store = createStore()
31+
.setValue('username', 'User')
32+
.setTable('messages', {
33+
'1': {username: 'Alice', text: 'Hello!', timestamp: Date.now() - 60000},
34+
'2': {username: 'Bob', text: 'Hi there!', timestamp: Date.now() - 30000},
35+
});
36+
37+
const usernameInput = document.getElementById('username-input') as HTMLInputElement;
38+
const messageInput = document.getElementById('message-input') as HTMLInputElement;
39+
const sendButton = document.getElementById('send-button')!;
40+
41+
usernameInput!.value = store.getValue('username') as string;
42+
usernameInput!.addEventListener('input', () => {
43+
store.setValue('username', usernameInput!.value);
44+
});
45+
46+
store.addValuesListener(() => {
47+
usernameInput!.value = store.getValue('username') as string;
48+
});
49+
50+
const sendMessage = () => {
51+
const text = messageInput!.value.trim();
52+
if (text) {
53+
store.addRow('messages', {
54+
username: store.getValue('username'),
55+
text,
56+
timestamp: Date.now(),
57+
});
58+
messageInput!.value = '';
59+
}
60+
};
61+
62+
sendButton.addEventListener('click', sendMessage);
63+
messageInput!.addEventListener('keypress', (e) => {
64+
if (e.key === 'Enter') {
65+
sendMessage();
66+
}
67+
});
68+
69+
store.addTablesListener(() => updateMessages(store));
70+
updateMessages(store);
71+
};
72+
{{#unless typescript}}
73+
export {app};
74+
{{/unless}}

0 commit comments

Comments
 (0)