feat(ui): replace unicode icons with solid-icons
Swap emoji and unicode glyphs for Tabler components so icons inherit currentColor and font-size and render consistently across platforms. - add src/components/doc-icons.ts to resolve doc-entry frontmatter icon keys to components; frontmatter now stores semantic keys - replace the align-*.png assets with TbOutlineAlign* icons - render RevealManager action SVGs as components instead of injected innerHTML strings - carry completion icons via an optional CompletionItem.icon field
This commit is contained in:
+8
-3
@@ -7,6 +7,11 @@ import {
|
|||||||
createSignal,
|
createSignal,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import { useLocation } from "@solidjs/router";
|
import { useLocation } from "@solidjs/router";
|
||||||
|
import {
|
||||||
|
TbOutlineMenu2,
|
||||||
|
TbOutlineClipboardList,
|
||||||
|
TbOutlineHelpCircle,
|
||||||
|
} from "solid-icons/tb";
|
||||||
import { useJournalStream } from "./components/stores/journalStream";
|
import { useJournalStream } from "./components/stores/journalStream";
|
||||||
|
|
||||||
// 导入组件以注册自定义元素
|
// 导入组件以注册自定义元素
|
||||||
@@ -99,7 +104,7 @@ const App: Component = () => {
|
|||||||
class="md:hidden text-gray-600 hover:text-gray-900 p-1 rounded hover:bg-gray-100"
|
class="md:hidden text-gray-600 hover:text-gray-900 p-1 rounded hover:bg-gray-100"
|
||||||
title="目录"
|
title="目录"
|
||||||
>
|
>
|
||||||
☰
|
<TbOutlineMenu2 class="inline-block align-[-0.125em]" />
|
||||||
</button>
|
</button>
|
||||||
<h1 class="text-2xl font-bold text-gray-900">Tabletop Toolkit</h1>
|
<h1 class="text-2xl font-bold text-gray-900">Tabletop Toolkit</h1>
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
@@ -122,14 +127,14 @@ const App: Component = () => {
|
|||||||
"bg-red-500": stream.connectionStatus === "error",
|
"bg-red-500": stream.connectionStatus === "error",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
📋
|
<TbOutlineClipboardList size={16} />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsDocOpen(true)}
|
onClick={() => setIsDocOpen(true)}
|
||||||
class="w-8 h-8 rounded-full border border-gray-300 text-gray-500 hover:text-gray-700 hover:border-gray-400 flex items-center justify-center text-sm font-medium"
|
class="w-8 h-8 rounded-full border border-gray-300 text-gray-500 hover:text-gray-700 hover:border-gray-400 flex items-center justify-center text-sm font-medium"
|
||||||
title="指令文档"
|
title="指令文档"
|
||||||
>
|
>
|
||||||
?
|
<TbOutlineHelpCircle size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -6,8 +6,17 @@ import {
|
|||||||
onCleanup,
|
onCleanup,
|
||||||
onMount,
|
onMount,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
|
import { Dynamic } from "solid-js/web";
|
||||||
|
import {
|
||||||
|
TbOutlineBook2,
|
||||||
|
TbOutlineCheck,
|
||||||
|
TbOutlineChevronDown,
|
||||||
|
TbOutlineClipboardList,
|
||||||
|
TbOutlineX,
|
||||||
|
} from "solid-icons/tb";
|
||||||
import { docEntries, type DocEntry } from "./doc-data";
|
import { docEntries, type DocEntry } from "./doc-data";
|
||||||
import { journalDocEntries, type JournalDocEntry } from "./journal-doc-data";
|
import { journalDocEntries, type JournalDocEntry } from "./journal-doc-data";
|
||||||
|
import { docIcon } from "./doc-icons";
|
||||||
import { parseMarkdown } from "../markdown";
|
import { parseMarkdown } from "../markdown";
|
||||||
|
|
||||||
type DocSet = "directives" | "journal";
|
type DocSet = "directives" | "journal";
|
||||||
@@ -77,7 +86,7 @@ const DocDialog: Component<DocDialogProps> = (props) => {
|
|||||||
class="text-lg font-bold text-gray-900 hover:text-blue-600 flex items-center gap-1"
|
class="text-lg font-bold text-gray-900 hover:text-blue-600 flex items-center gap-1"
|
||||||
>
|
>
|
||||||
{docSetLabel()}
|
{docSetLabel()}
|
||||||
<span class="text-gray-400 text-sm">▾</span>
|
<TbOutlineChevronDown class="text-gray-400 text-sm" />
|
||||||
</button>
|
</button>
|
||||||
<Show when={dropdownOpen()}>
|
<Show when={dropdownOpen()}>
|
||||||
<div class="absolute top-full left-0 mt-1 w-52 bg-white border border-gray-200 rounded shadow-lg z-60 py-1">
|
<div class="absolute top-full left-0 mt-1 w-52 bg-white border border-gray-200 rounded shadow-lg z-60 py-1">
|
||||||
@@ -89,10 +98,10 @@ const DocDialog: Component<DocDialogProps> = (props) => {
|
|||||||
: "text-gray-700"
|
: "text-gray-700"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>📖</span>
|
<TbOutlineBook2 />
|
||||||
<span>指令组件文档</span>
|
<span>指令组件文档</span>
|
||||||
<Show when={docSet() === "directives"}>
|
<Show when={docSet() === "directives"}>
|
||||||
<span class="ml-auto text-blue-500">✓</span>
|
<TbOutlineCheck class="ml-auto text-blue-500" />
|
||||||
</Show>
|
</Show>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -103,10 +112,10 @@ const DocDialog: Component<DocDialogProps> = (props) => {
|
|||||||
: "text-gray-700"
|
: "text-gray-700"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span>📋</span>
|
<TbOutlineClipboardList />
|
||||||
<span>Journal 服务器文档</span>
|
<span>Journal 服务器文档</span>
|
||||||
<Show when={docSet() === "journal"}>
|
<Show when={docSet() === "journal"}>
|
||||||
<span class="ml-auto text-blue-500">✓</span>
|
<TbOutlineCheck class="ml-auto text-blue-500" />
|
||||||
</Show>
|
</Show>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,7 +126,7 @@ const DocDialog: Component<DocDialogProps> = (props) => {
|
|||||||
class="text-gray-400 hover:text-gray-600 text-xl leading-none p-1"
|
class="text-gray-400 hover:text-gray-600 text-xl leading-none p-1"
|
||||||
title="关闭"
|
title="关闭"
|
||||||
>
|
>
|
||||||
✕
|
<TbOutlineX />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -133,7 +142,10 @@ const DocDialog: Component<DocDialogProps> = (props) => {
|
|||||||
: "text-gray-700 hover:bg-gray-100"
|
: "text-gray-700 hover:bg-gray-100"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span class="mr-2">{entry.icon}</span>
|
<Dynamic
|
||||||
|
component={docIcon(entry.icon)}
|
||||||
|
class="mr-2 inline-block align-[-0.125em]"
|
||||||
|
/>
|
||||||
<Show
|
<Show
|
||||||
when={docSet() === "directives"}
|
when={docSet() === "directives"}
|
||||||
fallback={<span class="text-xs">{entry.title}</span>}
|
fallback={<span class="text-xs">{entry.title}</span>}
|
||||||
@@ -164,7 +176,11 @@ const DocContent: Component<{ entry: AnyEntry }> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div class="max-w-none">
|
<div class="max-w-none">
|
||||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">
|
<h2 class="text-2xl font-bold text-gray-900 mb-6">
|
||||||
{e.icon} {e.title}
|
<Dynamic
|
||||||
|
component={docIcon(e.icon)}
|
||||||
|
class="inline-block align-[-0.125em]"
|
||||||
|
/>{" "}
|
||||||
|
{e.title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<article
|
<article
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { Component, createMemo, createSignal, Show } from "solid-js";
|
import { Component, createMemo, createSignal, Show } from "solid-js";
|
||||||
import { useLocation } from "@solidjs/router";
|
import { useLocation } from "@solidjs/router";
|
||||||
|
import {
|
||||||
|
TbOutlineFolderOpen,
|
||||||
|
TbOutlineFolder,
|
||||||
|
TbOutlineFile,
|
||||||
|
TbOutlineChevronRight,
|
||||||
|
} from "solid-icons/tb";
|
||||||
import { type FileNode, type TocNode } from "../data-loader";
|
import { type FileNode, type TocNode } from "../data-loader";
|
||||||
import { useNavigateWithParams } from "./useNavigateWithParams";
|
import { useNavigateWithParams } from "./useNavigateWithParams";
|
||||||
import { useScrollContainer } from "../App";
|
import { useScrollContainer } from "../App";
|
||||||
@@ -61,10 +67,14 @@ export const FileTreeNode: Component<{
|
|||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<Show when={isDir}>
|
<Show when={isDir}>
|
||||||
<span class="mr-1 text-gray-400">{isExpanded() ? "📂" : "📁"}</span>
|
{isExpanded() ? (
|
||||||
|
<TbOutlineFolderOpen class="mr-1 text-gray-400" />
|
||||||
|
) : (
|
||||||
|
<TbOutlineFolder class="mr-1 text-gray-400" />
|
||||||
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={!isDir}>
|
<Show when={!isDir}>
|
||||||
<span class="mr-1 text-gray-400">📄</span>
|
<TbOutlineFile class="mr-1 text-gray-400" />
|
||||||
</Show>
|
</Show>
|
||||||
<span class="text-sm truncate">{props.node.name}</span>
|
<span class="text-sm truncate">{props.node.name}</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -151,7 +161,7 @@ export const HeadingNode: Component<{
|
|||||||
<span
|
<span
|
||||||
class={`${hasChildren ? "" : "invisible"} ${isExpanded() ? "rotate-90" : ""} mt-1 inline-block transition-transform `}
|
class={`${hasChildren ? "" : "invisible"} ${isExpanded() ? "rotate-90" : ""} mt-1 inline-block transition-transform `}
|
||||||
>
|
>
|
||||||
▶
|
<TbOutlineChevronRight class="inline-block align-[-0.125em]" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ import {
|
|||||||
onCleanup,
|
onCleanup,
|
||||||
Show,
|
Show,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import { Portal } from "solid-js/web";
|
import { Portal, Dynamic } from "solid-js/web";
|
||||||
import { useLocation } from "@solidjs/router";
|
import { useLocation } from "@solidjs/router";
|
||||||
|
import type { IconTypes } from "solid-icons";
|
||||||
|
import { TbOutlineLink, TbOutlineSparkles } from "solid-icons/tb";
|
||||||
import { useArticleDom } from "./Article";
|
import { useArticleDom } from "./Article";
|
||||||
import { journalStreamState } from "./stores/journalStream";
|
import { journalStreamState } from "./stores/journalStream";
|
||||||
import { useJournalCompletions } from "./journal/completions";
|
import { useJournalCompletions } from "./journal/completions";
|
||||||
@@ -27,25 +29,6 @@ import {
|
|||||||
normalizePath,
|
normalizePath,
|
||||||
} from "./stores/reveal";
|
} from "./stores/reveal";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// SVG icons
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
const LINK_SVG =
|
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" ' +
|
|
||||||
'viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" ' +
|
|
||||||
'stroke-linecap="round" stroke-linejoin="round">' +
|
|
||||||
'<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>' +
|
|
||||||
'<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>' +
|
|
||||||
"</svg>";
|
|
||||||
|
|
||||||
const SPARK_SVG =
|
|
||||||
'<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" ' +
|
|
||||||
'viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" ' +
|
|
||||||
'stroke-linecap="round" stroke-linejoin="round">' +
|
|
||||||
'<path d="M12 2l1.5 6.5L18 7l-4.5 4.5L16 16l-4-2.5L8 16l1.5-4.5L5 7l4.5-.5z"/>' +
|
|
||||||
"</svg>";
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Floating button state
|
// Floating button state
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -54,7 +37,7 @@ interface FloatingButton {
|
|||||||
rect: DOMRect;
|
rect: DOMRect;
|
||||||
action: () => void;
|
action: () => void;
|
||||||
title: string;
|
title: string;
|
||||||
svg: string;
|
icon: IconTypes;
|
||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,7 +113,7 @@ export const RevealManager: Component = () => {
|
|||||||
text: combinedSlug,
|
text: combinedSlug,
|
||||||
}),
|
}),
|
||||||
title: "Roll spark table",
|
title: "Roll spark table",
|
||||||
svg: SPARK_SVG,
|
icon: TbOutlineSparkles,
|
||||||
color: "text-purple-400 hover:text-purple-600 hover:bg-purple-50",
|
color: "text-purple-400 hover:text-purple-600 hover:bg-purple-50",
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -150,7 +133,7 @@ export const RevealManager: Component = () => {
|
|||||||
text: `${normalized}#${text}`,
|
text: `${normalized}#${text}`,
|
||||||
}),
|
}),
|
||||||
title: "Send /link to stream",
|
title: "Send /link to stream",
|
||||||
svg: LINK_SVG,
|
icon: TbOutlineLink,
|
||||||
color: "text-blue-500 hover:bg-blue-50",
|
color: "text-blue-500 hover:bg-blue-50",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -183,14 +166,15 @@ export const RevealManager: Component = () => {
|
|||||||
left: `${btn().rect.left - 20}px`,
|
left: `${btn().rect.left - 20}px`,
|
||||||
}}
|
}}
|
||||||
title={btn().title}
|
title={btn().title}
|
||||||
innerHTML={btn().svg}
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
btn().action();
|
btn().action();
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => clearTimeout(hideTimer)}
|
onMouseEnter={() => clearTimeout(hideTimer)}
|
||||||
onMouseLeave={hide}
|
onMouseLeave={hide}
|
||||||
/>
|
>
|
||||||
|
<Dynamic component={btn().icon} size={12} />
|
||||||
|
</button>
|
||||||
</Portal>
|
</Portal>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Component, createMemo, createSignal, onMount, Show } from "solid-js";
|
import { Component, createMemo, createSignal, onMount, Show } from "solid-js";
|
||||||
import { generateToc, type FileNode, type TocNode } from "../data-loader";
|
import { generateToc, type FileNode, type TocNode } from "../data-loader";
|
||||||
import { useLocation } from "@solidjs/router";
|
import { useLocation } from "@solidjs/router";
|
||||||
|
import { TbOutlineFolderOpen, TbOutlineX } from "solid-icons/tb";
|
||||||
import { FileTreeNode, HeadingNode } from "./FileTree";
|
import { FileTreeNode, HeadingNode } from "./FileTree";
|
||||||
import { isPathRevealed } from "./stores/reveal";
|
import { isPathRevealed } from "./stores/reveal";
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ const SidebarContent: Component<SidebarContentProps> = (props) => {
|
|||||||
class="text-gray-400 hover:text-gray-600 p-1 rounded hover:bg-gray-100"
|
class="text-gray-400 hover:text-gray-600 p-1 rounded hover:bg-gray-100"
|
||||||
title="Content Source"
|
title="Content Source"
|
||||||
>
|
>
|
||||||
📂
|
<TbOutlineFolderOpen class="inline-block align-[-0.125em]" />
|
||||||
</button>
|
</button>
|
||||||
<Show when={!props.isDesktop}>
|
<Show when={!props.isDesktop}>
|
||||||
<button
|
<button
|
||||||
@@ -65,7 +66,7 @@ const SidebarContent: Component<SidebarContentProps> = (props) => {
|
|||||||
class="text-gray-500 hover:text-gray-700"
|
class="text-gray-500 hover:text-gray-700"
|
||||||
title="关闭"
|
title="关闭"
|
||||||
>
|
>
|
||||||
✕
|
<TbOutlineX class="inline-block align-[-0.125em]" />
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Icon registry for built-in doc entries.
|
||||||
|
*
|
||||||
|
* Doc-entry frontmatter `icon:` values are semantic keys (e.g. `dice`), not
|
||||||
|
* glyphs — YAML can't reference a component. This maps each key to a Tabler
|
||||||
|
* icon component; unknown keys fall back to a generic file icon.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { IconTypes } from "solid-icons";
|
||||||
|
import {
|
||||||
|
TbOutlineCards,
|
||||||
|
TbOutlineDice5,
|
||||||
|
TbOutlineFile,
|
||||||
|
TbOutlineFileText,
|
||||||
|
TbOutlineGitBranch,
|
||||||
|
TbOutlineHash,
|
||||||
|
TbOutlineLink,
|
||||||
|
TbOutlineMapPin,
|
||||||
|
TbOutlinePhoto,
|
||||||
|
TbOutlineSparkles,
|
||||||
|
TbOutlineSquare,
|
||||||
|
TbOutlineTable,
|
||||||
|
TbOutlineTheater,
|
||||||
|
TbOutlineTypography,
|
||||||
|
} from "solid-icons/tb";
|
||||||
|
|
||||||
|
const DOC_ICONS: Record<string, IconTypes> = {
|
||||||
|
dice: TbOutlineDice5,
|
||||||
|
table: TbOutlineTable,
|
||||||
|
link: TbOutlineLink,
|
||||||
|
pins: TbOutlineMapPin,
|
||||||
|
font: TbOutlineTypography,
|
||||||
|
background: TbOutlinePhoto,
|
||||||
|
border: TbOutlineSquare,
|
||||||
|
embed: TbOutlineFileText,
|
||||||
|
deck: TbOutlineCards,
|
||||||
|
yarn: TbOutlineGitBranch,
|
||||||
|
gm: TbOutlineTheater,
|
||||||
|
player: TbOutlineDice5,
|
||||||
|
roll: TbOutlineDice5,
|
||||||
|
set: TbOutlineHash,
|
||||||
|
spark: TbOutlineSparkles,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Resolve a frontmatter icon key to a Tabler icon component. */
|
||||||
|
export function docIcon(key: string): IconTypes {
|
||||||
|
return DOC_ICONS[key] ?? TbOutlineFile;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, For, Show, createEffect, onMount, onCleanup } from "solid-js";
|
import { Component, For, Show, createEffect, onMount, onCleanup } from "solid-js";
|
||||||
|
import { Dynamic } from "solid-js/web";
|
||||||
import type { CompletionItem } from "./command-parser";
|
import type { CompletionItem } from "./command-parser";
|
||||||
import type { CompletionsState } from "./completions";
|
import type { CompletionsState } from "./completions";
|
||||||
|
|
||||||
@@ -102,6 +103,11 @@ export const CompletionsDropdown: Component<CompletionsDropdownProps> = (
|
|||||||
{item.kind === "command" ? "cmd" : "val"}
|
{item.kind === "command" ? "cmd" : "val"}
|
||||||
</span>
|
</span>
|
||||||
</Show>
|
</Show>
|
||||||
|
<Show when={item.icon}>
|
||||||
|
{(icon) => (
|
||||||
|
<Dynamic component={icon()} size={14} class="shrink-0" />
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
<span class="font-mono truncate flex-1">{item.label}</span>
|
<span class="font-mono truncate flex-1">{item.label}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
* or creating a session is a separate step that reuses that connection.
|
* or creating a session is a separate step that reuses that connection.
|
||||||
*/
|
*/
|
||||||
import { Component, createSignal, For, onMount, Show } from "solid-js";
|
import { Component, createSignal, For, onMount, Show } from "solid-js";
|
||||||
|
import { TbOutlineTrash } from "solid-icons/tb";
|
||||||
import {
|
import {
|
||||||
connectBroker,
|
connectBroker,
|
||||||
createSession,
|
createSession,
|
||||||
@@ -167,7 +168,7 @@ export const ConnectDialog: Component = () => {
|
|||||||
class="shrink-0 px-2 py-1.5 text-[10px] text-gray-300 hover:text-red-500 disabled:opacity-50"
|
class="shrink-0 px-2 py-1.5 text-[10px] text-gray-300 hover:text-red-500 disabled:opacity-50"
|
||||||
title="删除会话"
|
title="删除会话"
|
||||||
>
|
>
|
||||||
🗑
|
<TbOutlineTrash />
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* CreateSessionDialog — modal for naming a new session
|
* CreateSessionDialog — modal for naming a new session
|
||||||
*/
|
*/
|
||||||
import { Component, createSignal, onMount } from "solid-js";
|
import { Component, createSignal, onMount } from "solid-js";
|
||||||
|
import { TbOutlineX } from "solid-icons/tb";
|
||||||
import { createSession, joinSession } from "../stores/journalStream";
|
import { createSession, joinSession } from "../stores/journalStream";
|
||||||
|
|
||||||
interface CreateSessionDialogProps {
|
interface CreateSessionDialogProps {
|
||||||
@@ -48,7 +49,7 @@ export const CreateSessionDialog: Component<CreateSessionDialogProps> = (
|
|||||||
onClick={props.onClose}
|
onClick={props.onClose}
|
||||||
class="text-gray-400 hover:text-gray-600 text-sm"
|
class="text-gray-400 hover:text-gray-600 text-sm"
|
||||||
>
|
>
|
||||||
✕
|
<TbOutlineX />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-gray-500">输入新会话的名称。</p>
|
<p class="text-xs text-gray-500">输入新会话的名称。</p>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
* log), so the warning spells out that it cannot be undone.
|
* log), so the warning spells out that it cannot be undone.
|
||||||
*/
|
*/
|
||||||
import { Component, createSignal, Show } from "solid-js";
|
import { Component, createSignal, Show } from "solid-js";
|
||||||
|
import { TbOutlineX } from "solid-icons/tb";
|
||||||
import { deleteSession } from "../stores/journalStream";
|
import { deleteSession } from "../stores/journalStream";
|
||||||
|
|
||||||
interface DeleteSessionDialogProps {
|
interface DeleteSessionDialogProps {
|
||||||
@@ -49,7 +50,7 @@ export const DeleteSessionDialog: Component<DeleteSessionDialogProps> = (
|
|||||||
onClick={props.onClose}
|
onClick={props.onClose}
|
||||||
class="text-gray-400 hover:text-gray-600 text-sm"
|
class="text-gray-400 hover:text-gray-600 text-sm"
|
||||||
>
|
>
|
||||||
✕
|
<TbOutlineX />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-gray-600">
|
<p class="text-xs text-gray-600">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
* the journal input area. Toggled by clicking the truncated error text.
|
* the journal input area. Toggled by clicking the truncated error text.
|
||||||
*/
|
*/
|
||||||
import { Component, Show } from "solid-js";
|
import { Component, Show } from "solid-js";
|
||||||
|
import { TbOutlineX } from "solid-icons/tb";
|
||||||
|
|
||||||
interface ErrorPopupProps {
|
interface ErrorPopupProps {
|
||||||
message: string | null;
|
message: string | null;
|
||||||
@@ -21,7 +22,7 @@ export const ErrorPopup: Component<ErrorPopupProps> = (props) => {
|
|||||||
class="text-red-400 hover:text-red-600 text-xs leading-none ml-2 shrink-0"
|
class="text-red-400 hover:text-red-600 text-xs leading-none ml-2 shrink-0"
|
||||||
title="关闭"
|
title="关闭"
|
||||||
>
|
>
|
||||||
✕
|
<TbOutlineX />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* InviteDialog — input player name, copy invite link
|
* InviteDialog — input player name, copy invite link
|
||||||
*/
|
*/
|
||||||
import { Component, createSignal } from "solid-js";
|
import { Component, createSignal } from "solid-js";
|
||||||
|
import { TbOutlineX } from "solid-icons/tb";
|
||||||
import { useJournalStream } from "../stores/journalStream";
|
import { useJournalStream } from "../stores/journalStream";
|
||||||
|
|
||||||
interface InviteDialogProps {
|
interface InviteDialogProps {
|
||||||
@@ -51,7 +52,7 @@ export const InviteDialog: Component<InviteDialogProps> = (props) => {
|
|||||||
onClick={props.onClose}
|
onClick={props.onClose}
|
||||||
class="text-gray-400 hover:text-gray-600 text-sm"
|
class="text-gray-400 hover:text-gray-600 text-sm"
|
||||||
>
|
>
|
||||||
✕
|
<TbOutlineX />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-gray-500">
|
<p class="text-xs text-gray-500">
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
* JournalHeader — status dot, player name, session dropdown, leave & close
|
* JournalHeader — status dot, player name, session dropdown, leave & close
|
||||||
*/
|
*/
|
||||||
import { Component, Show } from "solid-js";
|
import { Component, Show } from "solid-js";
|
||||||
|
import {
|
||||||
|
TbOutlineArrowLeft,
|
||||||
|
TbOutlineLayoutSidebarRightCollapse,
|
||||||
|
} from "solid-icons/tb";
|
||||||
import { useJournalStream, leaveSession } from "../stores/journalStream";
|
import { useJournalStream, leaveSession } from "../stores/journalStream";
|
||||||
import { SessionDropdown } from "./SessionDropdown";
|
import { SessionDropdown } from "./SessionDropdown";
|
||||||
|
|
||||||
@@ -70,7 +74,7 @@ export const JournalHeader: Component<JournalHeaderProps> = (props) => {
|
|||||||
class="text-xs text-gray-400 hover:text-blue-500 px-1"
|
class="text-xs text-gray-400 hover:text-blue-500 px-1"
|
||||||
title="返回会话列表"
|
title="返回会话列表"
|
||||||
>
|
>
|
||||||
←
|
<TbOutlineArrowLeft />
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
<button
|
<button
|
||||||
@@ -78,7 +82,7 @@ export const JournalHeader: Component<JournalHeaderProps> = (props) => {
|
|||||||
class="text-gray-400 hover:text-gray-600 text-sm px-1"
|
class="text-gray-400 hover:text-gray-600 text-sm px-1"
|
||||||
title="隐藏面板"
|
title="隐藏面板"
|
||||||
>
|
>
|
||||||
▸
|
<TbOutlineLayoutSidebarRightCollapse />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
* SessionDropdown — session switcher for the journal header (GM only)
|
* SessionDropdown — session switcher for the journal header (GM only)
|
||||||
*/
|
*/
|
||||||
import { Component, createSignal, For, Show } from "solid-js";
|
import { Component, createSignal, For, Show } from "solid-js";
|
||||||
|
import {
|
||||||
|
TbOutlineCheck,
|
||||||
|
TbOutlineChevronDown,
|
||||||
|
TbOutlineTrash,
|
||||||
|
} from "solid-icons/tb";
|
||||||
import {
|
import {
|
||||||
sessions,
|
sessions,
|
||||||
joinSession,
|
joinSession,
|
||||||
@@ -46,7 +51,7 @@ export const SessionDropdown: Component<SessionDropdownProps> = (props) => {
|
|||||||
class="text-[10px] text-gray-400 hover:text-gray-600 truncate leading-tight cursor-pointer text-left"
|
class="text-[10px] text-gray-400 hover:text-gray-600 truncate leading-tight cursor-pointer text-left"
|
||||||
>
|
>
|
||||||
{stream.sessionName || stream.sessionId}{" "}
|
{stream.sessionName || stream.sessionId}{" "}
|
||||||
<span class="text-gray-300">▾</span>
|
<TbOutlineChevronDown class="text-gray-300 inline-block align-[-0.125em]" />
|
||||||
</button>
|
</button>
|
||||||
<Show when={dropdownOpen()}>
|
<Show when={dropdownOpen()}>
|
||||||
<div
|
<div
|
||||||
@@ -71,7 +76,7 @@ export const SessionDropdown: Component<SessionDropdownProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<span class="truncate flex-1">{meta.name || id}</span>
|
<span class="truncate flex-1">{meta.name || id}</span>
|
||||||
<Show when={id === stream.sessionId}>
|
<Show when={id === stream.sessionId}>
|
||||||
<span class="text-blue-500 shrink-0">✓</span>
|
<TbOutlineCheck class="text-blue-500 shrink-0" />
|
||||||
</Show>
|
</Show>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -83,7 +88,7 @@ export const SessionDropdown: Component<SessionDropdownProps> = (props) => {
|
|||||||
class="shrink-0 px-2 py-1 text-[10px] text-gray-300 hover:text-red-500"
|
class="shrink-0 px-2 py-1 text-[10px] text-gray-300 hover:text-red-500"
|
||||||
title="删除会话"
|
title="删除会话"
|
||||||
>
|
>
|
||||||
🗑
|
<TbOutlineTrash />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, Show, createMemo } from "solid-js";
|
import { Component, Show, createMemo } from "solid-js";
|
||||||
|
import { TbOutlineArrowBackUp } from "solid-icons/tb";
|
||||||
import type { StreamMessage as StreamMessageType } from "./registry";
|
import type { StreamMessage as StreamMessageType } from "./registry";
|
||||||
import { getMessageType } from "./registry";
|
import { getMessageType } from "./registry";
|
||||||
import { revertLatest, useJournalStream } from "../stores/journalStream";
|
import { revertLatest, useJournalStream } from "../stores/journalStream";
|
||||||
@@ -47,7 +48,7 @@ export const StreamMessageCard: Component<{
|
|||||||
class="text-xs text-gray-300 hover:text-red-500 ml-auto px-0.5 leading-none"
|
class="text-xs text-gray-300 hover:text-red-500 ml-auto px-0.5 leading-none"
|
||||||
title="撤回此消息"
|
title="撤回此消息"
|
||||||
>
|
>
|
||||||
×
|
<TbOutlineArrowBackUp />
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import type { CompletionItem } from "./command-parser";
|
import type { CompletionItem } from "./command-parser";
|
||||||
import type { JournalCompletions } from "./completions";
|
import type { JournalCompletions } from "./completions";
|
||||||
|
import { TbOutlineDice5, TbOutlineSparkles } from "solid-icons/tb";
|
||||||
|
|
||||||
export interface CompletionsContext {
|
export interface CompletionsContext {
|
||||||
data: JournalCompletions;
|
data: JournalCompletions;
|
||||||
@@ -63,7 +64,8 @@ export function buildCompletions(
|
|||||||
|
|
||||||
for (const d of diceMatches) {
|
for (const d of diceMatches) {
|
||||||
items.push({
|
items.push({
|
||||||
label: `🎲 ${d.notation}`,
|
label: d.notation,
|
||||||
|
icon: TbOutlineDice5,
|
||||||
kind: "value",
|
kind: "value",
|
||||||
insertText: "/roll " + d.notation,
|
insertText: "/roll " + d.notation,
|
||||||
});
|
});
|
||||||
@@ -71,7 +73,8 @@ export function buildCompletions(
|
|||||||
|
|
||||||
for (const s of sparkMatches) {
|
for (const s of sparkMatches) {
|
||||||
items.push({
|
items.push({
|
||||||
label: `✨ ${s.slug} (${s.notation})`,
|
label: `${s.slug} (${s.notation})`,
|
||||||
|
icon: TbOutlineSparkles,
|
||||||
kind: "value",
|
kind: "value",
|
||||||
insertText: `/roll ${s.slug}`,
|
insertText: `/roll ${s.slug}`,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,10 +5,14 @@
|
|||||||
* module and keep JournalInput small.
|
* module and keep JournalInput small.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { IconTypes } from "solid-icons";
|
||||||
|
|
||||||
export interface CompletionItem {
|
export interface CompletionItem {
|
||||||
label: string;
|
label: string;
|
||||||
kind: "command" | "value" | "no-results";
|
kind: "command" | "value" | "no-results";
|
||||||
insertText: string;
|
insertText: string;
|
||||||
|
/** Optional icon rendered before the label in the completions dropdown. */
|
||||||
|
icon?: IconTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ParsedInput {
|
export interface ParsedInput {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { TbOutlineCircleCheck, TbOutlineHandStop } from "solid-icons/tb";
|
||||||
import { registerMessageType } from "../registry";
|
import { registerMessageType } from "../registry";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -25,7 +26,7 @@ registerMessageType<IntentPayload>({
|
|||||||
render: (p) => (
|
render: (p) => (
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-lg">🙋</span>
|
<TbOutlineHandStop class="text-lg" />
|
||||||
<span class="text-sm font-medium text-yellow-700">意图</span>
|
<span class="text-sm font-medium text-yellow-700">意图</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm">{p.description}</p>
|
<p class="text-sm">{p.description}</p>
|
||||||
@@ -58,7 +59,7 @@ registerMessageType<ResolutionPayload>({
|
|||||||
render: (p) => (
|
render: (p) => (
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-lg">✅</span>
|
<TbOutlineCircleCheck class="text-lg" />
|
||||||
<span class="text-sm font-medium text-green-700">裁决</span>
|
<span class="text-sm font-medium text-green-700">裁决</span>
|
||||||
<span class="text-xs text-gray-400">re: {p.intentId}</span>
|
<span class="text-xs text-gray-400">re: {p.intentId}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component } from "solid-js";
|
import { Component } from "solid-js";
|
||||||
|
import { TbOutlineFileText } from "solid-icons/tb";
|
||||||
import { useNavigateWithParams } from "../../useNavigateWithParams";
|
import { useNavigateWithParams } from "../../useNavigateWithParams";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { registerMessageType } from "../registry";
|
import { registerMessageType } from "../registry";
|
||||||
@@ -69,7 +70,7 @@ const RevealLink: Component<LinkPayload> = (p) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-lg">📄</span>
|
<TbOutlineFileText class="text-lg" />
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a
|
||||||
href={target()}
|
href={target()}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { TbOutlineArrowRight, TbOutlineDice5 } from "solid-icons/tb";
|
||||||
import { registerMessageType } from "../registry";
|
import { registerMessageType } from "../registry";
|
||||||
import { rollFormula } from "../../dice";
|
import { rollFormula } from "../../dice";
|
||||||
|
|
||||||
@@ -55,9 +56,10 @@ registerMessageType<RollPayload>({
|
|||||||
return (
|
return (
|
||||||
<div class="space-y-0.5">
|
<div class="space-y-0.5">
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
<span class="text-lg">🎲</span>
|
<TbOutlineDice5 class="text-lg" />
|
||||||
<span class="font-mono text-sm">
|
<span class="font-mono text-sm">
|
||||||
{p.notation} →{" "}
|
{p.notation}{" "}
|
||||||
|
<TbOutlineArrowRight class="inline-block align-[-0.125em]" />{" "}
|
||||||
<span class="font-bold text-base">{result.total}</span>
|
<span class="font-bold text-base">{result.total}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { TbOutlineHash } from "solid-icons/tb";
|
||||||
import { registerMessageType } from "../registry";
|
import { registerMessageType } from "../registry";
|
||||||
import { journalSetState } from "../../stores/journalStream";
|
import { journalSetState } from "../../stores/journalStream";
|
||||||
import { produce } from "solid-js/store";
|
import { produce } from "solid-js/store";
|
||||||
@@ -35,7 +36,7 @@ registerMessageType<VarPayload>({
|
|||||||
render: (p) => {
|
render: (p) => {
|
||||||
return (
|
return (
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
<span class="text-lg">🔢</span>
|
<TbOutlineHash class="text-lg" />
|
||||||
<span class="font-mono text-sm">
|
<span class="font-mono text-sm">
|
||||||
{p.key} = <span class="font-bold">{p.value}</span>
|
{p.key} = <span class="font-bold">{p.value}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { For, Show, createEffect, createSignal, on } from "solid-js";
|
import { For, Show, createEffect, createSignal, on } from "solid-js";
|
||||||
|
import { TbOutlineChevronLeft, TbOutlineChevronRight } from "solid-icons/tb";
|
||||||
import type { DeckStore } from "./hooks/deckStore";
|
import type { DeckStore } from "./hooks/deckStore";
|
||||||
|
|
||||||
export interface CardListProps {
|
export interface CardListProps {
|
||||||
@@ -67,7 +68,7 @@ export function CardList(props: CardListProps) {
|
|||||||
disabled={page() === 0}
|
disabled={page() === 0}
|
||||||
class="px-2 py-1 rounded text-sm text-gray-600 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
|
class="px-2 py-1 rounded text-sm text-gray-600 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
|
||||||
>
|
>
|
||||||
‹
|
<TbOutlineChevronLeft />
|
||||||
</button>
|
</button>
|
||||||
<span class="text-xs text-gray-500 tabular-nums">
|
<span class="text-xs text-gray-500 tabular-nums">
|
||||||
{page() + 1} / {totalPages()}
|
{page() + 1} / {totalPages()}
|
||||||
@@ -77,7 +78,7 @@ export function CardList(props: CardListProps) {
|
|||||||
disabled={page() >= totalPages() - 1}
|
disabled={page() >= totalPages() - 1}
|
||||||
class="px-2 py-1 rounded text-sm text-gray-600 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
|
class="px-2 py-1 rounded text-sm text-gray-600 hover:bg-gray-100 disabled:opacity-40 disabled:cursor-not-allowed cursor-pointer"
|
||||||
>
|
>
|
||||||
›
|
<TbOutlineChevronRight />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { Show, createSignal, onMount } from "solid-js";
|
import { Show, createSignal, onMount } from "solid-js";
|
||||||
import { Portal } from "solid-js/web";
|
import { Portal } from "solid-js/web";
|
||||||
|
import {
|
||||||
|
TbOutlineCheck,
|
||||||
|
TbOutlineCopy,
|
||||||
|
TbOutlineDownload,
|
||||||
|
TbOutlinePencil,
|
||||||
|
} from "solid-icons/tb";
|
||||||
import type { DeckStore } from "./hooks/deckStore";
|
import type { DeckStore } from "./hooks/deckStore";
|
||||||
|
|
||||||
export interface DeckHeaderProps {
|
export interface DeckHeaderProps {
|
||||||
@@ -43,7 +49,15 @@ export function DeckHeader(props: DeckHeaderProps) {
|
|||||||
: "bg-gray-100 text-gray-600 hover:bg-gray-200"
|
: "bg-gray-100 text-gray-600 hover:bg-gray-200"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{store.state.isEditing ? "✓ 编辑中" : "✏️ 编辑"}
|
{store.state.isEditing ? (
|
||||||
|
<>
|
||||||
|
<TbOutlineCheck class="inline-block align-[-0.125em]" /> 编辑中
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<TbOutlinePencil class="inline-block align-[-0.125em]" /> 编辑
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* 导出 PDF 按钮 */}
|
{/* 导出 PDF 按钮 */}
|
||||||
@@ -51,7 +65,7 @@ export function DeckHeader(props: DeckHeaderProps) {
|
|||||||
onClick={() => store.actions.exportDeck()}
|
onClick={() => store.actions.exportDeck()}
|
||||||
class="px-2 py-1 rounded text-xs font-medium transition-colors cursor-pointer bg-green-100 text-green-600 hover:bg-green-200"
|
class="px-2 py-1 rounded text-xs font-medium transition-colors cursor-pointer bg-green-100 text-green-600 hover:bg-green-200"
|
||||||
>
|
>
|
||||||
📥 导出 PDF
|
<TbOutlineDownload class="inline-block align-[-0.125em]" /> 导出 PDF
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* 复制代码按钮 */}
|
{/* 复制代码按钮 */}
|
||||||
@@ -59,7 +73,7 @@ export function DeckHeader(props: DeckHeaderProps) {
|
|||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
class="px-2 py-1 rounded text-xs font-medium transition-colors cursor-pointer bg-purple-100 text-purple-600 hover:bg-purple-200"
|
class="px-2 py-1 rounded text-xs font-medium transition-colors cursor-pointer bg-purple-100 text-purple-600 hover:bg-purple-200"
|
||||||
>
|
>
|
||||||
📋 复制代码
|
<TbOutlineCopy class="inline-block align-[-0.125em]" /> 复制代码
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createSignal, For, Show, createMemo } from "solid-js";
|
import { createSignal, For, Show, createMemo } from "solid-js";
|
||||||
import { Portal } from "solid-js/web";
|
import { Portal } from "solid-js/web";
|
||||||
|
import { TbOutlineDownload } from "solid-icons/tb";
|
||||||
import { parsePlt, extractCutPaths } from "../../plotcutter/parser";
|
import { parsePlt, extractCutPaths } from "../../plotcutter/parser";
|
||||||
import { generateTravelPaths, travelPathsToSvg } from "../../plotcutter/layout";
|
import { generateTravelPaths, travelPathsToSvg } from "../../plotcutter/layout";
|
||||||
import type { CardPath } from "../../plotcutter/types";
|
import type { CardPath } from "../../plotcutter/types";
|
||||||
@@ -115,7 +116,7 @@ export function PltPreview(props: PltPreviewProps) {
|
|||||||
class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1.5 rounded text-sm font-medium cursor-pointer flex items-center gap-1"
|
class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1.5 rounded text-sm font-medium cursor-pointer flex items-center gap-1"
|
||||||
disabled={parsedData().cardPaths.length === 0}
|
disabled={parsedData().cardPaths.length === 0}
|
||||||
>
|
>
|
||||||
📥 下载 PLT
|
<TbOutlineDownload /> 下载 PLT
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Show } from 'solid-js';
|
import { Show } from 'solid-js';
|
||||||
|
import { TbOutlineCircleX, TbOutlineX } from 'solid-icons/tb';
|
||||||
import type { DeckStore } from './hooks/deckStore';
|
import type { DeckStore } from './hooks/deckStore';
|
||||||
|
|
||||||
export interface PrintPreviewFooterProps {
|
export interface PrintPreviewFooterProps {
|
||||||
@@ -26,14 +27,14 @@ export function PrintPreviewFooter(props: PrintPreviewFooterProps) {
|
|||||||
<Show when={store.state.exportError}>
|
<Show when={store.state.exportError}>
|
||||||
<div class="flex items-center justify-between gap-4">
|
<div class="flex items-center justify-between gap-4">
|
||||||
<div class="flex items-center gap-2 text-red-600">
|
<div class="flex items-center gap-2 text-red-600">
|
||||||
<span>❌</span>
|
<TbOutlineCircleX />
|
||||||
<span class="text-sm font-medium">{store.state.exportError}</span>
|
<span class="text-sm font-medium">{store.state.exportError}</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => store.actions.clearExportError()}
|
onClick={() => store.actions.clearExportError()}
|
||||||
class="text-gray-500 hover:text-gray-700 cursor-pointer"
|
class="text-gray-500 hover:text-gray-700 cursor-pointer"
|
||||||
>
|
>
|
||||||
✕
|
<TbOutlineX />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { createSignal, For, onCleanup, onMount, Show } from "solid-js";
|
import { createSignal, For, onCleanup, onMount, Show } from "solid-js";
|
||||||
|
import {
|
||||||
|
TbOutlineDownload,
|
||||||
|
TbOutlinePlus,
|
||||||
|
TbOutlineRuler,
|
||||||
|
} from "solid-icons/tb";
|
||||||
import type { DeckStore } from "./hooks/deckStore";
|
import type { DeckStore } from "./hooks/deckStore";
|
||||||
import { getCompatibleDecks } from "./hooks/deck-registry";
|
import { getCompatibleDecks } from "./hooks/deck-registry";
|
||||||
|
|
||||||
@@ -77,7 +82,7 @@ export function PrintPreviewHeader(props: PrintPreviewHeaderProps) {
|
|||||||
: "添加其他牌组到打印"
|
: "添加其他牌组到打印"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span>➕</span>
|
<TbOutlinePlus />
|
||||||
<span>添加牌组</span>
|
<span>添加牌组</span>
|
||||||
<Show when={props.selectedDeckIds.length > 0}>
|
<Show when={props.selectedDeckIds.length > 0}>
|
||||||
<span class="bg-purple-600 text-white text-xs rounded-full w-5 h-5 inline-flex items-center justify-center">
|
<span class="bg-purple-600 text-white text-xs rounded-full w-5 h-5 inline-flex items-center justify-center">
|
||||||
@@ -232,14 +237,14 @@ export function PrintPreviewHeader(props: PrintPreviewHeaderProps) {
|
|||||||
onClick={props.onOpenPltPreview}
|
onClick={props.onOpenPltPreview}
|
||||||
class="bg-green-600 hover:bg-green-700 text-white px-4 py-1.5 rounded text-sm font-medium cursor-pointer flex items-center gap-2"
|
class="bg-green-600 hover:bg-green-700 text-white px-4 py-1.5 rounded text-sm font-medium cursor-pointer flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<span>📐</span>
|
<TbOutlineRuler />
|
||||||
<span>PLT 预览</span>
|
<span>PLT 预览</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={props.onExport}
|
onClick={props.onExport}
|
||||||
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-1.5 rounded text-sm font-medium cursor-pointer flex items-center gap-2"
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-1.5 rounded text-sm font-medium cursor-pointer flex items-center gap-2"
|
||||||
>
|
>
|
||||||
<span>📥</span>
|
<TbOutlineDownload />
|
||||||
<span>导出 PDF</span>
|
<span>导出 PDF</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,10 +1,27 @@
|
|||||||
import { For, createSignal, onCleanup, onMount } from "solid-js";
|
import { For, createSignal, onCleanup, onMount } from "solid-js";
|
||||||
|
import type { JSX } from "solid-js";
|
||||||
|
import { Dynamic } from "solid-js/web";
|
||||||
import { createSortable, useDragDropContext } from "@thisbeyond/solid-dnd";
|
import { createSortable, useDragDropContext } from "@thisbeyond/solid-dnd";
|
||||||
|
import type { IconTypes } from "solid-icons";
|
||||||
|
import {
|
||||||
|
TbOutlineAlignCenter,
|
||||||
|
TbOutlineAlignLeft,
|
||||||
|
TbOutlineAlignRight,
|
||||||
|
TbOutlineArrowDown,
|
||||||
|
TbOutlineArrowDownLeft,
|
||||||
|
TbOutlineArrowDownRight,
|
||||||
|
TbOutlineArrowLeft,
|
||||||
|
TbOutlineArrowRight,
|
||||||
|
TbOutlineArrowUp,
|
||||||
|
TbOutlineArrowUpLeft,
|
||||||
|
TbOutlineArrowUpRight,
|
||||||
|
TbOutlineEye,
|
||||||
|
TbOutlineEyeOff,
|
||||||
|
TbOutlineGripVertical,
|
||||||
|
TbOutlineTrash,
|
||||||
|
} from "solid-icons/tb";
|
||||||
import type { DeckStore } from "../hooks/deckStore";
|
import type { DeckStore } from "../hooks/deckStore";
|
||||||
import type { Align, LayerConfig } from "../types";
|
import type { Align, LayerConfig } from "../types";
|
||||||
import alignLeftIcon from "./icons/align-left.png";
|
|
||||||
import alignCenterIcon from "./icons/align-center.png";
|
|
||||||
import alignRightIcon from "./icons/align-right.png";
|
|
||||||
|
|
||||||
export interface LayerRowProps {
|
export interface LayerRowProps {
|
||||||
store: DeckStore;
|
store: DeckStore;
|
||||||
@@ -22,65 +39,67 @@ export interface LayerRowProps {
|
|||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ORIENTATIONS = [
|
const ORIENTATIONS: {
|
||||||
{ value: "n" as const, label: "↑ 北" },
|
value: "n" | "e" | "s" | "w";
|
||||||
{ value: "e" as const, label: "→ 东" },
|
label: string;
|
||||||
{ value: "s" as const, label: "↓ 南" },
|
icon: IconTypes;
|
||||||
{ value: "w" as const, label: "← 西" },
|
}[] = [
|
||||||
|
{ value: "n", label: "北", icon: TbOutlineArrowUp },
|
||||||
|
{ value: "e", label: "东", icon: TbOutlineArrowRight },
|
||||||
|
{ value: "s", label: "南", icon: TbOutlineArrowDown },
|
||||||
|
{ value: "w", label: "西", icon: TbOutlineArrowLeft },
|
||||||
];
|
];
|
||||||
|
|
||||||
const ALIGNS: { value: Align | ""; icon: string }[] = [
|
const ALIGNS: { value: Align | ""; icon: IconTypes }[] = [
|
||||||
{ value: "", icon: alignCenterIcon },
|
{ value: "", icon: TbOutlineAlignCenter },
|
||||||
{ value: "l", icon: alignLeftIcon },
|
{ value: "l", icon: TbOutlineAlignLeft },
|
||||||
{ value: "c", icon: alignCenterIcon },
|
{ value: "c", icon: TbOutlineAlignCenter },
|
||||||
{ value: "r", icon: alignRightIcon },
|
{ value: "r", icon: TbOutlineAlignRight },
|
||||||
{ value: "tl", icon: "↖" },
|
{ value: "tl", icon: TbOutlineArrowUpLeft },
|
||||||
{ value: "tc", icon: "↑" },
|
{ value: "tc", icon: TbOutlineArrowUp },
|
||||||
{ value: "tr", icon: "↗" },
|
{ value: "tr", icon: TbOutlineArrowUpRight },
|
||||||
{ value: "bl", icon: "↙" },
|
{ value: "bl", icon: TbOutlineArrowDownLeft },
|
||||||
{ value: "bc", icon: "↓" },
|
{ value: "bc", icon: TbOutlineArrowDown },
|
||||||
{ value: "br", icon: "↘" },
|
{ value: "br", icon: TbOutlineArrowDownRight },
|
||||||
];
|
];
|
||||||
|
|
||||||
const FONT_PRESETS = [3, 5, 8, 12] as const;
|
const FONT_PRESETS = [3, 5, 8, 12] as const;
|
||||||
|
|
||||||
function orientChar(v: string) {
|
function orientIcon(v: string): IconTypes {
|
||||||
switch (v) {
|
switch (v) {
|
||||||
case "n":
|
case "n":
|
||||||
return "↑";
|
return TbOutlineArrowUp;
|
||||||
case "e":
|
case "e":
|
||||||
return "→";
|
return TbOutlineArrowRight;
|
||||||
case "s":
|
case "s":
|
||||||
return "↓";
|
return TbOutlineArrowDown;
|
||||||
case "w":
|
case "w":
|
||||||
return "←";
|
return TbOutlineArrowLeft;
|
||||||
default:
|
default:
|
||||||
return "↑";
|
return TbOutlineArrowUp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function alignIcon(v: string): import("solid-js").JSX.Element {
|
function alignIcon(v: string): IconTypes {
|
||||||
switch (v) {
|
switch (v) {
|
||||||
case "tl":
|
case "tl":
|
||||||
return "↖";
|
return TbOutlineArrowUpLeft;
|
||||||
case "tc":
|
case "tc":
|
||||||
return "↑";
|
return TbOutlineArrowUp;
|
||||||
case "tr":
|
case "tr":
|
||||||
return "↗";
|
return TbOutlineArrowUpRight;
|
||||||
case "bl":
|
case "bl":
|
||||||
return "↙";
|
return TbOutlineArrowDownLeft;
|
||||||
case "bc":
|
case "bc":
|
||||||
return "↓";
|
return TbOutlineArrowDown;
|
||||||
case "br":
|
case "br":
|
||||||
return "↘";
|
return TbOutlineArrowDownRight;
|
||||||
case "l":
|
case "l":
|
||||||
return <img src={alignLeftIcon} alt="align" class="w-5 h-5 not-prose" />;
|
return TbOutlineAlignLeft;
|
||||||
case "r":
|
case "r":
|
||||||
return <img src={alignRightIcon} alt="align" class="w-5 h-5 not-prose" />;
|
return TbOutlineAlignRight;
|
||||||
default:
|
default:
|
||||||
return (
|
return TbOutlineAlignCenter;
|
||||||
<img src={alignCenterIcon} alt="align" class="w-5 h-5 not-prose" />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +144,7 @@ export function LayerRow(props: LayerRowProps) {
|
|||||||
class="cursor-grab text-gray-400 hover:text-gray-600 text-sm px-0.5 select-none shrink-0"
|
class="cursor-grab text-gray-400 hover:text-gray-600 text-sm px-0.5 select-none shrink-0"
|
||||||
title="拖动排序"
|
title="拖动排序"
|
||||||
>
|
>
|
||||||
⠿
|
<TbOutlineGripVertical class="inline-block align-[-0.125em]" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -133,7 +152,11 @@ export function LayerRow(props: LayerRowProps) {
|
|||||||
class="shrink-0 text-sm cursor-pointer select-none"
|
class="shrink-0 text-sm cursor-pointer select-none"
|
||||||
title={props.layer.visible ? "隐藏" : "显示"}
|
title={props.layer.visible ? "隐藏" : "显示"}
|
||||||
>
|
>
|
||||||
{props.layer.visible ? "👁" : "👁🗨"}
|
{props.layer.visible ? (
|
||||||
|
<TbOutlineEye class="inline-block align-[-0.125em]" />
|
||||||
|
) : (
|
||||||
|
<TbOutlineEyeOff class="inline-block align-[-0.125em]" />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
@@ -144,7 +167,7 @@ export function LayerRow(props: LayerRowProps) {
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
icon={orientChar(props.layer.orientation || "n")}
|
icon={orientIcon(props.layer.orientation || "n")}
|
||||||
visible={props.layer.visible}
|
visible={props.layer.visible}
|
||||||
open={props.openDropdown === `orient-${props.index}`}
|
open={props.openDropdown === `orient-${props.index}`}
|
||||||
onToggle={() =>
|
onToggle={() =>
|
||||||
@@ -162,6 +185,10 @@ export function LayerRow(props: LayerRowProps) {
|
|||||||
onClick={() => props.onUpdateOrientation(o.value)}
|
onClick={() => props.onUpdateOrientation(o.value)}
|
||||||
class="block w-full text-left px-3 py-1.5 text-sm hover:bg-gray-100 cursor-pointer whitespace-nowrap"
|
class="block w-full text-left px-3 py-1.5 text-sm hover:bg-gray-100 cursor-pointer whitespace-nowrap"
|
||||||
>
|
>
|
||||||
|
<Dynamic
|
||||||
|
component={o.icon}
|
||||||
|
class="inline-block align-[-0.125em]"
|
||||||
|
/>{" "}
|
||||||
{o.label}
|
{o.label}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
@@ -187,13 +214,11 @@ export function LayerRow(props: LayerRowProps) {
|
|||||||
onClick={() => props.onUpdateAlign(o.value || undefined)}
|
onClick={() => props.onUpdateAlign(o.value || undefined)}
|
||||||
class="flex items-center gap-2 w-full px-3 py-1.5 text-sm hover:bg-gray-100 cursor-pointer whitespace-nowrap"
|
class="flex items-center gap-2 w-full px-3 py-1.5 text-sm hover:bg-gray-100 cursor-pointer whitespace-nowrap"
|
||||||
>
|
>
|
||||||
{o.icon.endsWith(".png") ? (
|
<Dynamic
|
||||||
<img src={o.icon} alt="" class="w-4 h-4 not-prose max-w-none" />
|
component={o.icon}
|
||||||
) : (
|
size={16}
|
||||||
<span class="w-4 h-4 flex items-center justify-center not-prose">
|
class="not-prose max-w-none"
|
||||||
{o.icon}
|
/>
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
@@ -260,19 +285,19 @@ export function LayerRow(props: LayerRowProps) {
|
|||||||
class={`shrink-0 w-5 h-5 text-xs rounded cursor-pointer flex items-center justify-center text-red-400 hover:text-red-600 hover:bg-red-50 transition-opacity ${props.hovered ? "opacity-100" : "opacity-0"}`}
|
class={`shrink-0 w-5 h-5 text-xs rounded cursor-pointer flex items-center justify-center text-red-400 hover:text-red-600 hover:bg-red-50 transition-opacity ${props.hovered ? "opacity-100" : "opacity-0"}`}
|
||||||
title="删除图层"
|
title="删除图层"
|
||||||
>
|
>
|
||||||
✕
|
<TbOutlineTrash size={14} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DropdownButton(props: {
|
function DropdownButton(props: {
|
||||||
icon: import("solid-js").JSX.Element;
|
icon: IconTypes;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onToggle: () => void;
|
onToggle: () => void;
|
||||||
title: string;
|
title: string;
|
||||||
children: import("solid-js").JSX.Element;
|
children: JSX.Element;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -284,7 +309,7 @@ function DropdownButton(props: {
|
|||||||
class={`w-7 h-7 rounded cursor-pointer flex items-center justify-center bg-gray-200 hover:bg-gray-300 ${!props.visible ? "invisible pointer-events-none" : ""}`}
|
class={`w-7 h-7 rounded cursor-pointer flex items-center justify-center bg-gray-200 hover:bg-gray-300 ${!props.visible ? "invisible pointer-events-none" : ""}`}
|
||||||
title={props.title}
|
title={props.title}
|
||||||
>
|
>
|
||||||
{props.icon}
|
<Dynamic component={props.icon} size={16} />
|
||||||
</button>
|
</button>
|
||||||
{props.open && (
|
{props.open && (
|
||||||
<div
|
<div
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1002 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1006 B |
Binary file not shown.
|
Before Width: | Height: | Size: 996 B |
@@ -1,5 +1,6 @@
|
|||||||
import { customElement, noShadowDOM } from "solid-element";
|
import { customElement, noShadowDOM } from "solid-element";
|
||||||
import { createSignal, onMount } from "solid-js";
|
import { createSignal, onMount } from "solid-js";
|
||||||
|
import { TbOutlineDice5 } from "solid-icons/tb";
|
||||||
import { rollFormula } from "./dice";
|
import { rollFormula } from "./dice";
|
||||||
|
|
||||||
export interface DiceProps {
|
export interface DiceProps {
|
||||||
@@ -86,7 +87,7 @@ customElement("md-dice", { key: "" }, (props, { element }) => {
|
|||||||
class="text-blue-600 hover:text-blue-800 cursor-pointer"
|
class="text-blue-600 hover:text-blue-800 cursor-pointer"
|
||||||
title={rollDetail() || "掷骰子"}
|
title={rollDetail() || "掷骰子"}
|
||||||
>
|
>
|
||||||
🎲
|
<TbOutlineDice5 class="inline-block align-[-0.125em]" />
|
||||||
</span>
|
</span>
|
||||||
<a
|
<a
|
||||||
onClick={handleRoll}
|
onClick={handleRoll}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { customElement, noShadowDOM } from "solid-element";
|
import { customElement, noShadowDOM } from "solid-element";
|
||||||
import {Show, For, createResource, createMemo, createSignal} from "solid-js";
|
import {Show, For, createResource, createMemo, createSignal} from "solid-js";
|
||||||
|
import { TbOutlineCopy } from "solid-icons/tb";
|
||||||
import { resolvePath } from "./utils/path";
|
import { resolvePath } from "./utils/path";
|
||||||
import { createPinsStore } from "./stores/pinsStore";
|
import { createPinsStore } from "./stores/pinsStore";
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ customElement("md-pins", { pins: "", fixed: false, labelStart: "A" }, (props, {
|
|||||||
class="bg-gray-800 hover:bg-gray-700 text-white px-3 py-1.5 rounded shadow-lg text-sm flex items-center gap-1 transition-colors"
|
class="bg-gray-800 hover:bg-gray-700 text-white px-3 py-1.5 rounded shadow-lg text-sm flex items-center gap-1 transition-colors"
|
||||||
title="复制所有 pin 坐标"
|
title="复制所有 pin 坐标"
|
||||||
>
|
>
|
||||||
📋 复制
|
<TbOutlineCopy /> 复制
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
createMemo,
|
createMemo,
|
||||||
createResource,
|
createResource,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
|
import { TbOutlineDice5 } from "solid-icons/tb";
|
||||||
import { parseMarkdown } from "../markdown";
|
import { parseMarkdown } from "../markdown";
|
||||||
import { parseCSVString, CSV, processVariables } from "./utils/csv-loader";
|
import { parseCSVString, CSV, processVariables } from "./utils/csv-loader";
|
||||||
import { resolveContentRef } from "./utils/resolve-content";
|
import { resolveContentRef } from "./utils/resolve-content";
|
||||||
@@ -210,7 +211,7 @@ customElement(
|
|||||||
class="text-gray-500 hover:text-gray-700 shrink-0 cursor-pointer"
|
class="text-gray-500 hover:text-gray-700 shrink-0 cursor-pointer"
|
||||||
title="随机切换"
|
title="随机切换"
|
||||||
>
|
>
|
||||||
🎲
|
<TbOutlineDice5 class="inline-block align-[-0.125em]" />
|
||||||
</button>
|
</button>
|
||||||
</Show>
|
</Show>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { customElement, noShadowDOM } from 'solid-element';
|
import { customElement, noShadowDOM } from 'solid-element';
|
||||||
import { For, Show, createEffect } from 'solid-js';
|
import { For, Show, createEffect } from 'solid-js';
|
||||||
|
import {
|
||||||
|
TbOutlineRotate,
|
||||||
|
TbOutlinePlayerTrackNext,
|
||||||
|
TbOutlineArrowRight,
|
||||||
|
} from 'solid-icons/tb';
|
||||||
import type {TextResult, RuntimeResult, OptionsResult} from 'yarn-spinner-loader';
|
import type {TextResult, RuntimeResult, OptionsResult} from 'yarn-spinner-loader';
|
||||||
import { createYarnStore } from './stores/yarnStore';
|
import { createYarnStore } from './stores/yarnStore';
|
||||||
|
|
||||||
@@ -82,7 +87,7 @@ customElement<{start: string}>('md-yarn-spinner', {start: 'start'}, (props, { el
|
|||||||
rounded-bl-md transition-colors cursor-pointer"
|
rounded-bl-md transition-colors cursor-pointer"
|
||||||
title="重新开始"
|
title="重新开始"
|
||||||
>
|
>
|
||||||
🔄
|
<TbOutlineRotate class="inline-block align-[-0.125em]" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => advance()}
|
onClick={() => advance()}
|
||||||
@@ -90,7 +95,7 @@ customElement<{start: string}>('md-yarn-spinner', {start: 'start'}, (props, { el
|
|||||||
transition-colors cursor-pointer"
|
transition-colors cursor-pointer"
|
||||||
title="继续"
|
title="继续"
|
||||||
>
|
>
|
||||||
⏩
|
<TbOutlinePlayerTrackNext class="inline-block align-[-0.125em]" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -104,7 +109,7 @@ customElement<{start: string}>('md-yarn-spinner', {start: 'start'}, (props, { el
|
|||||||
class="option-button text-left px-4 py-2 bg-blue-50 hover:bg-blue-100
|
class="option-button text-left px-4 py-2 bg-blue-50 hover:bg-blue-100
|
||||||
rounded border border-blue-200 transition-colors cursor-pointer"
|
rounded border border-blue-200 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
→ {option.text}
|
<TbOutlineArrowRight class="inline-block align-[-0.125em]" /> {option.text}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
---
|
---
|
||||||
tag: journal-gm
|
tag: journal-gm
|
||||||
icon: 🎭
|
icon: gm
|
||||||
title: 主持人指南
|
title: 主持人指南
|
||||||
---
|
---
|
||||||
|
|
||||||
作为 GM 创建会话、管理玩家、使用命令与玩家互动。
|
作为 GM 创建会话、管理玩家、使用命令与玩家互动。
|
||||||
|
|
||||||
点击顶部导航栏的 📋 按钮打开 Journal 面板。
|
点击顶部导航栏的剪贴板按钮打开 Journal 面板。
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
1. 点击顶部导航栏的 📋 按钮打开 Journal 面板
|
1. 点击顶部导航栏的剪贴板按钮打开 Journal 面板
|
||||||
2. 输入你的名字,选择"主持人"角色,点击"加入"
|
2. 输入你的名字,选择"主持人"角色,点击"加入"
|
||||||
3. 连接成功后,你可以创建新会话或选择已有会话
|
3. 连接成功后,你可以创建新会话或选择已有会话
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: journal-link
|
tag: journal-link
|
||||||
icon: 🔗
|
icon: link
|
||||||
title: 链接命令
|
title: 链接命令
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: journal-player
|
tag: journal-player
|
||||||
icon: 🎲
|
icon: player
|
||||||
title: 玩家指南
|
title: 玩家指南
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ GM 会发送一个包含 `?session=xxx&player=你的名字&autojoin=1` 的链接
|
|||||||
|
|
||||||
### 手动加入
|
### 手动加入
|
||||||
|
|
||||||
1. 点击顶部导航栏的 📋 按钮打开 Journal 面板
|
1. 点击顶部导航栏的剪贴板按钮打开 Journal 面板
|
||||||
2. 输入你的名字
|
2. 输入你的名字
|
||||||
3. 选择"玩家"角色
|
3. 选择"玩家"角色
|
||||||
4. 点击"加入"
|
4. 点击"加入"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: journal-roll
|
tag: journal-roll
|
||||||
icon: 🎲
|
icon: roll
|
||||||
title: 掷骰与火花表命令
|
title: 掷骰与火花表命令
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: journal-set
|
tag: journal-set
|
||||||
icon: 🔢
|
icon: set
|
||||||
title: 变量系统
|
title: 变量系统
|
||||||
description: 在文档中声明变量和标签,通过 /set 命令设置变量值,自动计算派生变量和标签效果。
|
description: 在文档中声明变量和标签,通过 /set 命令设置变量值,自动计算派生变量和标签效果。
|
||||||
syntax: '```yaml role=declare'
|
syntax: '```yaml role=declare'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: journal-spark
|
tag: journal-spark
|
||||||
icon: 🎰
|
icon: spark
|
||||||
title: 火花表
|
title: 火花表
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: md-bg
|
tag: md-bg
|
||||||
icon: 🖼️
|
icon: background
|
||||||
title: 背景组件
|
title: 背景组件
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: md-border
|
tag: md-border
|
||||||
icon: 🖼️
|
icon: border
|
||||||
title: 边框组件
|
title: 边框组件
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: md-deck
|
tag: md-deck
|
||||||
icon: 🃏
|
icon: deck
|
||||||
title: 卡牌组件
|
title: 卡牌组件
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: md-dice
|
tag: md-dice
|
||||||
icon: 🎲
|
icon: dice
|
||||||
title: 骰子组件
|
title: 骰子组件
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ title: 骰子组件
|
|||||||
:md-dice[2d20k1+5]{key="advantage"}
|
:md-dice[2d20k1+5]{key="advantage"}
|
||||||
```
|
```
|
||||||
|
|
||||||
点击 🎲 图标执行投掷,显示结果;点击文字重置为原始公式。
|
点击骰子图标执行投掷,显示结果;点击文字重置为原始公式。
|
||||||
|
|
||||||
## 属性
|
## 属性
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: md-embed
|
tag: md-embed
|
||||||
icon: 📄
|
icon: embed
|
||||||
title: 嵌入组件
|
title: 嵌入组件
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: md-font
|
tag: md-font
|
||||||
icon: 🔤
|
icon: font
|
||||||
title: 字体组件
|
title: 字体组件
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: md-link
|
tag: md-link
|
||||||
icon: 🔗
|
icon: link
|
||||||
title: 链接组件
|
title: 链接组件
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: md-pins
|
tag: md-pins
|
||||||
icon: 📍
|
icon: pins
|
||||||
title: 标记组件
|
title: 标记组件
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ title: 标记组件
|
|||||||
|
|
||||||
## 复制坐标
|
## 复制坐标
|
||||||
|
|
||||||
非 `fixed` 模式下,图片右上角会显示 📋 复制按钮,点击可将所有标记的坐标复制到剪贴板,方便粘贴回文档的 `pins` 属性中。
|
非 `fixed` 模式下,图片右上角会显示复制按钮,点击可将所有标记的坐标复制到剪贴板,方便粘贴回文档的 `pins` 属性中。
|
||||||
|
|
||||||
## 属性
|
## 属性
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: md-table
|
tag: md-table
|
||||||
icon: 📊
|
icon: table
|
||||||
title: 表格组件
|
title: 表格组件
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ B,另一段描述,第二章]{}
|
|||||||
|
|
||||||
## 随机抽取
|
## 随机抽取
|
||||||
|
|
||||||
当 `roll=true` 时,表格顶部显示 🎲 随机按钮:
|
当 `roll=true` 时,表格顶部显示骰子随机按钮:
|
||||||
- 普通随机:从所有行中均匀随机选取
|
- 普通随机:从所有行中均匀随机选取
|
||||||
- **加权随机**:当 `label` 列全部为整数或整数范围格式(如 `1-3`)时,以 label 值为权重进行加权随机抽取
|
- **加权随机**:当 `label` 列全部为整数或整数范围格式(如 `1-3`)时,以 label 值为权重进行加权随机抽取
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
tag: md-yarn-spinner
|
tag: md-yarn-spinner
|
||||||
icon: 🧶
|
icon: yarn
|
||||||
title: 叙事线组件
|
title: 叙事线组件
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -19,8 +19,8 @@ Yarn Spinner 是用于游戏对话系统的格式,支持选项、条件分支
|
|||||||
|
|
||||||
- **对话历史**:上半部分显示已进行的对话,说话者以蓝色粗体显示,命令以灰色斜体显示
|
- **对话历史**:上半部分显示已进行的对话,说话者以蓝色粗体显示,命令以灰色斜体显示
|
||||||
- **当前选项**:下半部分显示可选的对话选项,点击选项推进剧情
|
- **当前选项**:下半部分显示可选的对话选项,点击选项推进剧情
|
||||||
- **⏩ 继续**:点击右上角继续按钮推进到下一段对话
|
- **继续**:点击右上角继续按钮推进到下一段对话
|
||||||
- **🔄 重新开始**:点击右上角重启按钮从头开始对话
|
- **重新开始**:点击右上角重启按钮从头开始对话
|
||||||
|
|
||||||
## 属性
|
## 属性
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user