add web manifest, favicon; implement edit modal - just need import/export

This commit is contained in:
Iris Lightshard 2022-08-19 22:38:55 -06:00
parent 7ef255c556
commit 30766accd6
Signed by: Iris Lightshard
GPG key ID: 3B7FBC22144E6398
11 changed files with 479 additions and 74 deletions

View file

@ -7,6 +7,11 @@ enum OverlayType {
POINT = 0, POINT = 0,
CIRCLE = 1, CIRCLE = 1,
POLYGON = 2, POLYGON = 2,
POLYLINE = 3,
}
interface OverlayMap {
[listName: string]: OverlayType;
} }
interface Overlay { interface Overlay {
@ -46,12 +51,49 @@ abstract class OverlayBase implements Overlay {
this.options = options; this.options = options;
} }
center(): Point {
return this.points[0];
}
static centerAsString(pt: Point): string {
let eastWest = "";
let northSouth = "";
const lat = pt.lat;
const long = pt.lng;
if (lat > 0) {
northSouth = "N";
} else if (lat < 0) {
northSouth = "S";
}
if (long > 0) {
eastWest = "E";
} else if (long < 0) {
eastWest = "W";
}
return `<span class="tiny">${String(long).substring(0, 7)}&deg;${eastWest}, ${String(lat).substring(0,7)}&deg;${northSouth}</span>`;
}
setPopupContent(content: string): void {
this.self.bindPopup(content);
}
abstract add(map: L.Map): void; abstract add(map: L.Map): void;
abstract remove(map: L.Map): void; abstract remove(map: L.Map): void;
abstract menuItem: Node | null; abstract menuItem: Node | null;
private static classSanitize(input: string): string {
return input.replace(/\-/g, "_");
}
private static overlayTypeMap: OverlayMap = {
markers_list: OverlayType.POINT,
circles_list: OverlayType.CIRCLE,
polygons_list: OverlayType.POLYGON,
}
static listAdd(self: OverlayBase, listName: string) { static listAdd(self: OverlayBase, listName: string) {
const list = document.getElementById(listName); const list = document.getElementById(listName);
if (list) { if (list) {
@ -61,7 +103,7 @@ abstract class OverlayBase implements Overlay {
a.innerText = self.name; a.innerText = self.name;
a.href = "#"; a.href = "#";
a.onclick = (e: any) => { a.onclick = (e: any) => {
//show EditOverlayModal with this overlay's data MapHandler.editOverlay(self, OverlayBase.overlayTypeMap[OverlayBase.classSanitize(listName)]);
}; };
li.appendChild(a); li.appendChild(a);
list.appendChild(li); list.appendChild(li);
@ -130,6 +172,10 @@ class Polygon extends OverlayBase {
this.self.bindPopup(`<h3>${name}</h3><p>${desc}</p>`); this.self.bindPopup(`<h3>${name}</h3><p>${desc}</p>`);
} }
center(): Point {
return this.self.getCenter();
}
add(map: L.Map) { add(map: L.Map) {
this.self.addTo(map); this.self.addTo(map);
OverlayBase.listAdd(this, "polygons-list"); OverlayBase.listAdd(this, "polygons-list");
@ -163,6 +209,10 @@ class Polyline extends OverlayBase {
return this.self.getLatLngs().length; return this.self.getLatLngs().length;
} }
center(): Point {
return this.self.getCenter();
}
add(map: L.Map) { add(map: L.Map) {
this.self.addTo(map); this.self.addTo(map);
} }
@ -229,6 +279,7 @@ class OverlayState {
private static fromData(data: OverlayData): OverlayBase { private static fromData(data: OverlayData): OverlayBase {
switch(data.type) { switch(data.type) {
case OverlayType.POINT: case OverlayType.POINT:
default:
return new Marker(data.name, data.desc, data.points[0], data.options); return new Marker(data.name, data.desc, data.points[0], data.options);
case OverlayType.CIRCLE: case OverlayType.CIRCLE:
return new Circle(data.name, data.desc, data.points[0], data.options); return new Circle(data.name, data.desc, data.points[0], data.options);

View file

@ -34,6 +34,27 @@ class CreateOverlayModal implements Modal {
return document.getElementById("radius-container"); return document.getElementById("radius-container");
} }
setName(name: string): void {
const self = document.getElementById("createOverlay-name") as HTMLInputElement;
if (self) {
self.value = name;
}
}
setDesc(desc: string): void {
const self = document.getElementById("createOverlay-desc") as HTMLInputElement;
if (self) {
self.value = desc;
}
}
setRadius(radius: Number) {
const self = document.getElementById("createOverlay-radius") as HTMLInputElement;
if (self) {
self.value = String(radius);
}
}
nameField(): string { nameField(): string {
return (document.getElementById("createOverlay-name") as HTMLInputElement)?.value ?? ""; return (document.getElementById("createOverlay-name") as HTMLInputElement)?.value ?? "";
} }
@ -57,6 +78,25 @@ class CreateOverlayModal implements Modal {
} }
} }
setExtraButtonsVisible(v: boolean): void {
const extraBtns = document.getElementById("edit-extra-btns");
if (extraBtns) {
extraBtns.style.display = v ? "inline" : "none";
}
}
gotoBtn(): HTMLElement | null {
return document.getElementById("goto-btn");
}
exportBtn(): HTMLElement | null {
return document.getElementById("export-btn");
}
deleteBtn(): HTMLElement | null {
return document.getElementById("delete-btn");
}
clearInputs(): void { clearInputs(): void {
const name = document.getElementById("createOverlay-name") as HTMLInputElement; const name = document.getElementById("createOverlay-name") as HTMLInputElement;
const desc = document.getElementById("createOverlay-desc") as HTMLInputElement; const desc = document.getElementById("createOverlay-desc") as HTMLInputElement;
@ -80,10 +120,68 @@ class CreateOverlayModal implements Modal {
const submitBtn = _this.submitBtn(); const submitBtn = _this.submitBtn();
_this.clearInputs(); _this.clearInputs();
if (radiusContainer) { if (radiusContainer) {
radiusContainer.style.display = state == OverlayType.CIRCLE ? "block" : "none"; radiusContainer.style.display = state == OverlayType.CIRCLE ? "block" : "none";
} }
const editing = args.self ? true : false;
this.setExtraButtonsVisible(editing);
if (editing) {
const gotoBtn = this.gotoBtn();
const exportBtn = this.exportBtn();
const deleteBtn = this.deleteBtn();
if (title) {
switch (state) {
case OverlayType.POINT:
title.innerHTML = "Edit Marker ";
break;
case OverlayType.CIRCLE:
title.innerHTML = "Edit Circle ";
break;
case OverlayType.POLYGON:
title.innerHTML = "Edit Polygon ";
break;
}
}
if (gotoBtn) {
gotoBtn.onclick = () => {
_this.setVisible(false);
args.map.setView(args.self.center());
}
}
if (exportBtn) {
// show export window with this Overlay's OverlayData
}
if (deleteBtn) {
deleteBtn.onclick = () => {
MapHandler.confirmDelete(args.self as OverlayBase);
}
}
this.setName(args.self.name);
this.setDesc(args.self.desc);
if (state == OverlayType.CIRCLE) {
this.setRadius(args.self.options.radius);
}
if (submitBtn) {
submitBtn.onclick = () => {
const name = TextUtils.encodeHTML(_this.nameField());
const desc = TextUtils.encodeHTML(_this.descField());
if (name.trim().length < 1) {
return;
}
args.self.name = name;
args.self.desc = desc;
args.self.setPopupContent(`<h3>${name}</h3><p>${desc}</p>`);
_this.setVisible(false);
}
}
} else {
switch (state) { switch (state) {
case OverlayType.POINT: case OverlayType.POINT:
@ -144,4 +242,8 @@ class CreateOverlayModal implements Modal {
break; break;
} }
} }
if (title) {
title.innerHTML += OverlayBase.centerAsString(args.self ? args.self.center() : (state === OverlayType.POLYGON ? args.overlays.polyline.center() : args.latlng));
}
}
} }

View file

@ -92,6 +92,20 @@ class MapHandler {
} }
} }
static editOverlay(overlay: OverlayBase, type: OverlayType): void {
const self = MapHandler.instance;
if (self) {
self.modals.closeAll();
self.modals.createOverlay.setState(type, {
self: overlay,
map: self.map,
overlays: self.overlays,
});
MapHandler.resetMapClick();
self.modals.createOverlay.setVisible(true);
}
}
static addCircle(e: any): void { static addCircle(e: any): void {
const self = MapHandler.instance; const self = MapHandler.instance;
if (self) { if (self) {
@ -362,4 +376,28 @@ class MapHandler {
} }
} }
} }
static confirmDelete(overlay: OverlayBase): void {
const self = MapHandler.instance;
if (self) {
self.modals.closeAll();
self.modals.okCancel.setMsg(`Delete "${overlay.name}"?`);
const okBtn = self.modals.okCancel.okBtn();
if (okBtn) {
okBtn.onclick = () => {
self.modals.closeAll();
overlay.remove(self.map);
self.modals.info.setMsg(`"${overlay.name}" deleted`);
self.modals.info.setVisible(true);
}
}
const cancelBtn = self.modals.okCancel.cancelBtn();
if (cancelBtn) {
cancelBtn.onclick = () => {
self.modals.closeAll();
}
}
self.modals.okCancel.setVisible(true);
}
}
} }

View file

@ -8,6 +8,10 @@ if [ ! -z "$1" ]; then
progname=$1 progname=$1
fi fi
if [ -e ${progname}.ts ]; then
rm ${progname}.ts
fi
# build the source map and concatenate the source # build the source map and concatenate the source
srcmap=$(mktemp) srcmap=$(mktemp)
for f in *.ts; do for f in *.ts; do
@ -25,9 +29,13 @@ errorOut=$(mktemp)
# compile and write output to temporary file # compile and write output to temporary file
tsc --strict --target ES2015 --outFile ../static/${progname}.js ${progname}.ts | sed -e s/\(/:/ -e s/,/:/ -e s/\):// | nobs >> ${errorOut} tsc --strict --target ES2015 --outFile ../static/${progname}.js ${progname}.ts | sed -e s/\(/:/ -e s/,/:/ -e s/\):// | nobs >> ${errorOut}
# if sourcemapper panics you can uncomment this
# cat ${errorOut}
# translate lines into original source with the source map and output to stdout # translate lines into original source with the source map and output to stdout
../buildtools/sourcemapper ${errorOut} ${srcmap} ../buildtools/sourcemapper ${errorOut} ${srcmap}
# delete the temporary files # delete the temporary files
rm ${errorOut} rm ${errorOut}
rm ${progname}.ts rm ${progname}.ts

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
static/favicon_192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
static/favicon_96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -5,7 +5,9 @@
<meta name='description' content='map annotation tool'/> <meta name='description' content='map annotation tool'/>
<meta name='viewport' content='width=device-width,initial-scale=1'> <meta name='viewport' content='width=device-width,initial-scale=1'>
<link rel='stylesheet' type='text/css' href='./style.css'> <link rel='stylesheet' type='text/css' href='./style.css'>
<!--<link rel='shortcut icon' href='/img/favicon.png'>--> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<link rel='shortcut icon' href='/favicon.png'>
<title>ONYX/scry</title> <title>ONYX/scry</title>
</head> </head>
<body> <body>
@ -45,8 +47,12 @@
<label for="createOverlay-radius">Radius (meters)</label><br/> <label for="createOverlay-radius">Radius (meters)</label><br/>
<input type="number" step="1" id="createOverlay-radius" value="500" required><br/> <input type="number" step="1" id="createOverlay-radius" value="500" required><br/>
</div> </div>
<div class="multiBtn-container" id="edit-extra-btns">
<button type="submit" id="createOverlay-submitBtn">OK</button> <button id="goto-btn">Go Here</button>
<button id="export-btn">Export</button>
<button id="delete-btn">Delete</button>
</div>
<button type="submit" class="positive-btn" id="createOverlay-submitBtn">OK</button>
</form> </form>
</div> </div>

14
static/manifest.json Normal file
View file

@ -0,0 +1,14 @@
{
"short_name": "ONYX/scry",
"name": "ONYX/scru map annotation tool",
"icons": [
{
"src": "/favicon_192.png",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "/index.html",
"display": "standalone",
"orientation": "auto"
}

View file

@ -10,6 +10,7 @@ var OverlayType;
OverlayType[OverlayType["POINT"] = 0] = "POINT"; OverlayType[OverlayType["POINT"] = 0] = "POINT";
OverlayType[OverlayType["CIRCLE"] = 1] = "CIRCLE"; OverlayType[OverlayType["CIRCLE"] = 1] = "CIRCLE";
OverlayType[OverlayType["POLYGON"] = 2] = "POLYGON"; OverlayType[OverlayType["POLYGON"] = 2] = "POLYGON";
OverlayType[OverlayType["POLYLINE"] = 3] = "POLYLINE";
})(OverlayType || (OverlayType = {})); })(OverlayType || (OverlayType = {}));
class OverlayData { class OverlayData {
constructor(type, name, desc, points, options) { constructor(type, name, desc, points, options) {
@ -27,6 +28,34 @@ class OverlayBase {
this.points = points; this.points = points;
this.options = options; this.options = options;
} }
center() {
return this.points[0];
}
static centerAsString(pt) {
let eastWest = "";
let northSouth = "";
const lat = pt.lat;
const long = pt.lng;
if (lat > 0) {
northSouth = "N";
}
else if (lat < 0) {
northSouth = "S";
}
if (long > 0) {
eastWest = "E";
}
else if (long < 0) {
eastWest = "W";
}
return `<span class="tiny">${String(long).substring(0, 7)}&deg;${eastWest}, ${String(lat).substring(0, 7)}&deg;${northSouth}</span>`;
}
setPopupContent(content) {
this.self.bindPopup(content);
}
static classSanitize(input) {
return input.replace(/\-/g, "_");
}
static listAdd(self, listName) { static listAdd(self, listName) {
const list = document.getElementById(listName); const list = document.getElementById(listName);
if (list) { if (list) {
@ -36,7 +65,7 @@ class OverlayBase {
a.innerText = self.name; a.innerText = self.name;
a.href = "#"; a.href = "#";
a.onclick = (e) => { a.onclick = (e) => {
//show EditOverlayModal with this overlay's data MapHandler.editOverlay(self, OverlayBase.overlayTypeMap[OverlayBase.classSanitize(listName)]);
}; };
li.appendChild(a); li.appendChild(a);
list.appendChild(li); list.appendChild(li);
@ -51,6 +80,11 @@ class OverlayBase {
} }
} }
} }
OverlayBase.overlayTypeMap = {
markers_list: OverlayType.POINT,
circles_list: OverlayType.CIRCLE,
polygons_list: OverlayType.POLYGON,
};
class Marker extends OverlayBase { class Marker extends OverlayBase {
constructor(name, desc, point, options) { constructor(name, desc, point, options) {
super(name, desc, [point], options); super(name, desc, [point], options);
@ -90,6 +124,9 @@ class Polygon extends OverlayBase {
this.self = L.polygon(points, options); this.self = L.polygon(points, options);
this.self.bindPopup(`<h3>${name}</h3><p>${desc}</p>`); this.self.bindPopup(`<h3>${name}</h3><p>${desc}</p>`);
} }
center() {
return this.self.getCenter();
}
add(map) { add(map) {
this.self.addTo(map); this.self.addTo(map);
OverlayBase.listAdd(this, "polygons-list"); OverlayBase.listAdd(this, "polygons-list");
@ -116,6 +153,9 @@ class Polyline extends OverlayBase {
numPoints() { numPoints() {
return this.self.getLatLngs().length; return this.self.getLatLngs().length;
} }
center() {
return this.self.getCenter();
}
add(map) { add(map) {
this.self.addTo(map); this.self.addTo(map);
} }
@ -171,6 +211,7 @@ class OverlayState {
static fromData(data) { static fromData(data) {
switch (data.type) { switch (data.type) {
case OverlayType.POINT: case OverlayType.POINT:
default:
return new Marker(data.name, data.desc, data.points[0], data.options); return new Marker(data.name, data.desc, data.points[0], data.options);
case OverlayType.CIRCLE: case OverlayType.CIRCLE:
return new Circle(data.name, data.desc, data.points[0], data.options); return new Circle(data.name, data.desc, data.points[0], data.options);
@ -255,6 +296,24 @@ class CreateOverlayModal {
radiusContainer() { radiusContainer() {
return document.getElementById("radius-container"); return document.getElementById("radius-container");
} }
setName(name) {
const self = document.getElementById("createOverlay-name");
if (self) {
self.value = name;
}
}
setDesc(desc) {
const self = document.getElementById("createOverlay-desc");
if (self) {
self.value = desc;
}
}
setRadius(radius) {
const self = document.getElementById("createOverlay-radius");
if (self) {
self.value = String(radius);
}
}
nameField() { nameField() {
var _a, _b; var _a, _b;
return (_b = (_a = document.getElementById("createOverlay-name")) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : ""; return (_b = (_a = document.getElementById("createOverlay-name")) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : "";
@ -277,6 +336,21 @@ class CreateOverlayModal {
modal.style.display = v ? "block" : "none"; modal.style.display = v ? "block" : "none";
} }
} }
setExtraButtonsVisible(v) {
const extraBtns = document.getElementById("edit-extra-btns");
if (extraBtns) {
extraBtns.style.display = v ? "inline" : "none";
}
}
gotoBtn() {
return document.getElementById("goto-btn");
}
exportBtn() {
return document.getElementById("export-btn");
}
deleteBtn() {
return document.getElementById("delete-btn");
}
clearInputs() { clearInputs() {
const name = document.getElementById("createOverlay-name"); const name = document.getElementById("createOverlay-name");
const desc = document.getElementById("createOverlay-desc"); const desc = document.getElementById("createOverlay-desc");
@ -300,6 +374,59 @@ class CreateOverlayModal {
if (radiusContainer) { if (radiusContainer) {
radiusContainer.style.display = state == OverlayType.CIRCLE ? "block" : "none"; radiusContainer.style.display = state == OverlayType.CIRCLE ? "block" : "none";
} }
const editing = args.self ? true : false;
this.setExtraButtonsVisible(editing);
if (editing) {
const gotoBtn = this.gotoBtn();
const exportBtn = this.exportBtn();
const deleteBtn = this.deleteBtn();
if (title) {
switch (state) {
case OverlayType.POINT:
title.innerHTML = "Edit Marker ";
break;
case OverlayType.CIRCLE:
title.innerHTML = "Edit Circle ";
break;
case OverlayType.POLYGON:
title.innerHTML = "Edit Polygon ";
break;
}
}
if (gotoBtn) {
gotoBtn.onclick = () => {
_this.setVisible(false);
args.map.setView(args.self.center());
};
}
if (exportBtn) {
// show export window with this Overlay's OverlayData
}
if (deleteBtn) {
deleteBtn.onclick = () => {
MapHandler.confirmDelete(args.self);
};
}
this.setName(args.self.name);
this.setDesc(args.self.desc);
if (state == OverlayType.CIRCLE) {
this.setRadius(args.self.options.radius);
}
if (submitBtn) {
submitBtn.onclick = () => {
const name = TextUtils.encodeHTML(_this.nameField());
const desc = TextUtils.encodeHTML(_this.descField());
if (name.trim().length < 1) {
return;
}
args.self.name = name;
args.self.desc = desc;
args.self.setPopupContent(`<h3>${name}</h3><p>${desc}</p>`);
_this.setVisible(false);
};
}
}
else {
switch (state) { switch (state) {
case OverlayType.POINT: case OverlayType.POINT:
if (title) { if (title) {
@ -358,6 +485,10 @@ class CreateOverlayModal {
break; break;
} }
} }
if (title) {
title.innerHTML += OverlayBase.centerAsString(args.self ? args.self.center() : (state === OverlayType.POLYGON ? args.overlays.polyline.center() : args.latlng));
}
}
} }
class CancelModal { class CancelModal {
self() { self() {
@ -569,6 +700,19 @@ class MapHandler {
self.modals.createOverlay.setVisible(true); self.modals.createOverlay.setVisible(true);
} }
} }
static editOverlay(overlay, type) {
const self = MapHandler.instance;
if (self) {
self.modals.closeAll();
self.modals.createOverlay.setState(type, {
self: overlay,
map: self.map,
overlays: self.overlays,
});
MapHandler.resetMapClick();
self.modals.createOverlay.setVisible(true);
}
}
static addCircle(e) { static addCircle(e) {
const self = MapHandler.instance; const self = MapHandler.instance;
if (self) { if (self) {
@ -819,6 +963,29 @@ class MapHandler {
} }
} }
} }
static confirmDelete(overlay) {
const self = MapHandler.instance;
if (self) {
self.modals.closeAll();
self.modals.okCancel.setMsg(`Delete "${overlay.name}"?`);
const okBtn = self.modals.okCancel.okBtn();
if (okBtn) {
okBtn.onclick = () => {
self.modals.closeAll();
overlay.remove(self.map);
self.modals.info.setMsg(`"${overlay.name}" deleted`);
self.modals.info.setVisible(true);
};
}
const cancelBtn = self.modals.okCancel.cancelBtn();
if (cancelBtn) {
cancelBtn.onclick = () => {
self.modals.closeAll();
};
}
self.modals.okCancel.setVisible(true);
}
}
} }
MapHandler.instance = null; MapHandler.instance = null;
function init() { function init() {

View file

@ -16,6 +16,11 @@ body {
position: relative; position: relative;
} }
.tiny {
font-size: 33%;
vertical-align: top;
}
#noscript-container { #noscript-container {
position: absolute; position: absolute;
top: 0; top: 0;
@ -89,7 +94,7 @@ body {
#createOverlay-container { #createOverlay-container {
background: #000000; background: #000000;
color: #c9c9c9; color: white;
position: fixed; position: fixed;
width: 100%; width: 100%;
max-width: 800px; max-width: 800px;
@ -114,12 +119,16 @@ body {
#createOverlay-container h2 { #createOverlay-container h2 {
text-align: left; text-align: left;
font-size: 200%; font-size: 200%;
font-wieght: normal; font-weight: normal;
text-transform: uppercase; text-transform: uppercase;
line-height: 2em; line-height: 2em;
margin-left: 1em; margin-left: 1em;
} }
#createOverlay-container .multiBtn-container {
display: none;
}
#createOverlay-content { #createOverlay-content {
margin: 2em; margin: 2em;
text-align: right; text-align: right;
@ -136,7 +145,9 @@ body {
display: none; display: none;
} }
#createOverlay-content input[type="text"], #createOverlay-content textarea, #createOverlay-content input[type="number"] { #createOverlay-content input[type="text"],
#createOverlay-content textarea,
#createOverlay-content input[type="number"] {
display: block; display: block;
width: 100%; width: 100%;
font-size: 150%; font-size: 150%;
@ -177,6 +188,7 @@ body {
color: crimson; color: crimson;
} }
#createOverlay-container .multiBtn-container button,
#createOverlay-submitBtn, #createOverlay-submitBtn,
.positive-btn, .positive-btn,
.negative-btn, .negative-btn,
@ -193,8 +205,10 @@ body {
text-transform: uppercase; text-transform: uppercase;
} }
#createOverlay-submitBtn:hover, #createOverlay-container .multiBtn-container button:hover,
#createOverlay-submitBtn:focus, #createOverlay-container .multiBtn-container button:focus,
#createOverlay-container submitBtn:hover,
#createOverlay-container submitBtn:focus,
#set-home-btn:hover, #set-home-btn:hover,
#set-home-btn:focus, #set-home-btn:focus,
#import-btn:hover, #import-btn:hover,
@ -212,14 +226,19 @@ body {
} }
.positive-btn { .positive-btn {
border: solid 2px #1f9b92; border: solid 2px #1f9b92 !important;
float: left; float: left;
} }
.positive-btn:hover, .positive-btn:hover,
.positive-btn:focus { .positive-btn:focus {
color: black; color: black !important;
background: #1f9b92; background: #1f9b92 !important;
}
#createOverlay-submitBtn {
float: none;
font-size: 150%;
} }
.negative-btn { .negative-btn {