forked from MapComplete/MapComplete
Compare commits
6 commits
develop
...
feature/of
| Author | SHA1 | Date | |
|---|---|---|---|
| 07a181aa1e | |||
| 561e4cb009 | |||
| f1da97285f | |||
| 7155cd7f61 | |||
| f671cd342f | |||
| 06aa8a3406 |
38 changed files with 431 additions and 144 deletions
|
|
@ -217,8 +217,8 @@
|
|||
},
|
||||
{
|
||||
"id": "debug",
|
||||
"metacondition": "__featureSwitchIsDebugging=true",
|
||||
"render": "{all_tags()}"
|
||||
"render": "{all_tags()}",
|
||||
"metacondition": "__featureSwitchIsDebugging=true"
|
||||
}
|
||||
],
|
||||
"filter": [
|
||||
|
|
|
|||
|
|
@ -114,9 +114,9 @@
|
|||
"lineRendering": [],
|
||||
"tagRenderings": [
|
||||
{
|
||||
"classes": "p-0",
|
||||
"id": "conversation",
|
||||
"render": "{visualize_note_comments()}"
|
||||
"render": "{visualize_note_comments()}",
|
||||
"classes": "p-0"
|
||||
},
|
||||
{
|
||||
"id": "add_image",
|
||||
|
|
|
|||
|
|
@ -66,16 +66,16 @@
|
|||
],
|
||||
"tagRenderings": [
|
||||
{
|
||||
"condition": "level=country",
|
||||
"description": "The name of the country",
|
||||
"id": "country_name",
|
||||
"render": "{nameEn} {emojiFlag}"
|
||||
"description": "The name of the country",
|
||||
"render": "{nameEn} {emojiFlag}",
|
||||
"condition": "level=country"
|
||||
},
|
||||
{
|
||||
"condition": "_community_links~*",
|
||||
"description": "Community Links (Discord, meetups, Slack groups, IRC channels, mailing lists etc...)",
|
||||
"id": "community_links",
|
||||
"render": "{_community_links}"
|
||||
"description": "Community Links (Discord, meetups, Slack groups, IRC channels, mailing lists etc...)",
|
||||
"render": "{_community_links}",
|
||||
"condition": "_community_links~*"
|
||||
}
|
||||
],
|
||||
"filter": [
|
||||
|
|
|
|||
|
|
@ -1898,6 +1898,46 @@
|
|||
"render": {
|
||||
"*": "{storage_all_tags()}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "debug_serviceworker_accordeon",
|
||||
"render": {
|
||||
"special": {
|
||||
"header": "debug_serviceworker_accordeon_title",
|
||||
"labels": "debug_serviceworker",
|
||||
"type": "group"
|
||||
}
|
||||
},
|
||||
"condition": "mapcomplete-show_debug=yes"
|
||||
},
|
||||
{
|
||||
"id": "debug_serviceworker_accordeon_title",
|
||||
"labels": [
|
||||
"hidden"
|
||||
],
|
||||
"render": {
|
||||
"en": "Debug information about the service worker"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "expl",
|
||||
"labels": [
|
||||
"debug_serviceworker",
|
||||
"hidden"
|
||||
],
|
||||
"render": {
|
||||
"en": "To clear the service worker data, use the 'clear caches' button"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "service_worker_tags",
|
||||
"labels": [
|
||||
"debug_serviceworker",
|
||||
"hidden"
|
||||
],
|
||||
"render": {
|
||||
"*": "{serviceworker_all_tags()}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"allowMove": false
|
||||
|
|
|
|||
|
|
@ -43,8 +43,6 @@
|
|||
<script src="./src/Logic/Web/AndroidPolyfill.ts" type="module"></script>
|
||||
<script type="module" src="./src/all_themes_index.ts"></script>
|
||||
|
||||
<script async src="./src/InstallServiceWorker.ts" type="module"></script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"allFilteredAway": "No feature in view meets all filters",
|
||||
"loadingData": "Loading data…",
|
||||
"noData": "There are no relevant features in the current view",
|
||||
"noDataOffline": "No data is loaded and you are offline",
|
||||
"ready": "Done!",
|
||||
"retrying": "Loading data failed. Trying again in {count} seconds…",
|
||||
"zoomIn": "Zoom in to view or edit the data"
|
||||
|
|
@ -335,6 +336,7 @@
|
|||
"next": "Next",
|
||||
"noTagsSelected": "No tags selected",
|
||||
"number": "number",
|
||||
"offline": "Your device is offline",
|
||||
"openTheMap": "Open the map",
|
||||
"openTheMapReason": "to view, edit and add information",
|
||||
"opening_hours": {
|
||||
|
|
@ -637,6 +639,7 @@
|
|||
"uploading": "{count} images are being uploaded…"
|
||||
},
|
||||
"noBlur": "Images will not be blurred. Do not photograph people",
|
||||
"offline": "You are currently offline. Uploading images be attempted when your internet is back",
|
||||
"one": {
|
||||
"done": "Your image was successfully uploaded. Thank you!",
|
||||
"failed": "Sorry, we could not upload your image",
|
||||
|
|
@ -651,7 +654,7 @@
|
|||
"confirmDeleteTitle": "Delete this image?",
|
||||
"delete": "Delete this image",
|
||||
"intro": "The following images are queued for upload",
|
||||
"menu": "Image upload queue ({count})",
|
||||
"menu": "Pending changes and image uploads ({count})",
|
||||
"noFailedImages": "There are currently no images in the upload queue",
|
||||
"retryAll": "Retry uploading all images"
|
||||
},
|
||||
|
|
@ -958,4 +961,4 @@
|
|||
"startsWithQ": "A wikidata identifier starts with Q and is followed by a number"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14095,6 +14095,9 @@
|
|||
"debug_accordeon_title": {
|
||||
"render": "Debug information"
|
||||
},
|
||||
"debug_serviceworker_accordeon_title": {
|
||||
"render": "Debug information about the service worker"
|
||||
},
|
||||
"debug_storage_accordeon_title": {
|
||||
"render": "Debug information about local storage"
|
||||
},
|
||||
|
|
@ -14105,6 +14108,9 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"expl": {
|
||||
"render": "To clear the service worker data, use the 'clear caches' button"
|
||||
},
|
||||
"fixate-north": {
|
||||
"mappings": {
|
||||
"0": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import * as fs from "fs"
|
||||
import Script from "./Script"
|
||||
|
||||
function genImages(dryrun = false) {
|
||||
function genImages() {
|
||||
console.log("Generating images")
|
||||
const dir = fs.readdirSync("./assets/svg")
|
||||
for (const path of dir) {
|
||||
|
|
@ -64,7 +64,7 @@ class GenerateIncludedImages extends Script {
|
|||
super("Converts all images from assets/svg into svelte-classes.")
|
||||
}
|
||||
|
||||
async main(args: string[]): Promise<void> {
|
||||
async main(): Promise<void> {
|
||||
genImages()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
DoesImageExist,
|
||||
PrevalidateTheme,
|
||||
ValidateLayer,
|
||||
ValidateThemeEnsemble,
|
||||
ValidateThemeEnsemble
|
||||
} from "../src/Models/ThemeConfig/Conversion/Validation"
|
||||
import { Translation } from "../src/UI/i18n/Translation"
|
||||
import { OrderLayer, PrepareLayer } from "../src/Models/ThemeConfig/Conversion/PrepareLayer"
|
||||
|
|
@ -19,7 +19,7 @@ import {
|
|||
DesugaringStep,
|
||||
Each,
|
||||
Fuse,
|
||||
On,
|
||||
On
|
||||
} from "../src/Models/ThemeConfig/Conversion/Conversion"
|
||||
import { Utils } from "../src/Utils"
|
||||
import Script from "./Script"
|
||||
|
|
@ -182,7 +182,7 @@ class LayerBuilder extends Conversion<object, Map<string, LayerConfigJson>> {
|
|||
return `./assets/layers/${id}/${id}.json`
|
||||
}
|
||||
|
||||
writeLayer(layer: LayerConfigJson) {
|
||||
public writeLayer(layer: LayerConfigJson) {
|
||||
if (layer.labels?.some((l) => this._labelBlacklist.has(l))) {
|
||||
console.log("Not writing layer " + layer.id + ", censored")
|
||||
return
|
||||
|
|
@ -191,6 +191,15 @@ class LayerBuilder extends Conversion<object, Map<string, LayerConfigJson>> {
|
|||
if (!existsSync(LayerOverviewUtils.layerPath)) {
|
||||
mkdirSync(LayerOverviewUtils.layerPath)
|
||||
}
|
||||
|
||||
const usedImages = Lists.dedup(new ExtractImages(true, new Set(this._desugaringState.tagRenderings.keys()))
|
||||
.convertStrict({ layers: [layer], id: "dummy", icon: undefined, title: undefined })
|
||||
.map((x) => x.path))
|
||||
usedImages.sort()
|
||||
|
||||
layer["_usedImages"] = usedImages
|
||||
|
||||
|
||||
writeFileSync(LayerBuilder.targetPath(layer.id), JSON.stringify(layer, null, " "), {
|
||||
encoding: "utf8",
|
||||
})
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class PrepareServiceWorker extends Script {
|
|||
}
|
||||
|
||||
public async main() {
|
||||
const v = Constants.vNumber
|
||||
const v = Constants.vNumber + "-" + new Date().getTime()
|
||||
writeFileSync("./src/service-worker/SWGenerated.ts",
|
||||
["export class SWGenerated {",
|
||||
"// generated by scripts/prepareServiceWorker.ts",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
export {}
|
||||
window.addEventListener("load", async () => {
|
||||
if (!("serviceWorker" in navigator)) {
|
||||
console.log("Service workers are not supported")
|
||||
return
|
||||
}
|
||||
try {
|
||||
export class InstallServiceWorker {
|
||||
|
||||
static async installServiceWorker() {
|
||||
if (!("serviceWorker" in navigator)) {
|
||||
throw ("Service workers are not supported")
|
||||
}
|
||||
await navigator.serviceWorker.register("/service-worker.js", { type: "module" })
|
||||
console.log("Service worker registration successful")
|
||||
} catch (err) {
|
||||
console.error("Service worker registration failed", err)
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
static async precache(assets: string[]) {
|
||||
if (assets?.length > 0) {
|
||||
await fetch("./service-worker/precache?assets=" + assets.join(";"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import OsmObjectDownloader from "../Osm/OsmObjectDownloader"
|
|||
import ExifReader from "exifreader"
|
||||
import { Utils } from "../../Utils"
|
||||
import { Lists } from "../../Utils/Lists"
|
||||
import { IsOnline } from "../Web/IsOnline"
|
||||
|
||||
/**
|
||||
* The ImageUploadManager has a
|
||||
|
|
@ -172,6 +173,9 @@ export class ImageUploadManager {
|
|||
if (this.uploadingAll) {
|
||||
return
|
||||
}
|
||||
if(!IsOnline.isOnline){
|
||||
return
|
||||
}
|
||||
try {
|
||||
let queue: ImageUploadArguments[]
|
||||
const failed: Set<ImageUploadArguments> = new Set()
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import MarkdownUtils from "../../Utils/MarkdownUtils"
|
|||
import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"
|
||||
import { Feature, Point } from "geojson"
|
||||
import { Lists } from "../../Utils/Lists"
|
||||
import { IsOnline } from "../Web/IsOnline"
|
||||
|
||||
/**
|
||||
* Handles all changes made to OSM.
|
||||
|
|
@ -287,6 +288,10 @@ export class Changes {
|
|||
if (this.pendingChanges.data.length === 0) {
|
||||
return
|
||||
}
|
||||
if(!IsOnline.isOnline.data){
|
||||
// No use to upload, we aren't connected anyway
|
||||
return
|
||||
}
|
||||
if (this.isUploading.data) {
|
||||
console.log("Is already uploading... Abort")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import Constants from "../../Models/Constants"
|
|||
import { Feature, Point } from "geojson"
|
||||
import { AndroidPolyfill } from "../Web/AndroidPolyfill"
|
||||
import { QueryParameters } from "../Web/QueryParameters"
|
||||
import { IsOnline } from "../Web/IsOnline"
|
||||
|
||||
interface OsmUserInfo {
|
||||
id: number
|
||||
|
|
@ -131,6 +132,7 @@ export class OsmConnection {
|
|||
* Details of the currently logged-in user; undefined if not logged in
|
||||
*/
|
||||
public userDetails: UIEventSource<UserDetails | undefined>
|
||||
|
||||
public isLoggedIn: Store<boolean>
|
||||
public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
|
||||
"unknown"
|
||||
|
|
@ -182,7 +184,7 @@ export class OsmConnection {
|
|||
this._oauth_config.oauth_secret = import.meta.env.VITE_OSM_OAUTH_SECRET
|
||||
}
|
||||
|
||||
this.userDetails = new UIEventSource<UserDetails>(undefined, "userDetails")
|
||||
this.userDetails = UIEventSource.asObject<UserDetails>(LocalStorageSource.get("user_details"), undefined)
|
||||
if (options.fakeUser) {
|
||||
const ud = this.userDetails.data
|
||||
ud.csCount = 5678
|
||||
|
|
@ -197,13 +199,7 @@ export class OsmConnection {
|
|||
}
|
||||
this.updateCapabilities()
|
||||
|
||||
this.isLoggedIn = this.userDetails.map(
|
||||
(user) =>
|
||||
!!user &&
|
||||
(this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"),
|
||||
[this.apiIsOnline]
|
||||
)
|
||||
|
||||
this.isLoggedIn = this.userDetails.map((user) => !!user)
|
||||
this._dryRun = options.dryRun ?? new UIEventSource<boolean>(false)
|
||||
|
||||
if (options?.shared_cookie) {
|
||||
|
|
@ -284,6 +280,9 @@ export class OsmConnection {
|
|||
}
|
||||
|
||||
public async AttemptLogin() {
|
||||
if (!IsOnline.isOnline.data) {
|
||||
return
|
||||
}
|
||||
this.updateCapabilities()
|
||||
if (this.loadingStatus.data !== "logged-in") {
|
||||
this.loadingStatus.setData("loading")
|
||||
|
|
@ -308,6 +307,9 @@ export class OsmConnection {
|
|||
}
|
||||
|
||||
private async loadUserInfo() {
|
||||
if (!IsOnline.isOnline.data) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const result = await this.interact("user/details.json")
|
||||
if (result === null) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Feature, Position } from "geojson"
|
|||
import { GeoOperations } from "../GeoOperations"
|
||||
import { SpecialVisualizationState } from "../../UI/SpecialVisualization"
|
||||
import { WithUserRelatedState } from "../../Models/ThemeViewState/WithUserRelatedState"
|
||||
import { IsOnline } from "./IsOnline"
|
||||
|
||||
export interface ReviewCollection {
|
||||
readonly subjectUri?: Store<string>
|
||||
|
|
@ -238,11 +239,14 @@ export default class FeatureReviews implements ReviewCollection {
|
|||
if (!loadingAllowed.data) {
|
||||
return
|
||||
}
|
||||
if (!IsOnline.isOnline.data) {
|
||||
return
|
||||
}
|
||||
const reviews = await MangroveReviews.getReviews({ sub })
|
||||
console.debug("Got reviews for", feature, reviews, sub)
|
||||
this.addReviews(reviews.reviews, this._name.data)
|
||||
},
|
||||
[this._name, loadingAllowed]
|
||||
[this._name, loadingAllowed, IsOnline.isOnline]
|
||||
)
|
||||
/* We also construct all subject queries _without_ encoding the name to work around a previous bug
|
||||
* See https://github.com/giggls/opencampsitemap/issues/30
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import LayerConfig from "./LayerConfig"
|
|||
import ComparingTag from "../../Logic/Tags/ComparingTag"
|
||||
import { Unit } from "../Unit"
|
||||
import { Lists } from "../../Utils/Lists"
|
||||
import { IsOnline } from "../../Logic/Web/IsOnline"
|
||||
|
||||
export interface Mapping {
|
||||
readonly if: UploadableTag
|
||||
|
|
@ -1154,10 +1155,10 @@ export class TagRenderingConfigUtils {
|
|||
const extraMappings = tags.bindD((tags) => {
|
||||
const country = tags._country
|
||||
if (country === undefined) {
|
||||
return undefined
|
||||
return undefined
|
||||
}
|
||||
const center = GeoOperations.centerpointCoordinates(feature)
|
||||
return UIEventSource.fromPromise(
|
||||
return UIEventSource.fromPromiseWithErr(
|
||||
NameSuggestionIndex.generateMappings(
|
||||
config.freeform.key,
|
||||
tags,
|
||||
|
|
@ -1167,7 +1168,20 @@ export class TagRenderingConfigUtils {
|
|||
)
|
||||
)
|
||||
})
|
||||
return extraMappings.mapD((extraMappings) => {
|
||||
return extraMappings.map((extraMappingsErr) => {
|
||||
if(extraMappingsErr?.["error"]){
|
||||
console.log("Could not download the NSI: ", extraMappingsErr["error"])
|
||||
return config
|
||||
}
|
||||
const extraMappings = extraMappingsErr?.success
|
||||
if(extraMappings === undefined){
|
||||
if(!IsOnline.isOnline.data){
|
||||
// The 'extraMappings' will still attempt to download the NSI - it might be in the service worker's cache
|
||||
// As such, if they happen to come through anyway, they'll be shown
|
||||
return config
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
if (extraMappings.length == 0) {
|
||||
return config
|
||||
}
|
||||
|
|
@ -1187,6 +1201,6 @@ export class TagRenderingConfigUtils {
|
|||
}) ?? []
|
||||
clone.mappings = [...oldMappingsCloned, ...extraMappings]
|
||||
return clone
|
||||
})
|
||||
}, [IsOnline.isOnline])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<LoginToggle {state}>
|
||||
<LoginToggle {state} offline>
|
||||
{#if $recentThemes.length > 2}
|
||||
<div class="my-4">
|
||||
<h2>
|
||||
|
|
|
|||
20
src/UI/Base/Avatar.svelte
Normal file
20
src/UI/Base/Avatar.svelte
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import type UserDetails from "../../Logic/Osm/OsmConnection"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import UserCircle from "@rgossiaux/svelte-heroicons/solid/UserCircle"
|
||||
import { IsOnline } from "../../Logic/Web/IsOnline"
|
||||
|
||||
/**
|
||||
* USer icon, if available
|
||||
*/
|
||||
export let userdetails: Store<UserDetails>
|
||||
|
||||
let loaded = new UIEventSource(false)
|
||||
let isOnline = IsOnline.isOnline
|
||||
</script>
|
||||
|
||||
{#if !$userdetails.img || !($loaded || $isOnline)}
|
||||
<UserCircle class="h-14 w-14" color="gray" />
|
||||
{:else}
|
||||
<img alt="avatar" src={$userdetails.img} class="h-12 w-12 rounded-full" on:load={() => {loaded.set(true)}} />
|
||||
{/if}
|
||||
|
|
@ -14,10 +14,15 @@
|
|||
osmConnection: OsmConnection
|
||||
featureSwitches?: { featureSwitchEnableLogin?: UIEventSource<boolean> }
|
||||
}
|
||||
/**
|
||||
* Do show this element when in offline mode
|
||||
*/
|
||||
export let offline = false
|
||||
/**
|
||||
* If set, 'loading' will act as if we are already logged in.
|
||||
*/
|
||||
export let ignoreLoading: boolean = false
|
||||
export let ignoreLoading: boolean = offline // If it works in offline mode, it'll work while we are logging in too
|
||||
|
||||
/**
|
||||
* If set and the OSM-api fails, do _not_ show any error messages nor the successful state, just hide.
|
||||
* Will still show the "not-logged-in"-slot
|
||||
|
|
@ -32,23 +37,26 @@
|
|||
unknown: t.loginFailedUnreachableMode,
|
||||
readonly: t.loginFailedReadonlyMode,
|
||||
}
|
||||
const apiState: Store<string> =
|
||||
const apiState: Store<OsmServiceState> =
|
||||
state?.osmConnection?.apiIsOnline ?? new ImmutableStore<OsmServiceState>("online")
|
||||
const online = IsOnline.isOnline
|
||||
let loggedIn = state?.osmConnection?.isLoggedIn
|
||||
</script>
|
||||
|
||||
{#if $badge}
|
||||
{#if !$online}
|
||||
{#if !$online && !offline}
|
||||
{#if !hiddenFail}
|
||||
<div class="alert">
|
||||
Your device is offline
|
||||
<Tr t={t.offline} />
|
||||
</div>
|
||||
{/if}
|
||||
{:else if !ignoreLoading && !hiddenFail && $loadingStatus === "loading"}
|
||||
<slot name="loading">
|
||||
<Loading />
|
||||
</slot>
|
||||
{:else if ($loadingStatus === "error" || $apiState === "readonly" || $apiState === "offline")}
|
||||
{:else if $loggedIn}
|
||||
<slot />
|
||||
{:else if ($loadingStatus === "error" || $apiState === "readonly" || $apiState === "offline" || $apiState === "unreachable")}
|
||||
{#if !hiddenFail}
|
||||
<slot name="error">
|
||||
<div class="alert flex flex-col items-center">
|
||||
|
|
@ -63,8 +71,7 @@
|
|||
</div>
|
||||
</slot>
|
||||
{/if}
|
||||
{:else if $loadingStatus === "logged-in"}
|
||||
<slot />
|
||||
|
||||
{:else if $loadingStatus === "not-attempted"}
|
||||
<slot name="not-logged-in" />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,11 @@
|
|||
import OfflineManagement from "./OfflineManagement.svelte"
|
||||
import { GlobeEuropeAfrica } from "@babeard/svelte-heroicons/solid/GlobeEuropeAfrica"
|
||||
import { onDestroy } from "svelte"
|
||||
import Avatar from "../Base/Avatar.svelte"
|
||||
import { SpecialVisualizationSvelte } from "../SpecialVisualization"
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
import { Changes } from "../../Logic/Osm/Changes"
|
||||
import PendingChangesView from "./PendingChangesView.svelte"
|
||||
|
||||
export let state: {
|
||||
favourites: FavouritesFeatureSource
|
||||
|
|
@ -72,16 +77,16 @@
|
|||
featureSwitches: Partial<FeatureSwitchState>
|
||||
mapProperties?: MapProperties
|
||||
userRelatedState?: UserRelatedState
|
||||
changes?: Changes
|
||||
}
|
||||
let userdetails = state.osmConnection.userDetails
|
||||
|
||||
let usersettingslayer = new LayerConfig(<LayerConfigJson>usersettings, "usersettings", true)
|
||||
|
||||
let theme = state.theme
|
||||
let featureSwitches = state.featureSwitches
|
||||
let showHome = featureSwitches?.featureSwitchBackToThemeOverview
|
||||
let pg = state.guistate.pageStates
|
||||
let location = state.mapProperties?.location
|
||||
let pendingChanges = state?.changes?.pendingChanges
|
||||
export let onlyLink: boolean
|
||||
const t = Translations.t.general.menu
|
||||
let shown = new UIEventSource(state.guistate.pageStates.menu.data || !onlyLink)
|
||||
|
|
@ -133,17 +138,13 @@
|
|||
|
||||
<!-- User related: avatar, settings, favourits, logout -->
|
||||
<SidebarUnit>
|
||||
<LoginToggle {state}>
|
||||
<LoginToggle {state} offline>
|
||||
<LoginButton osmConnection={state.osmConnection} slot="not-logged-in" />
|
||||
<div class="flex items-center gap-x-4 w-full m-2">
|
||||
{#if $userdetails.img}
|
||||
<img alt="avatar" src={$userdetails.img} class="h-12 w-12 rounded-full" />
|
||||
{:else}
|
||||
<UserCircle class="h-14 w-14" color="gray"/>
|
||||
{/if}
|
||||
<Avatar userdetails={state.osmConnection.userDetails} />
|
||||
<div class="flex flex-col w-full gap-y-2">
|
||||
|
||||
<b>{$userdetails.name}</b>
|
||||
<b>{$userdetails?.name ?? '<Username>'}</b>
|
||||
<LogoutButton clss="as-link small subtle text-sm" osmConnection={state.osmConnection} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -169,12 +170,14 @@
|
|||
/>
|
||||
</Page>
|
||||
|
||||
{#if $nrOfFailedImages.length > 0 || $failedImagesOpen}
|
||||
{#if $nrOfFailedImages.length > 0 || $failedImagesOpen || $pendingChanges?.length > 0 }
|
||||
<Page {onlyLink} shown={pg.failedImages} bodyPadding="p-0 pb-4">
|
||||
<svelte:fragment slot="header">
|
||||
<PhotoIcon />
|
||||
<Tr t={Translations.t.imageQueue.menu.Subs({ count: $nrOfFailedImages.length })} />
|
||||
<Tr
|
||||
t={Translations.t.imageQueue.menu.Subs({ count: ($nrOfFailedImages?.length ?? 0) + ($pendingChanges?.length ?? 0) })} />
|
||||
</svelte:fragment>
|
||||
<PendingChangesView {state} />
|
||||
<QueuedImagesView {state} />
|
||||
</Page>
|
||||
{/if}
|
||||
|
|
@ -281,7 +284,7 @@
|
|||
<Tr t={Translations.t.inspector.menu} />
|
||||
</a>
|
||||
|
||||
{#if !state.theme}
|
||||
{#if !state?.theme}
|
||||
<a class="flex" href={($isAndroid ? "https://mapcomplete.org" : ".") +`/statistics.html`}
|
||||
target="_blank">
|
||||
<ChartBar class="h-6 w-6" />
|
||||
|
|
|
|||
58
src/UI/BigComponents/PendingChangesView.svelte
Normal file
58
src/UI/BigComponents/PendingChangesView.svelte
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<script lang="ts">
|
||||
import { Changes } from "../../Logic/Osm/Changes"
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
|
||||
export let state: { changes: Changes } & SpecialVisualizationState
|
||||
let pending = state.changes.pendingChanges
|
||||
let backend = state.osmConnection.Backend()
|
||||
let debug = state.featureSwitches.featureSwitchIsDebugging
|
||||
</script>
|
||||
{#if $pending?.length > 0}
|
||||
<div class="p-4">
|
||||
|
||||
<h3>Pending changes</h3>
|
||||
|
||||
There are currently {$pending.length} pending changes:
|
||||
|
||||
<table class="gap-x-2">
|
||||
<tr>
|
||||
<th>
|
||||
Theme
|
||||
</th>
|
||||
<th>
|
||||
Type
|
||||
</th>
|
||||
<th>
|
||||
Object
|
||||
</th>
|
||||
</tr>
|
||||
{#each $pending as change}
|
||||
<tr>
|
||||
<td>{change.meta.theme}</td>
|
||||
<td>{change.meta.changeType}</td>
|
||||
<td>
|
||||
<a href={`${backend}/${change.type}/${change.id}`} target="_blank">
|
||||
{change.type}/{change.id}
|
||||
</a>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
|
||||
{#if $debug}
|
||||
{#each $pending as change}
|
||||
{JSON.stringify(change)}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{/if}
|
||||
|
||||
|
||||
<style>
|
||||
td {
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import { IsOnline } from "../../Logic/Web/IsOnline"
|
||||
|
||||
export let state: ThemeViewState
|
||||
/**
|
||||
|
|
@ -14,6 +15,7 @@
|
|||
|
||||
let dataIsLoading = state.dataIsLoading
|
||||
let currentState = state.hasDataInView
|
||||
let online = IsOnline.isOnline
|
||||
const t = Translations.t.centerMessage
|
||||
const showingSearch = state.searchState.showSearchDrawer
|
||||
</script>
|
||||
|
|
@ -34,6 +36,10 @@
|
|||
<Tr t={Translations.t.centerMessage.loadingData} />
|
||||
</Loading>
|
||||
</div>
|
||||
{:else if $currentState === "no-data" && !$online}
|
||||
<div class="alert w-fit p-4">
|
||||
<Tr t={t.noDataOffline} />
|
||||
</div>
|
||||
{:else if $currentState === "no-data"}
|
||||
<div class="alert w-fit p-4">
|
||||
<Tr t={t.noData} />
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { fade } from "svelte/transition"
|
||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
import { onDestroy } from "svelte"
|
||||
import Avatar from "../Base/Avatar.svelte"
|
||||
|
||||
let open = false
|
||||
export let state: { osmConnection: OsmConnection }
|
||||
|
|
@ -28,9 +29,7 @@
|
|||
>
|
||||
{#if $username !== undefined}
|
||||
<div style="width: max-content" class="flex items-center">
|
||||
{#if $userdetails.img}
|
||||
<img src={$userdetails.img} alt="avatar" class="mr-4 h-10 w-10 rounded-full" />
|
||||
{/if}
|
||||
<Avatar {userdetails} />
|
||||
<div>
|
||||
<div>Welcome back</div>
|
||||
<div class="normal-background" style="width: max-content">
|
||||
|
|
|
|||
|
|
@ -13,9 +13,10 @@
|
|||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||
import GlobeAlt from "@babeard/svelte-heroicons/mini/GlobeAlt"
|
||||
import { ComparisonState } from "./ComparisonState"
|
||||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
import { IsOnline } from "../../Logic/Web/IsOnline"
|
||||
import GlobeAlt from "@babeard/svelte-heroicons/mini/GlobeAlt"
|
||||
|
||||
export let externalData: Store<
|
||||
| { success: { content: Record<string, string> } }
|
||||
|
|
@ -33,7 +34,7 @@
|
|||
* A switch that signals that the information should be downloaded.
|
||||
* The actual 'download' code is _not_ implemented here
|
||||
*/
|
||||
export let downloadInformation : UIEventSource<boolean>
|
||||
export let downloadInformation: UIEventSource<boolean>
|
||||
export let collapsed: boolean
|
||||
const t = Translations.t.external
|
||||
|
||||
|
|
@ -48,45 +49,47 @@
|
|||
let propertyKeysExternal = comparisonState.mapD((ct) => ct.propertyKeysExternal)
|
||||
let hasDifferencesAtStart = comparisonState.mapD((ct) => ct.hasDifferencesAtStart)
|
||||
let enableLogin = state.featureSwitches.featureSwitchEnableLogin
|
||||
const online = IsOnline.isOnline
|
||||
</script>
|
||||
|
||||
<LoginToggle {state} silentFail>
|
||||
{#if !$sourceUrl || !$enableLogin}
|
||||
<!-- empty block -->
|
||||
{#if $online}
|
||||
<LoginToggle {state} hiddenFail>
|
||||
{#if !$sourceUrl || !$enableLogin}
|
||||
<!-- empty block -->
|
||||
{:else if !$downloadInformation}
|
||||
<button on:click={() => downloadInformation.set(true)}>
|
||||
Attempt to download information from the website {$sourceUrl}
|
||||
</button>
|
||||
{:else if $externalData === undefined}
|
||||
<div class="flex justify-center">
|
||||
<Loading />
|
||||
</div>
|
||||
{:else if $externalData["error"] !== undefined}
|
||||
<div class="subtle low-interaction rounded p-2 px-4 italic">
|
||||
<Tr t={Translations.t.external.error} />
|
||||
</div>
|
||||
{:else if $propertyKeysExternal.length === 0 && $knownImages.size + $unknownImages.length === 0}
|
||||
<Tr cls="subtle" t={t.noDataLoaded} />
|
||||
{:else if !$hasDifferencesAtStart}
|
||||
<button on:click={() => downloadInformation.set(true)}>
|
||||
Attempt to download information from the website {$sourceUrl}
|
||||
</button>
|
||||
{:else if $externalData === undefined}
|
||||
<div class="flex justify-center">
|
||||
<Loading />
|
||||
</div>
|
||||
{:else if $externalData["error"] !== undefined}
|
||||
<div class="subtle low-interaction rounded p-2 px-4 italic">
|
||||
<Tr t={Translations.t.external.error} />
|
||||
</div>
|
||||
{:else if $propertyKeysExternal.length === 0 && $knownImages.size + $unknownImages.length === 0}
|
||||
<Tr cls="subtle" t={t.noDataLoaded} />
|
||||
{:else if !$hasDifferencesAtStart}
|
||||
<span class="subtle text-sm">
|
||||
<Tr t={t.allIncluded.Subs({ source: $sourceUrl })} />
|
||||
</span>
|
||||
{:else if $comparisonState !== undefined}
|
||||
<AccordionSingle expanded={!collapsed}>
|
||||
{:else if $comparisonState !== undefined}
|
||||
<AccordionSingle expanded={!collapsed}>
|
||||
<span slot="header" class="flex">
|
||||
<GlobeAlt class="h-6 w-6" />
|
||||
<Tr t={Translations.t.external.title} />
|
||||
</span>
|
||||
<ComparisonTable
|
||||
externalProperties={$externalData["success"]}
|
||||
{state}
|
||||
{feature}
|
||||
{layer}
|
||||
{tags}
|
||||
{readonly}
|
||||
sourceUrl={$sourceUrl}
|
||||
comparisonState={$comparisonState}
|
||||
/>
|
||||
</AccordionSingle>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
<ComparisonTable
|
||||
externalProperties={$externalData["success"]}
|
||||
{state}
|
||||
{feature}
|
||||
{layer}
|
||||
{tags}
|
||||
{readonly}
|
||||
sourceUrl={$sourceUrl}
|
||||
comparisonState={$comparisonState}
|
||||
/>
|
||||
</AccordionSingle>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,11 @@
|
|||
import type { ImageUploadArguments } from "../../Logic/ImageProviders/ImageUploadQueue"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import UploadingImageCounter from "./UploadingImageCounter.svelte"
|
||||
import { IsOnline } from "../../Logic/Web/IsOnline"
|
||||
export let state: WithImageState
|
||||
let queued: Store<ImageUploadArguments[]> = state.imageUploadManager.queuedArgs
|
||||
let isUploading = state.imageUploadManager.isUploading
|
||||
let online = IsOnline.isOnline
|
||||
const t = Translations.t
|
||||
const q = t.imageQueue
|
||||
</script>
|
||||
|
|
@ -27,7 +29,7 @@
|
|||
|
||||
{#if $isUploading}
|
||||
<Loading />
|
||||
{:else}
|
||||
{:else if $online}
|
||||
<button class="primary" on:click={() => state.imageUploadManager.uploadQueue()}>
|
||||
<ArrowPathIcon class="m-1 h-8 w-8" />
|
||||
<Tr t={q.retryAll} />
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
let maintenanceBusy = false
|
||||
</script>
|
||||
|
||||
<LoginToggle {state}>
|
||||
<LoginToggle {state} offline>
|
||||
<LoginButton clss="small w-full" osmConnection={state.osmConnection} slot="not-logged-in">
|
||||
<Tr t={Translations.t.image.pleaseLogin} />
|
||||
</LoginButton>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
import Tr from "../Base/Tr.svelte"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import UploadFailedMessage from "./UploadFailedMessage.svelte"
|
||||
import { IsOnline } from "../../Logic/Web/IsOnline"
|
||||
|
||||
export let state: SpecialVisualizationState
|
||||
export let tags: Store<OsmTags> = undefined
|
||||
|
|
@ -59,6 +60,7 @@
|
|||
failed.addCallbackAndRun((failed) => {
|
||||
dismissed = Math.min(failed, dismissed)
|
||||
})
|
||||
let online = IsOnline.isOnline
|
||||
let progress = state.imageUploadManager.progressCurrentImage
|
||||
</script>
|
||||
|
||||
|
|
@ -91,8 +93,11 @@
|
|||
</Loading>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $failed > dismissed}
|
||||
{#if !$online && $pending > 0}
|
||||
<div class="alert">
|
||||
<Tr t={t.upload.offline} />
|
||||
</div>
|
||||
{:else if $failed > dismissed}
|
||||
<UploadFailedMessage failed={$failed} on:click={() => (dismissed = $failed)} {state} />
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
function clearCaches() {
|
||||
IdbLocalStorage.clearAll()
|
||||
Utils.download("./service-worker-clear")
|
||||
Utils.download("./service-worker/clear_caches.json")
|
||||
window.location.reload()
|
||||
}
|
||||
export let msg: string
|
||||
|
|
|
|||
|
|
@ -17,20 +17,18 @@ export class DeleteFlowState {
|
|||
private readonly _id: OsmId
|
||||
private readonly _allowDeletionAtChangesetCount: number
|
||||
private readonly _osmConnection: OsmConnection
|
||||
private readonly state: SpecialVisualizationState
|
||||
|
||||
constructor(
|
||||
id: OsmId,
|
||||
state: SpecialVisualizationState,
|
||||
allowDeletionAtChangesetCount?: number
|
||||
) {
|
||||
this.state = state
|
||||
this.objectDownloader = state.osmObjectDownloader
|
||||
this._id = id
|
||||
this._osmConnection = state.osmConnection
|
||||
this._allowDeletionAtChangesetCount = allowDeletionAtChangesetCount ?? Number.MAX_VALUE
|
||||
|
||||
this.CheckDeleteability(false)
|
||||
this.checkDeleteability(false)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -39,7 +37,7 @@ export class DeleteFlowState {
|
|||
* @constructor
|
||||
* @private
|
||||
*/
|
||||
public CheckDeleteability(useTheInternet: boolean): void {
|
||||
public checkDeleteability(useTheInternet: boolean): void {
|
||||
console.log("Checking deleteability (internet?", useTheInternet, ")")
|
||||
const t = Translations.t.delete
|
||||
const id = this._id
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
import Invalid from "../../../assets/svg/Invalid.svelte"
|
||||
import { And } from "../../../Logic/Tags/And"
|
||||
import type { UploadableTag } from "../../../Logic/Tags/TagTypes"
|
||||
import { IsOnline } from "../../../Logic/Web/IsOnline"
|
||||
|
||||
export let state: SpecialVisualizationState
|
||||
export let deleteConfig: DeleteConfig
|
||||
|
|
@ -39,9 +40,10 @@
|
|||
const canBeDeletedReason = deleteAbility.canBeDeletedReason
|
||||
|
||||
const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined
|
||||
const online = IsOnline.isOnline
|
||||
let currentState: "confirm" | "applying" | "deleted" = "confirm"
|
||||
$: {
|
||||
deleteAbility.CheckDeleteability(true)
|
||||
deleteAbility.checkDeleteability(true)
|
||||
}
|
||||
|
||||
const t = Translations.t.delete
|
||||
|
|
@ -97,8 +99,10 @@
|
|||
currentState = "deleted"
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoginToggle ignoreLoading={true} {state} silentFail>
|
||||
{#if !$online}
|
||||
<div class="subtle">You are offline. Deleting points is not possible</div>
|
||||
{:else}
|
||||
<LoginToggle ignoreLoading={true} {state} hiddenFail>
|
||||
{#if $canBeDeleted === false && !hasSoftDeletion}
|
||||
<div class="low-interaction subtle flex gap-x-1 rounded p-2 text-sm italic">
|
||||
<div class="relative h-fit">
|
||||
|
|
@ -171,3 +175,4 @@
|
|||
</AccordionSingle>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -7,41 +7,45 @@
|
|||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
import type { Feature } from "geojson"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { IsOnline } from "../../Logic/Web/IsOnline"
|
||||
import { Store } from "../../Logic/UIEventSource.js"
|
||||
|
||||
/**
|
||||
* A full-blown 'mark as favourite'-button
|
||||
*/
|
||||
export let state: SpecialVisualizationState
|
||||
export let feature: Feature
|
||||
export let tags: Record<string, string>
|
||||
export let tags: Store<Record<string, string>>
|
||||
export let layer: LayerConfig
|
||||
let isFavourite = tags?.map((tags) => tags._favourite === "yes")
|
||||
const t = Translations.t.favouritePoi
|
||||
const online = IsOnline.isOnline
|
||||
|
||||
function markFavourite(isFavourite: boolean) {
|
||||
state.favourites.markAsFavourite(feature, layer.id, state.theme.id, tags, isFavourite)
|
||||
}
|
||||
</script>
|
||||
|
||||
<LoginToggle ignoreLoading={true} {state}>
|
||||
{#if $isFavourite}
|
||||
<div class="flex h-fit items-start">
|
||||
<button class="w-full" on:click={() => markFavourite(false)}>
|
||||
<HeartSolidIcon class="mr-2 w-16 shrink-0" on:click={() => markFavourite(false)} />
|
||||
<div class="flex flex-col items-start">
|
||||
<Tr t={t.button.unmark} />
|
||||
<Tr cls="normal-font subtle" t={t.button.unmarkNotDeleted} />
|
||||
{#if $online}
|
||||
<LoginToggle ignoreLoading hiddenFail {state}>
|
||||
{#if $isFavourite}
|
||||
<div class="flex h-fit items-start">
|
||||
<button class="w-full" on:click={() => markFavourite(false)}>
|
||||
<HeartSolidIcon class="mr-2 w-16 shrink-0" on:click={() => markFavourite(false)} />
|
||||
<div class="flex flex-col items-start">
|
||||
<Tr t={t.button.unmark} />
|
||||
<Tr cls="normal-font subtle" t={t.button.unmarkNotDeleted} />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<Tr cls="font-bold thanks m-2 p-2 block" t={t.button.isFavourite} />
|
||||
{:else}
|
||||
<button class="w-full" on:click={() => markFavourite(true)}>
|
||||
<HeartOutlineIcon class="mr-2 w-16 shrink-0" on:click={() => markFavourite(true)} />
|
||||
<div class="flex w-full flex-col items-start">
|
||||
<Tr t={t.button.markAsFavouriteTitle} />
|
||||
<Tr cls="normal-font subtle" t={t.button.markDescription} />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<Tr cls="font-bold thanks m-2 p-2 block" t={t.button.isFavourite} />
|
||||
{:else}
|
||||
<button class="w-full" on:click={() => markFavourite(true)}>
|
||||
<HeartOutlineIcon class="mr-2 w-16 shrink-0" on:click={() => markFavourite(true)} />
|
||||
<div class="flex w-full flex-col items-start">
|
||||
<Tr t={t.button.markAsFavouriteTitle} />
|
||||
<Tr cls="normal-font subtle" t={t.button.markDescription} />
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
{/if}
|
||||
</LoginToggle>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@
|
|||
{layer}
|
||||
extraClasses="my-2"
|
||||
/>
|
||||
{#if (!editingEnabled || $editingEnabled) && $apiState !== "readonly" && $apiState !== "offline"}
|
||||
{#if !editingEnabled || $editingEnabled}
|
||||
<EditButton
|
||||
arialabel={config.editButtonAriaLabel}
|
||||
ariaLabelledBy={answerId}
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@
|
|||
{#if question !== undefined && $apiState !== "readonly" && $apiState !== "offline"}
|
||||
<div class={clss}>
|
||||
{#if layer?.isNormal()}
|
||||
<LoginToggle {state}>
|
||||
<LoginToggle {state} offline hiddenFail>
|
||||
<DotMenu hideBackground={true} open={menuIsOpened}>
|
||||
<SidebarUnit>
|
||||
{#if $disabledInTheme.indexOf(config.id) >= 0}
|
||||
|
|
@ -538,7 +538,7 @@
|
|||
{/if}
|
||||
|
||||
<!-- Save and cancel buttons, in a logintoggle -->
|
||||
<LoginToggle {state} ignoreLoading>
|
||||
<LoginToggle {state} ignoreLoading offline>
|
||||
<div class="flex w-full justify-end" slot="not-logged-in">
|
||||
{#if config.alwaysForceSaveButton}
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
import { WithSearchState } from "../Models/ThemeViewState/WithSearchState"
|
||||
import ThemeConfig from "../Models/ThemeConfig/ThemeConfig"
|
||||
import { AndroidPolyfill } from "../Logic/Web/AndroidPolyfill"
|
||||
import { InstallServiceWorker } from "../InstallServiceWorker"
|
||||
import type { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
|
||||
|
||||
function webgl_support() {
|
||||
try {
|
||||
|
|
@ -51,6 +53,36 @@
|
|||
|
||||
let availableLayers = UIEventSource.fromPromise(getAvailableLayers())
|
||||
const state = new WithSearchState(theme, availableLayers)
|
||||
InstallServiceWorker.installServiceWorker().then(() => {
|
||||
if (theme.source._usedImages) {
|
||||
|
||||
window.requestIdleCallback(() => {
|
||||
InstallServiceWorker.precache(theme.source._usedImages?.filter(i => i.startsWith("./")))
|
||||
})
|
||||
}
|
||||
for (const layer of (<LayerConfigJson[]>theme.source.layers)) {
|
||||
if (!Constants.isPriviliged(layer)) {
|
||||
continue
|
||||
}
|
||||
if (!layer["_usedImages"]) {
|
||||
continue
|
||||
}
|
||||
// The priviliged layers, which are injected, might have assets not included in the '_usedImages' of the theme
|
||||
window.requestIdleCallback(() => {
|
||||
InstallServiceWorker.precache(layer["_usedImages"]?.filter(i => i.startsWith("./")))
|
||||
})
|
||||
|
||||
// The NSI
|
||||
window.requestIdleCallback(() => {
|
||||
InstallServiceWorker.precache(
|
||||
[Constants.nsiLogosEndpoint + "nsi.min.json",
|
||||
Constants.nsiLogosEndpoint + "featureCollection.min.json",
|
||||
],
|
||||
)
|
||||
})
|
||||
|
||||
}
|
||||
}).catch(e => console.error("Could not install service worker:", e))
|
||||
</script>
|
||||
|
||||
{#if !webgl_supported}
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class GpsAllTags extends SpecialVisualizationSvelte {
|
|||
}
|
||||
}
|
||||
|
||||
class StorageAlLTags extends SpecialVisualizationSvelte {
|
||||
class StorageAllTags extends SpecialVisualizationSvelte {
|
||||
funcName = "storage_all_tags"
|
||||
group = "settings"
|
||||
docs = "Shows the current state of storage"
|
||||
|
|
@ -103,6 +103,27 @@ class StorageAlLTags extends SpecialVisualizationSvelte {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class ServiceWorkerAllTags extends SpecialVisualizationSvelte {
|
||||
funcName = "serviceworker_all_tags"
|
||||
group = "settings"
|
||||
docs = "Shows the current state of service worker"
|
||||
args = []
|
||||
|
||||
constr(state: SpecialVisualizationState): SvelteUIElement {
|
||||
const data = {}
|
||||
for (const key in localStorage) {
|
||||
data[key] = localStorage[key]
|
||||
}
|
||||
const tags = UIEventSource.fromPromise<Record<string, any>>(
|
||||
Utils.downloadJson(
|
||||
"./service-worker/status.json")
|
||||
)
|
||||
|
||||
return new SvelteUIElement(AllTagsPanel, { state, tags })
|
||||
}
|
||||
}
|
||||
|
||||
export class ClearCachesVis extends SpecialVisualizationSvelte {
|
||||
funcName = "clear_caches"
|
||||
docs =
|
||||
|
|
@ -234,7 +255,8 @@ export class SettingsVisualisations {
|
|||
new DisabledQuestionsVis(),
|
||||
new GyroscopeAllTags(),
|
||||
new GpsAllTags(),
|
||||
new StorageAlLTags(),
|
||||
new StorageAllTags(),
|
||||
new ServiceWorkerAllTags(),
|
||||
new ClearCachesVis(),
|
||||
new LoginButtonVis(),
|
||||
new QrLogin(),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { QueryParameters } from "./Logic/Web/QueryParameters"
|
||||
import AllThemesGui from "./UI/AllThemesGui.svelte"
|
||||
import { InstallServiceWorker } from "./InstallServiceWorker"
|
||||
|
||||
const theme = QueryParameters.GetQueryParameter("layout", undefined).data ?? ""
|
||||
const customLayout = QueryParameters.GetQueryParameter("userlayout", undefined).data ?? ""
|
||||
|
|
@ -26,7 +27,7 @@ if (theme !== "") {
|
|||
l.protocol + "//" + window.location.host + "/theme.html" + l.search + l.hash
|
||||
)
|
||||
}
|
||||
|
||||
InstallServiceWorker.installServiceWorker().catch(e => console.error(e))
|
||||
new AllThemesGui({
|
||||
target: document.getElementById("main"),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export class SWGenerated {
|
||||
// generated by scripts/prepareServiceWorker.ts
|
||||
static vNumber = "0.54.4"
|
||||
static vNumber = "0.54.6-1754230245885"
|
||||
}
|
||||
|
|
@ -37,6 +37,14 @@ async function listCachedRequests(): Promise<string[]> {
|
|||
return requests.map(req => req.url)
|
||||
}
|
||||
|
||||
async function clearCaches(): Promise<void> {
|
||||
const cache = await caches.open(SWGenerated.vNumber)
|
||||
const keys = await cache.keys()
|
||||
for (const key of keys) {
|
||||
await cache.delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
class Router {
|
||||
private readonly _endpoints: Record<string, (event: FetchEvent) => void>
|
||||
private readonly _subpaths: Record<string, (event: FetchEvent, rest: string) => void>
|
||||
|
|
@ -71,12 +79,33 @@ class Router {
|
|||
|
||||
|
||||
const allOffline = new Router({
|
||||
"clear_caches.Json": (event) => {
|
||||
event.respondWith(
|
||||
clearCaches().then(() =>
|
||||
jsonResponse({ status: "ok" })
|
||||
)
|
||||
)
|
||||
},
|
||||
"precache": (event) => {
|
||||
const url = new URL(event.request.url)
|
||||
const assets = url.searchParams.get("assets")?.split(";")
|
||||
if (assets) {
|
||||
console.log("Precaching:", assets)
|
||||
event.waitUntil(caches.open(SWGenerated.vNumber).then(cache =>
|
||||
cache.addAll(assets)
|
||||
))
|
||||
}
|
||||
event.respondWith(jsonResponse({ status: "ok" }))
|
||||
},
|
||||
"status.json": (event) => {
|
||||
event.respondWith(
|
||||
listCachedRequests().then(cached =>
|
||||
jsonResponse(
|
||||
{
|
||||
status: "ok", cached,
|
||||
status: "ok",
|
||||
vnumber: SWGenerated.vNumber,
|
||||
domain: selfDomain,
|
||||
cached
|
||||
}
|
||||
))
|
||||
)
|
||||
|
|
@ -89,7 +118,6 @@ self.addEventListener("fetch", (event) => {
|
|||
if (url.endsWith("/service-worker.js")) {
|
||||
return // Installation of a new version, we don't interfere
|
||||
}
|
||||
console.log("Intercepting event", event.request.url)
|
||||
if (url.indexOf("/service-worker/") >= 0) {
|
||||
allOffline.route(event)
|
||||
return
|
||||
|
|
@ -99,6 +127,11 @@ self.addEventListener("fetch", (event) => {
|
|||
respondFromCache(event)
|
||||
return
|
||||
}
|
||||
if (urlObj.hostname === "data.mapcomplete.org") {
|
||||
respondFromCache(event)
|
||||
return
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
self.addEventListener("install", () => self.skipWaiting())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue