Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.1.2/schema.json",
"$schema": "https://biomejs.dev/schemas/2.2.5/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
Expand Down Expand Up @@ -33,6 +33,12 @@
"fix": "safe",
},
},
"suspicious": {
"noUnknownAtRules": "off",
},
"nursery": {
"useSortedClasses": "error",
},
},
},
"javascript": {
Expand Down
414 changes: 218 additions & 196 deletions bun.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "prism",
"devDependencies": {
"@biomejs/biome": "^2.1.2",
"lefthook": "^1.12.2",
"@biomejs/biome": "^2.2.5",
"lefthook": "^1.13.6",
"prettier": "^3.6.2",
"prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.6.14"
},
"peerDependencies": {
"typescript": "^5.8.3"
"typescript": "^5.9.3"
},
"private": true,
"scripts": {
Expand Down
56 changes: 28 additions & 28 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,42 +18,42 @@
"build-storybook": "storybook build"
},
"devDependencies": {
"@chromatic-com/storybook": "^4.0.1",
"@storybook/addon-a11y": "^9.0.17",
"@storybook/addon-docs": "^9.0.17",
"@storybook/addon-svelte-csf": "^5.0.7",
"@storybook/addon-vitest": "^9.0.17",
"@storybook/sveltekit": "^9.0.17",
"@sveltejs/adapter-auto": "^6.0.1",
"@sveltejs/kit": "^2.25.1",
"@sveltejs/vite-plugin-svelte": "^6.1.0",
"@tailwindcss/vite": "^4.1.11",
"@tauri-apps/cli": "^2.6.2",
"@chromatic-com/storybook": "^4.1.1",
"@storybook/addon-a11y": "^9.1.10",
"@storybook/addon-docs": "^9.1.10",
"@storybook/addon-svelte-csf": "^5.0.10",
"@storybook/addon-vitest": "^9.1.10",
"@storybook/sveltekit": "^9.1.10",
"@sveltejs/adapter-auto": "^6.1.1",
"@sveltejs/kit": "^2.43.8",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@tailwindcss/vite": "^4.1.14",
"@tauri-apps/cli": "^2.8.4",
"@vitest/browser": "^3.2.4",
"@vitest/coverage-v8": "^3.2.4",
"daisyui": "^5.0.46",
"svelte": "^5.36.7",
"svelte-check": "^4.3.0",
"tailwindcss": "^4.1.11",
"typescript": "^5.8.3",
"vite": "^7.0.5"
"daisyui": "^5.1.27",
"svelte": "^5.39.8",
"svelte-check": "^4.3.2",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.3",
"vite": "^7.1.9"
},
"dependencies": {
"@auth/core": "^0.40.0",
"@convex-dev/auth": "^0.0.87",
"@convex-dev/auth": "^0.0.90",
"@iconify-json/mdi": "^1.2.3",
"@inlang/paraglide-js": "^2.2.0",
"@mmailaender/convex-auth-svelte": "^0.0.2",
"@inlang/paraglide-js": "^2.4.0",
"@mmailaender/convex-auth-svelte": "^0.1.0",
"@packages/convex": "workspace:*",
"@playwright/test": "^1.54.1",
"@sveltejs/adapter-static": "^3.0.8",
"@tauri-apps/api": "^2.6.0",
"@tauri-apps/plugin-opener": "^2.4.0",
"convex": "^1.25.4",
"@playwright/test": "^1.55.1",
"@sveltejs/adapter-static": "^3.0.10",
"@tauri-apps/api": "^2.8.0",
"@tauri-apps/plugin-opener": "^2.5.0",
"convex": "^1.27.3",
"convex-svelte": "^0.0.11",
"emoji-picker-element": "^1.27.0",
"robot3": "^1.1.1",
"runed": "^0.31.0",
"unplugin-icons": "^22.2.0"
"robot3": "^1.2.0",
"runed": "^0.34.0",
"unplugin-icons": "^22.4.2"
}
}
2 changes: 1 addition & 1 deletion packages/client/src/components/chat/MessageInput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
await sendMessageMutation.run({
channelId,
content: messageContent.trim() || "",
author: identity.data.name,
author: identity.data.name || "unregistered",
parentId: replyingTo?._id ?? undefined,
attachments,
vote: voteId,
Expand Down
200 changes: 118 additions & 82 deletions packages/client/src/components/chat/VoteViewer.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { api, type Id } from "@packages/convex";
import { useConvexClient, useQuery } from "convex-svelte";
import { proxify } from "~/lib/proxify.svelte";

const { voteId }: { voteId: Id<"votes"> } = $props();

Expand All @@ -9,99 +10,134 @@

const convex = useConvexClient();

let isResultVisible = $state(false);
const { numbersOfVotersPerOption } = $derived(calculateVotes());
const { isResultVisible } = $derived(myVotes());
let { selectedOptions } = $derived(proxify(myVotes()));

let numbersOfVotersPerOption: number[] = $state([]);

let selectedOptions: number[] = $state([]);
interface CalculateVotesReturn {
numbersOfVotersPerOption: number[];
}
interface MyVotesReturn {
isResultVisible: boolean;
selectedOptions: number[];
}

if (vote.data) {
let tempNumbersOfVotersPerOption: number[] = [];
for (let i = 0; i < vote.data.voteOptions.length; i++) {
let num = 0;
for (let j = 0; j < vote.data.voters.length; j++) {
if (vote.data.voters[j].votedOptions.includes(i)) {
num++;
}
}
tempNumbersOfVotersPerOption.push(num);
function myVotes(): MyVotesReturn {
let isResultVisible = false;
let selectedOptions: number[] = [];
if (!vote.data) {
return {
selectedOptions,
isResultVisible,
};
}
numbersOfVotersPerOption = tempNumbersOfVotersPerOption;

if (me.data) {
for (let i = 0; i < vote.data.voters.length; i++) {
if (vote.data.voters[i].userId === me.data._id) {
selectedOptions = vote.data.voters[i].votedOptions;
for (const voter of vote.data.voters) {
if (voter.userId === me.data._id) {
selectedOptions = voter.votedOptions;
isResultVisible = true;
}
}
}
return {
isResultVisible: false,
selectedOptions: [],
};
}

$effect(() => {
if (vote.data) {
let tempNumbersOfVotersPerOption: number[] = [];
for (let i = 0; i < vote.data.voteOptions.length; i++) {
let num = 0;
for (let j = 0; j < vote.data.voters.length; j++) {
if (vote.data.voters[j].votedOptions.includes(i)) {
num++;
}
}
tempNumbersOfVotersPerOption.push(num);
}
numbersOfVotersPerOption = tempNumbersOfVotersPerOption;
if (me.data) {
for (let i = 0; i < vote.data.voters.length; i++) {
if (vote.data.voters[i].userId === me.data._id) {
selectedOptions = vote.data.voters[i].votedOptions;
isResultVisible = true;
}
function calculateVotes(): CalculateVotesReturn {
if (!vote.data) {
return {
numbersOfVotersPerOption: [],
};
}
const numbersOfVotersPerOption: number[] = [];
for (let i = 0; i < vote.data.voteOptions.length; i++) {
let num = 0;
for (const voter of vote.data.voters) {
if (voter.votedOptions.includes(i)) {
num++;
}
}
numbersOfVotersPerOption.push(num);
}
numbersOfVotersPerOption;

return {
numbersOfVotersPerOption,
};
}

function clickableStatus(i: number): "selected" | "can select" | "capped" {
if (hasInSelectedOptions(i)) return "selected";
if (vote.data && selectedOptions.length < vote.data.maxVotes)
return "can select";
return "capped";
}
function toggleSelectionOption(i: number) {
if (selectedOptions.includes(i)) {
removeFromSelectedOptions(i);
} else {
addToSelectedOptions(i);
}
});
}
function hasInSelectedOptions(i: number) {
return selectedOptions.includes(i);
}
function removeFromSelectedOptions(i: number) {
selectedOptions = selectedOptions.filter((op) => op !== i);
}
function addToSelectedOptions(i: number) {
if (vote.data && selectedOptions.length < vote.data.maxVotes) {
selectedOptions.push(i);
}
}
</script>

<div class="card bg-base-200 rounded p-2 shadow">
<h2 class="text-primary m-1 font-mono text-4xl">投票:</h2>
<h1 class="m-1 font-mono text-5xl">{vote.data?.title}</h1>
<p class="text-secondary m-1 font-mono">
一人の最大投票数:{vote.data?.maxVotes}票
</p>
{#each vote.data?.voteOptions as option, i}
<div class="flex">
<p class="m-1 text-xl">
{option}{isResultVisible
? ":" + numbersOfVotersPerOption[i] + "人"
: ""}
</p>
<button
class="btn m-1 ml-auto {selectedOptions.includes(i)
? 'btn-secondary'
: 'btn-primary'}"
onclick={() => {
if (selectedOptions.includes(i)) {
selectedOptions = selectedOptions.filter((op) => op !== i);
} else {
if (vote.data && selectedOptions.length < vote.data.maxVotes) {
selectedOptions.push(i);
}
}
}}>{selectedOptions.includes(i) ? "削除" : "選択"}</button
>
</div>
{/each}
<button
class="btn btn-primary w-16"
onclick={async () => {
if (me.data) {
await convex.mutation(api.vote.vote, {
voteId: voteId,
userId: me.data._id as Id<"users">,
votedOptions: selectedOptions,
});
}
}}>投票</button
>
</div>
{#if vote.data}
<div class="card bg-base-200 rounded p-2 shadow">
<h2 class="text-primary m-1 font-mono text-4xl">投票:</h2>
<h1 class="m-1 font-mono text-5xl">{vote.data?.title}</h1>
<p class="text-secondary m-1 font-mono">
一人の最大投票数:{vote.data?.maxVotes}票
</p>
{#each vote.data?.voteOptions as option, i}
{@const status = clickableStatus(i)}
<div class="flex">
<p class="m-1 text-xl">
{#if isResultVisible && numbersOfVotersPerOption[i]}
{option}: {numbersOfVotersPerOption[i]}人
{:else}
{option}
{/if}
</p>
<button
class={[
"btn {selectedOptions.includes(i) m-1 ml-auto",
status === "can select" && "btn-primary",
status === "selected" && "btn-error",
]}
disabled={status === "capped"}
onclick={() => {
toggleSelectionOption(i);
}}
>
{hasInSelectedOptions(i) ? "解除" : "選択"}
</button>
</div>
{/each}
<button
class="btn btn-primary w-16"
onclick={async () => {
if (me.data) {
await convex.mutation(api.vote.vote, {
voteId: voteId,
userId: me.data._id as Id<"users">,
votedOptions: selectedOptions,
});
}
}}
>
投票
</button>
</div>
{:else}{/if}
6 changes: 6 additions & 0 deletions packages/client/src/lib/proxify.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// workaround of svelte's $derived not being reactive:
// see https://github.com/sveltejs/svelte/issues/16189#issuecomment-2979989750
export function proxify<T>(init: T) {
const s = $state(init);
return s;
}
10 changes: 5 additions & 5 deletions packages/convex/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@
"sync": "bun run convex codegen"
},
"devDependencies": {
"@types/bun": "^1.2.18"
"@types/bun": "^1.2.23"
},
"peerDependencies": {
"typescript": "^5.8.3"
"typescript": "^5.9.3"
},
"dependencies": {
"@auth/core": "^0.40.0",
"@convex-dev/auth": "^0.0.87",
"convex": "^1.27.1",
"resend": "^4.7.0"
"@convex-dev/auth": "^0.0.90",
"convex": "^1.27.3",
"resend": "^6.1.2"
}
}
1 change: 1 addition & 0 deletions packages/convex/src/convex/vote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const vote = mutation({
},
handler: async (ctx, args) => {
const vote = await ctx.db.get(args.voteId);
if (!vote) return;
const tempVoters = vote?.voters.filter(
(v: { userId: Id<"users">; votedOptions: Array<number> }) =>
v.userId !== args.userId,
Expand Down
Loading