aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/autoComplete.ts115
-rw-r--r--src/lib/base.ts32
-rw-r--r--src/lib/button.ts29
-rw-r--r--src/lib/color.ts36
-rw-r--r--src/lib/contextMenu.ts35
-rw-r--r--src/lib/dom.ts6
-rw-r--r--src/lib/form.ts54
-rw-r--r--src/lib/h.ts31
-rw-r--r--src/lib/icons.ts66
-rw-r--r--src/lib/layout.ts15
-rw-r--r--src/lib/modal.ts28
-rw-r--r--src/main.ts3
-rw-r--r--src/map.ts131
-rw-r--r--src/marker.ts171
-rw-r--r--src/markerForm.ts116
-rw-r--r--src/serialization.ts44
-rw-r--r--src/serialization/utils.ts9
-rw-r--r--src/serialization/v0.ts122
-rw-r--r--src/state.ts65
-rw-r--r--src/types/leaflet.d.ts95
20 files changed, 0 insertions, 1203 deletions
diff --git a/src/lib/autoComplete.ts b/src/lib/autoComplete.ts
deleted file mode 100644
index b0a79eb..0000000
--- a/src/lib/autoComplete.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import { h, Children, concatClassName } from 'lib/h'
-import * as Button from 'lib/button'
-
-export function create(
- attrs: object,
- id: string,
- keys: string[],
- renderEntry: (entry: string) => Element,
- onInput: (value: string) => void
-): Element {
- const completion = h('div', {})
-
- const updateCompletion = (target: EventTarget, value: string) => {
- const entries = search(value, keys)
- mountOn(
- completion,
- renderCompletion(
- renderEntry,
- selected => {
- (target as HTMLInputElement).value = selected
- completion.remove
- removeChildren(completion)
- onInput(selected)
- },
- entries
- )
- )
- }
-
- const input = h('input',
- concatClassName(
- { ...attrs,
- id,
- autocomplete: 'off',
- onfocus: (e: Event) => {
- if (e.target !== null) {
- const target = e.target as HTMLInputElement
- updateCompletion(target, target.value)
- }
- },
- oninput: (e: Event) => {
- if (e.target !== null) {
- const target = e.target as HTMLInputElement
- updateCompletion(target, target.value)
- onInput(target.value)
- }
- }
- },
- 'g-AutoComplete__Input'
- )
- ) as HTMLInputElement
-
- input.addEventListener('blur', (e: MouseEvent) => {
- if (e.relatedTarget === null) {
- removeChildren(completion)
- }
- })
-
- return h('div',
- { className: 'g-AutoComplete' },
- input,
- completion,
- Button.raw(
- { className: 'g-AutoComplete__Clear',
- type: 'button',
- onclick: () => {
- onInput('')
- input.value = ''
- input.focus()
- }
- },
- 'x'
- )
- )
-}
-
-function renderCompletion(
- renderEntry: (entry: string) => Element,
- onSelect: (entry: string) => void,
- entries: string[]
-): Element {
- return h('div',
- { className: 'g-AutoComplete__Completion' },
- ...entries.map(c =>
- Button.raw(
- { className: 'g-AutoComplete__Entry',
- type: 'button',
- onclick: (e: Event) => {
- e.stopPropagation()
- e.preventDefault()
- onSelect(c)
- }
- },
- renderEntry(c)
- )
- )
- )
-}
-
-function search(s: string, xs: string[]): string[] {
- return xs.filter(x => x.includes(s))
-}
-
-function mountOn(base: Element, ...children: Element[]) {
- removeChildren(base)
- children.forEach(child => base.appendChild(child))
-}
-
-function removeChildren(base: Element) {
- const firstChild = base.firstChild
- if (firstChild !== null) {
- base.removeChild(firstChild)
- removeChildren(base)
- }
-}
diff --git a/src/lib/base.ts b/src/lib/base.ts
deleted file mode 100644
index 59c91cc..0000000
--- a/src/lib/base.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-export const b2: string[] =
- '01'.split('')
-
-export const b16: string[] =
- '0123456789abcdef'.split('')
-
-export const b62: string[] =
- '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
-
-export function encode(n: bigint, charset: string[]): string {
- const base = BigInt(charset.length)
-
- if (n == BigInt(0)) {
- return '0'
- } else {
- var xs = []
- while (n > BigInt(0)) {
- xs.push(charset[Number(n % base)])
- n = n / base
- }
- return xs.reverse().join('')
- }
-}
-
-export function decode(xs: string, charset: string[]): bigint {
- const base = BigInt(charset.length)
-
- return xs
- .split('')
- .reverse()
- .reduce((acc, x, i) => acc + (BigInt(charset.indexOf(x)) * (base ** BigInt(i))), BigInt(0))
-}
diff --git a/src/lib/button.ts b/src/lib/button.ts
deleted file mode 100644
index 794df35..0000000
--- a/src/lib/button.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { h, Children, concatClassName } from 'lib/h'
-
-export function raw(attrs: object, ...children: Children): Element {
- return h('button',
- concatClassName(attrs, 'g-Button__Raw'),
- ...children
- )
-}
-
-export function text(attrs: object, ...children: Children): Element {
- return h('button',
- concatClassName(attrs, 'g-Button__Text'),
- ...children
- )
-}
-
-export function action(attrs: object, ...children: Children): Element {
- return h('button',
- concatClassName(attrs, 'g-Button__Action'),
- ...children
- )
-}
-
-export function cancel(attrs: object, ...children: Children): Element {
- return h('button',
- concatClassName(attrs, 'g-Button__Cancel'),
- ...children
- )
-}
diff --git a/src/lib/color.ts b/src/lib/color.ts
deleted file mode 100644
index 59b320d..0000000
--- a/src/lib/color.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-interface Color {
- red: number,
- green: number,
- blue: number,
-}
-
-export function parse(str: string): Color {
- return {
- red: parseInt(str.slice(1,3), 16),
- green: parseInt(str.slice(3,5), 16),
- blue: parseInt(str.slice(5,7), 16),
- }
-}
-
-// https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrastratio
-export function contrastRatio(c1: Color, c2: Color): number {
- const r1 = relativeLuminance(c1)
- const r2 = relativeLuminance(c2)
-
- return r1 > r2
- ? (r1 + 0.05) / (r2 + 0.05)
- : (r2 + 0.05) / (r1 + 0.05)
-}
-
-function relativeLuminance(c: Color): number {
- return (
- 0.2126 * fromSRGB(c.red / 255)
- + 0.7152 * fromSRGB(c.green / 255)
- + 0.0722 * fromSRGB(c.blue / 255))
-}
-
-function fromSRGB(sRGB: number): number {
- return sRGB <= 0.03928
- ? sRGB / 12.92
- : Math.pow(((sRGB + 0.055) / 1.055), 2.4)
-}
diff --git a/src/lib/contextMenu.ts b/src/lib/contextMenu.ts
deleted file mode 100644
index 6edd567..0000000
--- a/src/lib/contextMenu.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import { h } from 'lib/h'
-
-interface Action {
- label: string,
- action: () => void
-}
-
-export function show(event: MouseEvent, actions: Action[]) {
- const menu = h('div',
- { id: 'g-ContextMenu',
- style: `left: ${event.pageX.toString()}px; top: ${event.pageY.toString()}px`
- },
- ...actions.map(({ label, action }) =>
- h('div',
- { className: 'g-ContextMenu__Entry',
- onclick: () => action()
- },
- label
- )
- )
- )
-
- document.body.appendChild(menu)
-
- // Remove on click or context menu
- setTimeout(() => {
- const f = () => {
- document.body.removeChild(menu)
- document.body.removeEventListener('click', f)
- document.body.removeEventListener('contextmenu', f)
- }
- document.body.addEventListener('click', f)
- document.body.addEventListener('contextmenu', f)
- }, 0)
-}
diff --git a/src/lib/dom.ts b/src/lib/dom.ts
deleted file mode 100644
index 2ab4de5..0000000
--- a/src/lib/dom.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export function replaceChildren(parent: Element, ...newChildren: Element[]) {
- while (parent.lastChild) {
- parent.removeChild(parent.lastChild)
- }
- newChildren.forEach(c => parent.appendChild(c))
-}
diff --git a/src/lib/form.ts b/src/lib/form.ts
deleted file mode 100644
index 04a2654..0000000
--- a/src/lib/form.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { h } from 'lib/h'
-import * as Layout from 'lib/layout'
-import * as Button from 'lib/button'
-
-interface InputParams {
- label: string,
- attrs: object,
-}
-
-export function input({ label, attrs }: InputParams): Element {
- return h('label',
- { className: 'g-Form__Field' },
- label,
- h('input', attrs),
- )
-}
-
-interface ColorInputParams {
- colors: string[],
- label: string,
- initValue: string,
- onInput: (value: string) => void,
-}
-
-export function colorInput({ colors, label, initValue, onInput }: ColorInputParams): Element {
- const input = h('input',
- { value: initValue,
- type: 'color',
- oninput: (e: Event) => {
- if (e.target !== null) {
- onInput((e.target as HTMLInputElement).value)
- }
- }
- }
- ) as HTMLInputElement
- return h('label',
- { className: 'g-Form__Field' },
- label,
- Layout.line(
- {},
- input,
- ...colors.map(color =>
- Button.raw({ className: 'g-Form__Color',
- style: `background-color: ${color}`,
- type: 'button',
- onclick: () => {
- input.value = color
- onInput(color)
- }
- })
- )
- )
- )
-}
diff --git a/src/lib/h.ts b/src/lib/h.ts
deleted file mode 100644
index 7e93311..0000000
--- a/src/lib/h.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-type Child = Element | Text | string | number
-
-export type Children = Child[]
-
-export function h(tagName: string, attrs: object, ...children: Children): Element {
- let elem = document.createElement(tagName)
- elem = Object.assign(elem, attrs)
- appendChildren(elem, ...children)
- return elem
-}
-
-export function s(tagName: string, attrs: object, ...children: Children): Element {
- let elem = document.createElementNS('http://www.w3.org/2000/svg', tagName)
- Object.entries(attrs).forEach(([key, value]) => elem.setAttribute(key, value))
- appendChildren(elem, ...children)
- return elem
-}
-
-function appendChildren(elem: Element, ...children: Children) {
- for (const child of children) {
- if (typeof child === 'number')
- elem.append(child.toString())
- else
- elem.append(child)
- }
-}
-
-export function concatClassName(attrs: any, className: string): object {
- const existingClassName = 'className' in attrs ? attrs['className'] : undefined
- return { ...attrs, className: `${className} ${existingClassName}` }
-}
diff --git a/src/lib/icons.ts b/src/lib/icons.ts
deleted file mode 100644
index 8db4e17..0000000
--- a/src/lib/icons.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { h, s } from 'lib/h'
-
-export function get(key: string, attrs: object = {}): Element {
- const elem = fromKey(key)
- if (elem !== undefined) {
- Object.entries(attrs).forEach(([key, value]) => {
- elem.setAttribute(key, value)
- })
- return elem
- } else {
- return h('span', {})
- }
-}
-
-// https://yqnn.github.io/svg-path-editor/
-function fromKey(key: string): Element | undefined {
- if (key == 'house') {
- return s('svg',
- { viewBox: '0 0 10 10' },
- s('g', { 'stroke': 'none' },
- s('path', { d: 'M0 4V5H1.5V10H4V7C4.4 6.5 5.6 6.5 6 7V10H8.5V5H10V4L5 0Z' })
- )
- )
- } else if (key == 'music') {
- return s('svg',
- { viewBox: '0 0 10 10' },
- s('g', { 'stroke': 'none' },
- s('ellipse', { cx: '2', cy: '8.5', rx: '2', ry: '1.5' }),
- s('ellipse', { cx: '8', cy: '7', rx: '2', ry: '1.5' }),
- s('path', { d: 'M2.5 8.5 H4 V4.5 L8.5 3 V7 H10 V0 L2.5 2.5 Z' }),
- )
- )
- } else if (key == 'shopping-cart') {
- return s('svg',
- { viewBox: '0 0 10 10' },
- s('circle', { cx: '3.3', cy: '8.5', r: '0.8' }),
- s('circle', { cx: '7.3', cy: '8.5', r: '0.8' }),
- s('path', { d: 'M.5.6C1.3.6 1.8.7 2.1 1L2.3 6H8.5', fill: 'transparent' }),
- s('path', { d: 'M2.3 1.9H9.4L8.6 4H2.4' }),
- )
- } else if (key == 'medical') {
- return s('svg',
- { viewBox: '0 0 10 10' },
- s('path', { d: 'M5 1V9M1 5H9', style: 'stroke-width: 3' }),
- )
- } else if (key == 'envelope') {
- return s('svg',
- { viewBox: '0 0 10 10' },
- s('path', { d: 'M.5 2.5H9.5V7.5H.5ZM.5 3.4 3.5 5Q5 5.8 6.6 5L9.5 3.4', style: 'fill: transparent' }),
- )
- }
-}
-
-// Good to add:
-// - loisir / cinéma / piscine
-// - école
-// - gare
-// - bus
-export function keys(): string[] {
- return [ 'house',
- 'music',
- 'shopping-cart',
- 'medical',
- 'envelope',
- ]
-}
diff --git a/src/lib/layout.ts b/src/lib/layout.ts
deleted file mode 100644
index 1e38bfd..0000000
--- a/src/lib/layout.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { h, Children, concatClassName } from 'lib/h'
-
-export function section(attrs: object, ...children: Children): Element {
- return h('div',
- concatClassName(attrs, 'g-Layout__Section'),
- ...children
- )
-}
-
-export function line(attrs: object, ...children: Children): Element {
- return h('div',
- concatClassName(attrs, 'g-Layout__Line'),
- ...children
- )
-}
diff --git a/src/lib/modal.ts b/src/lib/modal.ts
deleted file mode 100644
index 8454e1c..0000000
--- a/src/lib/modal.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { h } from 'lib/h'
-import * as Button from 'lib/button'
-
-export function show(content: Element) {
- document.body.appendChild(h('div',
- { id: 'g-Modal' },
- h('div',
- { className: 'g-Modal__Curtain',
- onclick: () => hide()
- }
- ),
- h('div',
- { className: 'g-Modal__Window' },
- Button.raw(
- { className: 'g-Modal__Close',
- onclick: () => hide()
- },
- 'x'
- ),
- content
- )
- ))
-}
-
-export function hide() {
- const modal = document.querySelector('#g-Modal')
- modal && document.body.removeChild(modal)
-}
diff --git a/src/main.ts b/src/main.ts
deleted file mode 100644
index 36b1143..0000000
--- a/src/main.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import * as Map from 'map'
-
-document.body.appendChild(Map.view())
diff --git a/src/map.ts b/src/map.ts
deleted file mode 100644
index 04a1351..0000000
--- a/src/map.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-import { h } from 'lib/h'
-import * as Button from 'lib/button'
-import * as ContextMenu from 'lib/contextMenu'
-import * as Layout from 'lib/layout'
-import * as Modal from 'lib/modal'
-import * as Marker from 'marker'
-import * as MarkerForm from 'markerForm'
-import * as State from 'state'
-import * as Serialization from 'serialization'
-const L = window.L
-
-export function view() {
- // Wait for elements to be on page before installing map
- window.setTimeout(installMap, 0)
-
- return layout()
-}
-
-const mapId: string = 'g-Map__Content'
-
-function layout(): Element {
- return h('div',
- { className: 'g-Layout__Page' },
- h('div',
- { className: 'g-Layout__Header' },
- h('a',
- { className: 'g-Layout__Home',
- href: '#'
- },
- 'Map'
- )
- )
- , h('div',
- { className: 'g-Map' },
- h('div', { id: mapId})
- )
- )
-}
-
-function installMap(): object {
-
- const map = L.map(mapId, {
- center: [51.505, -0.09],
- zoom: 2,
- attributionControl: false
- })
-
- map.addLayer(L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png'))
-
- const mapMarkers = L.featureGroup()
- map.addLayer(mapMarkers)
-
- map.addEventListener('contextmenu', e => {
- ContextMenu.show(
- e.originalEvent,
- [ { label: 'Add a marker',
- action: () => {
- const pos = e.latlng
- const lastMarker = State.last()
- Modal.show(MarkerForm.view({
- onValidate: (color: string, icon: string, name: string, radius: number) => {
- const id = State.add({ pos, color, icon, name, radius })
- Marker.add({
- id,
- pos,
- color,
- icon,
- name,
- radius,
- addToMap: marker => mapMarkers.addLayer(marker),
- removeFromMap: marker => mapMarkers.removeLayer(marker)
- })
- Modal.hide()
- },
- onCancel: () => Modal.hide(),
- color: lastMarker ? lastMarker.color : '#3F92CF',
- icon: lastMarker ? lastMarker.icon : '',
- name: '',
- radius: 0,
- }))
- }
- }
- ]
- )
- })
-
- // Init from hash
- const hash = window.location.hash.substr(1)
- const state = Serialization.decode(hash)
- State.reset(state)
- addMarkers({ map, mapMarkers, state, isInit: true })
-
- // Reload from hash
- window.addEventListener('popstate', _ => reloadFromHash(map, mapMarkers))
-
- return map
-}
-
-export function reloadFromHash(map: L.Map, mapMarkers: L.FeatureGroup) {
- const state = Serialization.decode(window.location.hash.substr(1))
- mapMarkers.clearLayers()
- addMarkers({ map, mapMarkers, state, isInit: false })
-}
-
-interface AddMarkersOptions {
- map: L.Map,
- mapMarkers: L.FeatureGroup,
- state: State.State,
- isInit: boolean,
-}
-
-function addMarkers({ map, mapMarkers, state, isInit }: AddMarkersOptions) {
- state.forEach((marker, id) => {
- const { pos, color, icon, name, radius } = marker
- Marker.add({
- id,
- pos,
- color,
- icon,
- name,
- radius,
- addToMap: marker => mapMarkers.addLayer(marker),
- removeFromMap: marker => mapMarkers.removeLayer(marker)
- })
- })
-
- // Focus
- if (state.length > 0 && (isInit || !map.getBounds().contains(mapMarkers.getBounds()))) {
- map.fitBounds(mapMarkers.getBounds(), { padding: [ 50, 50 ] })
- }
-}
diff --git a/src/marker.ts b/src/marker.ts
deleted file mode 100644
index 9f59497..0000000
--- a/src/marker.ts
+++ /dev/null
@@ -1,171 +0,0 @@
-import { h, s } from 'lib/h'
-import * as Color from 'lib/color'
-import * as ContextMenu from 'lib/contextMenu'
-import * as MarkerForm from 'markerForm'
-import * as Icons from 'lib/icons'
-import * as Modal from 'lib/modal'
-import * as State from 'state'
-const L = window.L
-
-interface CreateParams {
- id: State.Index,
- pos: L.Pos,
- color: string,
- icon: string,
- name: string,
- radius: number,
- addToMap: (layer: L.Layer | L.FeatureGroup) => void,
- removeFromMap: (layer: L.Layer | L.FeatureGroup) => void,
-}
-
-export function add({ id, pos, color, icon, name, radius, addToMap, removeFromMap }: CreateParams) {
- const marker = L.marker(pos, {
- draggable: true,
- autoPan: true,
- icon: divIcon({ icon, color, name }),
- })
-
- const circle =
- radius !== 0
- ? L.circle(pos, { radius, color, fillColor: color })
- : undefined
-
- const layer =
- circle !== undefined
- ? L.featureGroup([ marker, circle ])
- : L.featureGroup([ marker ])
-
- const onUpdate = () =>
- Modal.show(MarkerForm.view({
- onValidate: (color: string, icon: string, name: string, radius: number) => {
- removeFromMap(layer)
- add({ id, pos, color, icon, name, radius, addToMap, removeFromMap })
- State.update(id, { pos, color, icon, name, radius })
- Modal.hide()
- },
- onCancel: () => Modal.hide(),
- color,
- icon,
- name,
- radius,
- }))
-
- marker.addEventListener('contextmenu', e => {
- ContextMenu.show(
- e.originalEvent,
- [ { label: 'Modify',
- action: onUpdate,
- }
- , { label: 'Remove',
- action: () => {
- removeFromMap(layer)
- State.remove(id)
- }
- }
- ]
- )
- })
-
- marker.addEventListener('drag', e => {
- circle && circle.setLatLng(marker.getLatLng())
- })
-
- marker.addEventListener('dragend', e => {
- const pos = marker.getLatLng()
- removeFromMap(layer)
- add({ id, pos, color, icon, name, radius, addToMap, removeFromMap })
- State.update(id, { pos, color, icon, name, radius })
- })
-
- marker.addEventListener('dblclick', onUpdate)
-
- addToMap(layer)
-}
-
-interface CreateIconParams {
- icon: string,
- color: string,
- name: string,
-}
-
-function divIcon({ icon, color, name }: CreateIconParams): L.Icon {
- const c = Color.parse(color)
- const crBlack = Color.contrastRatio({ red: 0, green: 0, blue: 0 }, c)
- const crWhite = Color.contrastRatio({ red: 255, green: 255, blue: 255 }, c)
- const textCol = crBlack > crWhite ? 'black' : 'white'
- const width = 10
- const height = 15
- const stroke = 'black'
- const strokeWidth = 0.6
- // Triangle
- const t = [
- { x: width * 0.15, y: 7.46 },
- { x: width / 2, y: height },
- { x: width * 0.85, y: 7.46 }
- ]
- return L.divIcon(
- { className: ''
- , popupAnchor: [ 0, -34 ]
- , html:
- h('div',
- { className: 'g-Marker' },
- s('svg',
- { viewBox: `0 0 ${width} ${height}`,
- class: 'g-Marker__Base'
- },
- s('circle',
- { cx: width / 2,
- cy: width / 2,
- r: (width - 2 * strokeWidth) / 2,
- stroke,
- 'stroke-width': strokeWidth,
- fill: color
- }
- ),
- s('polygon',
- { points: `${t[0].x},${t[0].y} ${t[1].x},${t[1].y} ${t[2].x},${t[2].y}`,
- fill: color
- }
- ),
- s('line',
- { x1: t[0].x,
- y1: t[0].y,
- x2: t[1].x,
- y2: t[1].y,
- stroke,
- 'stroke-width': strokeWidth
- }
- ),
- s('line',
- { x1: t[1].x,
- y1: t[1].y,
- x2: t[2].x,
- y2: t[2].y,
- stroke,
- 'stroke-width': strokeWidth
- }
- ),
- ),
- Icons.get(
- icon,
- { class: 'g-Marker__Icon'
- , style: `fill: ${textCol}; stroke: ${textCol}`
- }
- ),
- h('div',
- { className: 'g-Marker__Title',
- style: `color: black; text-shadow: ${textShadow('white', 1, 1)}`
- },
- name
- )
- )
- }
- )
-}
-
-function textShadow(color: string, w: number, blurr: number): string {
- return [[-w, -w], [-w, 0], [-w, w], [0, -w], [0, w], [w, -w], [w, 0], [w, w]]
- .map(xs => `${color} ${xs[0]}px ${xs[1]}px ${blurr}px`)
- .join(', ')
-}
-
diff --git a/src/markerForm.ts b/src/markerForm.ts
deleted file mode 100644
index 54670ae..0000000
--- a/src/markerForm.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-import { h } from 'lib/h'
-import * as AutoComplete from 'lib/autoComplete'
-import * as Button from 'lib/button'
-import * as Dom from 'lib/dom'
-import * as Form from 'lib/form'
-import * as Icons from 'lib/icons'
-import * as Layout from 'lib/layout'
-import * as State from 'state'
-
-interface FormParams {
- onValidate: (color: string, icon: string, name: string, radius: number) => void,
- onCancel: () => void,
- color: string,
- icon: string,
- name: string,
- radius: number,
-}
-
-export function view({ onValidate, onCancel, color, icon, name, radius }: FormParams): Element {
- var radiusStr = radius.toString()
- const onSubmit = () => onValidate(color, icon, name, parseInt(radiusStr) || 0)
- const domIcon = h('div',
- { className: 'g-MarkerForm__Icon' },
- Icons.get(icon, { fill: 'black', stroke: 'black' })
- )
- return h('div',
- {},
- Layout.section(
- {},
- h('form',
- { className: 'g-MarkerForm',
- onsubmit: (e: Event) => {
- e.preventDefault()
- onSubmit()
- }
- },
- Layout.section(
- {},
- Form.input({
- label: 'Name',
- attrs: {
- oninput: (e: Event) => {
- if (e.target !== null) {
- name = (e.target as HTMLInputElement).value
- }
- },
- onblur: (e: Event) => {
- if (e.target !== null) {
- name = (e.target as HTMLInputElement).value.trim()
- }
- },
- value: name
- }
- }),
- Form.colorInput({
- colors: State.colors(),
- label: 'Color',
- initValue: color,
- onInput: newColor => color = newColor
- }),
- h('div',
- { className: 'g-Form__Field' },
- h('div',
- { className: 'g-Form__Label' },
- h('label', { for: 'g-MarkerForm__IconInput' }, 'Icon')
- ),
- Layout.line(
- { className: 'g-MarkerForm__AutoCompleteAndIcon' },
- AutoComplete.create(
- { value: icon,
- className: 'g-MarkerForm__AutoComplete'
- },
- 'g-MarkerForm__IconInput',
- Icons.keys().sort(),
- iconKey => h('div',
- { className: 'g-MarkerForm__IconEntry' },
- h('div', { className: 'g-MarkerForm__IconElem' }, Icons.get(iconKey, { fill: 'black', stroke: 'black' }) ),
- iconKey
- ),
- newIcon => {
- icon = newIcon
- Dom.replaceChildren(domIcon, Icons.get(icon, { fill: 'black', stroke: 'black' }))
- }),
- domIcon
- )
- ),
- Form.input({
- label: 'Radius (m)',
- attrs: { oninput: (e: Event) => {
- if (e.target !== null) {
- radiusStr = (e.target as HTMLInputElement).value
- }
- },
- value: radiusStr,
- type: 'number',
- }
- })
- ),
- ),
- Layout.line(
- {},
- Button.action({ onclick: () => onSubmit() }, 'Save'),
- Button.cancel(
- { onclick: () => onCancel(),
- type: 'button'
- },
- 'Cancel'
- )
- )
- )
- )
-}
-
-function restrictCharacters(str: string, chars: string): string {
- return str.split('').filter(c => chars.indexOf(c) != -1).join('')
-}
diff --git a/src/serialization.ts b/src/serialization.ts
deleted file mode 100644
index 4289b36..0000000
--- a/src/serialization.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import * as Base from 'lib/base'
-import * as State from 'state'
-import * as Utils from 'serialization/utils'
-import * as V0 from 'serialization/v0'
-
-// Encoding
-
-const lastVersion = 0 // max is 62
-
-export function encode(s: State.State): string {
- if (s.length == 0) {
- return ''
- } else {
- const version = Base.encode(BigInt(lastVersion), Base.b62)
- const xs = V0.encode(s).map(binaryToBase62).join('-')
- return `${version}${xs}`
- }
-}
-
-function binaryToBase62(str: string): string {
- // Prepend 1 so that we don’t loose leading 0s
- return Base.encode(Base.decode('1' + str, Base.b2), Base.b62)
-}
-
-// Decoding
-
-export function decode(encoded: string): State.State {
- if (encoded == '') {
- return []
- } else {
- const version = Number(Base.decode(encoded.slice(0, 1), Base.b62))
- if (version == 0) return V0.decode(encoded.slice(1).split('-').map(base62ToBinary))
- else {
- console.error(`Unknown decoder version ${version} in order to decode state.`)
- return []
- }
- }
-}
-
-function base62ToBinary(str: string): string {
- // Remove prepended 1
- return Base.encode(Base.decode(str, Base.b62), Base.b2).slice(1)
-}
-
diff --git a/src/serialization/utils.ts b/src/serialization/utils.ts
deleted file mode 100644
index c94f199..0000000
--- a/src/serialization/utils.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import * as Base from 'lib/base'
-
-export function encodeNumber(n: bigint, length: number): string {
- return Base.encode(n, Base.b2).padStart(length, '0')
-}
-
-export function mod(a: number, b: number): number {
- return ((a % b) + b) % b
-}
diff --git a/src/serialization/v0.ts b/src/serialization/v0.ts
deleted file mode 100644
index f90eb66..0000000
--- a/src/serialization/v0.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import * as Base from 'lib/base'
-import * as Icons from 'lib/icons'
-import * as State from 'state'
-import * as Utils from 'serialization/utils'
-
-const posPrecision: number = 5
-const latLength: number = Base.encode(BigInt(`180${'0'.repeat(posPrecision)}`), Base.b2).length
-const lngLength: number = Base.encode(BigInt(`360${'0'.repeat(posPrecision)}`), Base.b2).length
-const colorLength: number = Base.encode(Base.decode('ffffff', Base.b16), Base.b2).length
-const iconLength: number = 8 // At most 255 icons
-const radiusLength: number = 5
-
-// Encoding
-
-export function encode(s: State.State): string[] {
- return s.map(encodeMarker)
-}
-
-function encodeMarker({ pos, name, color, icon, radius }: State.Marker): string {
- const lat = encodeLatOrLng(pos.lat, latLength, 180) // [-90; 90]
- const lng = encodeLatOrLng(pos.lng, lngLength, 360) // [-180; 180]
- return lat + lng + encodeColor(color) + encodeIcon(icon) + encodeRadius(radius) + encodeName(name)
-}
-
-// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
-export const uriComponentBase: string[] =
- '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.!~*\'()%'.split('')
-
-function encodeLatOrLng(n: number, length: number, range: number): string {
- const [a, b] = Utils.mod(n + range / 2, range).toFixed(posPrecision).split('.')
- return Utils.encodeNumber(BigInt(a + b), length)
-}
-
-function encodeColor(color: string): string {
- return Utils.encodeNumber(Base.decode(color.slice(1).toLowerCase(), Base.b16), colorLength)
-}
-
-function encodeIcon(icon: string): string {
- return icon == ''
- ? '0'
- : `1${Utils.encodeNumber(BigInt(Icons.keys().indexOf(icon) + 1), iconLength)}`
-}
-
-function encodeRadius(radius: number): string {
- if (radius == 0) {
- return '0'
- } else {
- const binary = Base.encode(BigInt(radius), Base.b2)
- const binaryLength = Utils.encodeNumber(BigInt(binary.length), radiusLength)
- return `1${binaryLength}${binary}`
- }
-}
-
-function encodeName(str: string): string {
- return str == ''
- ? ''
- : Base.encode(Base.decode(encodeURIComponent(str), uriComponentBase), Base.b2)
-}
-
-// Decoding
-
-export function decode(encoded: string[]): State.State {
- return encoded.map(binary => {
- const [ lat, i1 ] = decodeLatOrLng(binary, 0, latLength, 180)
- const [ lng, i2 ] = decodeLatOrLng(binary, i1, lngLength, 360)
- const [ color, i3 ] = decodeColor(binary, i2)
- const [ icon, i4 ] = decodeIcon(binary, i3)
- const [ radius, i5 ] = decodeRadius(binary, i4)
- const name = decodeName(binary, i5)
-
- return {
- pos: { lat, lng },
- name,
- color,
- icon,
- radius,
- }
- })
-}
-
-function decodeLatOrLng(encoded: string, i: number, length: number, range: number): [number, number] {
- const slice = encoded.slice(i, i + length)
- const digits = Base.decode(slice, Base.b2).toString()
- const latOrLng = parseFloat(`${digits.slice(0, -posPrecision)}.${digits.slice(-posPrecision)}`) - range / 2
- return [ latOrLng, i + length ]
-}
-
-function decodeColor(encoded: string, i: number): [ string, number ] {
- const slice = encoded.slice(i, i + colorLength)
- const color = `#${Base.encode(Base.decode(slice, Base.b2), Base.b16)}`
- return [ color, i + colorLength ]
-}
-
-function decodeIcon(encoded: string, i: number): [ string, number ] {
- if (encoded.slice(i, i + 1) == '0') {
- return [ '', i + 1 ]
- } else {
- const slice = encoded.slice(i + 1, i + 1 + iconLength)
- const iconIndex = Number(Base.decode(slice, Base.b2)) - 1
- const icon = iconIndex < 0 ? '' : Icons.keys()[iconIndex]
- return [ icon, i + 1 + iconLength ]
- }
-}
-
-function decodeRadius(encoded: string, i: number): [ number, number ] {
- if (encoded.slice(i, i + 1) == '0') {
- return [ 0, i + 1 ]
- } else {
- const binaryLength = encoded.slice(i + 1, i + 1 + radiusLength)
- const length = Number(Base.decode(binaryLength, Base.b2))
- const binary = encoded.slice(i + 1 + radiusLength, i + 1 + radiusLength + length)
- const radius = Number(Base.decode(binary, Base.b2))
- return [ radius, i + 1 + radiusLength + length ]
- }
-}
-
-function decodeName(encoded: string, i: number): string {
- const slice = encoded.slice(i)
- return slice == ''
- ? ''
- : decodeURIComponent(Base.encode(Base.decode(slice, Base.b2), uriComponentBase))
-}
diff --git a/src/state.ts b/src/state.ts
deleted file mode 100644
index 634319a..0000000
--- a/src/state.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import * as Serialization from 'serialization'
-
-const L = window.L
-
-// State
-
-var nextIndex: Index = 0
-
-export type State = Marker[]
-export type Index = number
-
-var state: State = []
-
-export interface Marker {
- pos: L.Pos,
- name: string,
- color: string,
- icon: string,
- radius: number,
-}
-
-export function reset(s: State) {
- state = s
- nextIndex = s.length
-}
-
-// CRUD
-
-export function add(marker: Marker): Index {
- const index = nextIndex
- state[index] = marker
- nextIndex += 1
- pushState()
- return index
-}
-
-export function update(index: Index, marker: Marker) {
- state[index] = marker
- pushState()
-}
-
-export function remove(index: Index) {
- delete state[index]
- pushState()
-}
-
-// History
-
-function pushState() {
- const encoded = Serialization.encode(Object.values(state))
- history.pushState('', '', `#${encoded}`)
-}
-
-// Inspection
-
-export function colors() {
- return [...new Set(Object.values(state).map(({ color }) => color))]
-}
-
-export function last(): Marker | undefined {
- const nonempty = Object.values(state)
- return nonempty.length > 0
- ? nonempty.slice(-1)[0]
- : undefined
-}
diff --git a/src/types/leaflet.d.ts b/src/types/leaflet.d.ts
deleted file mode 100644
index c1eef16..0000000
--- a/src/types/leaflet.d.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-export as namespace L
-
-// Map
-
-export function map(element: string, options?: MapOptions): Map
-
-export interface MapOptions {
- center: number[],
- zoom: number,
- attributionControl: boolean,
-}
-
-export interface Map {
- addLayer: (layer: Layer | FeatureGroup) => void,
- removeLayer: (layer: Layer | FeatureGroup) => void,
- addEventListener: (name: string, fn: (e: MapEvent) => void) => void,
- getBounds: () => LatLngBounds,
- fitBounds: (bounds: LatLngBounds, options: { padding: [number, number] } | undefined) => void,
-}
-
-// LatLngBounds
-
-export interface LatLngBounds {
- contains: (otherBounds: LatLngBounds) => boolean,
-}
-
-// Feature group
-
-export interface FeatureGroup {
- clearLayers: () => void,
- addLayer: (layer: Layer | FeatureGroup) => void,
- removeLayer: (layer: Layer | FeatureGroup) => void,
- getBounds: () => LatLngBounds,
-}
-
-export function featureGroup(xs?: Layer[]): L.FeatureGroup
-
-// Layer
-
-export interface Layer {
- addEventListener: (name: string, fn: (e: MapEvent) => void) => void,
- getLatLng: () => Pos,
- setLatLng: (pos: Pos) => void,
-}
-
-export function tileLayer(url: string): Layer
-
-// Marker
-
-export function marker(
- pos: Pos,
- options: {
- draggable: boolean,
- autoPan: boolean,
- icon: Icon,
- }
-): Layer
-
-// Circle
-
-export function circle(
- pos: Pos,
- options: {
- radius: number,
- color: string,
- fillColor: string,
- },
-): Layer
-
-// Icon
-
-export interface Icon {}
-
-export function divIcon(
- params: {
- className: string,
- popupAnchor: number[],
- html: Element,
- }
-): Icon
-
-// Pos
-
-export interface Pos {
- lat: number,
- lng: number,
-}
-
-// MapEvent
-
-interface MapEvent {
- originalEvent: MouseEvent,
- latlng: {lat: number, lng: number},
-}
-