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,
CIRCLE = 1,
POLYGON = 2,
POLYLINE = 3,
}
interface OverlayMap {
[listName: string]: OverlayType;
}
interface Overlay {
@ -46,12 +51,49 @@ abstract class OverlayBase implements Overlay {
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 remove(map: L.Map): void;
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) {
const list = document.getElementById(listName);
if (list) {
@ -61,7 +103,7 @@ abstract class OverlayBase implements Overlay {
a.innerText = self.name;
a.href = "#";
a.onclick = (e: any) => {
//show EditOverlayModal with this overlay's data
MapHandler.editOverlay(self, OverlayBase.overlayTypeMap[OverlayBase.classSanitize(listName)]);
};
li.appendChild(a);
list.appendChild(li);
@ -129,6 +171,10 @@ class Polygon extends OverlayBase {
this.self = L.polygon(points, options);
this.self.bindPopup(`<h3>${name}</h3><p>${desc}</p>`);
}
center(): Point {
return this.self.getCenter();
}
add(map: L.Map) {
this.self.addTo(map);
@ -163,6 +209,10 @@ class Polyline extends OverlayBase {
return this.self.getLatLngs().length;
}
center(): Point {
return this.self.getCenter();
}
add(map: L.Map) {
this.self.addTo(map);
}
@ -229,6 +279,7 @@ class OverlayState {
private static fromData(data: OverlayData): OverlayBase {
switch(data.type) {
case OverlayType.POINT:
default:
return new Marker(data.name, data.desc, data.points[0], data.options);
case OverlayType.CIRCLE:
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");
}
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 {
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 {
const name = document.getElementById("createOverlay-name") as HTMLInputElement;
const desc = document.getElementById("createOverlay-desc") as HTMLInputElement;
@ -80,15 +120,73 @@ class CreateOverlayModal implements Modal {
const submitBtn = _this.submitBtn();
_this.clearInputs();
if (radiusContainer) {
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) {
case OverlayType.POINT:
if (title) {
title.innerHTML = "Add Marker";
title.innerHTML = "Add Marker ";
}
if (submitBtn) {
@ -107,7 +205,7 @@ class CreateOverlayModal implements Modal {
break;
case OverlayType.CIRCLE:
if (title) {
title.innerHTML = "Add Circle";
title.innerHTML = "Add Circle ";
}
if (submitBtn) {
submitBtn.onclick = () => {
@ -126,7 +224,7 @@ class CreateOverlayModal implements Modal {
break;
case OverlayType.POLYGON:
if (title) {
title.innerHTML = "Add Polygon";
title.innerHTML = "Add Polygon ";
}
if (submitBtn) {
submitBtn.onclick = () => {
@ -143,5 +241,9 @@ class CreateOverlayModal implements Modal {
}
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 {
const self = MapHandler.instance;
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
fi
if [ -e ${progname}.ts ]; then
rm ${progname}.ts
fi
# build the source map and concatenate the source
srcmap=$(mktemp)
for f in *.ts; do
@ -25,10 +29,14 @@ errorOut=$(mktemp)
# 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}
# if sourcemapper panics you can uncomment this
# cat ${errorOut}
# 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
rm ${errorOut}
rm ${progname}.ts
rm ${progname}.ts
rm ${srcmap}

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='viewport' content='width=device-width,initial-scale=1'>
<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>
</head>
<body>
@ -45,8 +47,12 @@
<label for="createOverlay-radius">Radius (meters)</label><br/>
<input type="number" step="1" id="createOverlay-radius" value="500" required><br/>
</div>
<button type="submit" id="createOverlay-submitBtn">OK</button>
<div class="multiBtn-container" id="edit-extra-btns">
<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>
</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["CIRCLE"] = 1] = "CIRCLE";
OverlayType[OverlayType["POLYGON"] = 2] = "POLYGON";
OverlayType[OverlayType["POLYLINE"] = 3] = "POLYLINE";
})(OverlayType || (OverlayType = {}));
class OverlayData {
constructor(type, name, desc, points, options) {
@ -27,6 +28,34 @@ class OverlayBase {
this.points = points;
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) {
const list = document.getElementById(listName);
if (list) {
@ -36,7 +65,7 @@ class OverlayBase {
a.innerText = self.name;
a.href = "#";
a.onclick = (e) => {
//show EditOverlayModal with this overlay's data
MapHandler.editOverlay(self, OverlayBase.overlayTypeMap[OverlayBase.classSanitize(listName)]);
};
li.appendChild(a);
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 {
constructor(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.bindPopup(`<h3>${name}</h3><p>${desc}</p>`);
}
center() {
return this.self.getCenter();
}
add(map) {
this.self.addTo(map);
OverlayBase.listAdd(this, "polygons-list");
@ -116,6 +153,9 @@ class Polyline extends OverlayBase {
numPoints() {
return this.self.getLatLngs().length;
}
center() {
return this.self.getCenter();
}
add(map) {
this.self.addTo(map);
}
@ -171,6 +211,7 @@ class OverlayState {
static fromData(data) {
switch (data.type) {
case OverlayType.POINT:
default:
return new Marker(data.name, data.desc, data.points[0], data.options);
case OverlayType.CIRCLE:
return new Circle(data.name, data.desc, data.points[0], data.options);
@ -255,6 +296,24 @@ class CreateOverlayModal {
radiusContainer() {
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() {
var _a, _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";
}
}
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() {
const name = document.getElementById("createOverlay-name");
const desc = document.getElementById("createOverlay-desc");
@ -300,62 +374,119 @@ class CreateOverlayModal {
if (radiusContainer) {
radiusContainer.style.display = state == OverlayType.CIRCLE ? "block" : "none";
}
switch (state) {
case OverlayType.POINT:
if (title) {
title.innerHTML = "Add Marker";
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 (submitBtn) {
submitBtn.onclick = () => {
const name = TextUtils.encodeHTML(_this.nameField());
const desc = TextUtils.encodeHTML(_this.descField());
if (name.trim().length < 1) {
return;
}
const point = new Marker(name, desc, args.latlng, { title: name, alt: name });
point.add(args.map);
args.overlays.markers.push(point);
_this.setVisible(false);
};
}
break;
case OverlayType.CIRCLE:
if (title) {
title.innerHTML = "Add Circle";
}
if (submitBtn) {
submitBtn.onclick = () => {
const radius = _this.radiusField();
const name = TextUtils.encodeHTML(_this.nameField());
const desc = TextUtils.encodeHTML(_this.descField());
if (name.trim().length < 1) {
return;
}
const circle = new Circle(name, desc, args.latlng, { radius: Number(radius) || 500 });
circle.add(args.map);
args.overlays.circles.push(circle);
_this.setVisible(false);
};
}
break;
case OverlayType.POLYGON:
if (title) {
title.innerHTML = "Add Polygon";
}
if (submitBtn) {
submitBtn.onclick = () => {
const name = TextUtils.encodeHTML(_this.nameField());
const desc = TextUtils.encodeHTML(_this.descField());
if (name.trim().length < 1) {
return;
}
const polygon = new Polygon(name, desc, args.points, {});
polygon.add(args.map);
args.overlays.polygons.push(polygon);
_this.setVisible(false);
};
}
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) {
case OverlayType.POINT:
if (title) {
title.innerHTML = "Add Marker ";
}
if (submitBtn) {
submitBtn.onclick = () => {
const name = TextUtils.encodeHTML(_this.nameField());
const desc = TextUtils.encodeHTML(_this.descField());
if (name.trim().length < 1) {
return;
}
const point = new Marker(name, desc, args.latlng, { title: name, alt: name });
point.add(args.map);
args.overlays.markers.push(point);
_this.setVisible(false);
};
}
break;
case OverlayType.CIRCLE:
if (title) {
title.innerHTML = "Add Circle ";
}
if (submitBtn) {
submitBtn.onclick = () => {
const radius = _this.radiusField();
const name = TextUtils.encodeHTML(_this.nameField());
const desc = TextUtils.encodeHTML(_this.descField());
if (name.trim().length < 1) {
return;
}
const circle = new Circle(name, desc, args.latlng, { radius: Number(radius) || 500 });
circle.add(args.map);
args.overlays.circles.push(circle);
_this.setVisible(false);
};
}
break;
case OverlayType.POLYGON:
if (title) {
title.innerHTML = "Add Polygon ";
}
if (submitBtn) {
submitBtn.onclick = () => {
const name = TextUtils.encodeHTML(_this.nameField());
const desc = TextUtils.encodeHTML(_this.descField());
if (name.trim().length < 1) {
return;
}
const polygon = new Polygon(name, desc, args.points, {});
polygon.add(args.map);
args.overlays.polygons.push(polygon);
_this.setVisible(false);
};
}
break;
}
}
if (title) {
title.innerHTML += OverlayBase.centerAsString(args.self ? args.self.center() : (state === OverlayType.POLYGON ? args.overlays.polyline.center() : args.latlng));
}
}
}
@ -569,6 +700,19 @@ class MapHandler {
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) {
const self = MapHandler.instance;
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;
function init() {

View file

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