Added internet time to navigation menu.

This commit is contained in:
Daan Vanden Bosch 2020-01-17 14:23:50 +01:00
parent 606b0661f4
commit 79a68a6b7b
13 changed files with 310 additions and 57 deletions

View File

@ -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}`);
}
});

View File

@ -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<GuiTool>;
readonly internet_time: Property<string> = 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)}`;
};
}

View File

@ -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<Widget & Resizable>][]) {
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;
}
}

View File

@ -20,6 +20,11 @@
margin: 0 2px;
}
.application_NavigationView_time {
display: flex;
align-items: center;
}
.application_NavigationView_github {
display: flex;
flex-direction: row;

View File

@ -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.",
);
});

View File

@ -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]);
}
};

View File

@ -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`] = `
<div
class="application_NavigationView"
style="height: 30px;"
>
<span
class="application_NavigationButton"
>
<input
id="application_NavigationButton_Viewer"
name="application_NavigationButton"
type="radio"
value="Viewer"
/>
<label
for="application_NavigationButton_Viewer"
>
Viewer
</label>
</span>
<span
class="application_NavigationButton"
>
<input
id="application_NavigationButton_QuestEditor"
name="application_NavigationButton"
type="radio"
value="QuestEditor"
/>
<label
for="application_NavigationButton_QuestEditor"
>
Quest Editor
</label>
</span>
<span
class="application_NavigationButton"
>
<input
id="application_NavigationButton_HuntOptimizer"
name="application_NavigationButton"
type="radio"
value="HuntOptimizer"
/>
<label
for="application_NavigationButton_HuntOptimizer"
>
Hunt Optimizer
</label>
</span>
<div
class="application_NavigationView_spacer"
/>
<span
class="application_NavigationView_server"
>
<label
class="core_Label disabled"
for="core_LabelledControl_id_0"
title="Only Ephinea is supported at the moment"
>
Server:
</label>
<div
class="core_Select disabled"
id="core_LabelledControl_id_0"
title="Only Ephinea is supported at the moment"
>
<button
class="core_Button disabled"
disabled=""
>
<span
class="core_Button_inner"
>
<span
class="core_Button_center"
>
Ephinea
</span>
<span
class="core_Button_right"
>
<span>
<span
class="fas fa-caret-down"
/>
</span>
</span>
</span>
</button>
<div
class="core_Menu"
hidden=""
tabindex="-1"
>
<div
class="core_Menu_inner"
>
<div
data-index="0"
>
Ephinea
</div>
</div>
</div>
</div>
</span>
<span
class="application_NavigationView_time"
title="Internet time in beats"
>
@62
</span>
<a
class="application_NavigationView_github"
href="https://github.com/DaanVandenBosch/phantasmal-world"
rel="noopener noreferrer"
target="_blank"
title="Phantasmal World is open source, code available on GitHub"
>
<span>
<span
class="fab fa-github"
/>
</span>
</a>
</div>
`;

View File

@ -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(),
);

View File

@ -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.

9
src/core/Clock.ts Normal file
View File

@ -0,0 +1,9 @@
export interface Clock {
now(): Date;
}
export class DateClock implements Clock {
now(): Date {
return new Date();
}
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -1 +1 @@
49
50