add expo, massively simplify svelte and solid examples
This commit is contained in:
16
index.ts
16
index.ts
@@ -46,6 +46,9 @@ const project = await p.group(
|
|||||||
return undefined
|
return undefined
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
installDeps: async () => {
|
||||||
|
return await p.confirm({message: "Install dependencies?", })
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
@@ -69,15 +72,10 @@ const destDir = path.join("./", project.name);
|
|||||||
render(destDir, { project }, { reverseMap: true })
|
render(destDir, { project }, { reverseMap: true })
|
||||||
|
|
||||||
const s = p.spinner();
|
const s = p.spinner();
|
||||||
s.start("Installing dependencies");
|
if (project.installDeps) {
|
||||||
await Bun.$`bun install`.cwd(path.join(destDir)).quiet();
|
s.start("Installing dependencies");
|
||||||
await Bun.$`bun install`.cwd(path.join(destDir, 'packages', 'rpc')).quiet();
|
await Bun.$`bun install`.cwd(path.join(destDir)).quiet();
|
||||||
if (project.frontend !== "none") {
|
s.stop("Dependencies installed.");
|
||||||
await Bun.$`bun install`.cwd(path.join(destDir, 'apps', 'web')).quiet();
|
|
||||||
}
|
}
|
||||||
if (project.mobile !== "none") {
|
|
||||||
await Bun.$`bun install`.cwd(path.join(destDir, 'apps', 'mobile')).quiet();
|
|
||||||
}
|
|
||||||
s.stop("Dependencies installed.");
|
|
||||||
|
|
||||||
p.outro("You're all set!");
|
p.outro("You're all set!");
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
node_modules
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "example-basic",
|
"name": "example-bare",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
@@ -9,16 +9,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@<@var(context.project.name)>/rpc": "workspace:*",
|
"@<@var(context.project.name)>/rpc": "workspace:*",
|
||||||
"@solidjs/meta": "^0.29.4",
|
|
||||||
"@solidjs/router": "^0.15.0",
|
|
||||||
"@solidjs/start": "2.0.0-alpha.2",
|
"@solidjs/start": "2.0.0-alpha.2",
|
||||||
"@solidjs/vite-plugin-nitro-2": "^0.1.0",
|
"@solidjs/vite-plugin-nitro-2": "^0.1.0",
|
||||||
"@tailwindcss/vite": "^4.2.2",
|
|
||||||
"@tanstack/query-db-collection": "^1.0.35",
|
|
||||||
"@tanstack/solid-db": "^0.2.18",
|
|
||||||
"@tanstack/solid-query": "^5.96.2",
|
|
||||||
"solid-js": "^1.9.5",
|
"solid-js": "^1.9.5",
|
||||||
"tailwindcss": "^4.2.2",
|
|
||||||
"vite": "^7.0.0"
|
"vite": "^7.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -1,5 +1,61 @@
|
|||||||
@import "tailwindcss";
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: Gordita, Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
font-family: Gordita, Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1em;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #335d92;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 4rem;
|
||||||
|
font-weight: 100;
|
||||||
|
line-height: 1.1;
|
||||||
|
margin: 4rem auto;
|
||||||
|
max-width: 14rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
max-width: 14rem;
|
||||||
|
margin: 2rem auto;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 480px) {
|
||||||
|
h1 {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.increment {
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
padding: 1em 2em;
|
||||||
|
color: #335d92;
|
||||||
|
background-color: rgba(68, 107, 158, 0.1);
|
||||||
|
border-radius: 2em;
|
||||||
|
border: 2px solid rgba(68, 107, 158, 0);
|
||||||
|
outline: none;
|
||||||
|
width: 200px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.increment:focus {
|
||||||
|
border: 2px solid #335d92;
|
||||||
|
}
|
||||||
|
|
||||||
|
.increment:active {
|
||||||
|
background-color: rgba(68, 107, 158, 0.2);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,58 +1,58 @@
|
|||||||
import { MetaProvider, Title } from "@solidjs/meta";
|
import { createSignal, For } from "solid-js";
|
||||||
import { Router } from "@solidjs/router";
|
import { createRouter, Todo } from "@<@var(context.project.name)>/rpc"
|
||||||
import { FileRoutes } from "@solidjs/start/router";
|
import { createEffect } from 'solid-js'
|
||||||
import { Suspense } from "solid-js";
|
|
||||||
import { createCollection } from '@tanstack/solid-db'
|
|
||||||
import { queryCollectionOptions } from '@tanstack/query-db-collection'
|
|
||||||
import type { Todo, ExtractPayload } from '@<@var(context.project.name)>/rpc'
|
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
|
|
||||||
import { getRouter } from "./lib/getRouter"
|
|
||||||
import "./app.css";
|
import "./app.css";
|
||||||
import { TodoCollectionProvider } from "./context/todocollection/provider";
|
|
||||||
const queryClient = new QueryClient({
|
|
||||||
|
|
||||||
})
|
|
||||||
const router = getRouter();
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const todosCollection = createCollection(
|
const router = createRouter("http://127.0.0.1:8080")
|
||||||
queryCollectionOptions({
|
const [todos, setTodos] = createSignal<Todo[]>([]);
|
||||||
queryKey: ["todos"],
|
const [todoToCreateTask, setTodoToCreateTask] = createSignal<string>("");
|
||||||
queryFn: async () => {
|
const fetchTodos = () => {
|
||||||
const todos = await router.todos.listTodos({})
|
router.todos.listTodos({}).then((r) => {
|
||||||
return todos.todos as ExtractPayload<Todo>[];
|
setTodos(r.todos)
|
||||||
},
|
|
||||||
queryClient,
|
|
||||||
getKey: (item) => item.id ? item.id : crypto.randomUUID(),
|
|
||||||
onInsert: async ({ transaction }) => {
|
|
||||||
Promise.all(transaction.mutations.map((m) => {
|
|
||||||
router.todos.createTodo({ todo: m.modified })
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
onDelete: async ({ transaction }) => {
|
|
||||||
Promise.all(transaction.mutations.map((m) => {
|
|
||||||
router.todos.deleteTodo({ todo: m.modified })
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
onUpdate: async ({ transaction }) => {
|
|
||||||
Promise.all(transaction.mutations.map((m) => {
|
|
||||||
router.todos.updateTodo({ todo: m.modified })
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
)
|
}
|
||||||
|
createEffect(() => {
|
||||||
|
fetchTodos()
|
||||||
|
})
|
||||||
|
|
||||||
|
const setTodoDone = (id: string, task: string) => {
|
||||||
|
return async (event : Event & { currentTarget: HTMLInputElement }) => {
|
||||||
|
await router.todos.updateTodo({ todo: { id, task, done: event.currentTarget.checked } })
|
||||||
|
fetchTodos()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteTodo = (id: string) => {
|
||||||
|
return async () => {
|
||||||
|
await router.todos.deleteTodo({ todo: { id } })
|
||||||
|
fetchTodos()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTodo = () => {
|
||||||
|
router.todos.createTodo({ todo: { id: crypto.randomUUID(), task: todoToCreateTask() } }).then((r) => {
|
||||||
|
console.log(r)
|
||||||
|
fetchTodos()
|
||||||
|
}).catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router
|
<div>
|
||||||
root={props => (
|
<p>Create Todo</p>
|
||||||
<MetaProvider>
|
<input value={todoToCreateTask()} onInput={(e) => setTodoToCreateTask(e.currentTarget.value)} type="text"/>
|
||||||
<QueryClientProvider client={queryClient} >
|
<button onclick={createTodo}> create </button>
|
||||||
<TodoCollectionProvider value={todosCollection}>
|
<For each={todos()}>
|
||||||
<Suspense>{props.children}</Suspense>
|
{(item:Todo) =>
|
||||||
</TodoCollectionProvider>
|
<div>
|
||||||
</QueryClientProvider>
|
{item.task}
|
||||||
</MetaProvider>
|
<input type='checkbox' checked={item.done} onChange={setTodoDone(item.id || "",item.task)}/>
|
||||||
)}
|
<button onclick={deleteTodo(item.id || "")}>delete</button>
|
||||||
>
|
</div>
|
||||||
<FileRoutes/>
|
}
|
||||||
</Router>
|
</For>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import { ExtractPayload, type Todo } from '@<@var(context.project.name)>/rpc'
|
|
||||||
import { createSignal } from 'solid-js';
|
|
||||||
import { useTodoCollection } from '~/context/todocollection/create';
|
|
||||||
|
|
||||||
export const CreateTodo = () => {
|
|
||||||
const [todoState, setTodoState] = createSignal<ExtractPayload<Todo>>({
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
task: "",
|
|
||||||
done: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
let inputRef!: HTMLInputElement
|
|
||||||
|
|
||||||
const setTask = () => {
|
|
||||||
const todo = {...todoState()}
|
|
||||||
todo.task = inputRef.value;
|
|
||||||
setTodoState(todo)
|
|
||||||
}
|
|
||||||
|
|
||||||
const todoCollection = useTodoCollection()
|
|
||||||
|
|
||||||
const insertTodo = () => {
|
|
||||||
todoCollection.insert(todoState())
|
|
||||||
setTodoState({
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
task: "",
|
|
||||||
done: false
|
|
||||||
})
|
|
||||||
inputRef.value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<input class="border rounded-md px-3 py-1" ref={inputRef} type='text' onInput={setTask} />
|
|
||||||
<button class="bg-teal-800 text-teal-100 p-1 rounded-md cursor-pointer" onClick={insertTodo}> create </button>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { useLiveQuery } from "@tanstack/solid-db";
|
|
||||||
import { useTodoCollection } from "~/context/todocollection/create";
|
|
||||||
import { Todo } from './Todo'
|
|
||||||
import { For } from 'solid-js'
|
|
||||||
|
|
||||||
export const ListTodos = () => {
|
|
||||||
const todoCollection = useTodoCollection()
|
|
||||||
const data = useLiveQuery((q) =>
|
|
||||||
q.from({todos: todoCollection})
|
|
||||||
)
|
|
||||||
const todos = () => {
|
|
||||||
const items = Array.from(data.state.values());
|
|
||||||
return items.sort((a: any, b: any) => {
|
|
||||||
if (a.done != b.done) return a.done ? -1 : 1
|
|
||||||
if (!a.done && !b.done) {
|
|
||||||
const adate = a.createdAt ? new Date(a.createdAt) : new Date()
|
|
||||||
const bdate = b.createdAt ? new Date(b.createdAt) : new Date()
|
|
||||||
return bdate.getTime() - adate.getTime()
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<For each={todos()} fallback={<div>Loading...</div>}>
|
|
||||||
{(item) => <Todo todo={item} />}
|
|
||||||
</For>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { ExtractPayload, type Todo as RpcTodo } from '@<@var(context.project.name)>/rpc'
|
|
||||||
import { createSignal, type JSX } from 'solid-js';
|
|
||||||
import { useTodoCollection } from '~/context/todocollection/create';
|
|
||||||
|
|
||||||
export const Todo = ({ todo }: { todo: ExtractPayload<RpcTodo> }) => {
|
|
||||||
const [todoState, setTodoState] = createSignal(todo)
|
|
||||||
const todoCollection = useTodoCollection()
|
|
||||||
let commitUpdateTimeoutId: NodeJS.Timeout|null = null;
|
|
||||||
const updateTask: JSX.EventHandlerUnion<HTMLInputElement, Event> = (e) => {
|
|
||||||
if (commitUpdateTimeoutId != null) {
|
|
||||||
clearTimeout(commitUpdateTimeoutId)
|
|
||||||
}
|
|
||||||
const todo = { ...todoState() };
|
|
||||||
todo.task = e.currentTarget.value
|
|
||||||
setTodoState(todo)
|
|
||||||
commitUpdateTimeoutId = setTimeout(() => {
|
|
||||||
todoCollection.update(todoState().id, (draft) => {
|
|
||||||
draft.task = todoState().task
|
|
||||||
})
|
|
||||||
},3000)
|
|
||||||
|
|
||||||
}
|
|
||||||
const updateDone: JSX.EventHandlerUnion<HTMLInputElement, Event> = (e) => {
|
|
||||||
const todo = { ...todoState() }
|
|
||||||
todo.done = e.currentTarget.checked
|
|
||||||
setTodoState(todo)
|
|
||||||
todoCollection.update(todoState().id, (draft) => {
|
|
||||||
draft.done = todoState().done
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const del = () => {
|
|
||||||
todoCollection.delete(todoState().id || "")
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div class="flex flex-col items-center border rounded-md p-5 gap-2">
|
|
||||||
<input class="text-center border rounded-md px-3 py-1" oninput={updateTask} type="text" value={todoState().task} />
|
|
||||||
<span> {(new Date(todoState().createdAt || "")).toLocaleString()}</span>
|
|
||||||
<span> {(new Date(todoState().updatesAt || "")).toLocaleString()}</span>
|
|
||||||
<input type="checkbox" onchange={updateDone} checked={todoState().done} />
|
|
||||||
<button class="bg-amber-800 text-amber-100 p-1 rounded-md cursor-pointer" onclick={del} class=""> delete </button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { createContext,useContext } from 'solid-js'
|
|
||||||
import { type CollectionImpl, type CollectionLike } from '@tanstack/solid-db'
|
|
||||||
import type { ExtractPayload, Todo } from '@<@var(context.project.name)>/rpc';
|
|
||||||
export type TodoCollection = CollectionImpl<ExtractPayload<Todo>>
|
|
||||||
export const TodoCollectionContext = createContext<TodoCollection>()
|
|
||||||
export const useTodoCollection = () => {
|
|
||||||
const col = useContext(TodoCollectionContext)
|
|
||||||
if (col === undefined) {
|
|
||||||
throw new Error("Todo collection has not been initialized")
|
|
||||||
}
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { JSXElement } from 'solid-js';
|
|
||||||
import { TodoCollection, TodoCollectionContext } from './create'
|
|
||||||
|
|
||||||
export const TodoCollectionProvider = (props : {children:JSXElement, value: TodoCollection}) => {
|
|
||||||
return (
|
|
||||||
<TodoCollectionContext.Provider value={props.value}> {props.children} </TodoCollectionContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { createRouter } from "@<@var(context.project.name)>/rpc"
|
|
||||||
|
|
||||||
const router = createRouter("http://127.0.0.1:8080")
|
|
||||||
|
|
||||||
export const getRouter = () => {
|
|
||||||
return router;
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Title } from "@solidjs/meta";
|
|
||||||
import { HttpStatusCode } from "@solidjs/start";
|
|
||||||
|
|
||||||
export default function NotFound() {
|
|
||||||
return (
|
|
||||||
<main>
|
|
||||||
<Title>Not Found</Title>
|
|
||||||
<HttpStatusCode code={404} />
|
|
||||||
<h1>Page Not Found</h1>
|
|
||||||
<p>
|
|
||||||
Visit{" "}
|
|
||||||
<a href="https://start.solidjs.com" target="_blank">
|
|
||||||
start.solidjs.com
|
|
||||||
</a>{" "}
|
|
||||||
to learn how to build SolidStart apps.
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { Title } from "@solidjs/meta";
|
|
||||||
import { CreateTodo } from "~/components/CreateTodo";
|
|
||||||
import { ListTodos } from "~/components/ListTodos"
|
|
||||||
export default function Home() {
|
|
||||||
return (
|
|
||||||
<main class="flex flex-col items-center gap-2 py-5">
|
|
||||||
<Title>Todos</Title>
|
|
||||||
<h1 class="text-5xl"> Todos </h1>
|
|
||||||
<CreateTodo/>
|
|
||||||
<ListTodos/>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import { nitroV2Plugin as nitro } from "@solidjs/vite-plugin-nitro-2";
|
import { nitroV2Plugin as nitro } from "@solidjs/vite-plugin-nitro-2";
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
|
||||||
import { solidStart } from "@solidjs/start/config";
|
import { solidStart } from "@solidjs/start/config";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [solidStart(),
|
||||||
solidStart(),
|
nitro()
|
||||||
nitro(),
|
|
||||||
tailwindcss()
|
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,14 +21,6 @@
|
|||||||
"vite": "^8.0.7"
|
"vite": "^8.0.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bufbuild/protobuf": "^2.11.0",
|
"@<@var(context.project.name)>/rpc": "workspace:*"
|
||||||
"@connectrpc/connect": "^2.1.1",
|
|
||||||
"@connectrpc/connect-web": "^2.1.1",
|
|
||||||
"@<@var(context.project.name)>/rpc": "workspace:*",
|
|
||||||
"@tailwindcss/vite": "^4.3.0",
|
|
||||||
"@tanstack/query-db-collection": "^1.0.33",
|
|
||||||
"@tanstack/svelte-db": "^0.1.79",
|
|
||||||
"@tanstack/svelte-query": "^6.1.13",
|
|
||||||
"tailwindcss": "^4.3.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
import @tailwindcss;
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { ExtractPayload, Todo } from '@<@var(context.project.name)>/rpc';
|
|
||||||
import { getTodoCollection } from '$lib/todocollectionscontext';
|
|
||||||
|
|
||||||
const todoCollection = getTodoCollection();
|
|
||||||
let todo = $state<ExtractPayload<Todo>>({
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
task: '',
|
|
||||||
done: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const insertTodo = () => {
|
|
||||||
todoCollection.insert({ ...todo });
|
|
||||||
todo = {
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
task: '',
|
|
||||||
done: false
|
|
||||||
};
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<input class="rounded-md border px-3 py-1" type="text" bind:value={todo.task} />
|
|
||||||
<button class="cursor-pointer rounded-md bg-teal-800 p-1 text-teal-100" onclick={insertTodo}>
|
|
||||||
create
|
|
||||||
</button>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { getTodoCollection } from '$lib/todocollectionscontext';
|
|
||||||
import { useLiveQuery } from '@tanstack/svelte-db';
|
|
||||||
import Todo from './Todo.svelte';
|
|
||||||
|
|
||||||
const todoCollection = getTodoCollection();
|
|
||||||
const data = useLiveQuery((q) => q.from({ todos: todoCollection }));
|
|
||||||
const todos = $derived.by(() => {
|
|
||||||
return data.data.toSorted((a, b) => {
|
|
||||||
if (a.done != b.done) {
|
|
||||||
return a.done ? -1 : 1;
|
|
||||||
}
|
|
||||||
if (!a.done && !b.done) {
|
|
||||||
const adate = a.createdAt ? new Date(a.createdAt) : new Date();
|
|
||||||
const bdate = b.createdAt ? new Date(b.createdAt) : new Date();
|
|
||||||
return bdate.getTime() - adate.getTime();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if data.isLoading}
|
|
||||||
<div>Loading...</div>
|
|
||||||
{:else}
|
|
||||||
{#each todos as todo (todo.id)}
|
|
||||||
<Todo {todo} />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { ExtractPayload, Todo as RpcTodo } from '@<@var(context.project.name)>/rpc';
|
|
||||||
import { getTodoCollection } from '$lib/todocollectionscontext';
|
|
||||||
|
|
||||||
let { todo }: { todo: ExtractPayload<RpcTodo> } = $props();
|
|
||||||
const getInitialTodo = () => todo;
|
|
||||||
let todoState = $state<ExtractPayload<RpcTodo>>({ ...getInitialTodo() });
|
|
||||||
let commitUpdateTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
||||||
|
|
||||||
const todoCollection = getTodoCollection();
|
|
||||||
|
|
||||||
const updateTask = () => {
|
|
||||||
if (commitUpdateTimeoutId != null) {
|
|
||||||
clearTimeout(commitUpdateTimeoutId);
|
|
||||||
}
|
|
||||||
|
|
||||||
commitUpdateTimeoutId = setTimeout(() => {
|
|
||||||
todoCollection.update(todoState.id, (draft) => {
|
|
||||||
draft.task = todoState.task;
|
|
||||||
});
|
|
||||||
}, 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateDone = () => {
|
|
||||||
todoCollection.update(todoState.id, (draft) => {
|
|
||||||
draft.done = todoState.done;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const del = () => {
|
|
||||||
todoCollection.delete(todoState.id || '');
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="flex flex-col items-center gap-2 rounded-md border p-5">
|
|
||||||
<input
|
|
||||||
class="rounded-md border px-3 py-1 text-center"
|
|
||||||
type="text"
|
|
||||||
bind:value={todoState.task}
|
|
||||||
oninput={updateTask}
|
|
||||||
/>
|
|
||||||
<span>{new Date(todoState.createdAt || '').toLocaleString()}</span>
|
|
||||||
<span>{new Date(todoState.updatesAt || '').toLocaleString()}</span>
|
|
||||||
<input type="checkbox" bind:checked={todoState.done} onchange={updateDone} />
|
|
||||||
<button class="cursor-pointer rounded-md bg-amber-800 p-1 text-amber-100" onclick={del}>
|
|
||||||
delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import {createRouter} from '@<@var(context.project.name)>/rpc'
|
|
||||||
|
|
||||||
const router = createRouter("http://127.0.0.1:8080")
|
|
||||||
|
|
||||||
export const getRouter = () => {
|
|
||||||
return router;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { createContext } from "svelte";
|
|
||||||
import { type CollectionImpl } from '@tanstack/svelte-db'
|
|
||||||
import type { Todo, ExtractPayload } from "@<@var(context.project.name)>/rpc";
|
|
||||||
export const [getTodoCollection,setTodoCollection] = createContext<CollectionImpl<ExtractPayload<Todo>,string>>()
|
|
||||||
@@ -1,53 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createCollection } from '@tanstack/svelte-db';
|
|
||||||
import { queryCollectionOptions } from '@tanstack/query-db-collection';
|
|
||||||
import { getRouter } from "$lib/getconnectrouter"
|
|
||||||
import favicon from '$lib/assets/favicon.svg';
|
import favicon from '$lib/assets/favicon.svg';
|
||||||
import "../app.css"
|
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/svelte-query';
|
|
||||||
import type { ExtractPayload, Todo } from '@<@var(context.project.name)>/rpc';
|
|
||||||
import { browser } from '$app/environment';
|
|
||||||
import { setTodoCollection } from '$lib/todocollectionscontext';
|
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
const router = getRouter()
|
|
||||||
const queryClient = new QueryClient({
|
|
||||||
defaultOptions: {
|
|
||||||
queries: {
|
|
||||||
enabled: browser
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const todosCollection = createCollection(
|
|
||||||
queryCollectionOptions({
|
|
||||||
queryKey: ["todos"],
|
|
||||||
queryFn: async () => {
|
|
||||||
const todos = await router.todos.listTodos({})
|
|
||||||
return todos.todos as ExtractPayload<Todo>[];
|
|
||||||
},
|
|
||||||
queryClient,
|
|
||||||
getKey: (item) => item.id ? item.id : crypto.randomUUID(),
|
|
||||||
onInsert: async ({transaction}) => {
|
|
||||||
Promise.all(transaction.mutations.map((m) => {
|
|
||||||
router.todos.createTodo({ todo: m.modified })
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
onDelete: async ({transaction}) => {
|
|
||||||
Promise.all(transaction.mutations.map((m) => {
|
|
||||||
router.todos.deleteTodo({todo: m.modified})
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
onUpdate: async ({transaction}) => {
|
|
||||||
Promise.all(transaction.mutations.map((m) => {
|
|
||||||
router.todos.updateTodo({todo: m.modified})
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
setTodoCollection(todosCollection)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<link rel="icon" href={favicon} />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
<QueryClientProvider client={queryClient}>
|
{@render children()}
|
||||||
{@render children()}
|
|
||||||
</QueryClientProvider>
|
|
||||||
|
|||||||
@@ -1,11 +1,51 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import CreateTodo from "$lib/components/CreateTodo.svelte";
|
import { createRouter, type Todo } from "@glstack-test/rpc"
|
||||||
import ListTodos from "$lib/components/ListTodos.svelte";
|
import type { ChangeEventHandler } from "svelte/elements";
|
||||||
|
const router = createRouter("http://127.0.0.1:8080");
|
||||||
|
let todos = $state<Todo[]>([])
|
||||||
|
let todoToCreateTask = $state<string>("")
|
||||||
|
const fetchTodos = () => {
|
||||||
|
router.todos.listTodos({}).then((r) => {
|
||||||
|
todos = r.todos;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
$effect(() => {
|
||||||
|
fetchTodos()
|
||||||
|
})
|
||||||
|
|
||||||
|
const setTodoDone = (id: string, task: string) => {
|
||||||
|
return async (event : Event & { currentTarget: HTMLInputElement }) => {
|
||||||
|
await router.todos.updateTodo({ todo: { id, task, done: event.currentTarget.checked } })
|
||||||
|
fetchTodos()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteTodo = (id: string) => {
|
||||||
|
return async () => {
|
||||||
|
await router.todos.deleteTodo({ todo: { id } })
|
||||||
|
fetchTodos()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTodo = () => {
|
||||||
|
router.todos.createTodo({ todo: { id: crypto.randomUUID(), task: todoToCreateTask } }).then((r) => {
|
||||||
|
console.log(r)
|
||||||
|
fetchTodos()
|
||||||
|
}).catch((e) => {
|
||||||
|
console.log(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="flex flex-col items-center gap-2 py-5">
|
<div>
|
||||||
<title>Todos</title>
|
<h1>Create Todo</h1>
|
||||||
<h1 class="text-5xl"> Todos </h1>
|
<input bind:value={todoToCreateTask} type="text"/>
|
||||||
<CreateTodo/>
|
<button onclick={createTodo}> create </button>
|
||||||
<ListTodos/>
|
{#each todos as todo (todo.id)}
|
||||||
</main>
|
<div>
|
||||||
|
{todo.task}
|
||||||
|
<input type='checkbox' onchange={setTodoDone(todo.id || "",todo.task)}/>
|
||||||
|
<button onclick={deleteTodo(todo.id || "")}>delete</button>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [sveltekit()]
|
||||||
tailwindcss(),
|
|
||||||
sveltekit(),
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Stack } from "expo-router";
|
import { Stack } from "expo-router";
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
return <Stack />;
|
return <Stack screenOptions={{headerShown:false}} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export default function Index() {
|
|||||||
return (
|
return (
|
||||||
<Host style={{ flex: 1 }}>
|
<Host style={{ flex: 1 }}>
|
||||||
<Column alignment="center" modifiers={[fillMaxWidth()]}>
|
<Column alignment="center" modifiers={[fillMaxWidth()]}>
|
||||||
<Text> create todo </Text>
|
<Text textStyle={{fontSize:30}}> Create Todo </Text>
|
||||||
<TextInput value={todoToCreateTask} onChangeText={updateTodoToCreateTask} />
|
<TextInput value={todoToCreateTask} onChangeText={updateTodoToCreateTask} />
|
||||||
<Button modifiers={[controlSize('regular')]} label="create Todo" onPress={createTodo} />
|
<Button modifiers={[controlSize('regular')]} label="create Todo" onPress={createTodo} />
|
||||||
<ScrollView modifiers={[fillMaxWidth()]}>
|
<ScrollView modifiers={[fillMaxWidth()]}>
|
||||||
|
|||||||
@@ -30,6 +30,15 @@
|
|||||||
version = [ "27.1.12297006" ];
|
version = [ "27.1.12297006" ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
enterShell = ''
|
||||||
|
export LD_LIBRARY_PATH=$ANDROID_HOME/emulator/lib64:$LD_LIBRARY_PATH
|
||||||
|
'';
|
||||||
|
scripts.create-avd.exec = ''
|
||||||
|
if [ ! -f .avd-created ]; then
|
||||||
|
avdmanager create avd -n "Pixel_5_API34" -k "system-images;android-34;google_apis_playstore;x86_64" -d "pixel_5";
|
||||||
|
touch .avd-created;
|
||||||
|
fi
|
||||||
|
'';
|
||||||
<@endif>
|
<@endif>
|
||||||
languages.go.enable = true;
|
languages.go.enable = true;
|
||||||
languages.typescript.enable = true;
|
languages.typescript.enable = true;
|
||||||
@@ -44,15 +53,6 @@
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
scripts.create-avd.exec = ''
|
|
||||||
if [ ! -f .avd-created ]; then
|
|
||||||
avdmanager create avd
|
|
||||||
-n "Pixel_5_API34"
|
|
||||||
-k "system-images;android-34;google_apis_playstore;x86_64"
|
|
||||||
-d "pixel_5"
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
<@endif>
|
|
||||||
processes = {
|
processes = {
|
||||||
<@if(neq(context.project.frontend,"none"))>
|
<@if(neq(context.project.frontend,"none"))>
|
||||||
bundev = {
|
bundev = {
|
||||||
@@ -70,36 +70,39 @@
|
|||||||
exec = "buf generate";
|
exec = "buf generate";
|
||||||
cwd = "./packages/proto";
|
cwd = "./packages/proto";
|
||||||
watch = {
|
watch = {
|
||||||
paths = [ ./ ];
|
paths = [ ./packages/proto ];
|
||||||
extensions = [ "proto" ];
|
extensions = [ "proto" ];
|
||||||
};
|
};
|
||||||
after= ["devenv:processes:air@started"];
|
|
||||||
};
|
};
|
||||||
protojswatcher = {
|
protojswatcher = {
|
||||||
exec = "bun run ./scripts/gen-rpc-index.ts";
|
exec = "bun run ./scripts/gen-rpc-index.ts";
|
||||||
cwd = "./packages/rpc/src";
|
cwd = "./";
|
||||||
watch = {
|
watch = {
|
||||||
paths = [ ./ ];
|
paths = [ ./packages/rpc/src ];
|
||||||
extensions = ["js","ts"];
|
extensions = ["js" "ts"];
|
||||||
};
|
};
|
||||||
after= ["devenv:processes:protowatcher@after"];
|
after= ["devenv:processes:protowatcher@completed"];
|
||||||
};
|
};
|
||||||
<@if(eq(context.project.mobile,"expo"))>
|
<@if(eq(context.project.mobile,"expo"))>
|
||||||
createavd = {
|
createavd = {
|
||||||
exec = "create-avd"
|
exec = "create-avd";
|
||||||
};
|
};
|
||||||
emulator = {
|
emulator = {
|
||||||
exec = "emulator -avd Pixel_5_API34"
|
exec = "emulator -avd Pixel_5_API34";
|
||||||
after = ["devenv:processes:createavd@after"]
|
after = ["devenv:processes:createavd@completed"];
|
||||||
};
|
};
|
||||||
bundev = {
|
expodev = {
|
||||||
exec = "bunx expo run:android";
|
exec = "bunx expo run:android";
|
||||||
cwd = "./apps/mobile";
|
cwd = "./apps/mobile";
|
||||||
after= ["devenv:processes:emulator@started"];
|
after= ["devenv:processes:emulator@started"];
|
||||||
};
|
};
|
||||||
<@endif>
|
<@endif>
|
||||||
sqlwatcher = {
|
sqlwatcher = {
|
||||||
exec = "watchexec -w ./db/migrations -w ./db/query -r -e sql sqlc generate";
|
exec = "sqlc generate";
|
||||||
|
watch = {
|
||||||
|
paths = [ ./db/migrations ./db/query ];
|
||||||
|
extensions = ["sql" "sqlc"];
|
||||||
|
};
|
||||||
cwd = "./services/api";
|
cwd = "./services/api";
|
||||||
after= ["devenv:processes:air@started"];
|
after= ["devenv:processes:air@started"];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
package todo
|
package todo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"cmp"
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
"connectrpc.com/connect"
|
"connectrpc.com/connect"
|
||||||
"connectrpc.com/validate"
|
"connectrpc.com/validate"
|
||||||
"context"
|
"github.com/<@var(context.project.name)>/<@var(context.project.name)>/db"
|
||||||
"<@var(context.project.goprefix)>/<@var(context.project.name)>/db"
|
|
||||||
todov1 "<@var(context.project.goprefix)>/<@var(context.project.name)>/gen/todo/v1"
|
todov1 "<@var(context.project.goprefix)>/<@var(context.project.name)>/gen/todo/v1"
|
||||||
"<@var(context.project.goprefix)>/<@var(context.project.name)>/gen/todo/v1/todov1connect"
|
"<@var(context.project.goprefix)>/<@var(context.project.name)>/gen/todo/v1/todov1connect"
|
||||||
. "<@var(context.project.goprefix)>/<@var(context.project.name)>/utils"
|
. "<@var(context.project.goprefix)>/glstack-test/utils"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type TodoServer struct{}
|
type TodoServer struct{}
|
||||||
@@ -46,9 +49,9 @@ func (srv *TodoServer) ListTodos(ctx context.Context, req *connect.Request[todov
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
reponseTodos := []*todov1.Todo{}
|
responseTodos := []*todov1.Todo{}
|
||||||
for _, todo := range todos {
|
for _, todo := range todos {
|
||||||
reponseTodos = append(reponseTodos, &todov1.Todo{
|
responseTodos = append(responseTodos, &todov1.Todo{
|
||||||
Id: StrPtr(todo.ID.String()),
|
Id: StrPtr(todo.ID.String()),
|
||||||
Task: todo.Task,
|
Task: todo.Task,
|
||||||
CreatedAt: StrPtr(todo.CreatedAt.Time.Format(time.RFC3339)),
|
CreatedAt: StrPtr(todo.CreatedAt.Time.Format(time.RFC3339)),
|
||||||
@@ -56,10 +59,25 @@ func (srv *TodoServer) ListTodos(ctx context.Context, req *connect.Request[todov
|
|||||||
Done: BoolPtr(todo.Done.Bool),
|
Done: BoolPtr(todo.Done.Bool),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
slices.SortFunc(responseTodos, func(a, b *todov1.Todo) int {
|
||||||
|
dateCmp := cmp.Compare(a.GetCreatedAt(), b.GetCreatedAt())
|
||||||
|
return dateCmp
|
||||||
|
})
|
||||||
|
slices.SortFunc(responseTodos, func(a, b *todov1.Todo) int {
|
||||||
|
var boolToInt = func(b bool) int {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doneCmp := cmp.Compare(boolToInt(*a.Done), boolToInt(*b.Done))
|
||||||
|
return doneCmp
|
||||||
|
})
|
||||||
|
|
||||||
return &connect.Response[todov1.ListTodosResponse]{
|
return &connect.Response[todov1.ListTodosResponse]{
|
||||||
Msg: &todov1.ListTodosResponse{
|
Msg: &todov1.ListTodosResponse{
|
||||||
Todos: reponseTodos,
|
Todos: responseTodos,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user