From 79a68a6b7b9ae756c73610d7b464733fb3dc19bc Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Fri, 17 Jan 2020 14:23:50 +0100 Subject: [PATCH] Added internet time to navigation menu. --- .../controllers/NavigationController.test.ts | 20 +++ .../controllers/NavigationController.ts | 38 +++++ src/application/gui/ApplicationView.ts | 21 ++- src/application/gui/NavigationView.css | 5 + src/application/gui/NavigationView.test.ts | 14 ++ src/application/gui/NavigationView.ts | 23 ++- .../__snapshots__/NavigationView.test.ts.snap | 131 ++++++++++++++++++ src/application/index.test.ts | 2 + src/application/index.ts | 90 +++++++----- src/core/Clock.ts | 9 ++ src/index.ts | 3 +- test/src/core/StubClock.ts | 9 ++ version.txt | 2 +- 13 files changed, 310 insertions(+), 57 deletions(-) create mode 100644 src/application/controllers/NavigationController.test.ts create mode 100644 src/application/controllers/NavigationController.ts create mode 100644 src/application/gui/NavigationView.test.ts create mode 100644 src/application/gui/__snapshots__/NavigationView.test.ts.snap create mode 100644 src/core/Clock.ts create mode 100644 test/src/core/StubClock.ts diff --git a/src/application/controllers/NavigationController.test.ts b/src/application/controllers/NavigationController.test.ts new file mode 100644 index 00000000..0e66802f --- /dev/null +++ b/src/application/controllers/NavigationController.test.ts @@ -0,0 +1,20 @@ +import { NavigationController } from "./NavigationController"; +import { GuiStore } from "../../core/stores/GuiStore"; +import { StubClock } from "../../../test/src/core/StubClock"; + +test("Internet time should be calculated correctly.", () => { + for (const [time, beats] of [ + ["00:00:00", 41], + ["13:10:12", 590], + ["22:59:59", 999], + ["23:00:00", 0], + ["23:59:59", 41], + ]) { + const ctrl = new NavigationController( + new GuiStore(), + new StubClock(new Date(`2020-01-01T${time}Z`)), + ); + + expect(ctrl.internet_time.val).toBe(`@${beats}`); + } +}); diff --git a/src/application/controllers/NavigationController.ts b/src/application/controllers/NavigationController.ts new file mode 100644 index 00000000..dc043f00 --- /dev/null +++ b/src/application/controllers/NavigationController.ts @@ -0,0 +1,38 @@ +import { Controller } from "../../core/controllers/Controller"; +import { Property } from "../../core/observable/property/Property"; +import { GuiStore, GuiTool } from "../../core/stores/GuiStore"; +import { property } from "../../core/observable"; +import { Clock } from "../../core/Clock"; + +export class NavigationController extends Controller { + private readonly _internet_time = property("@"); + private readonly internet_time_interval: any; + + readonly tool: Property; + readonly internet_time: Property = this._internet_time; + + constructor(private readonly gui_store: GuiStore, private readonly clock: Clock) { + super(); + + this.tool = gui_store.tool; + this.internet_time_interval = setInterval(this.set_internet_time, 1000); + this.set_internet_time(); + } + + dispose(): void { + super.dispose(); + clearInterval(this.internet_time_interval); + } + + set_tool(tool: GuiTool): void { + this.gui_store.set_tool(tool); + } + + private set_internet_time = (): void => { + const now = this.clock.now(); + const s = now.getUTCSeconds(); + const m = now.getUTCMinutes(); + const h = (now.getUTCHours() + 1) % 24; // Internet time is calculated from UTC+01:00. + this._internet_time.val = `@${Math.floor((s + 60 * (m + 60 * h)) / 86.4)}`; + }; +} diff --git a/src/application/gui/ApplicationView.ts b/src/application/gui/ApplicationView.ts index b4f771ac..1626af23 100644 --- a/src/application/gui/ApplicationView.ts +++ b/src/application/gui/ApplicationView.ts @@ -1,40 +1,37 @@ import { NavigationView } from "./NavigationView"; import { MainContentView } from "./MainContentView"; -import { GuiStore, GuiTool } from "../../core/stores/GuiStore"; import { div } from "../../core/gui/dom"; import "./ApplicationView.css"; import { ResizableView } from "../../core/gui/ResizableView"; -import { Widget } from "../../core/gui/Widget"; -import { Resizable } from "../../core/gui/Resizable"; /** * The top-level view which contains all other views. */ export class ApplicationView extends ResizableView { - private menu_view: NavigationView; - private main_content_view: MainContentView; - readonly element: HTMLElement; - constructor(gui_store: GuiStore, tool_views: [GuiTool, () => Promise][]) { + constructor( + private readonly navigation_view: NavigationView, + private readonly main_content_view: MainContentView, + ) { super(); - this.menu_view = this.add(new NavigationView(gui_store)); - this.main_content_view = this.add(new MainContentView(gui_store, tool_views)); - this.element = div( { className: "application_ApplicationView" }, - this.menu_view.element, + this.navigation_view.element, this.main_content_view.element, ); this.element.id = "root"; + this.add(navigation_view); + this.add(main_content_view); + this.finalize_construction(); } resize(width: number, height: number): this { super.resize(width, height); - this.main_content_view.resize(width, height - this.menu_view.height); + this.main_content_view.resize(width, height - this.navigation_view.height); return this; } } diff --git a/src/application/gui/NavigationView.css b/src/application/gui/NavigationView.css index dd9638b9..31a97687 100644 --- a/src/application/gui/NavigationView.css +++ b/src/application/gui/NavigationView.css @@ -20,6 +20,11 @@ margin: 0 2px; } +.application_NavigationView_time { + display: flex; + align-items: center; +} + .application_NavigationView_github { display: flex; flex-direction: row; diff --git a/src/application/gui/NavigationView.test.ts b/src/application/gui/NavigationView.test.ts new file mode 100644 index 00000000..6e8641cc --- /dev/null +++ b/src/application/gui/NavigationView.test.ts @@ -0,0 +1,14 @@ +import { NavigationView } from "./NavigationView"; +import { NavigationController } from "../controllers/NavigationController"; +import { GuiStore } from "../../core/stores/GuiStore"; +import { StubClock } from "../../../test/src/core/StubClock"; + +test("Should render correctly.", () => { + const view = new NavigationView( + new NavigationController(new GuiStore(), new StubClock(new Date("2020-01-01T00:30:01Z"))), + ); + + expect(view.element).toMatchSnapshot( + "It should render a button per tool, the selected server, internet time and a github link icon.", + ); +}); diff --git a/src/application/gui/NavigationView.ts b/src/application/gui/NavigationView.ts index abbca850..b35e853d 100644 --- a/src/application/gui/NavigationView.ts +++ b/src/application/gui/NavigationView.ts @@ -1,9 +1,10 @@ import { a, div, icon, Icon, span } from "../../core/gui/dom"; import "./NavigationView.css"; -import { GuiStore, GuiTool } from "../../core/stores/GuiStore"; +import { GuiTool } from "../../core/stores/GuiStore"; import { NavigationButton } from "./NavigationButton"; import { Select } from "../../core/gui/Select"; import { View } from "../../core/gui/View"; +import { NavigationController } from "../controllers/NavigationController"; const TOOLS: [GuiTool, string][] = [ [GuiTool.Viewer, "Viewer"], @@ -25,6 +26,10 @@ export class NavigationView extends View { tooltip: "Only Ephinea is supported at the moment", }), ); + private readonly time_element = span({ + className: "application_NavigationView_time", + title: "Internet time in beats", + }); readonly element = div( { className: "application_NavigationView" }, @@ -39,11 +44,13 @@ export class NavigationView extends View { this.server_select.element, ), + this.time_element, + a( { className: "application_NavigationView_github", href: "https://github.com/DaanVandenBosch/phantasmal-world", - title: "GitHub", + title: "Phantasmal World is open source, code available on GitHub", }, icon(Icon.GitHub), ), @@ -51,14 +58,18 @@ export class NavigationView extends View { readonly height = 30; - constructor(private readonly gui_store: GuiStore) { + constructor(private readonly ctrl: NavigationController) { super(); this.element.style.height = `${this.height}px`; - this.element.onmousedown = this.mousedown; + this.element.addEventListener("mousedown", this.mousedown); this.disposables( - gui_store.tool.observe(({ value }) => this.mark_tool_button(value), { call_now: true }), + ctrl.tool.observe(({ value }) => this.mark_tool_button(value), { call_now: true }), + + ctrl.internet_time.observe(({ value }) => (this.time_element.textContent = value), { + call_now: true, + }), ); this.finalize_construction(); @@ -66,7 +77,7 @@ export class NavigationView extends View { private mousedown = (e: MouseEvent): void => { if (e.target instanceof HTMLLabelElement && e.target.control instanceof HTMLInputElement) { - this.gui_store.set_tool((GuiTool as any)[e.target.control.value]); + this.ctrl.set_tool((GuiTool as any)[e.target.control.value]); } }; diff --git a/src/application/gui/__snapshots__/NavigationView.test.ts.snap b/src/application/gui/__snapshots__/NavigationView.test.ts.snap new file mode 100644 index 00000000..d9ffd786 --- /dev/null +++ b/src/application/gui/__snapshots__/NavigationView.test.ts.snap @@ -0,0 +1,131 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Should render correctly.: It should render a button per tool, the selected server, internet time and a github link icon. 1`] = ` +
+ + + + + + + + + + + + +
+ + +
+ + +
+
+ + @62 + + + + + + +
+`; diff --git a/src/application/index.test.ts b/src/application/index.test.ts index eeaf7047..0fdb5a21 100644 --- a/src/application/index.test.ts +++ b/src/application/index.test.ts @@ -5,6 +5,7 @@ import { timeout } from "../../test/src/utils"; import { StubThreeRenderer } from "../../test/src/core/rendering/StubThreeRenderer"; import { Random } from "../core/Random"; import { Severity } from "../core/Severity"; +import { StubClock } from "../../test/src/core/StubClock"; for (const path of [undefined, "/viewer", "/quest_editor", "/hunt_optimizer"]) { const with_path = path == undefined ? "without specific path" : `with path ${path}`; @@ -26,6 +27,7 @@ for (const path of [undefined, "/viewer", "/quest_editor", "/hunt_optimizer"]) { const app = initialize_application( new FileSystemHttpClient(), new Random(() => 0.27), + new StubClock(new Date("2020-01-01T15:40:20Z")), () => new StubThreeRenderer(), ); diff --git a/src/application/index.ts b/src/application/index.ts index 7d743ef6..1377323c 100644 --- a/src/application/index.ts +++ b/src/application/index.ts @@ -9,10 +9,15 @@ import { DisposableThreeRenderer } from "../core/rendering/Renderer"; import { Disposer } from "../core/observable/Disposer"; import { disposable_custom_listener, disposable_listener } from "../core/gui/dom"; import { Random } from "../core/Random"; +import { NavigationController } from "./controllers/NavigationController"; +import { NavigationView } from "./gui/NavigationView"; +import { MainContentView } from "./gui/MainContentView"; +import { Clock } from "../core/Clock"; export function initialize_application( http_client: HttpClient, random: Random, + clock: Clock, create_three_renderer: () => DisposableThreeRenderer, ): Disposable { const disposer = new Disposer(); @@ -37,48 +42,59 @@ export function initialize_application( create_item_drop_stores(http_client, gui_store, item_type_stores), ); + // Controllers. + const navigation_controller = disposer.add(new NavigationController(gui_store, clock)); + // Initialize application view. const application_view = disposer.add( - new ApplicationView(gui_store, [ - [ - GuiTool.Viewer, - async () => { - const { initialize_viewer } = await import("../viewer"); - const viewer = disposer.add( - initialize_viewer(http_client, random, gui_store, create_three_renderer), - ); + new ApplicationView( + new NavigationView(navigation_controller), + new MainContentView(gui_store, [ + [ + GuiTool.Viewer, + async () => { + const { initialize_viewer } = await import("../viewer"); + const viewer = disposer.add( + initialize_viewer( + http_client, + random, + gui_store, + create_three_renderer, + ), + ); - return viewer.view; - }, - ], - [ - GuiTool.QuestEditor, - async () => { - const { initialize_quest_editor } = await import("../quest_editor"); - const quest_editor = disposer.add( - initialize_quest_editor(http_client, gui_store, create_three_renderer), - ); + return viewer.view; + }, + ], + [ + GuiTool.QuestEditor, + async () => { + const { initialize_quest_editor } = await import("../quest_editor"); + const quest_editor = disposer.add( + initialize_quest_editor(http_client, gui_store, create_three_renderer), + ); - return quest_editor.view; - }, - ], - [ - GuiTool.HuntOptimizer, - async () => { - const { initialize_hunt_optimizer } = await import("../hunt_optimizer"); - const hunt_optimizer = disposer.add( - initialize_hunt_optimizer( - http_client, - gui_store, - item_type_stores, - item_drop_stores, - ), - ); + return quest_editor.view; + }, + ], + [ + GuiTool.HuntOptimizer, + async () => { + const { initialize_hunt_optimizer } = await import("../hunt_optimizer"); + const hunt_optimizer = disposer.add( + initialize_hunt_optimizer( + http_client, + gui_store, + item_type_stores, + item_drop_stores, + ), + ); - return hunt_optimizer.view; - }, - ], - ]), + return hunt_optimizer.view; + }, + ], + ]), + ), ); // Resize the view on window resize. diff --git a/src/core/Clock.ts b/src/core/Clock.ts new file mode 100644 index 00000000..d7d9c0af --- /dev/null +++ b/src/core/Clock.ts @@ -0,0 +1,9 @@ +export interface Clock { + now(): Date; +} + +export class DateClock implements Clock { + now(): Date { + return new Date(); + } +} diff --git a/src/index.ts b/src/index.ts index c6b0a612..625bbf1d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ import { FetchClient } from "./core/HttpClient"; import { WebGLRenderer } from "three"; import { DisposableThreeRenderer } from "./core/rendering/Renderer"; import { Random } from "./core/Random"; +import { DateClock } from "./core/Clock"; function create_three_renderer(): DisposableThreeRenderer { const renderer = new WebGLRenderer({ antialias: true, alpha: true }); @@ -18,4 +19,4 @@ function create_three_renderer(): DisposableThreeRenderer { return renderer; } -initialize_application(new FetchClient(), new Random(), create_three_renderer); +initialize_application(new FetchClient(), new Random(), new DateClock(), create_three_renderer); diff --git a/test/src/core/StubClock.ts b/test/src/core/StubClock.ts new file mode 100644 index 00000000..5b008c88 --- /dev/null +++ b/test/src/core/StubClock.ts @@ -0,0 +1,9 @@ +import { Clock } from "../../../src/core/Clock"; + +export class StubClock implements Clock { + constructor(private readonly date: Date) {} + + now(): Date { + return this.date; + } +} diff --git a/version.txt b/version.txt index 95f9650f..e373ee69 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -49 +50