feat(player): add StatusBar and frontmatter-driven layout switching

- Render StatusBar component from frontmatter statusBar config
- Support layout switching via frontmatter display field
- Register reignsPlugin layout via PluginRegistry
- Refactor view lifecycle to read frontmatter on file load
- Expose getPluginSettings for external access
This commit is contained in:
Uglyboy 2026-05-17 20:31:10 +08:00
parent 672d91d5a4
commit ee605e5bdc
4 changed files with 42 additions and 17 deletions

View file

@ -1,12 +1,18 @@
import type { InkStory } from "@inkweave/core"; import type { InkStory, StatusBarConfig } from "@inkweave/core";
import { Image } from "@inkweave/plugins/solidjs"; import { Image } from "@inkweave/plugins/solidjs";
import { CommandBar, Story } from "@inkweave/solidjs"; import { CommandBar, StatusBar, Story } from "@inkweave/solidjs";
import { t } from "./locales/i18n"; import { t } from "./locales/i18n";
const App = (props: { ink: InkStory }) => { interface AppProps {
ink: InkStory;
statusBar?: StatusBarConfig[];
}
const App = (props: AppProps) => {
return ( return (
<div id="inkweave-player" class="container"> <div id="inkweave-player" class="container">
<nav class="view-header"> <nav class="view-header">
{props.statusBar && <StatusBar ink={props.ink} variables={props.statusBar} />}
<div class="view-header-title-container mod-at-start mod-fade" /> <div class="view-header-title-container mod-at-start mod-fade" />
<CommandBar <CommandBar
ink={props.ink} ink={props.ink}

View file

@ -1,11 +1,10 @@
import { PluginRegistry } from "@inkweave/core"; import { PluginRegistry } from "@inkweave/core";
import { Platform, Plugin, type TAbstractFile, type WorkspaceLeaf } from "obsidian"; import { Platform, Plugin, type TAbstractFile, type WorkspaceLeaf } from "obsidian";
import { setupCommands } from "./commands"; import { setupCommands } from "./commands";
import { I18n, type TransItemType } from "./locales/i18n"; import { I18n, type TransItemType } from "./locales/i18n";
import { SettingsTab } from "./settings"; import { SettingsTab } from "./settings";
import { DEFAULT_SETTINGS, type Settings } from "./types"; import { DEFAULT_SETTINGS, type Settings } from "./types";
import { plugins } from "./utils/plugins"; import { plugins, reignsPlugin } from "./utils/plugins";
import { StoryView, VIEW_TYPE } from "./view"; import { StoryView, VIEW_TYPE } from "./view";
export default class InkWeavePlugin extends Plugin { export default class InkWeavePlugin extends Plugin {
@ -16,7 +15,7 @@ export default class InkWeavePlugin extends Plugin {
return this.i18n.t(x, vars); return this.i18n.t(x, vars);
} }
private getPluginSettings(): Record<string, boolean> { getPluginSettings(): Record<string, boolean> {
const { linedelay, debug, ...pluginSettings } = this.settings; const { linedelay, debug, ...pluginSettings } = this.settings;
return pluginSettings; return pluginSettings;
} }
@ -31,11 +30,11 @@ export default class InkWeavePlugin extends Plugin {
this.registerExtensions(["ink"], "markdown"); this.registerExtensions(["ink"], "markdown");
setupCommands(this); setupCommands(this);
for (const p of plugins) PluginRegistry.register(p);
PluginRegistry.setEnabled(this.getPluginSettings());
} }
async loadSettings() { async loadSettings() {
PluginRegistry.registerLayout(reignsPlugin);
for (const p of plugins) PluginRegistry.register(p);
this.settings = Object.assign({}, DEFAULT_SETTINGS, (await this.loadData()) ?? {}); this.settings = Object.assign({}, DEFAULT_SETTINGS, (await this.loadData()) ?? {});
PluginRegistry.setEnabled(this.getPluginSettings()); PluginRegistry.setEnabled(this.getPluginSettings());
} }

View file

@ -26,3 +26,5 @@ export const plugins: Plugin[] = [
memoryPlugin, memoryPlugin,
scrollAfterChoicePlugin, scrollAfterChoicePlugin,
]; ];
export { reignsPlugin } from "@inkweave/plugins/solidjs";

View file

@ -2,7 +2,8 @@ import "@inkweave/solidjs/solidjs.css";
import "@inkweave/plugins/solidjs.css"; import "@inkweave/plugins/solidjs.css";
import "./styles/styles.custom.css"; import "./styles/styles.custom.css";
import type { InkStory } from "@inkweave/core"; import type { InkStory, StatusBarConfig } from "@inkweave/core";
import { PluginRegistry } from "@inkweave/core";
import { type EventRef, ItemView, type MarkdownView, TFile, type ViewStateResult } from "obsidian"; import { type EventRef, ItemView, type MarkdownView, TFile, type ViewStateResult } from "obsidian";
import { render } from "solid-js/web"; import { render } from "solid-js/web";
import App from "./App"; import App from "./App";
@ -15,6 +16,11 @@ export class StoryView extends ItemView {
appInstance: (() => void) | null = null; appInstance: (() => void) | null = null;
ink: InkStory | null = null; ink: InkStory | null = null;
private watcher: EventRef | null = null; private watcher: EventRef | null = null;
private frontmatter: {
title?: string;
display?: string;
statusBar?: StatusBarConfig[];
} = {};
override getViewType() { override getViewType() {
return VIEW_TYPE; return VIEW_TYPE;
@ -37,9 +43,8 @@ export class StoryView extends ItemView {
override async setState(state: { filePath?: string }, result: ViewStateResult) { override async setState(state: { filePath?: string }, result: ViewStateResult) {
const filePath = state?.filePath; const filePath = state?.filePath;
const currentFilePath = useFile.getState().filePath;
if (filePath && filePath !== currentFilePath) { if (filePath) {
await this.loadFile(filePath); await this.loadFile(filePath);
} }
await super.setState(state, result); await super.setState(state, result);
@ -60,7 +65,10 @@ export class StoryView extends ItemView {
if (this.ink) { if (this.ink) {
const ink = this.ink; const ink = this.ink;
this.appInstance = render(() => App({ ink }), container as HTMLElement); this.appInstance = render(
() => App({ ink, statusBar: this.frontmatter.statusBar }),
container as HTMLElement,
);
} }
} }
@ -85,15 +93,20 @@ export class StoryView extends ItemView {
if (markdown !== currentMarkdown || this.ink?.title !== filePath) { if (markdown !== currentMarkdown || this.ink?.title !== filePath) {
this.ink?.dispose(); this.ink?.dispose();
this.ink = compile(); this.ink = null;
this.render();
} }
if (!this.ink) {
const cache = this.app.metadataCache.getFileCache(file);
this.frontmatter = cache?.frontmatter ?? {};
PluginRegistry.setLayout(this.frontmatter.display ?? null);
this.ink = compile();
}
this.render();
} }
override async onOpen() { override async onOpen() {
this.ink = compile();
this.render();
this.watcher = this.app.vault.on("modify", async (file) => { this.watcher = this.app.vault.on("modify", async (file) => {
const filePath = useFile.getState().filePath; const filePath = useFile.getState().filePath;
if (file.path === filePath && file instanceof TFile) { if (file.path === filePath && file instanceof TFile) {
@ -103,6 +116,10 @@ export class StoryView extends ItemView {
if (markdown !== currentMarkdown) { if (markdown !== currentMarkdown) {
const resourcePath = useFile.getState().resourcePath; const resourcePath = useFile.getState().resourcePath;
useFile.getState().init(filePath, markdown, resourcePath); useFile.getState().init(filePath, markdown, resourcePath);
const cache = this.app.metadataCache.getFileCache(file);
this.frontmatter = cache?.frontmatter ?? {};
PluginRegistry.setLayout(this.frontmatter.display ?? null);
this.ink?.dispose();
this.ink = compile(); this.ink = compile();
this.render(); this.render();
} }
@ -115,6 +132,7 @@ export class StoryView extends ItemView {
this.app.vault.offref(this.watcher); this.app.vault.offref(this.watcher);
} }
this.ink?.dispose(); this.ink?.dispose();
this.ink = null;
if (this.appInstance) { if (this.appInstance) {
this.appInstance(); this.appInstance();
this.appInstance = null; this.appInstance = null;