From 7ef2f429f2be5e54451bbc83976c467797af86c0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 15 Nov 2020 03:10:44 +0100 Subject: [PATCH] Add fancy direction picker --- Svg.ts | 10 + UI/Input/Direction.ts | 39 ---- UI/Input/DirectionInput.ts | 92 ++++++++ UI/Input/ValidatedTextField.ts | 12 ++ UI/ShareScreen.ts | 24 ++- assets/svg/compass.svg | 199 ++++++++++++++++++ assets/svg/direction.svg | 31 +++ .../surveillance_cameras.json | 11 + index.css | 6 + package-lock.json | 6 +- test.ts | 9 +- 11 files changed, 386 insertions(+), 53 deletions(-) delete mode 100644 UI/Input/Direction.ts create mode 100644 UI/Input/DirectionInput.ts create mode 100644 assets/svg/compass.svg create mode 100644 assets/svg/direction.svg diff --git a/Svg.ts b/Svg.ts index 3041d22..4850a0c 100644 --- a/Svg.ts +++ b/Svg.ts @@ -49,6 +49,11 @@ export default class Svg { public static close_svg() { return new FixedUiElement(Svg.close);} public static close_ui() { return new FixedUiElement(Svg.close_img);} + public static compass = " image/svg+xml N S E W NW SW NE SE " + public static compass_img = Img.AsImageElement(Svg.compass) + public static compass_svg() { return new FixedUiElement(Svg.compass);} + public static compass_ui() { return new FixedUiElement(Svg.compass_img);} + public static crosshair_blue_center = " image/svg+xml " public static crosshair_blue_center_img = Img.AsImageElement(Svg.crosshair_blue_center) public static crosshair_blue_center_svg() { return new FixedUiElement(Svg.crosshair_blue_center);} @@ -69,6 +74,11 @@ export default class Svg { public static delete_icon_svg() { return new FixedUiElement(Svg.delete_icon);} public static delete_icon_ui() { return new FixedUiElement(Svg.delete_icon_img);} + public static direction = " image/svg+xml " + public static direction_img = Img.AsImageElement(Svg.direction) + public static direction_svg() { return new FixedUiElement(Svg.direction);} + public static direction_ui() { return new FixedUiElement(Svg.direction_img);} + public static down = " image/svg+xml " public static down_img = Img.AsImageElement(Svg.down) public static down_svg() { return new FixedUiElement(Svg.down);} diff --git a/UI/Input/Direction.ts b/UI/Input/Direction.ts deleted file mode 100644 index d7dc5dc..0000000 --- a/UI/Input/Direction.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {InputElement} from "./InputElement"; -import {UIEventSource} from "../../Logic/UIEventSource"; -import Combine from "../Base/Combine"; -import {FixedUiElement} from "../Base/FixedUiElement"; - -/** - * Selects a direction in degrees - */ -export default class Direction extends InputElement{ - - private readonly value: UIEventSource; - public readonly IsSelected: UIEventSource = new UIEventSource(false); - - constructor(value?: UIEventSource) { - super(); - this.value = value ?? new UIEventSource(undefined); - } - - - GetValue(): UIEventSource { - return this.value; - } - - InnerRender(): string { - return new Combine([ - new FixedUiElement("").SetStyle( - "position: absolute;top: calc(50% - 0.5em);left: calc(50% - 0.5em);width: 1em;height: 1em;background: red;border-radius: 1em"), - - ]) - .SetStyle("position:relative;display:block;width: min(100%, 25em); padding-top: min(100% , 25em); background:white; border: 1px solid black; border-radius: 999em") - .Render(); - } - - - IsValid(t: number): boolean { - return t >= 0 && t <= 360; - } - -} \ No newline at end of file diff --git a/UI/Input/DirectionInput.ts b/UI/Input/DirectionInput.ts new file mode 100644 index 0000000..7c6f152 --- /dev/null +++ b/UI/Input/DirectionInput.ts @@ -0,0 +1,92 @@ +import {InputElement} from "./InputElement"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import Combine from "../Base/Combine"; +import Svg from "../../Svg"; + +/** + * Selects a direction in degrees + */ +export default class DirectionInput extends InputElement { + + private readonly value: UIEventSource; + public readonly IsSelected: UIEventSource = new UIEventSource(false); + + constructor(value?: UIEventSource) { + super(); + this.dumbMode = false; + this.value = value ?? new UIEventSource(undefined); + + this.value.addCallbackAndRun(rotation => { + const selfElement = document.getElementById(this.id); + if (selfElement === null) { + return; + } + const cone = selfElement.getElementsByClassName("direction-svg")[0] as HTMLElement + cone.style.rotate = rotation + "deg"; + + }) + + } + + + GetValue(): UIEventSource { + return this.value; + } + + InnerRender(): string { + console.log("Inner render direction") + return new Combine([ + Svg.direction_svg().SetStyle( + `position: absolute;top: 0;left: 0;width: 100%;height: 100%;rotategs:${this.value.data}deg;`) + .SetClass("direction-svg"), + Svg.compass_svg().SetStyle( + "position: absolute;top: 0;left: 0;width: 100%;height: 100%;") + ]) + .SetStyle("position:relative;display:block;width: min(100%, 25em); padding-top: min(100% , 25em); background:white; border: 1px solid black; border-radius: 999em") + .Render(); + } + + protected InnerUpdate(htmlElement: HTMLElement) { + console.log("Inner update direction") + super.InnerUpdate(htmlElement); + const self = this; + + function onPosChange(x: number, y: number) { + const rect = htmlElement.getBoundingClientRect(); + const dx = -(rect.left + rect.right) / 2 + x; + const dy = (rect.top + rect.bottom) / 2 - y; + const angle = 180 * Math.atan2(dy, dx) / Math.PI; + const angleGeo = Math.floor((450 - angle) % 360); + self.value.setData(""+angleGeo) + } + + + htmlElement.ontouchmove = (ev: TouchEvent) => { + onPosChange(ev.touches[0].clientX, ev.touches[0].clientY); + } + + let isDown = false; + + htmlElement.onmousedown = (ev: MouseEvent) => { + isDown = true; + onPosChange(ev.x, ev.y); + ev.preventDefault(); + } + + htmlElement.onmouseup = (ev) => { + isDown = false; ev.preventDefault(); + } + + htmlElement.onmousemove = (ev: MouseEvent) => { + if (isDown) { + onPosChange(ev.x, ev.y); + } ev.preventDefault(); + } + } + + IsValid(str: string): boolean { + const t = Number(str); + return !isNaN(t) && t >= 0 && t <= 360; + } + +} \ No newline at end of file diff --git a/UI/Input/ValidatedTextField.ts b/UI/Input/ValidatedTextField.ts index 2c89ef6..9ba1a18 100644 --- a/UI/Input/ValidatedTextField.ts +++ b/UI/Input/ValidatedTextField.ts @@ -9,6 +9,7 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import CombinedInputElement from "./CombinedInputElement"; import SimpleDatePicker from "./SimpleDatePicker"; import OpeningHoursInput from "./OpeningHours/OpeningHoursInput"; +import DirectionInput from "./DirectionInput"; interface TextFieldDef { name: string, @@ -97,6 +98,17 @@ export default class ValidatedTextField { str = "" + str; return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) > 0 }), + ValidatedTextField.tp( + "direction", + "A geographical direction, in degrees. 0° is north, 90° is east", + (str) => { + str = "" + str; + return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) > 0 && Number(str) <= 360 + },str => str, + (value) => { + return new DirectionInput(value); + } + ), ValidatedTextField.tp( "float", "A decimal", diff --git a/UI/ShareScreen.ts b/UI/ShareScreen.ts index a682c6c..c9ea026 100644 --- a/UI/ShareScreen.ts +++ b/UI/ShareScreen.ts @@ -32,9 +32,17 @@ export class ShareScreen extends UIElement { const optionCheckboxes: UIElement[] = [] const optionParts: (UIEventSource)[] = []; + function check() { + return Svg.checkmark_svg().SetStyle("width: 1.5em; display:inline-block;"); + } + + function nocheck() { + return Svg.no_checkmark_svg().SetStyle("width: 1.5em; display: inline-block;"); + } + const includeLocation = new CheckBox( - new Combine([Svg.checkmark, tr.fsIncludeCurrentLocation]), - new Combine([Svg.no_checkmark, tr.fsIncludeCurrentLocation]), + new Combine([check(), tr.fsIncludeCurrentLocation]), + new Combine([nocheck(), tr.fsIncludeCurrentLocation]), true ) optionCheckboxes.push(includeLocation); @@ -68,8 +76,8 @@ export class ShareScreen extends UIElement { return tr.fsIncludeCurrentBackgroundMap.Subs({name: layer?.name ?? ""}).Render(); })); const includeCurrentBackground = new CheckBox( - new Combine([Svg.checkmark, currentBackground]), - new Combine([Svg.no_checkmark, currentBackground]), + new Combine([check(), currentBackground]), + new Combine([nocheck(), currentBackground]), true ) optionCheckboxes.push(includeCurrentBackground); @@ -83,8 +91,8 @@ export class ShareScreen extends UIElement { const includeLayerChoices = new CheckBox( - new Combine([Svg.checkmark, tr.fsIncludeCurrentLayers]), - new Combine([Svg.no_checkmark, tr.fsIncludeCurrentLayers]), + new Combine([check(), tr.fsIncludeCurrentLayers]), + new Combine([nocheck(), tr.fsIncludeCurrentLayers]), true ) optionCheckboxes.push(includeLayerChoices); @@ -113,8 +121,8 @@ export class ShareScreen extends UIElement { for (const swtch of switches) { const checkbox = new CheckBox( - new Combine([Svg.checkmark, Translations.W(swtch.human)]), - new Combine([Svg.no_checkmark, Translations.W(swtch.human)]), !swtch.reverse + new Combine([check(), Translations.W(swtch.human)]), + new Combine([nocheck(), Translations.W(swtch.human)]), !swtch.reverse ); optionCheckboxes.push(checkbox); optionParts.push(checkbox.isEnabled.map((isEn) => { diff --git a/assets/svg/compass.svg b/assets/svg/compass.svg new file mode 100644 index 0000000..52f0b97 --- /dev/null +++ b/assets/svg/compass.svg @@ -0,0 +1,199 @@ + + + + + + + image/svg+xml + + + + + + + N + S + E + W + NW + SW + NE + SE + diff --git a/assets/svg/direction.svg b/assets/svg/direction.svg new file mode 100644 index 0000000..e1eccac --- /dev/null +++ b/assets/svg/direction.svg @@ -0,0 +1,31 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/assets/themes/surveillance_cameras/surveillance_cameras.json b/assets/themes/surveillance_cameras/surveillance_cameras.json index 994a827..29d9e27 100644 --- a/assets/themes/surveillance_cameras/surveillance_cameras.json +++ b/assets/themes/surveillance_cameras/surveillance_cameras.json @@ -96,6 +96,17 @@ } ] }, + { + "question": { + "en": "In which geographical direction does this camera film?", + "nl": "Naar welke geografische richting filmt deze camera?" + }, + "render": "Films to {camera:direction}", + "freeform": { + "key": "camera:direction", + "type": "direction" + } + }, { "freeform": { "key": "operator" diff --git a/index.css b/index.css index d5c4f87..ab483a4 100644 --- a/index.css +++ b/index.css @@ -26,6 +26,8 @@ a { svg { fill: var(--foreground-color) !important; stroke: var(--foreground-color) !important; + width: 100%; + height: 100%; } svg path { @@ -33,6 +35,10 @@ svg path { stroke: var(--foreground-color) !important; } +.direction-svg svg path{ + fill: var(--catch-detail-color) !important; +} + #leafletDiv { height: 100%; diff --git a/package-lock.json b/package-lock.json index b175d16..21245de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1774,9 +1774,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001066", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001066.tgz", - "integrity": "sha512-Gfj/WAastBtfxLws0RCh2sDbTK/8rJuSeZMecrSkNGYxPcv7EzblmDGfWQCFEQcSqYE2BRgQiJh8HOD07N5hIw==" + "version": "1.0.30001157", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001157.tgz", + "integrity": "sha512-gOerH9Wz2IRZ2ZPdMfBvyOi3cjaz4O4dgNwPGzx8EhqAs4+2IL/O+fJsbt+znSigujoZG8bVcIAUM/I/E5K3MA==" }, "canvas": { "version": "2.6.1", diff --git a/test.ts b/test.ts index f1b3381..e578eb8 100644 --- a/test.ts +++ b/test.ts @@ -1,10 +1,13 @@ //* -import Direction from "./UI/Input/Direction"; - -new Direction().AttachTo("maindiv") +import Direction from "./UI/Input/DirectionInput"; +import {UIEventSource} from "./Logic/UIEventSource"; +import {VariableUiElement} from "./UI/Base/VariableUIElement"; +const d = new UIEventSource(90); +new Direction(d).AttachTo("maindiv") +new VariableUiElement(d.map(d => ""+d+"°")).AttachTo("extradiv") /*/