kripke: add share buttons
This commit is contained in:
@@ -17,6 +17,8 @@ export const buttonVariants = tv({
|
|||||||
"border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm",
|
"border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm",
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm",
|
||||||
|
foreground:
|
||||||
|
"bg-foreground text-background hover:bg-foreground/80 shadow-sm",
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
import * as Dialog from "$lib/components/ui/dialog";
|
import * as Dialog from "$lib/components/ui/dialog";
|
||||||
import { type Formula, isomorphic, validWorlds } from "@cannorin/kripke";
|
import { type Formula, isomorphic, validWorlds } from "@cannorin/kripke";
|
||||||
|
import LuRotateCw from "lucide-svelte/icons/rotate-cw";
|
||||||
import LuX from "lucide-svelte/icons/x";
|
import LuX from "lucide-svelte/icons/x";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
import FrameInput from "./frame-input.svelte";
|
import FrameInput from "./frame-input.svelte";
|
||||||
import Game, { type GameStatus, type Move } from "./game.svelte";
|
import Game, { type GameStatus, type Move } from "./game.svelte";
|
||||||
import Rules from "./rules.svelte";
|
import Rules from "./rules.svelte";
|
||||||
|
import Share from "./share.svelte";
|
||||||
|
|
||||||
import { daily } from "./store";
|
import { daily } from "./store";
|
||||||
import { getDailyFrame, getTimeUntilNextGame } from "./system";
|
import { getDailyFrame, getTimeUntilNextGame } from "./system";
|
||||||
@@ -82,12 +84,20 @@ onMount(() => {
|
|||||||
<FrameInput class="pb-6" disabled width={250} height={250} frame={frame} />
|
<FrameInput class="pb-6" disabled width={250} height={250} frame={frame} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog.Footer class="gap-x-1 gap-y-2">
|
<Dialog.Footer>
|
||||||
<Button
|
<div class="flex flex-col md:flex-row gap-2 w-full justify-end">
|
||||||
variant="outline"
|
<Share date={$daily.date} moves={$daily.moves} status={status} />
|
||||||
onclick={() => (dialogOpen = false)}>
|
<Button
|
||||||
<LuX class="w-4 h-4 mt-[2px]" /> Close
|
href="/kripke/random"
|
||||||
</Button>
|
data-sveltekit-reload>
|
||||||
|
<LuRotateCw class="w-4 h-4 mt-[2px]" /> Play Random Challenge
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="foreground"
|
||||||
|
onclick={() => (dialogOpen = false)}>
|
||||||
|
<LuX class="w-4 h-4 mt-[2px]" /> Close
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</Dialog.Footer>
|
</Dialog.Footer>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
|
|||||||
@@ -200,10 +200,8 @@ const colors: Record<number, string> = {
|
|||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
{#if status === "win"}
|
{#if status === "win"}
|
||||||
<li class="flex flex-col items-center gap-2 rounded bg-primary text-background p-5 animate-fade-in">
|
<li class="flex flex-col items-center gap-2 rounded bg-green-700 text-background p-5 animate-fade-in">
|
||||||
<p class="text-xl font-bold">YOU WIN!</p>
|
<p class="text-xl font-bold">YOU WIN!</p>
|
||||||
|
|
||||||
<p class="text-sm">Play <a class="underline font-medium" href="/kripke/random">random challenge</a>?</p>
|
|
||||||
</li>
|
</li>
|
||||||
{:else if status === "lose"}
|
{:else if status === "lose"}
|
||||||
{#await getAnswer() then answerId}
|
{#await getAnswer() then answerId}
|
||||||
@@ -216,7 +214,6 @@ const colors: Record<number, string> = {
|
|||||||
<span class="text-xs text-muted self-start px-2 py-1">id: {answerId}</span>
|
<span class="text-xs text-muted self-start px-2 py-1">id: {answerId}</span>
|
||||||
<FrameInput class="pb-6" disabled width={250} height={250} frame={getFrame(answerId)} />
|
<FrameInput class="pb-6" disabled width={250} height={250} frame={getFrame(answerId)} />
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm self-end">Play <a class="underline font-medium" href="/kripke/random">random challenge</a>?</p>
|
|
||||||
</li>
|
</li>
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
import { Button } from "$lib/components/ui/button";
|
import { Button } from "$lib/components/ui/button";
|
||||||
import * as Dialog from "$lib/components/ui/dialog";
|
import * as Dialog from "$lib/components/ui/dialog";
|
||||||
import { type Formula, isomorphic, validWorlds } from "@cannorin/kripke";
|
import { type Formula, isomorphic, validWorlds } from "@cannorin/kripke";
|
||||||
|
import LuRotateCw from "lucide-svelte/icons/rotate-cw";
|
||||||
import LuX from "lucide-svelte/icons/x";
|
import LuX from "lucide-svelte/icons/x";
|
||||||
|
|
||||||
import FrameInput from "../frame-input.svelte";
|
import FrameInput from "../frame-input.svelte";
|
||||||
import Game, { type GameStatus, type Move } from "../game.svelte";
|
import Game, { type GameStatus, type Move } from "../game.svelte";
|
||||||
import Rules from "../rules.svelte";
|
import Rules from "../rules.svelte";
|
||||||
|
import Share from "../share.svelte";
|
||||||
import { getRandomFrame } from "../system";
|
import { getRandomFrame } from "../system";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
@@ -78,12 +80,20 @@ $effect(() => {
|
|||||||
<FrameInput class="pb-6" disabled width={250} height={250} frame={frame} />
|
<FrameInput class="pb-6" disabled width={250} height={250} frame={frame} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Dialog.Footer class="gap-x-1 gap-y-2">
|
<Dialog.Footer>
|
||||||
<Button
|
<div class="flex flex-col md:flex-row gap-2 w-full justify-end">
|
||||||
variant="outline"
|
<Share seed={seed} moves={moves} status={status} />
|
||||||
onclick={() => (dialogOpen = false)}>
|
<Button
|
||||||
<LuX class="w-4 h-4 mt-[2px]" /> Close
|
href="/kripke/random"
|
||||||
</Button>
|
data-sveltekit-reload>
|
||||||
|
<LuRotateCw class="w-4 h-4 mt-[2px]" /> Play New Game
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="foreground"
|
||||||
|
onclick={() => (dialogOpen = false)}>
|
||||||
|
<LuX class="w-4 h-4 mt-[2px]" /> Close
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</Dialog.Footer>
|
</Dialog.Footer>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
|
|||||||
71
apps/web/src/routes/kripke/share.svelte
Normal file
71
apps/web/src/routes/kripke/share.svelte
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import SiBluesky from "@icons-pack/svelte-simple-icons/icons/SiBluesky";
|
||||||
|
import SiMastodon from "@icons-pack/svelte-simple-icons/icons/SiMastodon";
|
||||||
|
import SiMisskey from "@icons-pack/svelte-simple-icons/icons/SiMisskey";
|
||||||
|
import SiX from "@icons-pack/svelte-simple-icons/icons/SiX";
|
||||||
|
import type { GameStatus, Move } from "./game.svelte";
|
||||||
|
|
||||||
|
let {
|
||||||
|
date,
|
||||||
|
moves,
|
||||||
|
status,
|
||||||
|
seed,
|
||||||
|
}: { moves: Move[]; status: GameStatus } & (
|
||||||
|
| { date: string; seed?: undefined }
|
||||||
|
| { seed: number; date?: undefined }
|
||||||
|
) = $props();
|
||||||
|
|
||||||
|
const numberEmojis = ["0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣"];
|
||||||
|
|
||||||
|
let shareText = $derived.by(() => {
|
||||||
|
const history = moves
|
||||||
|
.map((move) => {
|
||||||
|
switch (move.type) {
|
||||||
|
case "guess": {
|
||||||
|
return move.correct ? "✅" : "❎";
|
||||||
|
}
|
||||||
|
case "check": {
|
||||||
|
return numberEmojis[move.valid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
const text = `#KRIPKE_game (${date ?? ""}${seed ? `seed ${seed}` : ""})
|
||||||
|
|
||||||
|
${history} ${status === "win" ? moves.length : "X"}/10
|
||||||
|
|
||||||
|
https://www.cannorin.net/kripke${seed ? `/random?seed=${seed}` : ""}`;
|
||||||
|
|
||||||
|
return encodeURIComponent(text);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav class="h-9 flex items-center justify-between gap-4 mr-auto">
|
||||||
|
<ul class="flex items-center gap-4">
|
||||||
|
<li>
|
||||||
|
<a href="https://x.com/intent/tweet?text={shareText}" target="_blank" rel="noopener noreferrer">
|
||||||
|
<SiX size={20} />
|
||||||
|
<span class="sr-only"> Share to X (Twitter)</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://mastoshare.net/share?text={shareText}" target="_blank" rel="noopener noreferrer">
|
||||||
|
<SiMastodon size={20} />
|
||||||
|
<span class="sr-only"> Share to Mastodon</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://misskeyshare.link/share.html?text={shareText}" target="_blank" rel="noopener noreferrer">
|
||||||
|
<SiMisskey size={20} />
|
||||||
|
<span class="sr-only"> Share to Misskey</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://bsky.app/intent/compose?text={shareText}" target="_blank" rel="noopener noreferrer">
|
||||||
|
<SiBluesky size={20} />
|
||||||
|
<span class="sr-only"> Share to Bluesky</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
Reference in New Issue
Block a user