From e6a51f162dc1241279f546436284b469c9498c71 Mon Sep 17 00:00:00 2001 From: Christian Lawson-Perfect Date: Tue, 11 Feb 2025 05:17:18 +0000 Subject: [PATCH] Show recent maps; name maps --- app.js | 184 ++++++++++++++++++++++++++++++++++++++++++-------- index.html | 12 ++-- load-app.js | 43 +++++++++--- manifest.json | 8 +++ src/App.elm | 60 ++++++++++++++-- style.css | 11 +++ 6 files changed, 268 insertions(+), 50 deletions(-) create mode 100644 manifest.json diff --git a/app.js b/app.js index f857b83..921fd36 100644 --- a/app.js +++ b/app.js @@ -5160,15 +5160,19 @@ var $elm$core$Task$perform = F2( }); var $elm$browser$Browser$document = _Browser_document; var $elm$json$Json$Decode$decodeValue = _Json_run; -var $author$project$App$Flags = F3( - function (emoji, markers, map_id) { - return {emoji: emoji, map_id: map_id, markers: markers}; +var $author$project$App$Flags = F5( + function (emoji, markers, map_id, map_ids, map_name) { + 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( function (emoji, description) { return {description: description, emoji: emoji}; }); -var $elm$json$Json$Decode$field = _Json_decodeField; var $elm$json$Json$Decode$string = _Json_decodeString; var $author$project$App$decode_emoji = A3( $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, 'note', $elm$json$Json$Decode$string)); var $elm$json$Json$Decode$list = _Json_decodeList; -var $elm$json$Json$Decode$map3 = _Json_map3; -var $author$project$App$decode_flags = A4( - $elm$json$Json$Decode$map3, +var $elm$json$Json$Decode$map5 = _Json_map5; +var $author$project$App$decode_flags = A6( + $elm$json$Json$Decode$map5, $author$project$App$Flags, A2( $elm$json$Json$Decode$field, 'emoji', $elm$json$Json$Decode$list($author$project$App$decode_emoji)), A2( - $elm$json$Json$Decode$field, - 'markers', + $elm$json$Json$Decode$at, + _List_fromArray( + ['data', 'markers']), $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$NoSelection = {$: 'NoSelection'}; var $author$project$App$blank_marker = { @@ -5228,6 +5250,8 @@ var $author$project$App$init_model = { current_position: {lat: 55.04, lon: -1.46}, emoji: _List_Nil, map_id: '', + map_ids: _List_Nil, + map_name: '', markers: _List_Nil, new_marker: $author$project$App$blank_marker, selection: $author$project$App$NoSelection @@ -5247,7 +5271,7 @@ var $author$project$App$init = function (vflags) { var flags = _v0.a; return _Utils_update( $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', $elm$json$Json$Encode$string('save')), _Utils_Tuple2( - 'markers', - A2( - $elm$json$Json$Encode$list, - $elm$core$Basics$identity, - A2( - $elm$core$List$indexedMap, - F2( - function (i, m) { - return A2( - $author$project$App$encode_marker, - $elm$core$String$fromInt(i), - m); - }), - model.markers))) + 'data', + $elm$json$Json$Encode$object( + _List_fromArray( + [ + _Utils_Tuple2( + 'markers', + A2( + $elm$json$Json$Encode$list, + $elm$core$Basics$identity, + A2( + $elm$core$List$indexedMap, + F2( + function (i, m) { + 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( @@ -5712,11 +5745,19 @@ var $author$project$App$update = F2( markers: A2($elm_community$list_extra$List$Extra$removeAt, i, model.markers), selection: $author$project$App$NoSelection })); - default: + case 'ClickCurrentPosition': return $author$project$App$nocmd( _Utils_update( model, {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'}; @@ -5727,6 +5768,10 @@ var $author$project$App$EditMarker = F2( var $author$project$App$RemoveMarker = function (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( function (a, 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) { 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( $elm$json$Json$Decode$map, $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$html$Html$h1 = _VirtualDom_node('h1'); var $elm$html$Html$h2 = _VirtualDom_node('h2'); +var $elm$html$Html$h3 = _VirtualDom_node('h3'); var $elm$html$Html$Attributes$href = function (url) { return A2( $elm$html$Html$Attributes$stringProperty, @@ -6011,6 +6053,7 @@ var $elm$core$Tuple$second = function (_v0) { var y = _v0.b; return y; }; +var $elm$html$Html$small = _VirtualDom_node('small'); var $elm$core$List$sortBy = _List_sortBy; var $author$project$App$space = $elm$html$Html$text(' '); 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') ])), 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, _List_Nil, A2( @@ -6409,6 +6478,61 @@ var $author$project$App$view = function (model) { [ $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') + ])) ])) ])); } diff --git a/index.html b/index.html index 4711bfe..a4e6372 100644 --- a/index.html +++ b/index.html @@ -4,14 +4,14 @@ Elm app by clp + - + - + integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" + crossorigin=""> diff --git a/load-app.js b/load-app.js index 09a71e0..6cf3475 100644 --- a/load-app.js +++ b/load-app.js @@ -146,33 +146,55 @@ async function init_app() { } const params = (new URL(window.location)).searchParams; - console.log(params); + + let data = { + name: '', + markers: [] + } + let map_id = params.get('map') || ''; if(!map_id) { map_id = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER).toString(16).padStart(8,'0').slice(0,8); } - - let markers = []; + map_id = map_id.slice(0,20).replace(/\W/g,''); + data.name = map_id; try { - markers = await (await fetch(`data/markers-${map_id}.json`)).json(); - + data = await (await fetch(`data/markers-${map_id}.json`)).json(); } catch(e) { try { const fh = await opfs.getFileHandle(`markers-${map_id}.json`); const f = await fh.getFile(); - markers = JSON.parse(await f.text()) + data = JSON.parse(await f.text()) } 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 app = Elm.App.init({ node: document.body, - flags: {emoji, markers, map_id} + flags: {emoji, data, map_id, map_ids} }); navigator.geolocation.watchPosition( @@ -184,18 +206,19 @@ async function init_app() { ); const send_value_handlers = { - save: async ({markers}) => { + save: async ({data}) => { + const content = JSON.stringify(data); try { const f = await opfs.getFileHandle(`markers-${map_id}.json`, {create:true}); const w = await f.createWritable(); - await w.write(JSON.stringify(markers)); + await w.write(content); await w.close(); } catch(e) { } const fd = new FormData(); - fd.set('content', JSON.stringify(markers)); + fd.set('content', content); fd.set('map_id', map_id); fetch('cgi-bin/save_data.py', { method: 'POST', diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..bd05a98 --- /dev/null +++ b/manifest.json @@ -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" +} \ No newline at end of file diff --git a/src/App.elm b/src/App.elm index a5d042e..80f37d7 100644 --- a/src/App.elm +++ b/src/App.elm @@ -51,6 +51,8 @@ type alias Model = { markers : List Marker , emoji : List Emoji , map_id : String + , map_name : String + , map_ids : List ({name: String, id: String}) , current_position : LatLon , selection : Selection , new_marker : Marker @@ -62,6 +64,8 @@ init_model = { markers = [] , emoji = [] , map_id = "" + , map_name = "" + , map_ids = [] , current_position = {lat = 55.04, lon = -1.46} , selection = NoSelection , new_marker = blank_marker @@ -79,6 +83,8 @@ type Msg | EditMarker Int Marker | RemoveMarker Int | ClickCurrentPosition + | SetMapName String + | Save type alias Emoji = { emoji : String @@ -94,19 +100,30 @@ type alias Flags = { emoji : List Emoji , markers : List Marker , map_id : String + , map_ids : List ({name: String, id: String}) + , map_name : String } decode_flags = - JD.map3 Flags + JD.map5 Flags (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_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 vflags = (case JD.decodeValue decode_flags vflags of 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 m = (m, Cmd.none) @@ -117,7 +134,11 @@ save model = , send_value <| JE.object [ ("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 + SetMapName name -> { model | map_name = name } |> nocmd + + Save -> model |> save + add_marker model = case model.selection of SelectedPosition pos -> let @@ -383,6 +408,21 @@ view model = , HA.class "marker-detail" ] [ 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 [] (List.map (\(i,m) -> @@ -397,6 +437,18 @@ view model = [ HA.href <| "?map=" ++ model.map_id ] [ 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 = diff --git a/style.css b/style.css index c8bf90b..6bf3618 100644 --- a/style.css +++ b/style.css @@ -24,6 +24,17 @@ body > main { overflow: auto; } +#name-form { + display: inline; + + & input { + border: none; + padding: 0; + font-size: inherit; + border-bottom: thin solid; + } +} + #marker-form { & label { margin: 0 0.5em;