big update
This commit is contained in:
@@ -3,9 +3,11 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Ledda's Room Climate</title>
|
<title>Ledda's Room Climate</title>
|
||||||
<link type="text/css" href="/styles.css" rel="stylesheet" />
|
|
||||||
<script type="application/javascript" src="/dashboard.js"></script>
|
|
||||||
<link rel="shortcut icon" type="image/jpg" href="/favicon64.png"/>
|
<link rel="shortcut icon" type="image/jpg" href="/favicon64.png"/>
|
||||||
|
<script type="module" crossorigin src="/assets/index-58d7c2d1.js"></script>
|
||||||
|
<link rel="stylesheet" href="/assets/index-25859e9b.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -47,12 +47,29 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
opacity: 0;
|
display: none;
|
||||||
z-index: -1;
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeseries-val {
|
||||||
|
white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeseries-val span {
|
||||||
|
margin-left: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
display: block;
|
display: block;
|
||||||
|
text-align: center;
|
||||||
font-family: 'Roboto Slab', serif;
|
font-family: 'Roboto Slab', serif;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: var(--accent-dark);
|
color: var(--accent-dark);
|
||||||
@@ -272,4 +289,4 @@ h1 {
|
|||||||
}
|
}
|
||||||
.button:active {
|
.button:active {
|
||||||
filter: brightness(0.9);
|
filter: brightness(0.9);
|
||||||
}
|
}
|
||||||
13
dashboard/index.html
Normal file
13
dashboard/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Ledda's Room Climate</title>
|
||||||
|
<link type="text/css" href="./assets/styles.css" rel="stylesheet" />
|
||||||
|
<script type="module" src="./src/main.ts"></script>
|
||||||
|
<link rel="shortcut icon" type="image/jpg" href="/favicon64.png"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
9571
dashboard/package-lock.json
generated
9571
dashboard/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,22 +11,7 @@
|
|||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
|
||||||
"@types/react": "^17.0.3",
|
|
||||||
"@webpack-cli/init": "^1.0.3",
|
|
||||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
|
||||||
"css-loader": "^5.0.1",
|
|
||||||
"style-loader": "^2.0.0",
|
|
||||||
"file-loader": "^6.0.0",
|
|
||||||
"terser-webpack-plugin": "^5.0.3",
|
|
||||||
"ts-loader": "^8.0.18",
|
|
||||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
|
||||||
"webpack": "^5.4.0",
|
|
||||||
"webpack-cli": "^4.2.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/chart.js": "^2.9.27",
|
"vite": "4.4.7"
|
||||||
"chart.js": "^2.9.4",
|
|
||||||
"webpack-dev-server": "^3.11.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ function createChildren(children: any[]): Node[] {
|
|||||||
if (Array.isArray(child)) {
|
if (Array.isArray(child)) {
|
||||||
childrenNodes.push(...createChildren(child));
|
childrenNodes.push(...createChildren(child));
|
||||||
}
|
}
|
||||||
else if (typeof child === "string") {
|
|
||||||
childrenNodes.push(document.createTextNode(String(child)));
|
|
||||||
}
|
|
||||||
else if (child instanceof Node) {
|
else if (child instanceof Node) {
|
||||||
childrenNodes.push(child);
|
childrenNodes.push(child);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
childrenNodes.push(document.createTextNode(String(child)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return childrenNodes;
|
return childrenNodes;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ function newDefaultState(): AppState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppStateStore {
|
class AppStateStore {
|
||||||
private readonly subscriptions: IAppStateSubscriptions;
|
private readonly subscriptions: IAppStateSubscriptions;
|
||||||
private readonly eventCallbacks: EventCallbackListing<keyof EventCallback>;
|
private readonly eventCallbacks: EventCallbackListing<keyof EventCallback>;
|
||||||
private readonly state: AppState;
|
private readonly state: AppState;
|
||||||
@@ -87,7 +87,7 @@ class AppStateStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
await this.updateTimeseriesFromSettings();
|
await this.updateTimeseriesFromSettings(true);
|
||||||
await this.getNewTimeseriesData();
|
await this.getNewTimeseriesData();
|
||||||
this.emit("ready");
|
this.emit("ready");
|
||||||
}
|
}
|
||||||
@@ -121,7 +121,7 @@ class AppStateStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateTimeseriesFromSettings() {
|
private async updateTimeseriesFromSettings(initial?: boolean) {
|
||||||
let start: number;
|
let start: number;
|
||||||
let stop: number;
|
let stop: number;
|
||||||
if (this.state.displayMode === "window") {
|
if (this.state.displayMode === "window") {
|
||||||
@@ -131,6 +131,9 @@ class AppStateStore {
|
|||||||
start = this.state.lastUpdateTime - this.state.minutesDisplayed * 60;
|
start = this.state.lastUpdateTime - this.state.minutesDisplayed * 60;
|
||||||
stop = this.state.lastUpdateTime;
|
stop = this.state.lastUpdateTime;
|
||||||
}
|
}
|
||||||
|
if (initial) {
|
||||||
|
start -= stop - start;
|
||||||
|
}
|
||||||
const allTimeseries = this.state.leftTimeseries.concat(this.state.rightTimeseries);
|
const allTimeseries = this.state.leftTimeseries.concat(this.state.rightTimeseries);
|
||||||
const allHistoriesComplete = !allTimeseries.some(timeseries => !timeseries.historyIsComplete());
|
const allHistoriesComplete = !allTimeseries.some(timeseries => !timeseries.historyIsComplete());
|
||||||
if (start < this.getExtrema().minIndex && allHistoriesComplete) {
|
if (start < this.getExtrema().minIndex && allHistoriesComplete) {
|
||||||
@@ -424,4 +427,6 @@ export function getAppState() {
|
|||||||
} else {
|
} else {
|
||||||
throw new AppStateError("Store not yet initialised!");
|
throw new AppStateError("Store not yet initialised!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type { AppStateStore };
|
||||||
|
|||||||
@@ -110,6 +110,10 @@ class Timeseries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
current() {
|
||||||
|
return this.cache[this.currentEndPointer - 2] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
async updateFromWindow(start: number, stop: number) {
|
async updateFromWindow(start: number, stop: number) {
|
||||||
if (!this.fetching) {
|
if (!this.fetching) {
|
||||||
try {
|
try {
|
||||||
@@ -245,4 +249,4 @@ class Timeseries {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Timeseries;
|
export default Timeseries;
|
||||||
|
|||||||
@@ -50,4 +50,4 @@ async function loadClimateTimeseriesData(dataType: "temp" | "humidity" | "co2",
|
|||||||
const message = "timeseries data couldn't be loaded from the server";
|
const message = "timeseries data couldn't be loaded from the server";
|
||||||
throw new ClayPIDashboardError(`${message}: ${e}`, message);
|
throw new ClayPIDashboardError(`${message}: ${e}`, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@
|
|||||||
"development": false,
|
"development": false,
|
||||||
"defaultMinuteSpan": 60,
|
"defaultMinuteSpan": 60,
|
||||||
"reloadIntervalSec": 30,
|
"reloadIntervalSec": 30,
|
||||||
"dataEndpoint": "/climate/api"
|
"dataEndpoint": "http://home.djledda.de:4040/climate/api"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import config from "./config.json";
|
import config from "./config.json";
|
||||||
import {AppStore, getAppState, initStore} from "./StateStore";
|
import {AppStore, type AppStateStore, getAppState, initStore} from "./StateStore";
|
||||||
import AppUI from "./ui-components/AppUI";
|
import AppUI from "./ui-components/AppUI";
|
||||||
import {
|
import {
|
||||||
newCo2Timeseries,
|
newCo2Timeseries,
|
||||||
@@ -36,7 +36,6 @@ document.onreadystatechange = async () => {
|
|||||||
await init();
|
await init();
|
||||||
AppStore().setDocumentReady(true);
|
AppStore().setDocumentReady(true);
|
||||||
AppStore().on("stateChange", () => debounce(() => updateUrlState())());
|
AppStore().on("stateChange", () => debounce(() => updateUrlState())());
|
||||||
// @ts-ignore
|
(window as unknown as { store: AppStateStore }).store = AppStore();
|
||||||
window.store = AppStore();
|
|
||||||
document.onreadystatechange = null;
|
document.onreadystatechange = null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ class AppUI extends UIComponent {
|
|||||||
private grid: HTMLDivElement = document.createElement("div");
|
private grid: HTMLDivElement = document.createElement("div");
|
||||||
private messageOverlay: MessageOverlay = new MessageOverlay();
|
private messageOverlay: MessageOverlay = new MessageOverlay();
|
||||||
private helpModal: HelpModal = new HelpModal();
|
private helpModal: HelpModal = new HelpModal();
|
||||||
|
private nowView: HTMLElement;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.setupGrid({width: 5, height: 10});
|
this.setupGrid({width: 5, height: 10});
|
||||||
|
this.nowView = <this.Now />;
|
||||||
this.element.append(
|
this.element.append(
|
||||||
<img
|
<img
|
||||||
alt={"Help"}
|
alt={"Help"}
|
||||||
@@ -34,11 +36,41 @@ class AppUI extends UIComponent {
|
|||||||
className={"help-button button"}
|
className={"help-button button"}
|
||||||
onclick={() => AppStore().showHelp()}/>,
|
onclick={() => AppStore().showHelp()}/>,
|
||||||
<h1>Ledda's Room Climate</h1>,
|
<h1>Ledda's Room Climate</h1>,
|
||||||
this.grid,
|
|
||||||
this.messageOverlay.current(),
|
this.messageOverlay.current(),
|
||||||
|
<this.Toggle />,
|
||||||
|
this.nowView,
|
||||||
|
this.grid,
|
||||||
this.helpModal.current(),
|
this.helpModal.current(),
|
||||||
);
|
);
|
||||||
|
this.grid.classList.add("hidden");
|
||||||
this.element.className = "center";
|
this.element.className = "center";
|
||||||
|
AppStore().on("timeseriesUpdated", () => {
|
||||||
|
const old = this.nowView;
|
||||||
|
this.nowView = <this.Now/>;
|
||||||
|
old.replaceWith(this.nowView);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Now = () => {
|
||||||
|
const state = AppStore().getState();
|
||||||
|
return <div>
|
||||||
|
{state.leftTimeseries.concat(state.rightTimeseries).map(timeseries => {
|
||||||
|
const val = timeseries.current() ?? "-";
|
||||||
|
return <div className={"timeseries-val"}>{timeseries.getName()}: <span className={"timeseries-val"}>{val}</span></div>;
|
||||||
|
})}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Toggle = () => {
|
||||||
|
return <div className={"toggle"}>
|
||||||
|
<button onclick={() => {
|
||||||
|
this.grid.classList.toggle("hidden");
|
||||||
|
this.nowView.classList.toggle("hidden");
|
||||||
|
this.chartWidget.updateDimensions();
|
||||||
|
}}>
|
||||||
|
Toggle Dashboard
|
||||||
|
</button>
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupGrid(size: GridSize) {
|
private setupGrid(size: GridSize) {
|
||||||
@@ -86,4 +118,4 @@ class AppUI extends UIComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AppUI;
|
export default AppUI;
|
||||||
|
|||||||
@@ -129,4 +129,4 @@ class ClimateChartWidget extends UIComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ClimateChartWidget;
|
export default ClimateChartWidget;
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ import UIComponent from "./UIComponent";
|
|||||||
|
|
||||||
class DisplayModeWidget extends UIComponent {
|
class DisplayModeWidget extends UIComponent {
|
||||||
private skeleton: GridWidget;
|
private skeleton: GridWidget;
|
||||||
private minsCounterRef: number;
|
private minsCounterRef: HTMLElement;
|
||||||
private windowStartTimeRef: number;
|
private windowStartTimeRef: HTMLElement;
|
||||||
private windowStartTimeInputRef: number;
|
private windowStartTimeInputRef: HTMLInputElement;
|
||||||
private windowStopTimeRef: number;
|
private windowStopTimeRef: HTMLElement;
|
||||||
private windowStopTimeInputRef: number;
|
private windowStopTimeInputRef: HTMLInputElement;
|
||||||
private windowedDisplayRef: number;
|
private windowedDisplayRef: HTMLElement;
|
||||||
private minsDisplayRef: number;
|
private minsDisplayRef: HTMLElement;
|
||||||
private mainDisplay: HTMLElement;
|
private mainDisplay: HTMLElement;
|
||||||
private minsInputRef: number;
|
private minsInputRef: HTMLInputElement;
|
||||||
|
|
||||||
constructor(gridProps: GridProps) {
|
constructor(gridProps: GridProps) {
|
||||||
super();
|
super();
|
||||||
this.mainDisplay = this.MainDisplay({ctx: this});
|
this.mainDisplay = <this.MainDisplay />;
|
||||||
this.skeleton = new GridWidget({
|
this.skeleton = new GridWidget({
|
||||||
...gridProps,
|
...gridProps,
|
||||||
title: "Displaying:",
|
title: "Displaying:",
|
||||||
@@ -30,45 +30,45 @@ class DisplayModeWidget extends UIComponent {
|
|||||||
this.updateDisplay();
|
this.updateDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
private WindowStartTime({ ctx }: {ctx: DisplayModeWidget}) {
|
private WindowStartTime = () => {
|
||||||
ctx.windowStartTimeInputRef = ctx.makeRef(<input
|
this.windowStartTimeInputRef = <input
|
||||||
type={"datetime-local"}
|
type={"datetime-local"}
|
||||||
onblur={() => ctx.onWindowStartInputBlur()}
|
onblur={() => this.onWindowStartInputBlur()}
|
||||||
/>);
|
/> as HTMLInputElement;
|
||||||
ctx.windowStartTimeRef = ctx.makeRef(<div
|
this.windowStartTimeRef = <div
|
||||||
className={"display-mode-widget-date"}
|
className={"display-mode-widget-date"}
|
||||||
onwheel={(e: WheelEvent) => ctx.onStartTimeInputScroll(e)}
|
onwheel={(e: WheelEvent) => this.onStartTimeInputScroll(e)}
|
||||||
onclick={() => ctx.onWindowStartDisplayClick()}>
|
onclick={() => this.onWindowStartDisplayClick()}>
|
||||||
{new Date(getAppState().displayWindow.start + getAppState().utcOffset * 60 * 60 * 1000).toLocaleString()}
|
{new Date(getAppState().displayWindow.start + getAppState().utcOffset * 60 * 60 * 1000).toLocaleString()}
|
||||||
</div>);
|
</div>;
|
||||||
return ctx.fromRef(ctx.windowStartTimeRef);
|
return this.windowStartTimeRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
private WindowStopTime({ctx}: {ctx: DisplayModeWidget}) {
|
private WindowStopTime = () => {
|
||||||
ctx.windowStopTimeInputRef = ctx.makeRef(<input
|
this.windowStopTimeInputRef = <input
|
||||||
value={new Date()}
|
value={new Date()}
|
||||||
type={"datetime-local"}
|
type={"datetime-local"}
|
||||||
onblur={() => ctx.onWindowStopInputBlur()}
|
onblur={() => this.onWindowStopInputBlur()}
|
||||||
/>);
|
/> as HTMLInputElement;
|
||||||
ctx.windowStopTimeRef = ctx.makeRef(<div
|
this.windowStopTimeRef = <div
|
||||||
className={"display-mode-widget-date"}
|
className={"display-mode-widget-date"}
|
||||||
onwheel={(e: WheelEvent) => ctx.onStopTimeInputScroll(e)}
|
onwheel={(e: WheelEvent) => this.onStopTimeInputScroll(e)}
|
||||||
onclick={() => ctx.onWindowStopDisplayClick()}>
|
onclick={() => this.onWindowStopDisplayClick()}>
|
||||||
{new Date(getAppState().displayWindow.stop + getAppState().utcOffset * 60 * 60 * 1000).toLocaleString()}
|
{new Date(getAppState().displayWindow.stop + getAppState().utcOffset * 60 * 60 * 1000).toLocaleString()}
|
||||||
</div>);
|
</div>;
|
||||||
return ctx.fromRef(ctx.windowStopTimeRef);
|
return this.windowStopTimeRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MinutesCounter({ctx, onclick}: {ctx: DisplayModeWidget, onclick: () => any}) {
|
private MinutesCounter = ({ onclick }: {onclick: () => any }) => {
|
||||||
ctx.minsInputRef = ctx.makeRef(
|
this.minsInputRef =
|
||||||
<input
|
<input
|
||||||
value={getAppState().minutesDisplayed.toString()}
|
value={getAppState().minutesDisplayed.toString()}
|
||||||
onblur={(e: FocusEvent) => ctx.onMinutesCounterInputBlur(e)}/>);
|
onblur={(e: FocusEvent) => this.onMinutesCounterInputBlur(e)}/> as HTMLInputElement;
|
||||||
ctx.minsCounterRef = ctx.makeRef(
|
this.minsCounterRef =
|
||||||
<div className={"min-count"} onclick={onclick} onwheel={(e: WheelEvent) => ctx.onMinutesCounterInputScroll(e)}>
|
<div className={"min-count"} onclick={onclick} onwheel={(e: WheelEvent) => this.onMinutesCounterInputScroll(e)}>
|
||||||
{getAppState().minutesDisplayed.toString()}
|
{getAppState().minutesDisplayed.toString()}
|
||||||
</div>);
|
</div>;
|
||||||
return ctx.fromRef(ctx.minsCounterRef);
|
return this.minsCounterRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onMinutesCounterInputScroll(e: WheelEvent) {
|
private onMinutesCounterInputScroll(e: WheelEvent) {
|
||||||
@@ -94,18 +94,18 @@ class DisplayModeWidget extends UIComponent {
|
|||||||
} else {
|
} else {
|
||||||
(e.target as HTMLInputElement).value = getAppState().minutesDisplayed.toString();
|
(e.target as HTMLInputElement).value = getAppState().minutesDisplayed.toString();
|
||||||
}
|
}
|
||||||
this.fromRef(this.minsInputRef).replaceWith(this.fromRef(this.minsCounterRef));
|
this.minsInputRef.replaceWith(this.minsCounterRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MinutesDisplay({ctx}: {ctx: DisplayModeWidget}) {
|
private MinutesDisplay = () => {
|
||||||
return (<div className={"display-mode-widget-mins"}>
|
return (<div className={"display-mode-widget-mins"}>
|
||||||
<div>Last</div>
|
<div>Last</div>
|
||||||
<ctx.MinusButton onclick={() => {
|
<this.MinusButton onclick={() => {
|
||||||
const mins = AppStore().getState().minutesDisplayed;
|
const mins = AppStore().getState().minutesDisplayed;
|
||||||
AppStore().setMinutesDisplayed(mins - 1);
|
AppStore().setMinutesDisplayed(mins - 1);
|
||||||
}}/>
|
}}/>
|
||||||
<ctx.MinutesCounter ctx={ctx} onclick={() => ctx.onMinutesCounterClick()}/>
|
<this.MinutesCounter onclick={() => this.onMinutesCounterClick()}/>
|
||||||
<ctx.PlusButton onclick={() => {
|
<this.PlusButton onclick={() => {
|
||||||
const mins = AppStore().getState().minutesDisplayed;
|
const mins = AppStore().getState().minutesDisplayed;
|
||||||
AppStore().setMinutesDisplayed(mins + 1);
|
AppStore().setMinutesDisplayed(mins + 1);
|
||||||
}}/>
|
}}/>
|
||||||
@@ -114,17 +114,17 @@ class DisplayModeWidget extends UIComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onMinutesCounterClick() {
|
private onMinutesCounterClick() {
|
||||||
const input = this.fromRef(this.minsInputRef) as HTMLInputElement;
|
const input = this.minsInputRef;
|
||||||
this.fromRef(this.minsCounterRef).replaceWith(input);
|
this.minsCounterRef.replaceWith(input);
|
||||||
input.focus();
|
input.focus();
|
||||||
input.selectionStart = 0;
|
input.selectionStart = 0;
|
||||||
input.selectionEnd = input.value.length;
|
input.selectionEnd = input.value.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onWindowStopDisplayClick() {
|
private onWindowStopDisplayClick() {
|
||||||
const stopTimeDisplay = this.fromRef(this.windowStopTimeRef);
|
const stopTimeDisplay = this.windowStopTimeRef;
|
||||||
(stopTimeDisplay as HTMLInputElement).valueAsDate = new Date(getAppState().displayWindow.stop);
|
const stopTimeInputDisplay = this.windowStopTimeInputRef;
|
||||||
const stopTimeInputDisplay = this.fromRef(this.windowStopTimeInputRef) as HTMLInputElement;
|
stopTimeInputDisplay.valueAsDate = new Date(getAppState().displayWindow.stop);
|
||||||
stopTimeDisplay.replaceWith(stopTimeInputDisplay);
|
stopTimeDisplay.replaceWith(stopTimeInputDisplay);
|
||||||
const date = new Date(getAppState().displayWindow.stop * 1000 + getAppState().utcOffset * 60 * 60 * 1000);
|
const date = new Date(getAppState().displayWindow.stop * 1000 + getAppState().utcOffset * 60 * 60 * 1000);
|
||||||
stopTimeInputDisplay.value = `${date.toLocaleDateString()}, ${date.toLocaleTimeString()}`;
|
stopTimeInputDisplay.value = `${date.toLocaleDateString()}, ${date.toLocaleTimeString()}`;
|
||||||
@@ -132,7 +132,7 @@ class DisplayModeWidget extends UIComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onWindowStopInputBlur() {
|
private onWindowStopInputBlur() {
|
||||||
const stopTimeInput = this.fromRef(this.windowStopTimeInputRef);
|
const stopTimeInput = this.windowStopTimeInputRef;
|
||||||
const val = new Date((stopTimeInput as HTMLInputElement).value).getTime() / 1000;
|
const val = new Date((stopTimeInput as HTMLInputElement).value).getTime() / 1000;
|
||||||
if (!isNaN(val)) {
|
if (!isNaN(val)) {
|
||||||
AppStore().setDisplayWindow({
|
AppStore().setDisplayWindow({
|
||||||
@@ -140,13 +140,13 @@ class DisplayModeWidget extends UIComponent {
|
|||||||
stop: val
|
stop: val
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
stopTimeInput.replaceWith(this.fromRef(this.windowStopTimeRef));
|
stopTimeInput.replaceWith(this.windowStopTimeRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onWindowStartDisplayClick() {
|
private onWindowStartDisplayClick() {
|
||||||
const startTimeDisplay = this.fromRef(this.windowStartTimeRef);
|
const startTimeDisplay = this.windowStartTimeRef;
|
||||||
(startTimeDisplay as HTMLInputElement).valueAsDate = new Date(getAppState().displayWindow.start);
|
const startTimeInputDisplay = this.windowStartTimeInputRef;
|
||||||
const startTimeInputDisplay = this.fromRef(this.windowStartTimeInputRef) as HTMLInputElement;
|
startTimeInputDisplay.valueAsDate = new Date(getAppState().displayWindow.start);
|
||||||
startTimeDisplay.replaceWith(startTimeInputDisplay);
|
startTimeDisplay.replaceWith(startTimeInputDisplay);
|
||||||
const date = new Date(getAppState().displayWindow.start * 1000 + getAppState().utcOffset * 60 * 60 * 1000);
|
const date = new Date(getAppState().displayWindow.start * 1000 + getAppState().utcOffset * 60 * 60 * 1000);
|
||||||
startTimeInputDisplay.value = `${date.toLocaleDateString()}, ${date.toLocaleTimeString()}`;
|
startTimeInputDisplay.value = `${date.toLocaleDateString()}, ${date.toLocaleTimeString()}`;
|
||||||
@@ -155,7 +155,7 @@ class DisplayModeWidget extends UIComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onWindowStartInputBlur() {
|
private onWindowStartInputBlur() {
|
||||||
const startTimeInput = this.fromRef(this.windowStartTimeInputRef);
|
const startTimeInput = this.windowStartTimeInputRef;
|
||||||
const val = new Date((startTimeInput as HTMLInputElement).value).getTime() / 1000;
|
const val = new Date((startTimeInput as HTMLInputElement).value).getTime() / 1000;
|
||||||
if (!isNaN(val)) {
|
if (!isNaN(val)) {
|
||||||
AppStore().setDisplayWindow({
|
AppStore().setDisplayWindow({
|
||||||
@@ -163,56 +163,56 @@ class DisplayModeWidget extends UIComponent {
|
|||||||
stop: getAppState().displayWindow.stop,
|
stop: getAppState().displayWindow.stop,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
startTimeInput.replaceWith(this.fromRef(this.windowStartTimeRef));
|
startTimeInput.replaceWith(this.windowStartTimeRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MinusButton(props: {onclick: () => any}) {
|
private MinusButton(props: {onclick: () => void}) {
|
||||||
return <div
|
return <div
|
||||||
className={"minus-button"}
|
className={"minus-button"}
|
||||||
onclick={props.onclick}
|
onclick={props.onclick}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlusButton(props: {onclick: () => any}) {
|
private PlusButton(props: {onclick: () => void}) {
|
||||||
return <div
|
return <div
|
||||||
className={"plus-button"}
|
className={"plus-button"}
|
||||||
onclick={props.onclick}
|
onclick={props.onclick}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
private WindowedDisplay({ctx}: {ctx: DisplayModeWidget}) {
|
private WindowedDisplay = () => {
|
||||||
return (<div>
|
return (<div>
|
||||||
<div>From</div>
|
<div>From</div>
|
||||||
<ctx.MinusButton onclick={() => {
|
<this.MinusButton onclick={() => {
|
||||||
const displayWindow = AppStore().getState().displayWindow;
|
const displayWindow = AppStore().getState().displayWindow;
|
||||||
AppStore().setDisplayWindow({start: displayWindow.start - 60, stop: displayWindow.stop});
|
AppStore().setDisplayWindow({start: displayWindow.start - 60, stop: displayWindow.stop});
|
||||||
}}/>
|
}}/>
|
||||||
<ctx.WindowStartTime ctx={ctx}/>
|
<this.WindowStartTime this={this}/>
|
||||||
<ctx.PlusButton onclick={() => {
|
<this.PlusButton onclick={() => {
|
||||||
const displayWindow = AppStore().getState().displayWindow;
|
const displayWindow = AppStore().getState().displayWindow;
|
||||||
AppStore().setDisplayWindow({start: displayWindow.start + 60, stop: displayWindow.stop});
|
AppStore().setDisplayWindow({start: displayWindow.start + 60, stop: displayWindow.stop});
|
||||||
}}/>
|
}}/>
|
||||||
<div>to</div>
|
<div>to</div>
|
||||||
<ctx.MinusButton onclick={() => {
|
<this.MinusButton onclick={() => {
|
||||||
const displayWindow = AppStore().getState().displayWindow;
|
const displayWindow = AppStore().getState().displayWindow;
|
||||||
AppStore().setDisplayWindow({start: displayWindow.start, stop: displayWindow.stop - 60});
|
AppStore().setDisplayWindow({start: displayWindow.start, stop: displayWindow.stop - 60});
|
||||||
}}/>
|
}}/>
|
||||||
<ctx.WindowStopTime ctx={ctx}/>
|
<this.WindowStopTime this={this}/>
|
||||||
<ctx.PlusButton onclick={() => {
|
<this.PlusButton onclick={() => {
|
||||||
const displayWindow = AppStore().getState().displayWindow;
|
const displayWindow = AppStore().getState().displayWindow;
|
||||||
AppStore().setDisplayWindow({start: displayWindow.start, stop: displayWindow.stop + 60});
|
AppStore().setDisplayWindow({start: displayWindow.start, stop: displayWindow.stop + 60});
|
||||||
}}/>
|
}}/>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MainDisplay({ ctx }: { ctx: DisplayModeWidget }) {
|
private MainDisplay = () => {
|
||||||
const windowMode = getAppState().displayMode === "window";
|
const windowMode = getAppState().displayMode === "window";
|
||||||
ctx.windowedDisplayRef = ctx.makeRef(<ctx.WindowedDisplay ctx={ctx}/>);
|
this.windowedDisplayRef = <this.WindowedDisplay />;
|
||||||
ctx.minsDisplayRef = ctx.makeRef(<ctx.MinutesDisplay ctx={ctx}/>);
|
this.minsDisplayRef = <this.MinutesDisplay />;
|
||||||
return <div className={"display-mode-widget"}>
|
return <div className={"display-mode-widget"}>
|
||||||
{windowMode
|
{windowMode
|
||||||
? ctx.fromRef(ctx.windowedDisplayRef)
|
? this.windowedDisplayRef
|
||||||
: ctx.fromRef(ctx.minsDisplayRef)}
|
: this.minsDisplayRef}
|
||||||
</div> as HTMLElement;
|
</div> as HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,16 +222,16 @@ class DisplayModeWidget extends UIComponent {
|
|||||||
|
|
||||||
private updateDisplay() {
|
private updateDisplay() {
|
||||||
if (getAppState().displayMode === "window") {
|
if (getAppState().displayMode === "window") {
|
||||||
this.mainDisplay.children.item(0).replaceWith(this.fromRef(this.windowedDisplayRef));
|
this.mainDisplay.children.item(0).replaceWith(this.windowedDisplayRef);
|
||||||
const offset = getAppState().utcOffset * 60 * 60;
|
const offset = getAppState().utcOffset * 60 * 60;
|
||||||
const startDate = new Date((getAppState().displayWindow.start + offset) * 1000);
|
const startDate = new Date((getAppState().displayWindow.start + offset) * 1000);
|
||||||
const stopDate = new Date((getAppState().displayWindow.stop + offset) * 1000);
|
const stopDate = new Date((getAppState().displayWindow.stop + offset) * 1000);
|
||||||
this.fromRef(this.windowStartTimeRef).innerText = startDate.toLocaleString();
|
this.windowStartTimeRef.innerText = startDate.toLocaleString();
|
||||||
this.fromRef(this.windowStopTimeRef).innerText = stopDate.toLocaleString();
|
this.windowStopTimeRef.innerText = stopDate.toLocaleString();
|
||||||
} else {
|
} else {
|
||||||
this.mainDisplay.children.item(0).replaceWith(this.fromRef(this.minsDisplayRef));
|
this.mainDisplay.children.item(0).replaceWith(this.minsDisplayRef);
|
||||||
this.fromRef(this.minsCounterRef).innerText = getAppState().minutesDisplayed.toString();
|
this.minsCounterRef.innerText = getAppState().minutesDisplayed.toString();
|
||||||
(this.fromRef(this.minsInputRef) as HTMLInputElement).value = getAppState().minutesDisplayed.toString();
|
this.minsInputRef.value = getAppState().minutesDisplayed.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,4 +240,4 @@ class DisplayModeWidget extends UIComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DisplayModeWidget;
|
export default DisplayModeWidget;
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import Timeseries from "../Timeseries";
|
|||||||
class LegendWidget extends UIComponent {
|
class LegendWidget extends UIComponent {
|
||||||
private skeleton: GridWidget;
|
private skeleton: GridWidget;
|
||||||
private display: HTMLSpanElement = document.createElement("span");
|
private display: HTMLSpanElement = document.createElement("span");
|
||||||
private bodyRef: number;
|
private bodyRef: HTMLElement;
|
||||||
|
|
||||||
constructor(gridProps: GridProps) {
|
constructor(gridProps: GridProps) {
|
||||||
super();
|
super();
|
||||||
this.display = <this.MainBody ctx={this}/>;
|
this.display = <this.MainBody />;
|
||||||
this.skeleton = new GridWidget({
|
this.skeleton = new GridWidget({
|
||||||
...gridProps,
|
...gridProps,
|
||||||
title: "Legend:",
|
title: "Legend:",
|
||||||
@@ -23,29 +23,29 @@ class LegendWidget extends UIComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateDisplay() {
|
private updateDisplay() {
|
||||||
this.fromRef(this.bodyRef).replaceWith(<this.MainBody ctx={this}/>);
|
this.bodyRef.replaceWith(<this.MainBody />);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MainBody({ctx}: {ctx: LegendWidget}) {
|
private MainBody = () => {
|
||||||
ctx.bodyRef = ctx.makeRef(<div><ctx.TimeseriesList ctx={ctx}/></div>);
|
this.bodyRef = <div><this.TimeseriesList /></div>;
|
||||||
return ctx.fromRef(ctx.bodyRef);
|
return this.bodyRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TimeseriesList({ctx}: { ctx: LegendWidget }) {
|
private TimeseriesList = () => {
|
||||||
const highlightedTimeseries = getAppState().highlightedTimeseries;
|
const highlightedTimeseries = getAppState().highlightedTimeseries;
|
||||||
return <ul>
|
return <ul>
|
||||||
{ ...getAppState().rightTimeseries.map(timeseries =>
|
{ ...getAppState().rightTimeseries.map(timeseries =>
|
||||||
<ctx.TimeseriesLegendEntry
|
<this.TimeseriesLegendEntry
|
||||||
timeseries={timeseries}
|
timeseries={timeseries}
|
||||||
highlighted={timeseries.getName() === highlightedTimeseries}/>) }
|
highlighted={timeseries.getName() === highlightedTimeseries}/>) }
|
||||||
{ ...getAppState().leftTimeseries.map(timeseries =>
|
{ ...getAppState().leftTimeseries.map(timeseries =>
|
||||||
<ctx.TimeseriesLegendEntry
|
<this.TimeseriesLegendEntry
|
||||||
timeseries={timeseries}
|
timeseries={timeseries}
|
||||||
highlighted={timeseries.getName() === highlightedTimeseries}/>) }
|
highlighted={timeseries.getName() === highlightedTimeseries}/>) }
|
||||||
</ul>;
|
</ul>;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TimeseriesLegendEntry({timeseries, highlighted}: {timeseries: Timeseries, highlighted: boolean}) {
|
private TimeseriesLegendEntry = ({timeseries, highlighted}: {timeseries: Timeseries, highlighted: boolean}) => {
|
||||||
const option = new Option();
|
const option = new Option();
|
||||||
option.style.color = timeseries.getColour();
|
option.style.color = timeseries.getColour();
|
||||||
return <li
|
return <li
|
||||||
@@ -62,4 +62,4 @@ class LegendWidget extends UIComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LegendWidget;
|
export default LegendWidget;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class MessageOverlay extends UIComponent {
|
|||||||
|
|
||||||
private build() {
|
private build() {
|
||||||
this.element = document.createElement("div");
|
this.element = document.createElement("div");
|
||||||
this.element.classList.add("overlay", "center");
|
this.element.classList.add("overlay");
|
||||||
this.textElement = document.createElement("span");
|
this.textElement = document.createElement("span");
|
||||||
this.textElement.innerText = "";
|
this.textElement.innerText = "";
|
||||||
this.element.appendChild(this.textElement);
|
this.element.appendChild(this.textElement);
|
||||||
@@ -60,4 +60,4 @@ class MessageOverlay extends UIComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MessageOverlay;
|
export default MessageOverlay;
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ import {AppStore, DisplayMode, getAppState} from "../StateStore";
|
|||||||
export default class SelectDisplayModeWidget extends UIComponent {
|
export default class SelectDisplayModeWidget extends UIComponent {
|
||||||
private mainBody: HTMLElement;
|
private mainBody: HTMLElement;
|
||||||
private gridWidgetSkeleton: GridWidget;
|
private gridWidgetSkeleton: GridWidget;
|
||||||
private windowInputRef: number;
|
private windowInputRef: HTMLInputElement;
|
||||||
private minSpanInputRef: number;
|
private minSpanInputRef: HTMLInputElement;
|
||||||
private windowInputContainerRef: number;
|
private windowInputContainerRef: HTMLElement;
|
||||||
private minSpanInputContainerRef: number;
|
private minSpanInputContainerRef: HTMLElement;
|
||||||
|
|
||||||
constructor(gridProps: GridProps) {
|
constructor(gridProps: GridProps) {
|
||||||
super();
|
super();
|
||||||
this.mainBody = this.MainBody({ctx: this});
|
this.mainBody = <this.MainBody />;
|
||||||
this.gridWidgetSkeleton = new GridWidget({
|
this.gridWidgetSkeleton = new GridWidget({
|
||||||
...gridProps,
|
...gridProps,
|
||||||
title: "Display Mode:",
|
title: "Display Mode:",
|
||||||
@@ -27,44 +28,44 @@ export default class SelectDisplayModeWidget extends UIComponent {
|
|||||||
|
|
||||||
private update() {
|
private update() {
|
||||||
const windowedMode = getAppState().displayMode === "window";
|
const windowedMode = getAppState().displayMode === "window";
|
||||||
(this.fromRef(this.windowInputRef) as HTMLInputElement).checked = windowedMode;
|
this.windowInputRef.checked = windowedMode;
|
||||||
(this.fromRef(this.minSpanInputRef) as HTMLInputElement).checked = !windowedMode;
|
this.minSpanInputRef.checked = !windowedMode;
|
||||||
if (!windowedMode) {
|
if (!windowedMode) {
|
||||||
this.fromRef(this.minSpanInputContainerRef).classList.add("selected");
|
this.minSpanInputContainerRef.classList.add("selected");
|
||||||
this.fromRef(this.windowInputContainerRef).classList.remove("selected");
|
this.windowInputContainerRef.classList.remove("selected");
|
||||||
} else {
|
} else {
|
||||||
this.fromRef(this.minSpanInputContainerRef).classList.remove("selected");
|
this.minSpanInputContainerRef.classList.remove("selected");
|
||||||
this.fromRef(this.windowInputContainerRef).classList.add("selected");
|
this.windowInputContainerRef.classList.add("selected");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private MainBody({ ctx }: { ctx: SelectDisplayModeWidget }) {
|
private MainBody = () => {
|
||||||
const isInWindowMode = getAppState().displayMode === "window";
|
const isInWindowMode = getAppState().displayMode === "window";
|
||||||
ctx.windowInputRef = this.makeRef(<input
|
this.windowInputRef = <input
|
||||||
type={"radio"}
|
type={"radio"}
|
||||||
id={"window"}
|
id={"window"}
|
||||||
name={"display-mode"}
|
name={"display-mode"}
|
||||||
checked={isInWindowMode}/>);
|
checked={isInWindowMode}/> as HTMLInputElement;
|
||||||
ctx.minSpanInputRef = this.makeRef(<input
|
this.minSpanInputRef = <input
|
||||||
type={"radio"}
|
type={"radio"}
|
||||||
id={"min-span"}
|
id={"min-span"}
|
||||||
name={"display-mode"}
|
name={"display-mode"}
|
||||||
checked={!isInWindowMode}/>);
|
checked={!isInWindowMode}/> as HTMLInputElement;
|
||||||
ctx.windowInputContainerRef = this.makeRef(<div
|
this.windowInputContainerRef = <div
|
||||||
className={`display-mode-option${isInWindowMode ? " selected" : ""}`}
|
className={`display-mode-option${isInWindowMode ? " selected" : ""}`}
|
||||||
onclick={() => ctx.selectMode("window")}>
|
onclick={() => this.selectMode("window")}>
|
||||||
{this.fromRef(ctx.windowInputRef)}
|
{this.windowInputRef}
|
||||||
<label htmlFor={"window"}>Time Window</label>
|
<label htmlFor={"window"}>Time Window</label>
|
||||||
</div>);
|
</div>;
|
||||||
ctx.minSpanInputContainerRef = this.makeRef(<div
|
this.minSpanInputContainerRef = <div
|
||||||
className={`display-mode-option${!isInWindowMode ? " selected" : ""}`}
|
className={`display-mode-option${!isInWindowMode ? " selected" : ""}`}
|
||||||
onclick={() => ctx.selectMode("pastMins")}>
|
onclick={() => this.selectMode("pastMins")}>
|
||||||
{this.fromRef(ctx.minSpanInputRef)}
|
{this.minSpanInputRef}
|
||||||
<label htmlFor={"minSpan"}>Rolling Minute Span</label>
|
<label htmlFor={"minSpan"}>Rolling Minute Span</label>
|
||||||
</div>);
|
</div>;
|
||||||
return (<div>
|
return (<div>
|
||||||
{this.fromRef(ctx.windowInputContainerRef)}
|
{this.windowInputContainerRef}
|
||||||
{this.fromRef(ctx.minSpanInputContainerRef)}
|
{this.minSpanInputContainerRef}
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ class TimerWidget extends UIComponent {
|
|||||||
private readonly display: HTMLElement;
|
private readonly display: HTMLElement;
|
||||||
private skeleton: GridWidget;
|
private skeleton: GridWidget;
|
||||||
private nextUpdateTime: number;
|
private nextUpdateTime: number;
|
||||||
private timerRef: number;
|
private timerRef: HTMLElement;
|
||||||
private lastUpdateRef: number;
|
private lastUpdateRef: HTMLElement;
|
||||||
|
|
||||||
constructor(gridProps: GridProps) {
|
constructor(gridProps: GridProps) {
|
||||||
super();
|
super();
|
||||||
this.display = <this.MainDisplay ctx={this}/>;
|
this.display = <this.MainDisplay />;
|
||||||
this.skeleton = new GridWidget({
|
this.skeleton = new GridWidget({
|
||||||
...gridProps,
|
...gridProps,
|
||||||
className: "timer-widget",
|
className: "timer-widget",
|
||||||
@@ -32,20 +32,20 @@ class TimerWidget extends UIComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateUpdateText() {
|
private updateUpdateText() {
|
||||||
this.fromRef(this.lastUpdateRef).innerText = new Date(getAppState().lastUpdateTime * 1000 + getAppState().utcOffset * 60 * 60 * 1000).toLocaleString();
|
this.lastUpdateRef.innerText = new Date(getAppState().lastUpdateTime * 1000 + getAppState().utcOffset * 60 * 60 * 1000).toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private MainDisplay({ ctx }: { ctx: TimerWidget }) {
|
private MainDisplay = () => {
|
||||||
ctx.timerRef = ctx.makeRef(<div className={"countdown"}/>);
|
this.timerRef = <div className={"countdown"}/>;
|
||||||
ctx.lastUpdateRef = ctx.makeRef(
|
this.lastUpdateRef =
|
||||||
<span className={"last-update"}>
|
<span className={"last-update"}>
|
||||||
{new Date(getAppState().lastUpdateTime).toLocaleString()}
|
{new Date(getAppState().lastUpdateTime).toLocaleString()}
|
||||||
</span>);
|
</span>;
|
||||||
return (<div>
|
return (<div>
|
||||||
{ctx.fromRef(ctx.timerRef)}
|
{this.timerRef}
|
||||||
<div>
|
<div>
|
||||||
<div className={"last-update"}>Last update was at:</div>
|
<div className={"last-update"}>Last update was at:</div>
|
||||||
<div>{ctx.fromRef(ctx.lastUpdateRef)}</div>
|
<div>{this.lastUpdateRef}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
@@ -53,9 +53,9 @@ class TimerWidget extends UIComponent {
|
|||||||
private refreshTimer() {
|
private refreshTimer() {
|
||||||
const now = new Date().getTime() / 1000;
|
const now = new Date().getTime() / 1000;
|
||||||
if (now <= this.nextUpdateTime) {
|
if (now <= this.nextUpdateTime) {
|
||||||
this.fromRef(this.timerRef).innerText = `${(this.nextUpdateTime - now).toFixed(2)}s`;
|
this.timerRef.innerText = `${(this.nextUpdateTime - now).toFixed(2)}s`;
|
||||||
} else {
|
} else {
|
||||||
this.fromRef(this.timerRef).innerText = "0.00s";
|
this.timerRef.innerText = "0.00s";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,4 +64,4 @@ class TimerWidget extends UIComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TimerWidget;
|
export default TimerWidget;
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import * as JSX from "../JSXFactory";
|
|||||||
class TimezoneWidget extends UIComponent {
|
class TimezoneWidget extends UIComponent {
|
||||||
private skeleton: GridWidget;
|
private skeleton: GridWidget;
|
||||||
private display: HTMLSpanElement = document.createElement("span");
|
private display: HTMLSpanElement = document.createElement("span");
|
||||||
private timezoneInputRef: number;
|
private timezoneInputRef: HTMLInputElement;
|
||||||
private timezoneDisplayRef: number;
|
private timezoneDisplayRef: HTMLElement;
|
||||||
|
|
||||||
constructor(gridProps: GridProps) {
|
constructor(gridProps: GridProps) {
|
||||||
super();
|
super();
|
||||||
this.display = <this.MainBody ctx={this}/>;
|
this.display = <this.MainBody />;
|
||||||
this.skeleton = new GridWidget({
|
this.skeleton = new GridWidget({
|
||||||
...gridProps,
|
...gridProps,
|
||||||
title: "Displayed Timezone:",
|
title: "Displayed Timezone:",
|
||||||
@@ -23,39 +23,39 @@ class TimezoneWidget extends UIComponent {
|
|||||||
|
|
||||||
private updateDisplay() {
|
private updateDisplay() {
|
||||||
const offset = AppStore().getState().utcOffset;
|
const offset = AppStore().getState().utcOffset;
|
||||||
this.fromRef(this.timezoneDisplayRef).innerText = `${offset > 0 ? "+" : "−"} ${Math.abs(offset)}`;
|
this.timezoneDisplayRef.innerText = `${offset > 0 ? "+" : "−"} ${Math.abs(offset)}`;
|
||||||
(this.fromRef(this.timezoneInputRef) as HTMLInputElement).value = `${offset > 0 ? "" : "-"}${Math.abs(offset)}`;
|
(this.timezoneInputRef as HTMLInputElement).value = `${offset > 0 ? "" : "-"}${Math.abs(offset)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MainBody({ctx}: {ctx: TimezoneWidget}) {
|
private MainBody = () => {
|
||||||
return <div
|
return <div
|
||||||
className={"timezone-widget"}
|
className={"timezone-widget"}
|
||||||
onclick={() => ctx.onTimezoneClick()}>
|
onclick={() => this.onTimezoneClick()}>
|
||||||
<span>UTC </span>
|
<span>UTC </span>
|
||||||
<ctx.TimezoneDisplay ctx={ctx} />
|
<this.TimezoneDisplay />
|
||||||
<span>:00</span>
|
<span>:00</span>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TimezoneDisplay({ctx}: {ctx: TimezoneWidget}) {
|
private TimezoneDisplay = () => {
|
||||||
ctx.timezoneDisplayRef = ctx.makeRef(<span/>);
|
this.timezoneDisplayRef = <span/>;
|
||||||
ctx.timezoneInputRef = ctx.makeRef(<input
|
this.timezoneInputRef = <input
|
||||||
type={"text"}
|
type={"text"}
|
||||||
onblur={() => ctx.onTimezoneInputBlur()}/>);
|
onblur={() => this.onTimezoneInputBlur()}/> as HTMLInputElement;
|
||||||
return ctx.fromRef(ctx.timezoneDisplayRef);
|
return this.timezoneDisplayRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onTimezoneInputBlur() {
|
private onTimezoneInputBlur() {
|
||||||
const input = this.fromRef(this.timezoneInputRef) as HTMLInputElement;
|
const input = this.timezoneInputRef;
|
||||||
const display = this.fromRef(this.timezoneDisplayRef);
|
const display = this.timezoneDisplayRef;
|
||||||
AppStore().setUtcOffset(Number(input.value));
|
AppStore().setUtcOffset(Number(input.value));
|
||||||
input.replaceWith(display);
|
input.replaceWith(display);
|
||||||
this.updateDisplay();
|
this.updateDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
private onTimezoneClick() {
|
private onTimezoneClick() {
|
||||||
const input = this.fromRef(this.timezoneInputRef) as HTMLInputElement;
|
const input = this.timezoneInputRef as HTMLInputElement;
|
||||||
this.fromRef(this.timezoneDisplayRef).replaceWith(input);
|
this.timezoneDisplayRef.replaceWith(input);
|
||||||
input.focus();
|
input.focus();
|
||||||
input.selectionStart = 0;
|
input.selectionStart = 0;
|
||||||
input.selectionEnd = input.value.length;
|
input.selectionEnd = input.value.length;
|
||||||
@@ -66,4 +66,4 @@ class TimezoneWidget extends UIComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TimezoneWidget;
|
export default TimezoneWidget;
|
||||||
|
|||||||
@@ -1,22 +1,11 @@
|
|||||||
export default abstract class UIComponent {
|
export default abstract class UIComponent {
|
||||||
public readonly id: number;
|
public readonly id: number;
|
||||||
private static componentCount = 0;
|
private static componentCount = 0;
|
||||||
private static reffedComponentCount = 0;
|
|
||||||
private static readonly reffedComponents: HTMLElement[] = [];
|
|
||||||
|
|
||||||
protected constructor() {
|
protected constructor() {
|
||||||
this.id = UIComponent.componentCount;
|
this.id = UIComponent.componentCount;
|
||||||
UIComponent.componentCount++;
|
UIComponent.componentCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected makeRef(el: HTMLElement | DocumentFragment): number {
|
|
||||||
UIComponent.reffedComponents.push(el as HTMLElement);
|
|
||||||
return UIComponent.reffedComponentCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fromRef(ref: number): HTMLElement | null {
|
|
||||||
return UIComponent.reffedComponents[ref] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract current(): HTMLElement;
|
abstract current(): HTMLElement;
|
||||||
}
|
}
|
||||||
|
|||||||
7
dashboard/vite.config.ts
Normal file
7
dashboard/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import {defineConfig} from "vite";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
outDir: '../app-dist/static',
|
||||||
|
},
|
||||||
|
});
|
||||||
2871
package-lock.json
generated
2871
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@
|
|||||||
"concurrently": "^6.0.0",
|
"concurrently": "^6.0.0",
|
||||||
"eslint": "^7.21.0",
|
"eslint": "^7.21.0",
|
||||||
"prettier": "^2.1.2",
|
"prettier": "^2.1.2",
|
||||||
"typescript": "^4.2.3"
|
"typescript": "^5.1.0"
|
||||||
},
|
},
|
||||||
"author": "Daniel Ledda",
|
"author": "Daniel Ledda",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
|
|||||||
Reference in New Issue
Block a user