Show recent maps; name maps

This commit is contained in:
Christian Lawson-Perfect 2025-02-11 05:17:18 +00:00
parent 6b02b73b9b
commit e6a51f162d
6 changed files with 268 additions and 50 deletions

184
app.js
View file

@ -5160,15 +5160,19 @@ var $elm$core$Task$perform = F2(
}); });
var $elm$browser$Browser$document = _Browser_document; var $elm$browser$Browser$document = _Browser_document;
var $elm$json$Json$Decode$decodeValue = _Json_run; var $elm$json$Json$Decode$decodeValue = _Json_run;
var $author$project$App$Flags = F3( var $author$project$App$Flags = F5(
function (emoji, markers, map_id) { function (emoji, markers, map_id, map_ids, map_name) {
return {emoji: emoji, map_id: map_id, markers: markers}; return {emoji: emoji, map_id: map_id, map_ids: map_ids, map_name: map_name, markers: markers};
});
var $elm$json$Json$Decode$field = _Json_decodeField;
var $elm$json$Json$Decode$at = F2(
function (fields, decoder) {
return A3($elm$core$List$foldr, $elm$json$Json$Decode$field, decoder, fields);
}); });
var $author$project$App$Emoji = F2( var $author$project$App$Emoji = F2(
function (emoji, description) { function (emoji, description) {
return {description: description, emoji: emoji}; return {description: description, emoji: emoji};
}); });
var $elm$json$Json$Decode$field = _Json_decodeField;
var $elm$json$Json$Decode$string = _Json_decodeString; var $elm$json$Json$Decode$string = _Json_decodeString;
var $author$project$App$decode_emoji = A3( var $author$project$App$decode_emoji = A3(
$elm$json$Json$Decode$map2, $elm$json$Json$Decode$map2,
@ -5198,19 +5202,37 @@ var $author$project$App$decode_marker = A5(
A2($elm$json$Json$Decode$field, 'name', $elm$json$Json$Decode$string), A2($elm$json$Json$Decode$field, 'name', $elm$json$Json$Decode$string),
A2($elm$json$Json$Decode$field, 'note', $elm$json$Json$Decode$string)); A2($elm$json$Json$Decode$field, 'note', $elm$json$Json$Decode$string));
var $elm$json$Json$Decode$list = _Json_decodeList; var $elm$json$Json$Decode$list = _Json_decodeList;
var $elm$json$Json$Decode$map3 = _Json_map3; var $elm$json$Json$Decode$map5 = _Json_map5;
var $author$project$App$decode_flags = A4( var $author$project$App$decode_flags = A6(
$elm$json$Json$Decode$map3, $elm$json$Json$Decode$map5,
$author$project$App$Flags, $author$project$App$Flags,
A2( A2(
$elm$json$Json$Decode$field, $elm$json$Json$Decode$field,
'emoji', 'emoji',
$elm$json$Json$Decode$list($author$project$App$decode_emoji)), $elm$json$Json$Decode$list($author$project$App$decode_emoji)),
A2( A2(
$elm$json$Json$Decode$field, $elm$json$Json$Decode$at,
'markers', _List_fromArray(
['data', 'markers']),
$elm$json$Json$Decode$list($author$project$App$decode_marker)), $elm$json$Json$Decode$list($author$project$App$decode_marker)),
A2($elm$json$Json$Decode$field, 'map_id', $elm$json$Json$Decode$string)); A2($elm$json$Json$Decode$field, 'map_id', $elm$json$Json$Decode$string),
A2(
$elm$json$Json$Decode$field,
'map_ids',
$elm$json$Json$Decode$list(
A3(
$elm$json$Json$Decode$map2,
F2(
function (name, id) {
return {id: id, name: name};
}),
A2($elm$json$Json$Decode$field, 'name', $elm$json$Json$Decode$string),
A2($elm$json$Json$Decode$field, 'id', $elm$json$Json$Decode$string)))),
A2(
$elm$json$Json$Decode$at,
_List_fromArray(
['data', 'name']),
$elm$json$Json$Decode$string));
var $author$project$App$CurrentPositionCentre = {$: 'CurrentPositionCentre'}; var $author$project$App$CurrentPositionCentre = {$: 'CurrentPositionCentre'};
var $author$project$App$NoSelection = {$: 'NoSelection'}; var $author$project$App$NoSelection = {$: 'NoSelection'};
var $author$project$App$blank_marker = { var $author$project$App$blank_marker = {
@ -5228,6 +5250,8 @@ var $author$project$App$init_model = {
current_position: {lat: 55.04, lon: -1.46}, current_position: {lat: 55.04, lon: -1.46},
emoji: _List_Nil, emoji: _List_Nil,
map_id: '', map_id: '',
map_ids: _List_Nil,
map_name: '',
markers: _List_Nil, markers: _List_Nil,
new_marker: $author$project$App$blank_marker, new_marker: $author$project$App$blank_marker,
selection: $author$project$App$NoSelection selection: $author$project$App$NoSelection
@ -5247,7 +5271,7 @@ var $author$project$App$init = function (vflags) {
var flags = _v0.a; var flags = _v0.a;
return _Utils_update( return _Utils_update(
$author$project$App$init_model, $author$project$App$init_model,
{emoji: flags.emoji, map_id: flags.map_id, markers: flags.markers}); {emoji: flags.emoji, map_id: flags.map_id, map_ids: flags.map_ids, map_name: flags.map_name, markers: flags.markers});
} }
}()); }());
}; };
@ -5567,20 +5591,29 @@ var $author$project$App$save = function (model) {
'type', 'type',
$elm$json$Json$Encode$string('save')), $elm$json$Json$Encode$string('save')),
_Utils_Tuple2( _Utils_Tuple2(
'markers', 'data',
A2( $elm$json$Json$Encode$object(
$elm$json$Json$Encode$list, _List_fromArray(
$elm$core$Basics$identity, [
A2( _Utils_Tuple2(
$elm$core$List$indexedMap, 'markers',
F2( A2(
function (i, m) { $elm$json$Json$Encode$list,
return A2( $elm$core$Basics$identity,
$author$project$App$encode_marker, A2(
$elm$core$String$fromInt(i), $elm$core$List$indexedMap,
m); F2(
}), function (i, m) {
model.markers))) return A2(
$author$project$App$encode_marker,
$elm$core$String$fromInt(i),
m);
}),
model.markers))),
_Utils_Tuple2(
'name',
$elm$json$Json$Encode$string(model.map_name))
])))
])))); ]))));
}; };
var $elm$core$Basics$always = F2( var $elm$core$Basics$always = F2(
@ -5712,11 +5745,19 @@ var $author$project$App$update = F2(
markers: A2($elm_community$list_extra$List$Extra$removeAt, i, model.markers), markers: A2($elm_community$list_extra$List$Extra$removeAt, i, model.markers),
selection: $author$project$App$NoSelection selection: $author$project$App$NoSelection
})); }));
default: case 'ClickCurrentPosition':
return $author$project$App$nocmd( return $author$project$App$nocmd(
_Utils_update( _Utils_update(
model, model,
{centre: $author$project$App$CurrentPositionCentre, selection: $author$project$App$NoSelection})); {centre: $author$project$App$CurrentPositionCentre, selection: $author$project$App$NoSelection}));
case 'SetMapName':
var name = msg.a;
return $author$project$App$nocmd(
_Utils_update(
model,
{map_name: name}));
default:
return $author$project$App$save(model);
} }
}); });
var $author$project$App$AddMarker = {$: 'AddMarker'}; var $author$project$App$AddMarker = {$: 'AddMarker'};
@ -5727,6 +5768,10 @@ var $author$project$App$EditMarker = F2(
var $author$project$App$RemoveMarker = function (a) { var $author$project$App$RemoveMarker = function (a) {
return {$: 'RemoveMarker', a: a}; return {$: 'RemoveMarker', a: a};
}; };
var $author$project$App$Save = {$: 'Save'};
var $author$project$App$SetMapName = function (a) {
return {$: 'SetMapName', a: a};
};
var $author$project$App$UpdateExistingMarker = F2( var $author$project$App$UpdateExistingMarker = F2(
function (a, b) { function (a, b) {
return {$: 'UpdateExistingMarker', a: a, b: b}; return {$: 'UpdateExistingMarker', a: a, b: b};
@ -5761,10 +5806,6 @@ var $elm$html$Html$datalist = _VirtualDom_node('datalist');
var $author$project$App$MapClicked = function (a) { var $author$project$App$MapClicked = function (a) {
return {$: 'MapClicked', a: a}; return {$: 'MapClicked', a: a};
}; };
var $elm$json$Json$Decode$at = F2(
function (fields, decoder) {
return A3($elm$core$List$foldr, $elm$json$Json$Decode$field, decoder, fields);
});
var $author$project$App$decode_map_click = A2( var $author$project$App$decode_map_click = A2(
$elm$json$Json$Decode$map, $elm$json$Json$Decode$map,
$author$project$App$MapClicked, $author$project$App$MapClicked,
@ -5845,6 +5886,7 @@ var $elm$html$Html$form = _VirtualDom_node('form');
var $elm$core$String$fromFloat = _String_fromNumber; var $elm$core$String$fromFloat = _String_fromNumber;
var $elm$html$Html$h1 = _VirtualDom_node('h1'); var $elm$html$Html$h1 = _VirtualDom_node('h1');
var $elm$html$Html$h2 = _VirtualDom_node('h2'); var $elm$html$Html$h2 = _VirtualDom_node('h2');
var $elm$html$Html$h3 = _VirtualDom_node('h3');
var $elm$html$Html$Attributes$href = function (url) { var $elm$html$Html$Attributes$href = function (url) {
return A2( return A2(
$elm$html$Html$Attributes$stringProperty, $elm$html$Html$Attributes$stringProperty,
@ -6011,6 +6053,7 @@ var $elm$core$Tuple$second = function (_v0) {
var y = _v0.b; var y = _v0.b;
return y; return y;
}; };
var $elm$html$Html$small = _VirtualDom_node('small');
var $elm$core$List$sortBy = _List_sortBy; var $elm$core$List$sortBy = _List_sortBy;
var $author$project$App$space = $elm$html$Html$text(' '); var $author$project$App$space = $elm$html$Html$text(' ');
var $elm$html$Html$span = _VirtualDom_node('span'); var $elm$html$Html$span = _VirtualDom_node('span');
@ -6378,6 +6421,32 @@ var $author$project$App$view = function (model) {
$elm$html$Html$text('Closest markers') $elm$html$Html$text('Closest markers')
])), ])),
A2( A2(
$elm$html$Html$p,
_List_Nil,
_List_fromArray(
[
$elm$html$Html$text('🗺️ '),
A2(
$elm$html$Html$form,
_List_fromArray(
[
$elm$html$Html$Events$onSubmit($author$project$App$Save),
$elm$html$Html$Attributes$id('name-form')
]),
_List_fromArray(
[
A2(
$elm$html$Html$input,
_List_fromArray(
[
$elm$html$Html$Attributes$type_('text'),
$elm$html$Html$Events$onInput($author$project$App$SetMapName),
$elm$html$Html$Attributes$value(model.map_name)
]),
_List_Nil)
]))
])),
A2(
$elm$html$Html$ul, $elm$html$Html$ul,
_List_Nil, _List_Nil,
A2( A2(
@ -6409,6 +6478,61 @@ var $author$project$App$view = function (model) {
[ [
$elm$html$Html$text('🔗 Link to this map') $elm$html$Html$text('🔗 Link to this map')
])) ]))
])),
A2(
$elm$html$Html$h3,
_List_Nil,
_List_fromArray(
[
$elm$html$Html$text('Recently-used maps')
])),
A2(
$elm$html$Html$ul,
_List_Nil,
A2(
$elm$core$List$map,
function (d) {
return A2(
$elm$html$Html$li,
_List_Nil,
_List_fromArray(
[
A2(
$elm$html$Html$a,
_List_fromArray(
[
$elm$html$Html$Attributes$href('?map=' + d.id)
]),
_List_fromArray(
[
$elm$html$Html$text(d.name),
$elm$html$Html$text(' '),
A2(
$elm$html$Html$small,
_List_Nil,
_List_fromArray(
[
$elm$html$Html$text(d.id)
]))
]))
]));
},
model.map_ids)),
A2(
$elm$html$Html$p,
_List_Nil,
_List_fromArray(
[
A2(
$elm$html$Html$a,
_List_fromArray(
[
$elm$html$Html$Attributes$href('?')
]),
_List_fromArray(
[
$elm$html$Html$text('New map')
]))
])) ]))
])); ]));
} }

View file

@ -4,14 +4,14 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Elm app by clp</title> <title>Elm app by clp</title>
<link rel="manifest" href="manifest.json" />
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/> crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script> crossorigin=""></script>
<script src="https://unpkg.com/protomaps-leaflet@4.0.1/dist/protomaps-leaflet.js"></script>
</head> </head>
<body> <body>

View file

@ -146,33 +146,55 @@ async function init_app() {
} }
const params = (new URL(window.location)).searchParams; const params = (new URL(window.location)).searchParams;
console.log(params);
let data = {
name: '',
markers: []
}
let map_id = params.get('map') || ''; let map_id = params.get('map') || '';
if(!map_id) { if(!map_id) {
map_id = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16).padStart(8,'0').slice(0,8); map_id = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16).padStart(8,'0').slice(0,8);
} }
map_id = map_id.slice(0,20).replace(/\W/g,'');
let markers = []; data.name = map_id;
try { try {
markers = await (await fetch(`data/markers-${map_id}.json`)).json(); data = await (await fetch(`data/markers-${map_id}.json`)).json();
} catch(e) { } catch(e) {
try { try {
const fh = await opfs.getFileHandle(`markers-${map_id}.json`); const fh = await opfs.getFileHandle(`markers-${map_id}.json`);
const f = await fh.getFile(); const f = await fh.getFile();
markers = JSON.parse(await f.text()) data = JSON.parse(await f.text())
} catch(e) { } catch(e) {
} }
} }
let map_ids = [];
try {
for await(const [f,fh] of opfs.entries()) {
const m = f.match(/^markers-(.*).json/);
const data = JSON.parse(await (await fh.getFile()).text());
const name = data.name;
const id = m[1];
if(m) {
map_ids.push({name,id});
}
}
} catch(e) {
console.error(e);
}
const emoji = await (await fetch('emoji_metadata.json')).json(); const emoji = await (await fetch('emoji_metadata.json')).json();
const app = Elm.App.init({ const app = Elm.App.init({
node: document.body, node: document.body,
flags: {emoji, markers, map_id} flags: {emoji, data, map_id, map_ids}
}); });
navigator.geolocation.watchPosition( navigator.geolocation.watchPosition(
@ -184,18 +206,19 @@ async function init_app() {
); );
const send_value_handlers = { const send_value_handlers = {
save: async ({markers}) => { save: async ({data}) => {
const content = JSON.stringify(data);
try { try {
const f = await opfs.getFileHandle(`markers-${map_id}.json`, {create:true}); const f = await opfs.getFileHandle(`markers-${map_id}.json`, {create:true});
const w = await f.createWritable(); const w = await f.createWritable();
await w.write(JSON.stringify(markers)); await w.write(content);
await w.close(); await w.close();
} catch(e) { } catch(e) {
} }
const fd = new FormData(); const fd = new FormData();
fd.set('content', JSON.stringify(markers)); fd.set('content', content);
fd.set('map_id', map_id); fd.set('map_id', map_id);
fetch('cgi-bin/save_data.py', { fetch('cgi-bin/save_data.py', {
method: 'POST', method: 'POST',

8
manifest.json Normal file
View file

@ -0,0 +1,8 @@
{
"name": "CLP's map",
"description": "A personalised map",
"start_url": "https://map.think.somethingorotherwhatever.com/",
"display": "standalone",
"background_color": "#feb300",
"theme_color": "#feb300"
}

View file

@ -51,6 +51,8 @@ type alias Model =
{ markers : List Marker { markers : List Marker
, emoji : List Emoji , emoji : List Emoji
, map_id : String , map_id : String
, map_name : String
, map_ids : List ({name: String, id: String})
, current_position : LatLon , current_position : LatLon
, selection : Selection , selection : Selection
, new_marker : Marker , new_marker : Marker
@ -62,6 +64,8 @@ init_model =
{ markers = [] { markers = []
, emoji = [] , emoji = []
, map_id = "" , map_id = ""
, map_name = ""
, map_ids = []
, current_position = {lat = 55.04, lon = -1.46} , current_position = {lat = 55.04, lon = -1.46}
, selection = NoSelection , selection = NoSelection
, new_marker = blank_marker , new_marker = blank_marker
@ -79,6 +83,8 @@ type Msg
| EditMarker Int Marker | EditMarker Int Marker
| RemoveMarker Int | RemoveMarker Int
| ClickCurrentPosition | ClickCurrentPosition
| SetMapName String
| Save
type alias Emoji = type alias Emoji =
{ emoji : String { emoji : String
@ -94,19 +100,30 @@ type alias Flags =
{ emoji : List Emoji { emoji : List Emoji
, markers : List Marker , markers : List Marker
, map_id : String , map_id : String
, map_ids : List ({name: String, id: String})
, map_name : String
} }
decode_flags = decode_flags =
JD.map3 Flags JD.map5 Flags
(JD.field "emoji" (JD.list decode_emoji)) (JD.field "emoji" (JD.list decode_emoji))
(JD.field "markers" (JD.list decode_marker)) (JD.at ["data", "markers"] (JD.list decode_marker))
(JD.field "map_id" JD.string) (JD.field "map_id" JD.string)
(JD.field "map_ids"
(JD.list
(JD.map2 (\name id -> { name = name, id = id})
(JD.field "name" JD.string)
(JD.field "id" JD.string)
)
)
)
(JD.at ["data", "name"] JD.string)
init : (JD.Value) -> (Model, Cmd msg) init : (JD.Value) -> (Model, Cmd msg)
init vflags = init vflags =
(case JD.decodeValue decode_flags vflags of (case JD.decodeValue decode_flags vflags of
Err _ -> init_model Err _ -> init_model
Ok flags -> { init_model | emoji = flags.emoji, markers = flags.markers, map_id = flags.map_id } Ok flags -> { init_model | emoji = flags.emoji, markers = flags.markers, map_id = flags.map_id, map_ids = flags.map_ids, map_name = flags.map_name }
) |> nocmd ) |> nocmd
nocmd m = (m, Cmd.none) nocmd m = (m, Cmd.none)
@ -117,7 +134,11 @@ save model =
, send_value , send_value
<| JE.object <| JE.object
[ ("type", JE.string "save") [ ("type", JE.string "save")
, ("markers", JE.list identity <| List.indexedMap (\i m -> encode_marker (String.fromInt i) m) model.markers) , ("data", JE.object
[ ("markers", JE.list identity <| List.indexedMap (\i m -> encode_marker (String.fromInt i) m) model.markers)
, ("name", JE.string model.map_name)
]
)
] ]
) )
@ -157,6 +178,10 @@ update msg model =
ClickCurrentPosition -> { model | centre = CurrentPositionCentre, selection = NoSelection } |> nocmd ClickCurrentPosition -> { model | centre = CurrentPositionCentre, selection = NoSelection } |> nocmd
SetMapName name -> { model | map_name = name } |> nocmd
Save -> model |> save
add_marker model = case model.selection of add_marker model = case model.selection of
SelectedPosition pos -> SelectedPosition pos ->
let let
@ -383,6 +408,21 @@ view model =
, HA.class "marker-detail" , HA.class "marker-detail"
] ]
[ H.h1 [] [ H.text "Closest markers" ] [ H.h1 [] [ H.text "Closest markers" ]
, H.p
[]
[ H.text <| "🗺 "
, H.form
[ HE.onSubmit Save
, HA.id "name-form"
]
[ H.input
[ HA.type_ "text"
, HE.onInput SetMapName
, HA.value model.map_name
]
[]
]
]
, H.ul , H.ul
[] []
(List.map (\(i,m) -> (List.map (\(i,m) ->
@ -397,6 +437,18 @@ view model =
[ HA.href <| "?map=" ++ model.map_id ] [ HA.href <| "?map=" ++ model.map_id ]
[ H.text "🔗 Link to this map" ] [ H.text "🔗 Link to this map" ]
] ]
, H.h3
[]
[ H.text "Recently-used maps"]
, H.ul
[]
(List.map
(\d -> H.li [] [H.a [HA.href <| "?map="++d.id] [H.text d.name, H.text " ", H.small [] [H.text d.id]]])
model.map_ids
)
, H.p
[]
[ H.a [HA.href "?"] [H.text "New map"]]
] ]
closest_markers = closest_markers =

View file

@ -24,6 +24,17 @@ body > main {
overflow: auto; overflow: auto;
} }
#name-form {
display: inline;
& input {
border: none;
padding: 0;
font-size: inherit;
border-bottom: thin solid;
}
}
#marker-form { #marker-form {
& label { & label {
margin: 0 0.5em; margin: 0 0.5em;