add skeleton for most UI elements, implement cirlces

This commit is contained in:
Iris Lightshard 2022-08-13 15:40:38 -06:00
parent cd2f0ab2ec
commit d60cf73184
Signed by: Iris Lightshard
GPG key ID: 3B7FBC22144E6398
10 changed files with 302 additions and 53 deletions

View file

@ -2,7 +2,7 @@
## about ## about
`onyx/scry` is a lightweight map annotation and location data management and sharing tool built using [leaflet](https://leafletjs.org) and [typescript](https://typescriptlang.org). It is intended as a standalone tool to generate, manage, and share simple location data in the form of points, circles, and polygons. All of these have associated titles and descriptions and can be easily imported or exported to a `json` format for easy sharing. All data is saved locally via the `localStorage` API, and the only network calls are those to retrieve either the streetmap or satellite tile data. `onyx/scry` is a lightweight map annotation and location data management and sharing tool built using [leaflet](https://leafletjs.com) and [typescript](https://typescriptlang.org). It is intended as a standalone tool to generate, manage, and share simple location data in the form of points, circles, and polygons. All of these have associated titles and descriptions and can be easily imported or exported to a `json` format for easy sharing. All data is saved locally via the `localStorage` API, and the only network calls are those to retrieve either the streetmap or satellite tile data.
## usage ## usage

View file

@ -11,6 +11,7 @@
<body> <body>
<div id="map"></div> <div id="map"></div>
<div id="mapControls"> <div id="mapControls">
<button id="addPoint-btn">&middot;</button> <button id="addPoint-btn">&middot;</button>
<button id="addCircle-btn">&compfn;</button> <button id="addCircle-btn">&compfn;</button>
@ -29,6 +30,32 @@
<div id="modal-content"> <div id="modal-content">
</div> </div>
</div> </div>
<div id="cancel-container">
<button class="cancel-btn">Cancel</button>
</div>
<div id="confirm-container">
<span id="confirm-msg"></span>
<button id="yes-btn">Yes</button>
<button id="no-btn">No</button>
</div>
<div id="import-export-container">
<h2></h2>
<textarea id="import-export-textarea"></textarea>
<button id="import-export-ok-btn">OK</button>
<button id="import-export-cancel-btn">Cancel</button>
</div>
<div id="overlays-menu-container">
<details id="markers-wrapper"><summary>Markers</summary><ul id="markers-list"></ul></details>
<details id="circles-wrapper"><summary>Circles</summary><ul id="circles-list"></ul></details>
<details id="polygons-wrapper"><summary>Polygons</summary><ul id="polygons-list"></ul></details>
<button id="import-btn">Import</button>
<button id="export-all-btn">Export All</button>
</div>
</body> </body>
<link rel='stylesheet' type="text/css" href="/static/leaflet.css"> <link rel='stylesheet' type="text/css" href="/static/leaflet.css">
<script src="/static/leaflet.js"></script> <script src="/static/leaflet.js"></script>

View file

@ -1,5 +1,5 @@
10-overlay.ts 146 10-overlay.ts 147
11-tilelayer.ts 37 11-tilelayer.ts 37
20-modal.ts 78 20-modal.ts 112
30-handlers.ts 2 30-handlers.ts 2
99-onyx-scry.ts 79 99-onyx-scry.ts 103

View file

@ -70,6 +70,7 @@ class Circle extends OverlayBase {
constructor(name: string, desc: string, point: Point, options: any) { constructor(name: string, desc: string, point: Point, options: any) {
super(name, desc, [ point ], options); super(name, desc, [ point ], options);
this.self = L.circle(point, options); this.self = L.circle(point, options);
this.self.bindPopup(`<h3>${name}</h3><p>${desc}</p>`);
} }
} }

View file

@ -24,6 +24,10 @@ class Modal {
return document.getElementById("modal-submitBtn"); return document.getElementById("modal-submitBtn");
} }
radiusContainer(): HTMLElement | null {
return document.getElementById("radius-container");
}
nameField(): string { nameField(): string {
return (document.getElementById("modal-name") as HTMLInputElement)?.value ?? ""; return (document.getElementById("modal-name") as HTMLInputElement)?.value ?? "";
} }
@ -32,6 +36,10 @@ class Modal {
return (document.getElementById("modal-desc") as HTMLInputElement)?.value ?? ""; return (document.getElementById("modal-desc") as HTMLInputElement)?.value ?? "";
} }
radiusField(): string {
return (document.getElementById("modal-radius") as HTMLInputElement)?.value ?? "";
}
visible(): boolean { visible(): boolean {
return this.self()?.style.display != "none"; return this.self()?.style.display != "none";
} }
@ -45,9 +53,9 @@ class Modal {
setState(state: OverlayType, args: any): void { setState(state: OverlayType, args: any): void {
const _this = this; const _this = this;
const title = this.title()
switch (state) { switch (state) {
case OverlayType.POINT: case OverlayType.POINT:
const title = this.title()
if (title) { if (title) {
title.innerHTML = "Add Marker"; title.innerHTML = "Add Marker";
} }
@ -71,6 +79,32 @@ class Modal {
break; break;
case OverlayType.CIRCLE: case OverlayType.CIRCLE:
if (title) {
title.innerHTML = "Add Circle";
}
fetch("/static/pointModal.html")
.then(r=>r.text())
.then(t=> {
const content = _this.content();
if (content) { content.innerHTML = t; }
const radiusContainer = _this.radiusContainer();
if (radiusContainer) {
radiusContainer.style.display = "block";
}
const submitBtn = _this.submitBtn();
if (submitBtn) {
submitBtn.onclick = () => {
const name = _this.nameField();
const desc = _this.descField();
const radius = _this.radiusField();
const circle = new Circle(name, desc, args.latlng, {radius: Number(radius)});
circle.add(args.map);
args.overlays.circles.push(circle);
_this.setVisible(false);
}
}
});
break; break;
case OverlayType.POLYGON: case OverlayType.POLYGON:
break; break;

View file

@ -29,7 +29,16 @@ function init(): void {
const modal = new Modal(); const modal = new Modal();
const addMarkerHandler = (e: any) => { const resetMapClick = (): void => {
try {
map.off("click", addMarkerHandler);
} catch {}
try {
map.off("click", addCircleHandler);
} catch {}
}
const addMarkerHandler = (e: any): void => {
modal.setVisible(true); modal.setVisible(true);
modal.setState(OverlayType.POINT, { modal.setState(OverlayType.POINT, {
latlng: e.latlng, latlng: e.latlng,
@ -39,34 +48,49 @@ function init(): void {
map.off("click", addMarkerHandler); map.off("click", addMarkerHandler);
} }
const addCircleHandler = (e: any): void => {
modal.setVisible(true);
modal.setState(OverlayType.CIRCLE, {
latlng: e.latlng,
map: map,
overlays: overlays,
});
map.off("click", addCircleHandler);
}
const addMarkerBtn = document.getElementById("addPoint-btn"); const addMarkerBtn = document.getElementById("addPoint-btn");
if (addMarkerBtn) { if (addMarkerBtn) {
addMarkerBtn.onclick = (e: any)=>{ addMarkerBtn.onclick = (e: any): void => {
try{ resetMapClick()
map.off("click", addMarkerHandler);
} finally {
map.on("click", addMarkerHandler); map.on("click", addMarkerHandler);
}
}; };
} }
const addCircleBtn = document.getElementById("addCircle-btn");
if (addCircleBtn) {
addCircleBtn.onclick = (e: any): void => {
resetMapClick();
map.on("click", addCircleHandler);
}
}
const saveBtn = document.getElementById("save-btn"); const saveBtn = document.getElementById("save-btn");
if (saveBtn) { if (saveBtn) {
saveBtn.onclick = (e: any) => { saveBtn.onclick = (e: any): void => {
OverlayState.save(overlays); OverlayState.save(overlays);
}; };
} }
const clearBtn = document.getElementById("clear-btn"); const clearBtn = document.getElementById("clear-btn");
if (clearBtn) { if (clearBtn) {
clearBtn.onclick = (e: any) => { clearBtn.onclick = (e: any): void => {
overlays = OverlayState.clear(overlays, map); overlays = OverlayState.clear(overlays, map);
} }
} }
const tilesBtn = document.getElementById("tiles-btn"); const tilesBtn = document.getElementById("tiles-btn");
if (tilesBtn) { if (tilesBtn) {
tilesBtn.onclick = (e: any) => { tilesBtn.onclick = (e: any): void => {
if (TileLayerWrapper.getActiveLayer() == satelliteLayer) { if (TileLayerWrapper.getActiveLayer() == satelliteLayer) {
TileLayerWrapper.enableOnly(streetLayer, map); TileLayerWrapper.enableOnly(streetLayer, map);
} else { } else {

View file

@ -70,6 +70,7 @@ class Circle extends OverlayBase {
constructor(name: string, desc: string, point: Point, options: any) { constructor(name: string, desc: string, point: Point, options: any) {
super(name, desc, [ point ], options); super(name, desc, [ point ], options);
this.self = L.circle(point, options); this.self = L.circle(point, options);
this.self.bindPopup(`<h3>${name}</h3><p>${desc}</p>`);
} }
} }
@ -207,6 +208,10 @@ class OverlayState {
return document.getElementById("modal-submitBtn"); return document.getElementById("modal-submitBtn");
} }
radiusContainer(): HTMLElement | null {
return document.getElementById("radius-container");
}
nameField(): string { nameField(): string {
return (document.getElementById("modal-name") as HTMLInputElement)?.value ?? ""; return (document.getElementById("modal-name") as HTMLInputElement)?.value ?? "";
} }
@ -215,6 +220,10 @@ class OverlayState {
return (document.getElementById("modal-desc") as HTMLInputElement)?.value ?? ""; return (document.getElementById("modal-desc") as HTMLInputElement)?.value ?? "";
} }
radiusField(): string {
return (document.getElementById("modal-radius") as HTMLInputElement)?.value ?? "";
}
visible(): boolean { visible(): boolean {
return this.self()?.style.display != "none"; return this.self()?.style.display != "none";
} }
@ -228,9 +237,9 @@ class OverlayState {
setState(state: OverlayType, args: any): void { setState(state: OverlayType, args: any): void {
const _this = this; const _this = this;
const title = this.title()
switch (state) { switch (state) {
case OverlayType.POINT: case OverlayType.POINT:
const title = this.title()
if (title) { if (title) {
title.innerHTML = "Add Marker"; title.innerHTML = "Add Marker";
} }
@ -254,6 +263,32 @@ class OverlayState {
break; break;
case OverlayType.CIRCLE: case OverlayType.CIRCLE:
if (title) {
title.innerHTML = "Add Circle";
}
fetch("/static/pointModal.html")
.then(r=>r.text())
.then(t=> {
const content = _this.content();
if (content) { content.innerHTML = t; }
const radiusContainer = _this.radiusContainer();
if (radiusContainer) {
radiusContainer.style.display = "block";
}
const submitBtn = _this.submitBtn();
if (submitBtn) {
submitBtn.onclick = () => {
const name = _this.nameField();
const desc = _this.descField();
const radius = _this.radiusField();
const circle = new Circle(name, desc, args.latlng, {radius: Number(radius)});
circle.add(args.map);
args.overlays.circles.push(circle);
_this.setVisible(false);
}
}
});
break; break;
case OverlayType.POLYGON: case OverlayType.POLYGON:
break; break;
@ -292,7 +327,16 @@ function init(): void {
const modal = new Modal(); const modal = new Modal();
const addMarkerHandler = (e: any) => { const resetMapClick = (): void => {
try {
map.off("click", addMarkerHandler);
} catch {}
try {
map.off("click", addCircleHandler);
} catch {}
}
const addMarkerHandler = (e: any): void => {
modal.setVisible(true); modal.setVisible(true);
modal.setState(OverlayType.POINT, { modal.setState(OverlayType.POINT, {
latlng: e.latlng, latlng: e.latlng,
@ -302,34 +346,49 @@ function init(): void {
map.off("click", addMarkerHandler); map.off("click", addMarkerHandler);
} }
const addCircleHandler = (e: any): void => {
modal.setVisible(true);
modal.setState(OverlayType.CIRCLE, {
latlng: e.latlng,
map: map,
overlays: overlays,
});
map.off("click", addCircleHandler);
}
const addMarkerBtn = document.getElementById("addPoint-btn"); const addMarkerBtn = document.getElementById("addPoint-btn");
if (addMarkerBtn) { if (addMarkerBtn) {
addMarkerBtn.onclick = (e: any)=>{ addMarkerBtn.onclick = (e: any): void => {
try{ resetMapClick()
map.off("click", addMarkerHandler);
} finally {
map.on("click", addMarkerHandler); map.on("click", addMarkerHandler);
}
}; };
} }
const addCircleBtn = document.getElementById("addCircle-btn");
if (addCircleBtn) {
addCircleBtn.onclick = (e: any): void => {
resetMapClick();
map.on("click", addCircleHandler);
}
}
const saveBtn = document.getElementById("save-btn"); const saveBtn = document.getElementById("save-btn");
if (saveBtn) { if (saveBtn) {
saveBtn.onclick = (e: any) => { saveBtn.onclick = (e: any): void => {
OverlayState.save(overlays); OverlayState.save(overlays);
}; };
} }
const clearBtn = document.getElementById("clear-btn"); const clearBtn = document.getElementById("clear-btn");
if (clearBtn) { if (clearBtn) {
clearBtn.onclick = (e: any) => { clearBtn.onclick = (e: any): void => {
overlays = OverlayState.clear(overlays, map); overlays = OverlayState.clear(overlays, map);
} }
} }
const tilesBtn = document.getElementById("tiles-btn"); const tilesBtn = document.getElementById("tiles-btn");
if (tilesBtn) { if (tilesBtn) {
tilesBtn.onclick = (e: any) => { tilesBtn.onclick = (e: any): void => {
if (TileLayerWrapper.getActiveLayer() == satelliteLayer) { if (TileLayerWrapper.getActiveLayer() == satelliteLayer) {
TileLayerWrapper.enableOnly(streetLayer, map); TileLayerWrapper.enableOnly(streetLayer, map);
} else { } else {

View file

@ -65,6 +65,7 @@ var Circle = /** @class */ (function (_super) {
function Circle(name, desc, point, options) { function Circle(name, desc, point, options) {
var _this_1 = _super.call(this, name, desc, [point], options) || this; var _this_1 = _super.call(this, name, desc, [point], options) || this;
_this_1.self = L.circle(point, options); _this_1.self = L.circle(point, options);
_this_1.self.bindPopup("<h3>" + name + "</h3><p>" + desc + "</p>");
return _this_1; return _this_1;
} }
return Circle; return Circle;
@ -189,6 +190,9 @@ var Modal = /** @class */ (function () {
Modal.prototype.submitBtn = function () { Modal.prototype.submitBtn = function () {
return document.getElementById("modal-submitBtn"); return document.getElementById("modal-submitBtn");
}; };
Modal.prototype.radiusContainer = function () {
return document.getElementById("radius-container");
};
Modal.prototype.nameField = function () { Modal.prototype.nameField = function () {
var _a, _b; var _a, _b;
return (_b = (_a = document.getElementById("modal-name")) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ""; return (_b = (_a = document.getElementById("modal-name")) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : "";
@ -197,6 +201,10 @@ var Modal = /** @class */ (function () {
var _a, _b; var _a, _b;
return (_b = (_a = document.getElementById("modal-desc")) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ""; return (_b = (_a = document.getElementById("modal-desc")) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : "";
}; };
Modal.prototype.radiusField = function () {
var _a, _b;
return (_b = (_a = document.getElementById("modal-radius")) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : "";
};
Modal.prototype.visible = function () { Modal.prototype.visible = function () {
var _a; var _a;
return ((_a = this.self()) === null || _a === void 0 ? void 0 : _a.style.display) != "none"; return ((_a = this.self()) === null || _a === void 0 ? void 0 : _a.style.display) != "none";
@ -209,9 +217,9 @@ var Modal = /** @class */ (function () {
}; };
Modal.prototype.setState = function (state, args) { Modal.prototype.setState = function (state, args) {
var _this = this; var _this = this;
var title = this.title();
switch (state) { switch (state) {
case OverlayType.POINT: case OverlayType.POINT:
var title = this.title();
if (title) { if (title) {
title.innerHTML = "Add Marker"; title.innerHTML = "Add Marker";
} }
@ -236,6 +244,33 @@ var Modal = /** @class */ (function () {
}); });
break; break;
case OverlayType.CIRCLE: case OverlayType.CIRCLE:
if (title) {
title.innerHTML = "Add Circle";
}
fetch("/static/pointModal.html")
.then(function (r) { return r.text(); })
.then(function (t) {
var content = _this.content();
if (content) {
content.innerHTML = t;
}
var radiusContainer = _this.radiusContainer();
if (radiusContainer) {
radiusContainer.style.display = "block";
}
var submitBtn = _this.submitBtn();
if (submitBtn) {
submitBtn.onclick = function () {
var name = _this.nameField();
var desc = _this.descField();
var radius = _this.radiusField();
var circle = new Circle(name, desc, args.latlng, { radius: Number(radius) });
circle.add(args.map);
args.overlays.circles.push(circle);
_this.setVisible(false);
};
}
});
break; break;
case OverlayType.POLYGON: case OverlayType.POLYGON:
break; break;
@ -265,6 +300,16 @@ function init() {
overlays.circles.forEach(function (m) { return m.add(map); }); overlays.circles.forEach(function (m) { return m.add(map); });
overlays.polygons.forEach(function (m) { return m.add(map); }); overlays.polygons.forEach(function (m) { return m.add(map); });
var modal = new Modal(); var modal = new Modal();
var resetMapClick = function () {
try {
map.off("click", addMarkerHandler);
}
catch (_a) { }
try {
map.off("click", addCircleHandler);
}
catch (_b) { }
};
var addMarkerHandler = function (e) { var addMarkerHandler = function (e) {
modal.setVisible(true); modal.setVisible(true);
modal.setState(OverlayType.POINT, { modal.setState(OverlayType.POINT, {
@ -274,15 +319,27 @@ function init() {
}); });
map.off("click", addMarkerHandler); map.off("click", addMarkerHandler);
}; };
var addCircleHandler = function (e) {
modal.setVisible(true);
modal.setState(OverlayType.CIRCLE, {
latlng: e.latlng,
map: map,
overlays: overlays
});
map.off("click", addCircleHandler);
};
var addMarkerBtn = document.getElementById("addPoint-btn"); var addMarkerBtn = document.getElementById("addPoint-btn");
if (addMarkerBtn) { if (addMarkerBtn) {
addMarkerBtn.onclick = function (e) { addMarkerBtn.onclick = function (e) {
try { resetMapClick();
map.off("click", addMarkerHandler);
}
finally {
map.on("click", addMarkerHandler); map.on("click", addMarkerHandler);
};
} }
var addCircleBtn = document.getElementById("addCircle-btn");
if (addCircleBtn) {
addCircleBtn.onclick = function (e) {
resetMapClick();
map.on("click", addCircleHandler);
}; };
} }
var saveBtn = document.getElementById("save-btn"); var saveBtn = document.getElementById("save-btn");

View file

@ -1,5 +1,9 @@
<label for="modal-name">Name</label> <label for="modal-name">Name</label><br/>
<input type="text" id="modal-name"> <input type="text" id="modal-name"><br/>
<label for="modal-desc">Description</label> <label for="modal-desc">Description</label><br/>
<textarea id="modal-desc"></textarea> <textarea id="modal-desc"></textarea><br/>
<div id="radius-container">
<label for="modal-radius">Radius (meters)</label><br/>
<input type="number" step="1" id="modal-radius" value="500"><br/>
</div>
<button id="modal-submitBtn">Add</button> <button id="modal-submitBtn">Add</button>

View file

@ -47,15 +47,16 @@ body {
} }
#modal-container { #modal-container {
background: #222222; background: #000000;
color: #c9c9c9; color: #c9c9c9;
position: fixed; position: fixed;
width: 1200px; width: 100%;
max-width: 80vw; max-width: 800px;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate3d(-50%, -50%, 0); transform: translate3d(-50%, -50%, 0);
max-height: 80%; height: auto;
max-height: calc(100vh - 2.5em);
z-index: 4; z-index: 4;
text-align: left; text-align: left;
display: none; display: none;
@ -78,23 +79,24 @@ body {
display: block; display: block;
float: left; float: left;
text-transform: uppercase; text-transform: uppercase;
background: dimgray; color: white;
color: black;
} }
#modal-content input[type="text"], #modal-content textarea { #modal-content #radius-container {
display: none;
}
#modal-content input[type="text"], #modal-content textarea, #modal-content input[type="number"] {
display: block; display: block;
width: 100%; width: 100%;
font-size: 150%; font-size: 150%;
color: #c9c9c9; color: white;
background: transparent;
border: none; border: none;
border: solid 2px dimgray;
margin-bottom: 1em; margin-bottom: 1em;
font-family: sans-serif; font-family: sans-serif;
box-sizing: border-box; box-sizing: border-box;
padding: 0.5em; padding: 0.5em;
background: rgba(0, 0, 0, 0.5); background: #222222;
} }
@ -103,6 +105,12 @@ body {
height: 8em; height: 8em;
} }
#modal-content input[type="number"] {
width: 12ch;
text-align: right;
}
button.closeBtn { button.closeBtn {
float: right; float: right;
background: transparent; background: transparent;
@ -120,13 +128,48 @@ button.closeBtn:hover {
button#modal-submitBtn { button#modal-submitBtn {
font-size: 150%; font-size: 150%;
background: black; background: transparent;;
color: #c9c9c9; color: white;
border: none; border: solid 2px dimgray;
border-left: solid 2px dimgray;
border-right: solid 2px dimgray;
position: relative; position: relative;
margin-left: auto; margin-left: auto;
padding: 0.25em; padding: 0.25em;
text-transform: uppercase; text-transform: uppercase;
} }
button#modal-submitBtn:hover {
background: white;
color: black;
border: solid 2px white;
}
#cancel-container, #confirm-container {
position: fixed;
font-size: 200%;
bottom: 1.5em;
display: none;
left: 50%;
transform: translateY(-50%);
background: black;
}
#import-export-container {
position: fixed;
width: 100%;
max-width: 800px;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
height: calc(100vh - 2.5em);
max-height: 600px;
background: black;
}
#overlays-menu-container {
position: fixed;
height: calc(100vh - 2.5em);
right: 0;
width: 100%;
max-width: 800px;
background: black;
}