diff --git a/README.md b/README.md index 72fae7a..f0736a7 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,9 @@ import { type App, Commands, Entity, + HtmlTitle, inState, + Location, OnEnter, OnExit, query, @@ -279,7 +281,6 @@ import { UiText, Update, } from "@tsukinoko-kun/ecs.ts" -import { Location } from "../../../lib/builtin/state" // this resource is used to store the counter value class Counter { @@ -291,6 +292,11 @@ class CounterButtonMarker {} class CounterPageMarker {} +function setTitle() { + const t = res(HtmlTitle) + t.title = "Counter example" +} + // this system is used to spawn the UI elements initially function spawnUi() { Commands.spawn( @@ -355,9 +361,8 @@ function despawnUi() { // this plugin bundles everything that is needed for this counter example to work export function CounterPlugin(app: App) { - Commands.insertResource(new Counter()) - - app + app.insertResource(new Counter()) + .addSystem(OnEnter(Location.fromPath("/")), setTitle) // this system should run when the location changes to "/" .addSystem(OnEnter(Location.fromPath("/")), spawnUi) // this systems should only run if the current location is "/" @@ -370,11 +375,27 @@ export function CounterPlugin(app: App) { ```ts // meep.ts -import { type App, Commands, Entity, OnEnter, OnExit, query, UiNode, UiText } from "@tsukinoko-kun/ecs.ts" -import { Location } from "../../../lib/builtin/state" +import { + type App, + Commands, + Entity, + HtmlTitle, + Location, + OnEnter, + OnExit, + query, + res, + UiNode, + UiText, +} from "@tsukinoko-kun/ecs.ts" class MeepPageMarker {} +function setTitle() { + const t = res(HtmlTitle) + t.title = "Meep!" +} + // this system is used to spawn the UI elements initially function spawnUi() { Commands.spawn(new MeepPageMarker(), new UiNode("h1"), new UiText("Meep?")) @@ -387,7 +408,7 @@ function despawnUi() { } export function MeepPlugin(app: App) { - app + app.addSystem(OnEnter(Location.fromPath("/meep")), setTitle) // this system should run when the location changes to "/meep" .addSystem(OnEnter(Location.fromPath("/meep")), spawnUi) // this system should run when the location changes from "/" to something else diff --git a/apps/demo/src/counter.ts b/apps/demo/src/counter.ts index e2ab115..e435122 100644 --- a/apps/demo/src/counter.ts +++ b/apps/demo/src/counter.ts @@ -2,7 +2,9 @@ import { type App, Commands, Entity, + HtmlTitle, inState, + Location, OnEnter, OnExit, query, @@ -15,7 +17,6 @@ import { UiText, Update, } from "@tsukinoko-kun/ecs.ts" -import { Location } from "../../../lib/builtin/state" // this resource is used to store the counter value class Counter { @@ -27,6 +28,11 @@ class CounterButtonMarker {} class CounterPageMarker {} +function setTitle() { + const t = res(HtmlTitle) + t.title = "Counter example" +} + // this system is used to spawn the UI elements initially function spawnUi() { Commands.spawn( @@ -91,9 +97,8 @@ function despawnUi() { // this plugin bundles everything that is needed for this counter example to work export function CounterPlugin(app: App) { - Commands.insertResource(new Counter()) - - app + app.insertResource(new Counter()) + .addSystem(OnEnter(Location.fromPath("/")), setTitle) // this system should run when the location changes to "/" .addSystem(OnEnter(Location.fromPath("/")), spawnUi) // this systems should only run if the current location is "/" diff --git a/apps/demo/src/meep.ts b/apps/demo/src/meep.ts index d804885..65495b7 100644 --- a/apps/demo/src/meep.ts +++ b/apps/demo/src/meep.ts @@ -1,8 +1,24 @@ -import { type App, Commands, Entity, OnEnter, OnExit, query, UiNode, UiText } from "@tsukinoko-kun/ecs.ts" -import { Location } from "../../../lib/builtin/state" +import { + type App, + Commands, + Entity, + HtmlTitle, + Location, + OnEnter, + OnExit, + query, + res, + UiNode, + UiText, +} from "@tsukinoko-kun/ecs.ts" class MeepPageMarker {} +function setTitle() { + const t = res(HtmlTitle) + t.title = "Meep!" +} + // this system is used to spawn the UI elements initially function spawnUi() { Commands.spawn(new MeepPageMarker(), new UiNode("h1"), new UiText("Meep?")) @@ -15,7 +31,7 @@ function despawnUi() { } export function MeepPlugin(app: App) { - app + app.addSystem(OnEnter(Location.fromPath("/meep")), setTitle) // this system should run when the location changes to "/meep" .addSystem(OnEnter(Location.fromPath("/meep")), spawnUi) // this system should run when the location changes from "/" to something else diff --git a/lib/builtin/index.ts b/lib/builtin/index.ts index f231a5a..5c41a92 100644 --- a/lib/builtin/index.ts +++ b/lib/builtin/index.ts @@ -1,4 +1,5 @@ export * from "./components" export * from "./plugins" export * from "./resources" +export * from "./state" export * from "./systems" diff --git a/lib/builtin/plugins/default.ts b/lib/builtin/plugins/default.ts index ec7904e..43e28ae 100644 --- a/lib/builtin/plugins/default.ts +++ b/lib/builtin/plugins/default.ts @@ -1,12 +1,11 @@ import type { Plugin } from "../../plugin" import { LogicalButtonInput, PhysicalButtonInput } from "../resources" import { Last } from "../../schedule" -import { resetLogicalButtonInput, resetPhysicalButtonInput } from "../systems/buttonInput" -import { Commands } from "../../commands" +import { resetLogicalButtonInput, resetPhysicalButtonInput } from "../systems" export const DefaultPlugin: Plugin = (app) => { - Commands.insertResource(new LogicalButtonInput()) - Commands.insertResource(new PhysicalButtonInput()) - app.addSystem(Last, resetLogicalButtonInput) - app.addSystem(Last, resetPhysicalButtonInput) + app.insertResource(new LogicalButtonInput()) + .insertResource(new PhysicalButtonInput()) + .addSystem(Last, resetLogicalButtonInput) + .addSystem(Last, resetPhysicalButtonInput) } diff --git a/lib/builtin/plugins/html.ts b/lib/builtin/plugins/html.ts index b545d7a..b06ff54 100644 --- a/lib/builtin/plugins/html.ts +++ b/lib/builtin/plugins/html.ts @@ -1,18 +1,16 @@ import type { Plugin } from "../../plugin" -import { HtmlRoot } from "../resources" -import { Last, Startup, Update } from "../../schedule" -import { cleanupHtmlInteraction, htmlInteraction, renderHtmlRoot } from "../systems" -import { Commands } from "../../commands" -import { res } from "../../resource" +import { HtmlRoot, HtmlTitle } from "../resources" +import { Last, PreStartup, Startup, Update } from "../../schedule" +import { cleanupHtmlInteraction, clearHtmlRoot, htmlInteraction, renderHtmlRoot } from "../systems" export function HtmlPlugin(rootSelector: string): Plugin export function HtmlPlugin(rootElement: Element): Plugin export function HtmlPlugin(root: string | Element): Plugin { return (app) => { - Commands.insertResource(new HtmlRoot(root)) - const rootElement = res(HtmlRoot).element - rootElement.innerHTML = "" - app.addSystem(Update, renderHtmlRoot) + app.insertResource(new HtmlRoot(root)) + .insertResource(new HtmlTitle()) + .addSystem(PreStartup, clearHtmlRoot) + .addSystem(Update, renderHtmlRoot) .addSystem(Startup, htmlInteraction) .addSystem(Last, cleanupHtmlInteraction) } diff --git a/lib/builtin/plugins/router.ts b/lib/builtin/plugins/router.ts index 27af788..e530fdd 100644 --- a/lib/builtin/plugins/router.ts +++ b/lib/builtin/plugins/router.ts @@ -1,17 +1,20 @@ import type { App } from "../../app" import { Location, type TrainingSlash } from "../state" -import { updateLocationState } from "../systems/location" -import { Startup } from "../../schedule" -import { BasePath } from "../resources/basePath" +import { updateLocationState } from "../systems" +import { PreStartup } from "../../schedule" +import { BasePath } from "../resources" export function RouterPlugin(app: App) { - app.insertResource(new BasePath("/")).insertState(Location.windowLocation()).addSystem(Startup, updateLocationState) + app.insertResource(new BasePath("/")) + .insertState(Location.windowLocation()) + .addSystem(PreStartup, updateLocationState) } RouterPlugin.withSettings = (basePath: string, trailingSlash: TrainingSlash, trimIndexHtml: boolean) => (app: App) => { Location.trimIndexHtml = trimIndexHtml Location.trailingSlash = trailingSlash + app.insertResource(new BasePath(basePath)) .insertState(Location.windowLocation()) - .addSystem(Startup, updateLocationState) + .addSystem(PreStartup, updateLocationState) } diff --git a/lib/builtin/resources/htmlTitle.ts b/lib/builtin/resources/htmlTitle.ts new file mode 100644 index 0000000..e653e46 --- /dev/null +++ b/lib/builtin/resources/htmlTitle.ts @@ -0,0 +1,9 @@ +export class HtmlTitle { + public get title(): string { + return document.title + } + + public set title(value: string) { + document.title = value + } +} diff --git a/lib/builtin/resources/index.ts b/lib/builtin/resources/index.ts index 4656fe0..921fffe 100644 --- a/lib/builtin/resources/index.ts +++ b/lib/builtin/resources/index.ts @@ -1,3 +1,5 @@ +export * from "./basePath" export * from "./buttonInput" export * from "./htmlRoot" +export * from "./htmlTitle" export * from "./time" diff --git a/lib/builtin/state/location.ts b/lib/builtin/state/location.ts index 937cfbe..ed596b3 100644 --- a/lib/builtin/state/location.ts +++ b/lib/builtin/state/location.ts @@ -1,6 +1,6 @@ import type { Equals } from "../../traits" import { res } from "../../resource" -import { BasePath } from "../resources/basePath" +import { BasePath } from "../resources" function trimBasePath(basePath: string, path: string): string { if (basePath === "/") { diff --git a/lib/builtin/systems/html.ts b/lib/builtin/systems/html.ts index 6634b7a..a53aab1 100644 --- a/lib/builtin/systems/html.ts +++ b/lib/builtin/systems/html.ts @@ -115,7 +115,17 @@ export function renderHtmlRoot(): void { diffRender(root.element, el, false) } -function diffRender(old: Element, next: HTMLElement, deleteAttributes = true): void { +function diffRender(old: Element, next: HTMLElement, rude = true): void { + if (old.outerHTML === next.outerHTML) { + return + } + + // check tag name + if (rude && old.tagName !== next.tagName) { + old.replaceWith(next) + return + } + // check attributes const oldAttrs = old.attributes const nextAttrs = next.attributes @@ -126,7 +136,7 @@ function diffRender(old: Element, next: HTMLElement, deleteAttributes = true): v old.setAttribute(nextAttr.name, nextAttr.value) } } - if (deleteAttributes) { + if (rude) { for (let i = 0; i < oldAttrs.length; i++) { const oldAttr = oldAttrs[i]! if (nextAttrs.getNamedItem(oldAttr.name) === null) { @@ -159,3 +169,8 @@ function diffRender(old: Element, next: HTMLElement, deleteAttributes = true): v } } } + +export function clearHtmlRoot(): void { + const root = res(HtmlRoot) + root.element.innerHTML = "" +} diff --git a/lib/builtin/systems/index.ts b/lib/builtin/systems/index.ts index 3fbb637..2e54519 100644 --- a/lib/builtin/systems/index.ts +++ b/lib/builtin/systems/index.ts @@ -1 +1,3 @@ +export * from "./buttonInput" export * from "./html" +export * from "./location"