diff --git a/Svg.ts b/Svg.ts
index 3041d22cc4..4850a0c719 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 = " "
+ 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 = " "
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 = " "
+ 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 = " "
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 d7dc5dc10f..0000000000
--- 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 0000000000..7c6f152289
--- /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 2c89ef6910..9ba1a18a36 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 a682c6c51c..c9ea0263b2 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 0000000000..52f0b97f97
--- /dev/null
+++ b/assets/svg/compass.svg
@@ -0,0 +1,199 @@
+
+
diff --git a/assets/svg/direction.svg b/assets/svg/direction.svg
new file mode 100644
index 0000000000..e1eccac39c
--- /dev/null
+++ b/assets/svg/direction.svg
@@ -0,0 +1,31 @@
+
+
diff --git a/assets/themes/surveillance_cameras/surveillance_cameras.json b/assets/themes/surveillance_cameras/surveillance_cameras.json
index 994a827f1b..29d9e2755c 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 d5c4f874ce..ab483a4dac 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 b175d16c5a..21245de458 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 f1b3381222..e578eb8f1f 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")
/*/