diff --git a/bun.lock b/bun.lock index f62c77e..58cf289 100644 --- a/bun.lock +++ b/bun.lock @@ -14,6 +14,7 @@ "ace-builds": "1.36.3", "clsx": "2.1.1", "delay": "6.0.0", + "lucide-preact": "^0.577.0", "preact": "10.22.0", }, "devDependencies": { @@ -116,6 +117,8 @@ "lower-case": ["lower-case@1.1.4", "", {}, "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA=="], + "lucide-preact": ["lucide-preact@0.577.0", "", { "peerDependencies": { "preact": "^10.27.2" } }, "sha512-fCY59YQ2OMYWqE1V7k8HwfXyiBMHAfTI1roCOasdc+Cekya7BIObSJ/cil+tVMSbU6siv4uZlaz5twAGmkYqIQ=="], + "mute-stream": ["mute-stream@1.0.0", "", {}, "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA=="], "no-case": ["no-case@2.3.2", "", { "dependencies": { "lower-case": "^1.1.1" } }, "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ=="], diff --git a/package.json b/package.json index 0c87c7c..867b173 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,11 @@ "ace-builds": "1.36.3", "clsx": "2.1.1", "delay": "6.0.0", + "lucide-preact": "0.577.0", "preact": "10.22.0" }, "devDependencies": { - "@types/bun": "^1.3.10", + "@types/bun": "latest", "@types/html-minifier": "4.0.5", "@types/inquirer": "9.0.7", "@types/web-bluetooth": "0.0.21", diff --git a/src/games/storywriter/assets/settings-modal.module.css b/src/games/storywriter/assets/settings-modal.module.css index dd660ab..5a4906d 100644 --- a/src/games/storywriter/assets/settings-modal.module.css +++ b/src/games/storywriter/assets/settings-modal.module.css @@ -114,3 +114,49 @@ background: var(--accent); color: var(--accent-text); } + +.inputRow { + display: flex; + gap: 8px; +} + +.inputRow .input { + flex: 1; +} + +.divider { + height: 1px; + background: var(--border); + margin: 16px 0; +} + +.tokenList { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 8px; +} + +.tokenItem { + display: flex; + align-items: center; + gap: 4px; + padding: 4px 8px; + background: var(--bg-active); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--text); +} + +.tokenRemoveButton { + background: none; + border: none; + cursor: pointer; + padding: 0 2px; + font-size: 14px; + color: var(--text-muted); +} + +.emptyText { + color: var(--text-muted); +} diff --git a/src/games/storywriter/assets/style.css b/src/games/storywriter/assets/style.css index ba18ba0..a73fe5d 100644 --- a/src/games/storywriter/assets/style.css +++ b/src/games/storywriter/assets/style.css @@ -1,27 +1,27 @@ :root { /* Monokai-inspired palette */ - --bg: #272822; - --bg-panel: #1e1f1a; - --bg-hover: #3e3d32; - --bg-active: #49483e; - --border: #3e3d32; - --accent: #f92672; - --accent-alt: #a6e22e; - --text: #f8f8f2; - --text-muted: #75715e; - --text-dim: #cfcfc2; - --yellow: #e6db74; - --orange: #fd971f; - --blue: #66d9ef; - --purple: #ae81ff; + --bg: #272822; + --bg-panel: #1e1f1a; + --bg-hover: #3e3d32; + --bg-active: #49483e; + --border: #3e3d32; + --accent: #f92672; + --accent-alt: #a6e22e; + --text: #f8f8f2; + --text-muted: #75715e; + --text-dim: #cfcfc2; + --yellow: #e6db74; + --orange: #fd971f; + --blue: #66d9ef; + --purple: #ae81ff; - --radius: 4px; - --transition: 0.15s ease; - - --textColor: #DCDCD2; + --radius: 4px; + --transition: 0.15s ease; + + --textColor: #DCDCD2; --italicColor: #AFAFAF; - --quoteColor: #D4E5FF; - --codeBg: #49483e; + --quoteColor: #D4E5FF; + --codeBg: #49483e; } * { @@ -51,9 +51,13 @@ button { font-family: inherit; transition: color var(--transition), background var(--transition); border-radius: var(--radius); + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; &:hover { color: var(--text); background: var(--bg-hover); } -} +} \ No newline at end of file diff --git a/src/games/storywriter/components/banned-tokens-modal.tsx b/src/games/storywriter/components/banned-tokens-modal.tsx new file mode 100644 index 0000000..763ba44 --- /dev/null +++ b/src/games/storywriter/components/banned-tokens-modal.tsx @@ -0,0 +1,98 @@ +import clsx from "clsx"; + +import { useInputState } from "@common/hooks/useInputState"; + +import { useAppState } from "../contexts/state"; +import styles from "../assets/settings-modal.module.css"; +import { X } from "lucide-preact"; + +interface Props { + onClose: () => void; +} + +export const BannedTokensModal = ({ onClose }: Props) => { + const { bannedTokens, dispatch } = useAppState(); + const [inputValue, setInputValue] = useInputState(); + + const handleAdd = () => { + const trimmed = inputValue.trim(); + if (trimmed && !bannedTokens.includes(trimmed)) { + dispatch({ + type: "SET_BANNED_TOKENS", + tokens: [...bannedTokens, trimmed], + }); + setInputValue(""); + } + }; + + const handleRemove = (token: string) => { + dispatch({ + type: "SET_BANNED_TOKENS", + tokens: bannedTokens.filter((t) => t !== token), + }); + }; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Enter") { + handleAdd(); + } else if (e.key === "Escape") { + onClose(); + } + }; + + const sortedTokens = [...bannedTokens].sort((a, b) => + a.trim().toLowerCase().localeCompare(b.trim().toLowerCase()) + ); + + return ( +
+ ); +}; diff --git a/src/games/storywriter/components/menu-sidebar.tsx b/src/games/storywriter/components/menu-sidebar.tsx index c0ef6f8..63d7370 100644 --- a/src/games/storywriter/components/menu-sidebar.tsx +++ b/src/games/storywriter/components/menu-sidebar.tsx @@ -1,11 +1,13 @@ import clsx from "clsx"; import { Sidebar } from "./sidebar"; import { SettingsModal } from "./settings-modal"; +import { BannedTokensModal } from "./banned-tokens-modal"; import { useAppState } from "../contexts/state"; import { useBool } from "@common/hooks/useBool"; import type { Story } from "../contexts/state"; import styles from '../assets/menu-sidebar.module.css'; import { useState } from "preact/hooks"; +import { Pencil, X, Plus, Settings, Ban } from "lucide-preact"; // ─── Story Item ─────────────────────────────────────────────────────────────── @@ -66,10 +68,10 @@ const StoryItem = ({ story, active, onSelect, onRename, onDelete }: StoryItemPro