UX+Refactoring: use side-drawer for menu, reorder menu structure
This commit is contained in:
parent
8465b59c7f
commit
124e816abe
25 changed files with 645 additions and 1059 deletions
|
@ -811,14 +811,14 @@ video {
|
|||
z-index: -10;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.z-50 {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.z-30 {
|
||||
z-index: 30;
|
||||
}
|
||||
|
@ -943,6 +943,11 @@ video {
|
|||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.my-4 {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
|
@ -963,11 +968,6 @@ video {
|
|||
margin-bottom: -0.25rem;
|
||||
}
|
||||
|
||||
.mx-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.-mx-1\.5 {
|
||||
margin-left: -0.375rem;
|
||||
margin-right: -0.375rem;
|
||||
|
@ -1037,6 +1037,10 @@ video {
|
|||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.mr-10 {
|
||||
margin-right: 2.5rem;
|
||||
}
|
||||
|
@ -1053,10 +1057,6 @@ video {
|
|||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.mt-2 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
@ -1999,14 +1999,14 @@ video {
|
|||
gap: 1rem;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.gap-3 {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.gap-8 {
|
||||
gap: 2rem;
|
||||
}
|
||||
|
@ -2043,6 +2043,12 @@ video {
|
|||
row-gap: 2rem;
|
||||
}
|
||||
|
||||
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.space-x-0\.5 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(0.125rem * var(--tw-space-x-reverse));
|
||||
|
@ -2091,12 +2097,6 @@ video {
|
|||
margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
|
||||
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
||||
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.-space-x-px > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(-1px * var(--tw-space-x-reverse));
|
||||
|
@ -2702,6 +2702,11 @@ video {
|
|||
border-color: inherit;
|
||||
}
|
||||
|
||||
.border-t-gray-300 {
|
||||
--tw-border-opacity: 1;
|
||||
border-top-color: rgb(209 213 219 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-opacity-50 {
|
||||
--tw-border-opacity: 0.5;
|
||||
}
|
||||
|
@ -3344,11 +3349,6 @@ video {
|
|||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.py-8 {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.px-3 {
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
|
@ -4084,6 +4084,12 @@ video {
|
|||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.shadow-sm {
|
||||
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
|
||||
|
@ -4108,12 +4114,6 @@ video {
|
|||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.shadow-md {
|
||||
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.shadow-gray-300 {
|
||||
--tw-shadow-color: #d1d5db;
|
||||
--tw-shadow: var(--tw-shadow-colored);
|
||||
|
@ -4249,13 +4249,13 @@ video {
|
|||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
}
|
||||
|
||||
.drop-shadow-md {
|
||||
--tw-drop-shadow: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06));
|
||||
.drop-shadow-2xl {
|
||||
--tw-drop-shadow: drop-shadow(0 25px 25px rgb(0 0 0 / 0.15));
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
}
|
||||
|
||||
.drop-shadow-2xl {
|
||||
--tw-drop-shadow: drop-shadow(0 25px 25px rgb(0 0 0 / 0.15));
|
||||
.drop-shadow-md {
|
||||
--tw-drop-shadow: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06));
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
}
|
||||
|
||||
|
@ -4806,6 +4806,10 @@ code {
|
|||
color: var(--background-color);
|
||||
}
|
||||
|
||||
.h-full-child > div {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bg-black-transparent {
|
||||
background-color: #00000088;
|
||||
}
|
||||
|
@ -5964,16 +5968,16 @@ svg.apply-fill path {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.dark\:divide-gray-600 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-opacity: 1;
|
||||
border-color: rgb(75 85 99 / var(--tw-divide-opacity));
|
||||
}
|
||||
|
||||
.dark\:divide-gray-700 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-opacity: 1;
|
||||
border-color: rgb(55 65 81 / var(--tw-divide-opacity));
|
||||
}
|
||||
|
||||
.dark\:divide-gray-600 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-opacity: 1;
|
||||
border-color: rgb(75 85 99 / var(--tw-divide-opacity));
|
||||
}
|
||||
|
||||
.dark\:divide-gray-800 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-opacity: 1;
|
||||
border-color: rgb(31 41 55 / var(--tw-divide-opacity));
|
||||
|
@ -7248,6 +7252,10 @@ svg.apply-fill path {
|
|||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.sm\:flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sm\:flex-nowrap {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
import Hash from "./Hash"
|
||||
import { MenuState } from "../../Models/MenuState"
|
||||
import hash from "svelte/types/compiler/compile/utils/hash"
|
||||
|
||||
export default class ThemeViewStateHashActor {
|
||||
private readonly _state: ThemeViewState
|
||||
|
@ -10,18 +11,16 @@ export default class ThemeViewStateHashActor {
|
|||
"",
|
||||
"- The id of the currently selected object, e.g. `node/1234`",
|
||||
"- The currently opened menu view",
|
||||
"- The base64-encoded JSON-file specifying a custom theme (only when loading)",
|
||||
"",
|
||||
"### Possible hashes to open a menu",
|
||||
"",
|
||||
"The possible hashes are:",
|
||||
"",
|
||||
MenuState._menuviewTabs.map((tab) => "`menu:" + tab + "`").join(","),
|
||||
MenuState._themeviewTabs.map((tab) => "`theme-menu:" + tab + "`").join(","),
|
||||
MenuState.pageNames.map((tab) => "`" + tab + "`").join(",")
|
||||
]
|
||||
|
||||
/**
|
||||
* Converts the hash to the appropriate themeview state and, vice versa, sets the hash.
|
||||
* Converts the hash to the appropriate theme-view state and, vice versa, sets the hash.
|
||||
*
|
||||
* As the navigator-back-button changes the hash first, this class thus also handles the 'back'-button events.
|
||||
*
|
||||
|
@ -33,46 +32,38 @@ export default class ThemeViewStateHashActor {
|
|||
constructor(state: ThemeViewState) {
|
||||
this._state = state
|
||||
|
||||
const hashOnLoad = Hash.hash.data
|
||||
const containsMenu = this.loadStateFromHash(hashOnLoad)
|
||||
// First of all, try to recover the selected element
|
||||
if (Hash.hash.data) {
|
||||
const hash = Hash.hash.data
|
||||
this.loadStateFromHash(hash)
|
||||
Hash.hash.setData(hash) // reapply the previous hash
|
||||
state.indexedFeatures.featuresById.addCallbackAndRunD((_) => {
|
||||
let unregister = this.loadSelectedElementFromHash(hash)
|
||||
if (!containsMenu && hashOnLoad.length > 0) {
|
||||
state.indexedFeatures.featuresById.addCallbackAndRunD(() => {
|
||||
// once that we have found a matching element, we can be sure the indexedFeaturesource was popuplated and that the job is done
|
||||
return unregister
|
||||
return this.loadSelectedElementFromHash(hashOnLoad)
|
||||
})
|
||||
}
|
||||
|
||||
// Register a hash change listener to correctly handle the back button
|
||||
Hash.hash.addCallback((hash) => {
|
||||
if (!!hash) {
|
||||
// There is still a hash
|
||||
// We _only_ have to (at most) close the overlays in this case
|
||||
if (state.previewedImage.data) {
|
||||
state.previewedImage.setData(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
const parts = hash.split(";")
|
||||
if (parts.indexOf("background") < 0) {
|
||||
state.guistate.backgroundLayerSelectionIsOpened.setData(false)
|
||||
}
|
||||
this.loadSelectedElementFromHash(hash)
|
||||
} else {
|
||||
this.back()
|
||||
}
|
||||
})
|
||||
|
||||
// At last, register callbacks on the state to update the hash when they change.
|
||||
// Note: these should use 'addCallback', not 'addCallbackAndRun'
|
||||
state.selectedElement.addCallback((_) => this.setHash())
|
||||
state.guistate.allToggles.forEach(({ toggle, submenu }) => {
|
||||
submenu?.addCallback((_) => this.setHash())
|
||||
toggle.addCallback((_) => this.setHash())
|
||||
state.selectedElement.addCallback(() => this.setHash())
|
||||
|
||||
// Register a hash change listener to correctly handle the back button
|
||||
Hash.hash.addCallback((hash) => {
|
||||
console.trace("Going back with hash", hash)
|
||||
if (!hash) {
|
||||
this.back()
|
||||
}else{
|
||||
if(!this.loadStateFromHash(hash)){
|
||||
this.loadSelectedElementFromHash(hash)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
for (const key in state.guistate.pageStates) {
|
||||
const toggle = state.guistate.pageStates[key]
|
||||
toggle.addCallback(() => this.setHash())
|
||||
}
|
||||
|
||||
// When all is done, set the hash. This must happen last to give the code above correct info
|
||||
this.setHash()
|
||||
}
|
||||
|
@ -80,15 +71,10 @@ export default class ThemeViewStateHashActor {
|
|||
/**
|
||||
* Selects the appropriate element
|
||||
* Returns true if this method can be unregistered for the first run
|
||||
* @param hash
|
||||
* @private
|
||||
*/
|
||||
private loadSelectedElementFromHash(hash: string): boolean {
|
||||
const state = this._state
|
||||
const selectedElement = state.selectedElement
|
||||
// state.indexedFeatures.featuresById.stabilized(250)
|
||||
|
||||
hash = hash.split(";")[0] // The 'selectedElement' is always the _first_ item in the hash (if any)
|
||||
|
||||
// Set the hash based on the selected element...
|
||||
// ... search and select an element based on the hash
|
||||
|
@ -101,7 +87,7 @@ export default class ThemeViewStateHashActor {
|
|||
if (!found) {
|
||||
return false
|
||||
}
|
||||
if (found.properties.id === "last_click") {
|
||||
if (found.properties.id.startsWith("last_click")) {
|
||||
return true
|
||||
}
|
||||
console.log(
|
||||
|
@ -114,82 +100,53 @@ export default class ThemeViewStateHashActor {
|
|||
return true
|
||||
}
|
||||
|
||||
private loadStateFromHash(hash: string) {
|
||||
const state = this._state
|
||||
for (const superpart of hash.split(";")) {
|
||||
const parts = superpart.at(-1)?.split(":") ?? []
|
||||
|
||||
outer: for (const { toggle, name, submenu } of state.guistate.allToggles) {
|
||||
for (const part of parts) {
|
||||
if (part.indexOf(":") < 0) {
|
||||
if (part === name) {
|
||||
toggle.setData(true)
|
||||
continue outer
|
||||
}
|
||||
continue
|
||||
}
|
||||
const [main, submenuValue] = part.split(":")
|
||||
if (part !== main) {
|
||||
continue
|
||||
}
|
||||
toggle.setData(true)
|
||||
submenu?.setData(submenuValue)
|
||||
continue outer
|
||||
}
|
||||
|
||||
// If we arrive here, the loop above has not found any match
|
||||
toggle.setData(false)
|
||||
private loadStateFromHash(hash: string): boolean {
|
||||
for (const page in this._state.guistate.pageStates) {
|
||||
if (page === hash) {
|
||||
const toggle = this._state.guistate.pageStates[page]
|
||||
toggle.set(true)
|
||||
console.log("Loading menu view from hash:", page)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private setHash() {
|
||||
const s = this._state
|
||||
let h = ""
|
||||
|
||||
for (const { toggle, showOverOthers, name, submenu } of s.guistate.allToggles) {
|
||||
if (showOverOthers || !toggle.data) {
|
||||
continue
|
||||
}
|
||||
h = name
|
||||
if (submenu?.data) {
|
||||
h += ":" + submenu.data
|
||||
/**
|
||||
* Sets the hash based on:
|
||||
*
|
||||
* 1. Selected element ID
|
||||
* 2. A selected 'page' from the menu
|
||||
*
|
||||
* returns 'true' if a hash was set
|
||||
*/
|
||||
private setHash(): boolean {
|
||||
const selectedElement = this._state.selectedElement.data
|
||||
if(selectedElement){
|
||||
Hash.hash.set(selectedElement.properties.id)
|
||||
return true
|
||||
}
|
||||
for (const page in this._state.guistate.pageStates) {
|
||||
const toggle = this._state.guistate.pageStates[page]
|
||||
if (toggle.data) {
|
||||
Hash.hash.set(page)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (s.selectedElement.data !== undefined) {
|
||||
h = s.selectedElement.data.properties.id
|
||||
}
|
||||
|
||||
for (const { toggle, showOverOthers, name, submenu } of s.guistate.allToggles) {
|
||||
if (!showOverOthers || !toggle.data) {
|
||||
continue
|
||||
}
|
||||
if (h) {
|
||||
h += ";" + name
|
||||
} else {
|
||||
h = name
|
||||
}
|
||||
if (submenu?.data) {
|
||||
h += ":" + submenu.data
|
||||
}
|
||||
}
|
||||
Hash.hash.setData(h)
|
||||
Hash.hash.set(undefined)
|
||||
return false
|
||||
}
|
||||
|
||||
private back() {
|
||||
const state = this._state
|
||||
console.log("Got a 'back' event")
|
||||
if (state.previewedImage.data) {
|
||||
state.previewedImage.setData(undefined)
|
||||
return
|
||||
}
|
||||
// history.pushState(null, null, window.location.pathname);
|
||||
if (state.selectedElement.data) {
|
||||
state.selectedElement.setData(undefined)
|
||||
return
|
||||
}
|
||||
if (state.guistate.closeAll()) {
|
||||
return
|
||||
}
|
||||
state.selectedElement.setData(undefined)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,10 @@ import LayerConfig from "./ThemeConfig/LayerConfig"
|
|||
import { UIEventSource } from "../Logic/UIEventSource"
|
||||
import UserRelatedState from "../Logic/State/UserRelatedState"
|
||||
import { Utils } from "../Utils"
|
||||
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
|
||||
import Zoomcontrol from "../UI/Zoomcontrol"
|
||||
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
|
||||
|
||||
export type ThemeViewTabStates = (typeof MenuState._themeviewTabs)[number]
|
||||
export type MenuViewTabStates = (typeof MenuState._menuviewTabs)[number]
|
||||
export type PageType = (typeof MenuState.pageNames)[number]
|
||||
|
||||
/**
|
||||
* Indicates if a menu is open, and if so, which tab is selected;
|
||||
|
@ -15,141 +14,44 @@ export type MenuViewTabStates = (typeof MenuState._menuviewTabs)[number]
|
|||
* Some convenience methods are provided for this as well
|
||||
*/
|
||||
export class MenuState {
|
||||
public static readonly _themeviewTabs = ["intro", "download", "copyright", "share"] as const
|
||||
public static readonly _menuviewTabs = [
|
||||
"about",
|
||||
"settings",
|
||||
"favourites",
|
||||
"community",
|
||||
"privacy",
|
||||
"advanced",
|
||||
|
||||
|
||||
public static readonly pageNames = [
|
||||
"copyright", "copyright_icons", "community_index", "hotkeys",
|
||||
"privacy", "filter", "background", "about_theme", "download", "favourites",
|
||||
"usersettings", "share"
|
||||
] as const
|
||||
public readonly themeIsOpened: UIEventSource<boolean>
|
||||
public readonly themeViewTabIndex: UIEventSource<number>
|
||||
public readonly themeViewTab: UIEventSource<ThemeViewTabStates>
|
||||
public readonly menuIsOpened: UIEventSource<boolean>
|
||||
public readonly menuViewTabIndex: UIEventSource<number>
|
||||
public readonly menuViewTab: UIEventSource<MenuViewTabStates>
|
||||
|
||||
public readonly backgroundLayerSelectionIsOpened: UIEventSource<boolean> =
|
||||
new UIEventSource<boolean>(false)
|
||||
|
||||
public readonly filtersPanelIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||
public readonly privacyPanelIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(false)
|
||||
/**
|
||||
* Standalone copyright panel
|
||||
*/
|
||||
public readonly copyrightPanelIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(
|
||||
false
|
||||
)
|
||||
|
||||
public readonly communityIndexPanelIsOpened: UIEventSource<boolean> = new UIEventSource(false)
|
||||
public readonly allToggles: {
|
||||
toggle: UIEventSource<boolean>
|
||||
name: string
|
||||
submenu?: UIEventSource<string>
|
||||
showOverOthers?: boolean
|
||||
}[]
|
||||
public readonly menuIsOpened = new UIEventSource(false)
|
||||
public readonly pageStates: Record<PageType, UIEventSource<boolean>>
|
||||
|
||||
public readonly highlightedLayerInFilters: UIEventSource<string> = new UIEventSource<string>(
|
||||
undefined
|
||||
)
|
||||
public highlightedUserSetting: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
|
||||
constructor(shouldOpenWelcomeMessage: boolean, themeid: string = "") {
|
||||
constructor(shouldShowWelcomeMessage: boolean, themeid: string) {
|
||||
// Note: this class is _not_ responsible to update the Hash, @see ThemeViewStateHashActor for this
|
||||
if (themeid) {
|
||||
themeid += "-"
|
||||
}
|
||||
this.themeIsOpened = LocalStorageSource.GetParsed(
|
||||
themeid + "thememenuisopened",
|
||||
shouldOpenWelcomeMessage
|
||||
)
|
||||
this.themeViewTabIndex = LocalStorageSource.GetParsed(themeid + "themeviewtabindex", 0)
|
||||
this.themeViewTab = this.themeViewTabIndex.sync(
|
||||
(i) => MenuState._themeviewTabs[i],
|
||||
[],
|
||||
(str) => MenuState._themeviewTabs.indexOf(<any>str)
|
||||
)
|
||||
const states = {}
|
||||
for (const pageName of MenuState.pageNames) {
|
||||
const toggle = new UIEventSource(false)
|
||||
states[pageName] = toggle
|
||||
|
||||
this.menuIsOpened = LocalStorageSource.GetParsed(themeid + "menuisopened", false)
|
||||
this.menuViewTabIndex = LocalStorageSource.GetParsed(themeid + "menuviewtabindex", 0)
|
||||
this.menuViewTab = this.menuViewTabIndex.sync(
|
||||
(i) => MenuState._menuviewTabs[i],
|
||||
[],
|
||||
(str) => MenuState._menuviewTabs.indexOf(<any>str)
|
||||
)
|
||||
this.menuIsOpened.addCallbackAndRun((isOpen) => {
|
||||
if (!isOpen) {
|
||||
this.highlightedUserSetting.setData(undefined)
|
||||
}
|
||||
})
|
||||
this.menuViewTab.addCallbackD((tab) => {
|
||||
if (tab !== "settings") {
|
||||
this.highlightedUserSetting.setData(undefined)
|
||||
}
|
||||
})
|
||||
this.filtersPanelIsOpened.addCallbackAndRun((isOpen) => {
|
||||
if (!isOpen) {
|
||||
this.highlightedLayerInFilters.setData(undefined)
|
||||
}
|
||||
})
|
||||
|
||||
this.menuIsOpened.addCallbackAndRunD((opened) => {
|
||||
if (opened) {
|
||||
this.themeIsOpened.setData(false)
|
||||
}
|
||||
})
|
||||
this.themeIsOpened.addCallbackAndRunD((opened) => {
|
||||
if (opened) {
|
||||
this.menuIsOpened.setData(false)
|
||||
}
|
||||
})
|
||||
|
||||
this.allToggles = [
|
||||
{
|
||||
toggle: this.privacyPanelIsOpened,
|
||||
name: "privacy",
|
||||
showOverOthers: true,
|
||||
},
|
||||
{
|
||||
toggle: this.copyrightPanelIsOpened,
|
||||
name: "copyright",
|
||||
showOverOthers: true,
|
||||
},
|
||||
{
|
||||
toggle: this.communityIndexPanelIsOpened,
|
||||
name: "community",
|
||||
showOverOthers: true,
|
||||
},
|
||||
{
|
||||
toggle: this.filtersPanelIsOpened,
|
||||
name: "filters",
|
||||
showOverOthers: true,
|
||||
},
|
||||
{
|
||||
toggle: this.menuIsOpened,
|
||||
name: "menu",
|
||||
submenu: this.menuViewTab,
|
||||
},
|
||||
{
|
||||
toggle: this.themeIsOpened,
|
||||
name: "theme-menu",
|
||||
submenu: this.themeViewTab,
|
||||
},
|
||||
{
|
||||
toggle: this.backgroundLayerSelectionIsOpened,
|
||||
name: "background",
|
||||
showOverOthers: true,
|
||||
},
|
||||
]
|
||||
for (const toggle of this.allToggles) {
|
||||
toggle.toggle.addCallback((isOpen) => {
|
||||
if (!isOpen) {
|
||||
this.resetZoomIfAllClosed()
|
||||
toggle.addCallback(enabled => {
|
||||
if (enabled) {
|
||||
this.menuIsOpened.set(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
this.pageStates = <Record<PageType, UIEventSource<boolean>>>states
|
||||
|
||||
const visitedBefore = LocalStorageSource.GetParsed<boolean>(
|
||||
themeid + "thememenuisopened", false
|
||||
)
|
||||
if (!visitedBefore.data && shouldShowWelcomeMessage) {
|
||||
this.pageStates.about_theme.set(true)
|
||||
visitedBefore.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
private resetZoomIfAllClosed() {
|
||||
|
@ -160,7 +62,7 @@ export class MenuState {
|
|||
}
|
||||
|
||||
public openFilterView(highlightLayer?: LayerConfig | string) {
|
||||
this.filtersPanelIsOpened.setData(true)
|
||||
this.pageStates.filter.setData(true)
|
||||
if (highlightLayer) {
|
||||
if (typeof highlightLayer !== "string") {
|
||||
highlightLayer = highlightLayer.id
|
||||
|
@ -170,8 +72,6 @@ export class MenuState {
|
|||
}
|
||||
|
||||
public openUsersettings(highlightTagRendering?: string) {
|
||||
this.menuIsOpened.setData(true)
|
||||
this.menuViewTab.setData("settings")
|
||||
if (
|
||||
highlightTagRendering !== undefined &&
|
||||
!UserRelatedState.availableUserSettingsIds.some((tr) => tr === highlightTagRendering)
|
||||
|
@ -189,7 +89,7 @@ export class MenuState {
|
|||
}
|
||||
|
||||
public isSomethingOpen(): boolean {
|
||||
return this.allToggles.some((t) => t.toggle.data)
|
||||
return Object.values(this.pageStates).some((t) => t.data)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -197,14 +97,18 @@ export class MenuState {
|
|||
* Returns 'true' if at least one menu was opened
|
||||
*/
|
||||
public closeAll(): boolean {
|
||||
let somethingWasOpen = false
|
||||
for (const t of this.allToggles) {
|
||||
somethingWasOpen = t.toggle.data
|
||||
t.toggle.setData(false)
|
||||
if (somethingWasOpen) {
|
||||
break
|
||||
for (const key in this.pageStates) {
|
||||
const toggle = this.pageStates[key]
|
||||
const wasOpen = toggle.data
|
||||
toggle.setData(false)
|
||||
if (wasOpen) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return somethingWasOpen
|
||||
if (this.menuIsOpened.data) {
|
||||
this.menuIsOpened.set(false)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Store, UIEventSource } from "../Logic/UIEventSource"
|
|||
import {
|
||||
FeatureSource,
|
||||
IndexedFeatureSource,
|
||||
WritableFeatureSource,
|
||||
WritableFeatureSource
|
||||
} from "../Logic/FeatureSource/FeatureSource"
|
||||
import { OsmConnection } from "../Logic/Osm/OsmConnection"
|
||||
import { ExportableMap, MapProperties } from "./MapProperties"
|
||||
|
@ -51,7 +51,7 @@ import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveF
|
|||
import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
|
||||
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
|
||||
import NoElementsInViewDetector, {
|
||||
FeatureViewState,
|
||||
FeatureViewState
|
||||
} from "../Logic/Actors/NoElementsInViewDetector"
|
||||
import FilteredLayer from "./FilteredLayer"
|
||||
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"
|
||||
|
@ -64,7 +64,7 @@ import { GeolocationControlState } from "../UI/BigComponents/GeolocationControl"
|
|||
import Zoomcontrol from "../UI/Zoomcontrol"
|
||||
import {
|
||||
SummaryTileSource,
|
||||
SummaryTileSourceRewriter,
|
||||
SummaryTileSourceRewriter
|
||||
} from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource"
|
||||
import summaryLayer from "../assets/generated/layers/summary.json"
|
||||
import last_click_layerconfig from "../assets/generated/layers/last_click.json"
|
||||
|
@ -178,7 +178,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
"oauth_token",
|
||||
undefined,
|
||||
"Used to complete the login"
|
||||
),
|
||||
)
|
||||
})
|
||||
this.userRelatedState = new UserRelatedState(
|
||||
this.osmConnection,
|
||||
|
@ -257,8 +257,8 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
bbox.asGeoJson({
|
||||
zoom: this.mapProperties.zoom.data,
|
||||
...this.mapProperties.location.data,
|
||||
id: "current_view_" + currentViewIndex,
|
||||
}),
|
||||
id: "current_view_" + currentViewIndex
|
||||
})
|
||||
]
|
||||
})
|
||||
)
|
||||
|
@ -275,10 +275,10 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
featurePropertiesStore: this.featureProperties,
|
||||
osmConnection: this.osmConnection,
|
||||
historicalUserLocations: this.geolocation.historicalUserLocations,
|
||||
featureSwitches: this.featureSwitches,
|
||||
featureSwitches: this.featureSwitches
|
||||
},
|
||||
layout?.isLeftRightSensitive() ?? false,
|
||||
(e, extraMsg) => this.reportError(e, extraMsg),
|
||||
(e, extraMsg) => this.reportError(e, extraMsg)
|
||||
)
|
||||
this.historicalUserLocations = this.geolocation.historicalUserLocations
|
||||
this.newFeatures = new NewGeometryFromChangesFeatureSource(
|
||||
|
@ -303,7 +303,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
"leftover features, such as",
|
||||
features[0].properties
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
this.perLayer = perLayer.perLayer
|
||||
|
@ -359,7 +359,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
{
|
||||
currentZoom: this.mapProperties.zoom,
|
||||
layerState: this.layerState,
|
||||
bounds: this.visualFeedbackViewportBounds,
|
||||
bounds: this.visualFeedbackViewportBounds
|
||||
}
|
||||
)
|
||||
this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView
|
||||
|
@ -391,7 +391,6 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
public focusOnMap() {
|
||||
if (this.map.data) {
|
||||
this.map.data.getCanvas().focus()
|
||||
console.log("Focused on map")
|
||||
return
|
||||
}
|
||||
this.map.addCallbackAndRunD((map) => {
|
||||
|
@ -454,7 +453,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
doShowLayer,
|
||||
metaTags: this.userRelatedState.preferencesAsTags,
|
||||
selectedElement: this.selectedElement,
|
||||
fetchStore: (id) => this.featureProperties.getStore(id),
|
||||
fetchStore: (id) => this.featureProperties.getStore(id)
|
||||
})
|
||||
})
|
||||
return filteringFeatureSource
|
||||
|
@ -481,7 +480,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
doShowLayer: flayerGps.isDisplayed,
|
||||
layer: flayerGps.layerDef,
|
||||
metaTags: this.userRelatedState.preferencesAsTags,
|
||||
selectedElement: this.selectedElement,
|
||||
selectedElement: this.selectedElement
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -527,8 +526,6 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
|
||||
/**
|
||||
* Selects the feature that is 'i' closest to the map center
|
||||
* @param i
|
||||
* @private
|
||||
*/
|
||||
private selectClosestAtCenter(i: number = 0) {
|
||||
if (this.userRelatedState.a11y.data !== "never") {
|
||||
|
@ -557,23 +554,22 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
this.previewedImage.setData(undefined)
|
||||
return
|
||||
}
|
||||
this.selectedElement.setData(undefined)
|
||||
this.guistate.closeAll()
|
||||
if (!this.guistate.isSomethingOpen()) {
|
||||
Zoomcontrol.resetzoom()
|
||||
this.focusOnMap()
|
||||
if(this.guistate.closeAll()){
|
||||
return
|
||||
}
|
||||
this.selectedElement.setData(undefined)
|
||||
Zoomcontrol.resetzoom()
|
||||
this.focusOnMap()
|
||||
})
|
||||
|
||||
Hotkeys.RegisterHotkey({ nomod: "f" }, docs.selectFavourites, () => {
|
||||
this.guistate.menuViewTab.setData("favourites")
|
||||
this.guistate.menuIsOpened.setData(true)
|
||||
this.guistate.pageStates.favourites.set(true)
|
||||
})
|
||||
|
||||
Hotkeys.RegisterHotkey(
|
||||
{
|
||||
nomod: " ",
|
||||
onUp: true,
|
||||
onUp: true
|
||||
},
|
||||
docs.selectItem,
|
||||
() => {
|
||||
|
@ -581,8 +577,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
return false
|
||||
}
|
||||
if (
|
||||
this.guistate.menuIsOpened.data ||
|
||||
this.guistate.themeIsOpened.data ||
|
||||
this.guistate.isSomethingOpen() ||
|
||||
this.previewedImage.data !== undefined
|
||||
) {
|
||||
return
|
||||
|
@ -603,7 +598,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
Hotkeys.RegisterHotkey(
|
||||
{
|
||||
nomod: "" + i,
|
||||
onUp: true,
|
||||
onUp: true
|
||||
},
|
||||
doc,
|
||||
() => this.selectClosestAtCenter(i - 1)
|
||||
|
@ -616,22 +611,21 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
}
|
||||
Hotkeys.RegisterHotkey(
|
||||
{
|
||||
nomod: "b",
|
||||
nomod: "b"
|
||||
},
|
||||
docs.openLayersPanel,
|
||||
() => {
|
||||
if (this.featureSwitches.featureSwitchBackgroundSelection.data) {
|
||||
this.guistate.backgroundLayerSelectionIsOpened.setData(true)
|
||||
this.guistate.pageStates.background.setData(true)
|
||||
}
|
||||
}
|
||||
)
|
||||
Hotkeys.RegisterHotkey(
|
||||
{
|
||||
nomod: "s",
|
||||
nomod: "s"
|
||||
},
|
||||
Translations.t.hotkeyDocumentation.openFilterPanel,
|
||||
() => {
|
||||
console.log("S pressed")
|
||||
if (this.featureSwitches.featureSwitchFilter.data) {
|
||||
this.guistate.openFilterView()
|
||||
}
|
||||
|
@ -650,7 +644,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
available,
|
||||
category,
|
||||
current.data,
|
||||
skipLayers,
|
||||
skipLayers
|
||||
)
|
||||
if (!best) {
|
||||
return
|
||||
|
@ -706,7 +700,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
|
||||
Hotkeys.RegisterHotkey(
|
||||
{
|
||||
shift: "T",
|
||||
shift: "T"
|
||||
},
|
||||
Translations.t.hotkeyDocumentation.translationMode,
|
||||
() => {
|
||||
|
@ -738,7 +732,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
this.mapProperties.zoom.map((z) => Math.max(Math.floor(z), 0)),
|
||||
this.mapProperties,
|
||||
{
|
||||
isActive: this.mapProperties.zoom.map((z) => z < maxzoom),
|
||||
isActive: this.mapProperties.zoom.map((z) => z < maxzoom)
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -770,7 +764,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
current_view: this.currentView,
|
||||
favourite: this.favourites,
|
||||
summary: this.featureSummary,
|
||||
last_click: this.lastClickObject,
|
||||
last_click: this.lastClickObject
|
||||
}
|
||||
|
||||
this.closestFeatures.registerSource(specialLayers.favourite, "favourite")
|
||||
|
@ -825,7 +819,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
doShowLayer: flayer.isDisplayed,
|
||||
layer: flayer.layerDef,
|
||||
metaTags: this.userRelatedState.preferencesAsTags,
|
||||
selectedElement: this.selectedElement,
|
||||
selectedElement: this.selectedElement
|
||||
})
|
||||
})
|
||||
const summaryLayerConfig = new LayerConfig(<LayerConfigJson>summaryLayer, "summaryLayer")
|
||||
|
@ -833,7 +827,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
features: specialLayers.summary,
|
||||
layer: summaryLayerConfig,
|
||||
// doShowLayer: this.mapProperties.zoom.map((z) => z < maxzoom),
|
||||
selectedElement: this.selectedElement,
|
||||
selectedElement: this.selectedElement
|
||||
})
|
||||
|
||||
const lastClickLayerConfig = new LayerConfig(
|
||||
|
@ -844,28 +838,27 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
lastClickLayerConfig.isShown === undefined
|
||||
? specialLayers.last_click
|
||||
: specialLayers.last_click.features.mapD((fs) =>
|
||||
fs.filter((f) => {
|
||||
const matches = lastClickLayerConfig.isShown.matchesProperties(
|
||||
f.properties
|
||||
)
|
||||
console.debug("LastClick ", f, "matches", matches)
|
||||
return matches
|
||||
})
|
||||
)
|
||||
fs.filter((f) => {
|
||||
const matches = lastClickLayerConfig.isShown.matchesProperties(
|
||||
f.properties
|
||||
)
|
||||
console.debug("LastClick ", f, "matches", matches)
|
||||
return matches
|
||||
})
|
||||
)
|
||||
new ShowDataLayer(this.map, {
|
||||
features: new StaticFeatureSource(lastClickFiltered),
|
||||
layer: lastClickLayerConfig,
|
||||
onClick: (feature) => {
|
||||
console.log("Last click was clicked", feature)
|
||||
if (this.mapProperties.zoom.data >= Constants.minZoomLevelToAddNewPoint) {
|
||||
this.selectedElement.setData(feature)
|
||||
return
|
||||
}
|
||||
this.map.data.flyTo({
|
||||
zoom: Constants.minZoomLevelToAddNewPoint,
|
||||
center: GeoOperations.centerpointCoordinates(feature),
|
||||
center: GeoOperations.centerpointCoordinates(feature)
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -880,10 +873,12 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
this.lastClickObject.clear()
|
||||
}
|
||||
})
|
||||
this.guistate.allToggles.forEach((toggle) => {
|
||||
toggle.toggle.addCallbackD((isOpened) => {
|
||||
Object.values(this.guistate.pageStates).forEach((toggle) => {
|
||||
toggle.addCallbackD((isOpened) => {
|
||||
if (!isOpened) {
|
||||
this.focusOnMap()
|
||||
if (!this.guistate.isSomethingOpen()) {
|
||||
this.focusOnMap()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -950,8 +945,8 @@ export default class ThemeViewState implements SpecialVisualizationState {
|
|||
userid: this.osmConnection.userDetails.data?.uid,
|
||||
pendingChanges: this.changes.pendingChanges.data,
|
||||
previousChanges: this.changes.allChanges.data,
|
||||
changeRewrites: Utils.MapToObj(this.changes._changesetHandler._remappings),
|
||||
}),
|
||||
changeRewrites: Utils.MapToObj(this.changes._changesetHandler._remappings)
|
||||
})
|
||||
})
|
||||
} catch (e) {
|
||||
console.error("Could not upload an error report")
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import { onDestroy, onMount } from "svelte"
|
||||
|
||||
let elem: HTMLElement
|
||||
let targetOuter: HTMLElement
|
||||
export let isOpened: Store<boolean>
|
||||
export let moveTo: Store<HTMLElement>
|
||||
|
||||
export let debug: string
|
||||
function copySizeOf(htmlElem: HTMLElement) {
|
||||
const target = htmlElem.getBoundingClientRect()
|
||||
elem.style.left = target.x + "px"
|
||||
elem.style.top = target.y + "px"
|
||||
elem.style.width = target.width + "px"
|
||||
elem.style.height = target.height + "px"
|
||||
}
|
||||
|
||||
function animate(opened: boolean) {
|
||||
const moveToElem = moveTo.data
|
||||
if (opened) {
|
||||
copySizeOf(targetOuter)
|
||||
elem.style.background = "var(--background-color)"
|
||||
} else if (moveToElem !== undefined) {
|
||||
copySizeOf(moveToElem)
|
||||
elem.style.background = "#ffffff00"
|
||||
} else {
|
||||
elem.style.left = "0px"
|
||||
elem.style.top = "0px"
|
||||
elem.style.background = "#ffffff00"
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(isOpened.addCallback((opened) => animate(opened)))
|
||||
onMount(() => requestAnimationFrame(() => animate(isOpened.data)))
|
||||
</script>
|
||||
|
||||
<div class={"pointer-events-none invisible absolute bottom-0 right-0 h-full w-screen p-4 md:p-6"}>
|
||||
<div class="content h-full" bind:this={targetOuter} style="background: red" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
bind:this={elem}
|
||||
class="low-interaction pointer-events-none absolute bottom-0 right-0 rounded-2xl"
|
||||
style="transition: all 0.5s ease-out, background-color 1.4s ease-out; background: var(--background-color);"
|
||||
>
|
||||
<!-- Classes should be the same as the 'floatoaver' -->
|
||||
</div>
|
20
src/UI/Base/DrawerLeft.svelte
Normal file
20
src/UI/Base/DrawerLeft.svelte
Normal file
|
@ -0,0 +1,20 @@
|
|||
<script lang="ts">
|
||||
import { Drawer } from "flowbite-svelte"
|
||||
import { sineIn } from "svelte/easing"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource.js"
|
||||
|
||||
export let shown: UIEventSource<boolean>;
|
||||
let transitionParams = {
|
||||
x: -320,
|
||||
duration: 200,
|
||||
easing: sineIn
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<Drawer placement="left" transitionType="fly" transitionParams={transitionParams} hidden={!$shown} on:close={() => shown.set(false)}>
|
||||
<slot>
|
||||
CONTENTS
|
||||
</slot>
|
||||
</Drawer>
|
||||
|
|
@ -14,15 +14,10 @@
|
|||
export let arialabel: Translation = undefined
|
||||
export let arialabelDynamic: Store<Translation> = new ImmutableStore(arialabel)
|
||||
let arialabelString = arialabelDynamic.bind((tr) => tr?.current)
|
||||
export let htmlElem: UIEventSource<HTMLElement> = undefined
|
||||
let _htmlElem: HTMLElement
|
||||
$: {
|
||||
htmlElem?.setData(_htmlElem)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<button
|
||||
bind:this={_htmlElem}
|
||||
on:click={(e) => dispatch("click", e)}
|
||||
on:keydown
|
||||
use:ariaLabelStore={arialabelString}
|
||||
|
|
38
src/UI/Base/Page.svelte
Normal file
38
src/UI/Base/Page.svelte
Normal file
|
@ -0,0 +1,38 @@
|
|||
<script lang="ts">
|
||||
// A fake 'page' which can be shown; kind of a modal
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Modal } from "flowbite-svelte"
|
||||
|
||||
export let shown: UIEventSource<boolean>
|
||||
let _shown = false
|
||||
export let onlyLink: boolean = false
|
||||
shown.addCallbackAndRun(sh => {
|
||||
_shown = sh
|
||||
})
|
||||
export let fullscreen: boolean = false
|
||||
|
||||
const shared = "defaultClass normal-background dark:bg-gray-800 rounded-lg border-gray-200 dark:border-gray-700 border-gray-200 dark:border-gray-700 divide-gray-200 dark:divide-gray-700 shadow-md"
|
||||
let defaultClass = "relative flex flex-col mx-auto w-full divide-y " + shared
|
||||
if (fullscreen) {
|
||||
defaultClass = shared
|
||||
}
|
||||
let dialogClass = "fixed top-0 start-0 end-0 h-modal inset-0 z-50 w-full p-4 flex";
|
||||
if(fullscreen){
|
||||
dialogClass += " h-full-child"
|
||||
}
|
||||
let bodyClass = "h-full p-4 md:p-5 space-y-4 flex-1 overflow-y-auto overscroll-contain"
|
||||
</script>
|
||||
|
||||
{#if !onlyLink}
|
||||
<Modal open={_shown} on:close={() => shown.set(false)} size="xl" {defaultClass} {bodyClass} {dialogClass} color="none">
|
||||
<slot name="header" slot="header" />
|
||||
<slot />
|
||||
{#if $$slots.footer}
|
||||
<slot name="footer" />
|
||||
{/if}
|
||||
</Modal>
|
||||
{:else}
|
||||
<button class="as-link" on:click={() => shown.setData(true)}>
|
||||
<slot name="header" />
|
||||
</button>
|
||||
{/if}
|
|
@ -1,197 +0,0 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* Thin wrapper around 'TabGroup' which binds the state
|
||||
*/
|
||||
|
||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@rgossiaux/svelte-headlessui"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { twJoin } from "tailwind-merge"
|
||||
|
||||
/**
|
||||
* If a condition is given for a certain tab, it will only be shown if this condition is true.
|
||||
* E.g.
|
||||
* condition3 = new ImmutableStore(false) will always hide tab3 (the fourth tab)
|
||||
*/
|
||||
const tr = new ImmutableStore(true)
|
||||
export let condition0: Store<boolean> = tr
|
||||
export let condition1: Store<boolean> = tr
|
||||
export let condition2: Store<boolean> = tr
|
||||
export let condition3: Store<boolean> = tr
|
||||
export let condition4: Store<boolean> = tr
|
||||
export let condition5: Store<boolean> = tr
|
||||
export let condition6: Store<boolean> = tr
|
||||
export let tab: UIEventSource<number> = new UIEventSource<number>(0)
|
||||
let tabElements: HTMLElement[] = []
|
||||
$: tabElements[$tab]?.click()
|
||||
$: {
|
||||
if (tabElements[tab.data]) {
|
||||
window.setTimeout(() => tabElements[tab.data].click(), 50)
|
||||
}
|
||||
}
|
||||
export function getTab() {
|
||||
return tab
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="tabbedgroup flex h-full w-full">
|
||||
<TabGroup
|
||||
class="flex h-full w-full flex-col"
|
||||
defaultIndex={1}
|
||||
on:change={(e) => {
|
||||
if (e.detail >= 0) {
|
||||
tab.setData(e.detail)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="interactive sticky top-0 flex items-center justify-between">
|
||||
<TabList class="flex flex-wrap">
|
||||
{#if $$slots.title0}
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition0 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[0]} class="flex">
|
||||
<slot name="title0">Tab 0</slot>
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title1}
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition1 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[1]} class="flex">
|
||||
<slot name="title1" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title2}
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition2 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[2]} class="flex">
|
||||
<slot name="title2" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title3}
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition3 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[3]} class="flex">
|
||||
<slot name="title3" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title4}
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition4 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[4]} class="flex">
|
||||
<slot name="title4" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title5}
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition5 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[5]} class="flex">
|
||||
<slot name="title5" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
{#if $$slots.title6}
|
||||
<Tab
|
||||
class={({ selected }) => twJoin("tab", selected && "primary", !$condition6 && "hidden")}
|
||||
>
|
||||
<div bind:this={tabElements[6]} class="flex">
|
||||
<slot name="title6" />
|
||||
</div>
|
||||
</Tab>
|
||||
{/if}
|
||||
</TabList>
|
||||
<slot name="post-tablist" />
|
||||
</div>
|
||||
<div class="normal-background h-full overflow-y-auto">
|
||||
<TabPanels class="tabpanels" defaultIndex={$tab}>
|
||||
<TabPanel class="tabpanel">
|
||||
<slot name="content0">
|
||||
<div>Empty</div>
|
||||
</slot>
|
||||
</TabPanel>
|
||||
<TabPanel class="tabpanel">
|
||||
<slot name="content1">
|
||||
<div />
|
||||
</slot>
|
||||
</TabPanel>
|
||||
<TabPanel class="tabpanel">
|
||||
<slot name="content2">
|
||||
<div />
|
||||
</slot>
|
||||
</TabPanel>
|
||||
<TabPanel class="tabpanel">
|
||||
<slot name="content3">
|
||||
<div />
|
||||
</slot>
|
||||
</TabPanel>
|
||||
<TabPanel class="tabpanel">
|
||||
<slot name="content4">
|
||||
<div />
|
||||
</slot>
|
||||
</TabPanel>
|
||||
<TabPanel class="tabpanel">
|
||||
<slot name="content5">
|
||||
<div />
|
||||
</slot>
|
||||
</TabPanel>
|
||||
<TabPanel class="tabpanel">
|
||||
<slot name="content6">
|
||||
<div />
|
||||
</slot>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</div>
|
||||
</TabGroup>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tabbedgroup {
|
||||
max-height: 100vh;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:global(.tabpanel) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:global(.tabpanels) {
|
||||
height: calc(100% - 2rem);
|
||||
}
|
||||
|
||||
:global(.tab) {
|
||||
margin: 0.25rem;
|
||||
padding: 0.25rem;
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
:global(.tab .flex) {
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
:global(.tab span|div) {
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:global(.tab-selected svg) {
|
||||
fill: var(--catch-detail-color-contrast);
|
||||
}
|
||||
|
||||
:global(.tab-unselected) {
|
||||
background-color: var(--background-color) !important;
|
||||
color: var(--foreground-color) !important;
|
||||
}
|
||||
</style>
|
|
@ -6,4 +6,7 @@
|
|||
<div class="flex h-full flex-col overflow-auto border-b-2 p-4">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
|
||||
<slot class="border-t-gray-300 mt-1" name="footer" />
|
||||
</div>
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
<script lang="ts">
|
||||
import Translations from "../i18n/Translations"
|
||||
import { Utils } from "../../Utils"
|
||||
import Constants from "../../Models/Constants"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Add from "../../assets/svg/Add.svelte"
|
||||
import Github from "../../assets/svg/Github.svelte"
|
||||
import Mastodon from "../../assets/svg/Mastodon.svelte"
|
||||
import Liberapay from "../../assets/svg/Liberapay.svelte"
|
||||
import { EyeIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import MapillaryLink from "./MapillaryLink.svelte"
|
||||
import OpenJosm from "../Base/OpenJosm.svelte"
|
||||
import OpenIdEditor from "./OpenIdEditor.svelte"
|
||||
import If from "../Base/If.svelte"
|
||||
import Community from "../../assets/svg/Community.svelte"
|
||||
import Bug from "../../assets/svg/Bug.svelte"
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
import DocumentChartBar from "@babeard/svelte-heroicons/outline/DocumentChartBar"
|
||||
import DocumentMagnifyingGlass from "@babeard/svelte-heroicons/outline/DocumentMagnifyingGlass"
|
||||
|
||||
export let state: ThemeViewState
|
||||
|
||||
let layout = state.layout
|
||||
let featureSwitches = state.featureSwitches
|
||||
let showHome = featureSwitches.featureSwitchBackToThemeOverview
|
||||
</script>
|
||||
|
||||
<div class="link-underline links-w-full m-2 flex flex-col gap-y-1">
|
||||
<Tr t={Translations.t.general.aboutMapComplete.intro} />
|
||||
|
||||
{#if $showHome}
|
||||
<a class="flex" href={Utils.HomepageLink()}>
|
||||
<Add class="h-6 w-6" />
|
||||
{#if Utils.isIframe}
|
||||
<Tr t={Translations.t.general.seeIndex} />
|
||||
{:else}
|
||||
<Tr t={Translations.t.general.backToIndex} />
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<a class="flex" href="https://github.com/pietervdvn/MapComplete/" target="_blank">
|
||||
<Github class="h-6 w-6" />
|
||||
<Tr t={Translations.t.general.attribution.gotoSourceCode} />
|
||||
</a>
|
||||
|
||||
<a class="flex" href="https://github.com/pietervdvn/MapComplete/issues" target="_blank">
|
||||
<Bug class="h-6 w-6" />
|
||||
<Tr t={Translations.t.general.attribution.openIssueTracker} />
|
||||
</a>
|
||||
|
||||
{#if layout.official}
|
||||
<a
|
||||
class="flex"
|
||||
href={"https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Themes/" +
|
||||
layout.id +
|
||||
".md"}
|
||||
target="_blank"
|
||||
>
|
||||
<DocumentMagnifyingGlass class="h-6 w-6" />
|
||||
<Tr
|
||||
t={Translations.t.general.attribution.openThemeDocumentation.Subs({
|
||||
name: layout.title,
|
||||
})}
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a class="flex" href={Utils.OsmChaLinkFor(31, layout.id)}>
|
||||
<DocumentChartBar class="h-6 w-6" />
|
||||
<Tr t={Translations.t.general.attribution.openOsmcha.Subs({ theme: layout.title })} />
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<a class="flex" href="https://en.osm.town/@MapComplete" target="_blank">
|
||||
<Mastodon class="h-6 w-6" />
|
||||
<Tr t={Translations.t.general.attribution.followOnMastodon} />
|
||||
</a>
|
||||
|
||||
<a class="flex" href="https://liberapay.com/pietervdvn/" target="_blank">
|
||||
<Liberapay class="h-6 w-6" />
|
||||
<Tr t={Translations.t.general.attribution.donate} />
|
||||
</a>
|
||||
|
||||
<button class="as-link" on:click={() => state.guistate.communityIndexPanelIsOpened.setData(true)}>
|
||||
<Community class="h-6 w-6" />
|
||||
<Tr t={Translations.t.communityIndex.title} />
|
||||
</button>
|
||||
|
||||
<If condition={featureSwitches.featureSwitchEnableLogin}>
|
||||
<OpenIdEditor mapProperties={state.mapProperties} />
|
||||
<OpenJosm {state} />
|
||||
<MapillaryLink large={false} mapProperties={state.mapProperties} />
|
||||
</If>
|
||||
|
||||
<button class="as-link" on:click={() => state.guistate.privacyPanelIsOpened.setData(true)}>
|
||||
<EyeIcon class="h-6 w-6 pr-1" />
|
||||
<Tr t={Translations.t.privacy.title} />
|
||||
</button>
|
||||
|
||||
<div class="subtle">
|
||||
{Constants.vNumber}
|
||||
</div>
|
||||
</div>
|
22
src/UI/BigComponents/CopyrightAllIcons.svelte
Normal file
22
src/UI/BigComponents/CopyrightAllIcons.svelte
Normal file
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts">
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import IconCopyrightPanel from "./CopyrightSingleIcon.svelte"
|
||||
import licenses from "../../assets/generated/license_info.json"
|
||||
import type SmallLicense from "../../Models/smallLicense"
|
||||
|
||||
export let state: SpecialVisualizationState
|
||||
|
||||
let layoutToUse = state.layout
|
||||
let iconAttributions: string[] = layoutToUse.getUsedImages()
|
||||
|
||||
const allLicenses = {}
|
||||
for (const key in licenses) {
|
||||
const license: SmallLicense = licenses[key]
|
||||
allLicenses[license.path] = license
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#each iconAttributions as iconAttribution}
|
||||
<IconCopyrightPanel iconPath={iconAttribution} license={allLicenses[iconAttribution]} />
|
||||
{/each}
|
|
@ -4,11 +4,7 @@
|
|||
import contributors from "../../assets/contributors.json"
|
||||
import translators from "../../assets/translators.json"
|
||||
import { Translation, TypedTranslation } from "../i18n/Translation"
|
||||
import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import IconCopyrightPanel from "./IconCopyrightPanel.svelte"
|
||||
import licenses from "../../assets/generated/license_info.json"
|
||||
import type SmallLicense from "../../Models/smallLicense"
|
||||
import Constants from "../../Models/Constants"
|
||||
import ContributorCount from "../../Logic/ContributorCount"
|
||||
import BaseUIElement from "../BaseUIElement"
|
||||
|
@ -24,7 +20,6 @@
|
|||
const t = Translations.t.general.attribution
|
||||
const layoutToUse = state.layout
|
||||
|
||||
const iconAttributions: string[] = layoutToUse.getUsedImages()
|
||||
|
||||
let maintainer: Translation = undefined
|
||||
if (layoutToUse.credits !== undefined && layoutToUse.credits !== "") {
|
||||
|
@ -53,11 +48,7 @@
|
|||
return Translations.t.general.attribution.attributionBackgroundLayer.Subs(props)
|
||||
})
|
||||
|
||||
const allLicenses = {}
|
||||
for (const key in licenses) {
|
||||
const license: SmallLicense = licenses[key]
|
||||
allLicenses[license.path] = license
|
||||
}
|
||||
|
||||
|
||||
function calculateDataContributions(contributions: Map<string, number>): Translation {
|
||||
if (contributions === undefined) {
|
||||
|
@ -121,9 +112,6 @@
|
|||
</script>
|
||||
|
||||
<div class="link-underline flex flex-col gap-y-4">
|
||||
<h3>
|
||||
<Tr t={t.attributionTitle} />
|
||||
</h3>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<Osm_logo class="h-8 w-8 shrink-0" />
|
||||
<Tr t={t.attributionContent} />
|
||||
|
@ -159,14 +147,6 @@
|
|||
<Tr t={codeContributors(translators, t.translatedBy)} />
|
||||
</div>
|
||||
|
||||
<AccordionSingle>
|
||||
<div slot="header">
|
||||
<Tr t={t.iconAttribution.title} />
|
||||
</div>
|
||||
{#each iconAttributions as iconAttribution}
|
||||
<IconCopyrightPanel iconPath={iconAttribution} license={allLicenses[iconAttribution]} />
|
||||
{/each}
|
||||
</AccordionSingle>
|
||||
|
||||
<div class="self-end">
|
||||
MapComplete {Constants.vNumber}
|
||||
|
|
|
@ -9,9 +9,11 @@
|
|||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Filter from "../../assets/svg/Filter.svelte"
|
||||
import TitledPanel from "../Base/TitledPanel.svelte"
|
||||
import Page from "../Base/Page.svelte"
|
||||
|
||||
export let state: ThemeViewState
|
||||
export let onlyLink: boolean
|
||||
|
||||
let layout = state.layout
|
||||
|
||||
let allEnabled: boolean
|
||||
|
@ -47,8 +49,8 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<TitledPanel>
|
||||
<div class="mr-10 flex w-full flex-wrap items-center justify-between" slot="title">
|
||||
<Page {onlyLink} shown={state.guistate.pageStates.filter}>
|
||||
<div class="mr-10 flex w-full flex-wrap items-center justify-between" slot="header">
|
||||
<div class="flex">
|
||||
<Filter class="h-6 w-6 pr-2" />
|
||||
<Tr t={Translations.t.general.menu.filter} />
|
||||
|
@ -80,4 +82,4 @@
|
|||
zoomlevel={state.mapProperties.zoom}
|
||||
/>
|
||||
{/each}
|
||||
</TitledPanel>
|
||||
</Page>
|
|
@ -15,10 +15,6 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<AccordionSingle>
|
||||
<div slot="header">
|
||||
<Tr t={t.title} />
|
||||
</div>
|
||||
<Tr t={t.intro} />
|
||||
<table>
|
||||
<tr>
|
||||
|
@ -47,4 +43,3 @@
|
|||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
</AccordionSingle>
|
||||
|
|
296
src/UI/BigComponents/MenuDrawer.svelte
Normal file
296
src/UI/BigComponents/MenuDrawer.svelte
Normal file
|
@ -0,0 +1,296 @@
|
|||
<script lang="ts">
|
||||
|
||||
// All the relevant links
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
import Translations from "../i18n/Translations"
|
||||
import { CogIcon, EyeIcon, HeartIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import Page from "../Base/Page.svelte"
|
||||
import PrivacyPolicy from "./PrivacyPolicy.svelte"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import If from "../Base/If.svelte"
|
||||
import CommunityIndexView from "./CommunityIndexView.svelte"
|
||||
import Community from "../../assets/svg/Community.svelte"
|
||||
import LoginToggle from "../Base/LoginToggle.svelte"
|
||||
import { Avatar, Sidebar, SidebarWrapper } from "flowbite-svelte"
|
||||
import HotkeyTable from "./HotkeyTable.svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
import Constants from "../../Models/Constants"
|
||||
import Mastodon from "../../assets/svg/Mastodon.svelte"
|
||||
import Liberapay from "../../assets/svg/Liberapay.svelte"
|
||||
import DocumentMagnifyingGlass from "@babeard/svelte-heroicons/outline/DocumentMagnifyingGlass"
|
||||
import DocumentChartBar from "@babeard/svelte-heroicons/outline/DocumentChartBar"
|
||||
import OpenIdEditor from "./OpenIdEditor.svelte"
|
||||
import OpenJosm from "../Base/OpenJosm.svelte"
|
||||
import MapillaryLink from "./MapillaryLink.svelte"
|
||||
import Github from "../../assets/svg/Github.svelte"
|
||||
import Bug from "../../assets/svg/Bug.svelte"
|
||||
import Add from "../../assets/svg/Add.svelte"
|
||||
import CopyrightPanel from "./CopyrightPanel.svelte"
|
||||
import CopyrightAllIcons from "./CopyrightAllIcons.svelte"
|
||||
import LanguagePicker from "../InputElement/LanguagePicker.svelte"
|
||||
import LoginButton from "../Base/LoginButton.svelte"
|
||||
import SelectedElementView from "./SelectedElementView.svelte"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||
import usersettings from "../../assets/generated/layers/usersettings.json"
|
||||
import UserRelatedState from "../../Logic/State/UserRelatedState"
|
||||
import ArrowDownTray from "@babeard/svelte-heroicons/mini/ArrowDownTray"
|
||||
import DownloadPanel from "../DownloadFlow/DownloadPanel.svelte"
|
||||
import Favourites from "../Favourites/Favourites.svelte"
|
||||
import ReviewsOverview from "../Reviews/ReviewsOverview.svelte"
|
||||
import Share from "@babeard/svelte-heroicons/solid/Share"
|
||||
import ShareScreen from "./ShareScreen.svelte"
|
||||
import FilterPage from "./FilterPage.svelte"
|
||||
import RasterLayerOverview from "../Map/RasterLayerOverview.svelte"
|
||||
import ThemeIntroPanel from "./ThemeIntroPanel.svelte"
|
||||
import Marker from "../Map/Marker.svelte"
|
||||
import LogoutButton from "../Base/LogoutButton.svelte"
|
||||
|
||||
export let state: ThemeViewState
|
||||
let userdetails = state.osmConnection.userDetails
|
||||
|
||||
let usersettingslayer = new LayerConfig(<LayerConfigJson>usersettings, "usersettings", true)
|
||||
|
||||
let layout = state.layout
|
||||
let featureSwitches = state.featureSwitches
|
||||
let showHome = featureSwitches.featureSwitchBackToThemeOverview
|
||||
let pg = state.guistate.pageStates
|
||||
export let onlyLink: boolean
|
||||
</script>
|
||||
|
||||
<Sidebar>
|
||||
<SidebarWrapper divClass="link-underline">
|
||||
|
||||
<!-- User related: avatar, settings, favourits, logout -->
|
||||
<div>
|
||||
<LoginToggle {state} >
|
||||
<LoginButton osmConnection={state.osmConnection} slot="not-logged-in"></LoginButton>
|
||||
<div class="flex">
|
||||
|
||||
<Avatar src={$userdetails.img} rounded />
|
||||
Welcome <b>{$userdetails.name}</b>
|
||||
</div>
|
||||
<LogoutButton osmConnection={state.osmConnection}/>
|
||||
</LoginToggle>
|
||||
|
||||
|
||||
<Page {onlyLink} shown={pg.usersettings}>
|
||||
<div class="flex" slot="header">
|
||||
<CogIcon class="h-6 w-6" />
|
||||
<Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})} />
|
||||
</div>
|
||||
|
||||
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
|
||||
<LoginToggle {state}>
|
||||
<div class="flex flex-col" slot="not-logged-in">
|
||||
<LanguagePicker availableLanguages={layout.language} />
|
||||
<Tr cls="alert" t={Translations.t.userinfo.notLoggedIn} />
|
||||
<LoginButton clss="primary" osmConnection={state.osmConnection} />
|
||||
</div>
|
||||
<SelectedElementView
|
||||
highlightedRendering={state.guistate.highlightedUserSetting}
|
||||
layer={usersettingslayer}
|
||||
selectedElement={{
|
||||
type: "Feature",
|
||||
properties: { id: "settings" },
|
||||
geometry: { type: "Point", coordinates: [0, 0] },
|
||||
}}
|
||||
|
||||
{state}
|
||||
tags={state.userRelatedState.preferencesAsTags}
|
||||
/>
|
||||
</LoginToggle>
|
||||
|
||||
|
||||
</Page>
|
||||
|
||||
<LoginToggle {state}>
|
||||
<Page {onlyLink} shown={pg.favourites}>
|
||||
|
||||
<div class="flex" slot="header">
|
||||
<HeartIcon class="h-6 w-6" />
|
||||
<Tr t={Translations.t.favouritePoi.tab} />
|
||||
</div>
|
||||
<h3>
|
||||
|
||||
<Tr t={Translations.t.favouritePoi.title} />
|
||||
</h3>
|
||||
<div>
|
||||
<Favourites {state} />
|
||||
<h3>
|
||||
<Tr t={Translations.t.reviews.your_reviews} />
|
||||
</h3>
|
||||
<ReviewsOverview {state} />
|
||||
</div>
|
||||
</Page>
|
||||
</LoginToggle>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Theme related: documentation links, download, ... -->
|
||||
<div>
|
||||
<h3>
|
||||
About {layout.id}
|
||||
</h3>
|
||||
|
||||
<Page {onlyLink} shown={pg.about_theme}>
|
||||
<div class="flex" slot="header">
|
||||
<Marker icons={layout.icon} size="h-4 w-4" />
|
||||
<Tr t={layout.title} />
|
||||
</div>
|
||||
<ThemeIntroPanel {state} />
|
||||
</Page>
|
||||
|
||||
<FilterPage {onlyLink} {state} />
|
||||
|
||||
<RasterLayerOverview {onlyLink} {state} />
|
||||
|
||||
<Page {onlyLink} shown={pg.share}>
|
||||
<div class="flex" slot="header">
|
||||
<Share class="h-4 w-4" />
|
||||
<Tr t={Translations.t.general.sharescreen.title} />
|
||||
</div>
|
||||
<ShareScreen {state} />
|
||||
</Page>
|
||||
|
||||
|
||||
{#if state.featureSwitches.featureSwitchEnableExport}
|
||||
<Page {onlyLink} shown={pg.download}>
|
||||
<div slot="header" class="flex">
|
||||
<ArrowDownTray class="h-4 w-4" />
|
||||
<Tr t={Translations.t.general.download.title} />
|
||||
</div>
|
||||
<DownloadPanel {state} />
|
||||
</Page>
|
||||
{/if}
|
||||
|
||||
{#if layout.official}
|
||||
<a
|
||||
class="flex"
|
||||
href={"https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Themes/" +
|
||||
layout.id +
|
||||
".md"}
|
||||
target="_blank"
|
||||
>
|
||||
<DocumentMagnifyingGlass class="h-6 w-6" />
|
||||
<Tr
|
||||
t={Translations.t.general.attribution.openThemeDocumentation.Subs({
|
||||
name: layout.title,
|
||||
})}
|
||||
/>
|
||||
</a>
|
||||
|
||||
<a class="flex" href={Utils.OsmChaLinkFor(31, layout.id)} target="_blank">
|
||||
<DocumentChartBar class="h-6 w-6" />
|
||||
<Tr t={Translations.t.general.attribution.openOsmcha.Subs({ theme: layout.title })} />
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Legal info: privacy policy, map attribution, icon attribution -->
|
||||
<div>
|
||||
<h3>Legal</h3>
|
||||
|
||||
<Page {onlyLink} shown={pg.copyright}>
|
||||
<Tr slot="header" t={Translations.t.general.attribution.attributionTitle} />
|
||||
<CopyrightPanel {state} />
|
||||
</Page>
|
||||
|
||||
|
||||
<Page {onlyLink} shown={pg.copyright_icons}>
|
||||
<div slot="header">
|
||||
<Tr t={ Translations.t.general.attribution.iconAttribution.title} />
|
||||
</div>
|
||||
<CopyrightAllIcons {state} />
|
||||
|
||||
</Page>
|
||||
|
||||
|
||||
<Page {onlyLink} shown={pg.privacy}>
|
||||
<div class="flex gap-x-2" slot="header">
|
||||
<EyeIcon class="w-6 pr-2" />
|
||||
<Tr t={Translations.t.privacy.title} />
|
||||
</div>
|
||||
<PrivacyPolicy {state} />
|
||||
</Page>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Other links and tools for the given location: open iD/JOSM; community index, ... -->
|
||||
<div>
|
||||
|
||||
<h3>
|
||||
Discover more
|
||||
</h3>
|
||||
|
||||
<Page {onlyLink} shown={pg.community_index}>
|
||||
<div class="flex gap-x-2" slot="header">
|
||||
<Community class="h-6 w-6" />
|
||||
<Tr t={Translations.t.communityIndex.title} />
|
||||
</div>
|
||||
<CommunityIndexView location={state.mapProperties.location} />
|
||||
</Page>
|
||||
|
||||
|
||||
<If condition={featureSwitches.featureSwitchEnableLogin}>
|
||||
<OpenIdEditor mapProperties={state.mapProperties} />
|
||||
<OpenJosm {state} />
|
||||
<MapillaryLink large={false} mapProperties={state.mapProperties} />
|
||||
</If>
|
||||
|
||||
</div>
|
||||
<!-- About MC: various hints -->
|
||||
<div>
|
||||
|
||||
<h3>
|
||||
<Tr t={Translations.t.general.menu.aboutMapComplete} />
|
||||
</h3>
|
||||
|
||||
{#if $showHome}
|
||||
<a class="flex" href={Utils.HomepageLink()}>
|
||||
<Add class="h-6 w-6" />
|
||||
{#if Utils.isIframe}
|
||||
<Tr t={Translations.t.general.seeIndex} />
|
||||
{:else}
|
||||
<Tr t={Translations.t.general.backToIndex} />
|
||||
{/if}
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
|
||||
<Page {onlyLink} shown={pg.hotkeys}>
|
||||
<Tr t={ Translations.t.hotkeyDocumentation.title} slot="header" />
|
||||
<HotkeyTable />
|
||||
</Page>
|
||||
|
||||
<a class="flex" href="https://github.com/pietervdvn/MapComplete/" target="_blank">
|
||||
<Github class="h-6 w-6" />
|
||||
<Tr t={Translations.t.general.attribution.gotoSourceCode} />
|
||||
</a>
|
||||
|
||||
<a class="flex" href="https://github.com/pietervdvn/MapComplete/issues" target="_blank">
|
||||
<Bug class="h-6 w-6" />
|
||||
<Tr t={Translations.t.general.attribution.openIssueTracker} />
|
||||
</a>
|
||||
|
||||
|
||||
<a class="flex" href="https://en.osm.town/@MapComplete" target="_blank">
|
||||
<Mastodon class="h-6 w-6" />
|
||||
<Tr t={Translations.t.general.attribution.followOnMastodon} />
|
||||
</a>
|
||||
|
||||
<a class="flex" href="https://liberapay.com/pietervdvn/" target="_blank">
|
||||
<Liberapay class="h-6 w-6" />
|
||||
<Tr t={Translations.t.general.attribution.donate} />
|
||||
</a>
|
||||
|
||||
<div class="subtle">
|
||||
{Constants.vNumber}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</SidebarWrapper>
|
||||
</Sidebar>
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
<MapControlButton
|
||||
arialabel={Translations.t.general.labels.background}
|
||||
on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}
|
||||
on:click={() => state.guistate.pageStates.background.setData(true)}
|
||||
{htmlElem}
|
||||
>
|
||||
<StyleLoadingIndicator map={map ?? state.map} rasterLayer={state.mapProperties.rasterLayer}>
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
import { GeoLocationState } from "../../Logic/State/GeoLocationState"
|
||||
import If from "../Base/If.svelte"
|
||||
import { ExclamationTriangleIcon } from "@babeard/svelte-heroicons/mini"
|
||||
import Location_refused from "../../assets/svg/Location_refused.svelte"
|
||||
import Location from "../../assets/svg/Location.svelte"
|
||||
import ChevronDoubleLeft from "@babeard/svelte-heroicons/solid/ChevronDoubleLeft"
|
||||
import GeolocationIndicator from "./GeolocationIndicator.svelte"
|
||||
|
||||
|
@ -38,7 +36,7 @@
|
|||
const glstate = state.geolocation.geolocationState
|
||||
if (glstate.currentGPSLocation.data !== undefined) {
|
||||
const c: GeolocationCoordinates = glstate.currentGPSLocation.data
|
||||
state.guistate.themeIsOpened.setData(false)
|
||||
state.guistate.pageStates.about_theme.setData(false)
|
||||
const coor = { lon: c.longitude, lat: c.latitude }
|
||||
state.mapProperties.location.setData(coor)
|
||||
}
|
||||
|
@ -65,7 +63,7 @@
|
|||
<Tr t={layout.descriptionTail} />
|
||||
|
||||
<!-- Buttons: open map, go to location, search -->
|
||||
<NextButton clss="primary w-full" on:click={() => state.guistate.themeIsOpened.setData(false)}>
|
||||
<NextButton clss="primary w-full" on:click={() => state.guistate.pageStates.about_theme.setData(false)}>
|
||||
<div class="flex w-full flex-col items-center">
|
||||
<div class="flex w-full justify-center text-2xl">
|
||||
<Tr t={Translations.t.general.openTheMap} />
|
||||
|
@ -96,7 +94,7 @@
|
|||
<div style="min-width: 16rem; " class="grow">
|
||||
<Geosearch
|
||||
bounds={state.mapProperties.bounds}
|
||||
on:searchCompleted={() => state.guistate.themeIsOpened.setData(false)}
|
||||
on:searchCompleted={() => state.guistate.pageStates.about_theme.setData(false)}
|
||||
on:searchIsValid={(event) => {
|
||||
searchEnabled = event.detail
|
||||
}}
|
||||
|
@ -138,20 +136,10 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
{#if Utils.isIframe}
|
||||
<div class="link-underline flex justify-end">
|
||||
<a href="https://mapcomplete.org" target="_blank">
|
||||
<Tr t={Translations.t.general.poweredByMapComplete} />
|
||||
</a>
|
||||
</div>
|
||||
{:else}
|
||||
<If condition={state.featureSwitches.featureSwitchBackToThemeOverview}>
|
||||
<div class="link-underline m-2 mx-4 flex w-full">
|
||||
<a class="flex w-fit items-center justify-end" href={Utils.HomepageLink()}>
|
||||
<ChevronDoubleLeft class="h-4 w-4" />
|
||||
<Tr t={Translations.t.general.backToIndex} />
|
||||
</a>
|
||||
</div>
|
||||
</If>
|
||||
{/if}
|
||||
<div class="link-underline flex justify-end text-sm mt-8">
|
||||
<a href="https://mapcomplete.org" target="_blank">
|
||||
<Tr t={Translations.t.general.poweredByMapComplete} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -65,10 +65,6 @@
|
|||
<Tr cls="alert" t={Translations.t.general.download.toMuch} />
|
||||
{:else}
|
||||
<div class="flex w-full flex-col" />
|
||||
<h3>
|
||||
<Tr t={t.title} />
|
||||
</h3>
|
||||
|
||||
<DownloadButton
|
||||
{state}
|
||||
extension="geojson"
|
||||
|
|
|
@ -61,7 +61,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
if (!MapLibreAdaptor.pmtilesInited) {
|
||||
maplibregl.addProtocol("pmtiles", new Protocol().tile)
|
||||
MapLibreAdaptor.pmtilesInited = true
|
||||
console.log("PM-tiles protocol added" + "")
|
||||
}
|
||||
this._maplibreMap = maplibreMap
|
||||
|
||||
|
|
|
@ -4,32 +4,29 @@
|
|||
*/
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { RasterLayerPolygon } from "../../Models/RasterLayers"
|
||||
import type { MapProperties } from "../../Models/MapProperties"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import RasterLayerPicker from "./RasterLayerPicker.svelte"
|
||||
import type { EliCategory } from "../../Models/RasterLayerProperties"
|
||||
import UserRelatedState from "../../Logic/State/UserRelatedState"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import TitledPanel from "../Base/TitledPanel.svelte"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import Page from "../Base/Page.svelte"
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
|
||||
export let availableLayers: { store: Store<RasterLayerPolygon[]> }
|
||||
export let mapproperties: MapProperties
|
||||
export let userstate: UserRelatedState
|
||||
export let map: Store<MlMap>
|
||||
export let state: ThemeViewState
|
||||
|
||||
let map = state.map
|
||||
let mapproperties = state.mapProperties
|
||||
let userstate = state.userRelatedState
|
||||
let shown = state.guistate.pageStates.background
|
||||
let availableLayers: { store: Store<RasterLayerPolygon[]> } = state.availableLayers
|
||||
let _availableLayers = availableLayers.store
|
||||
/**
|
||||
* Used to toggle the background layers on/off
|
||||
*/
|
||||
export let visible: UIEventSource<boolean> = undefined
|
||||
|
||||
type CategoryType = "photo" | "map" | "other" | "osmbasedmap"
|
||||
const categories: Record<CategoryType, EliCategory[]> = {
|
||||
photo: ["photo", "historicphoto"],
|
||||
map: ["map", "historicmap"],
|
||||
other: ["other", "elevation"],
|
||||
osmbasedmap: ["osmbasedmap"],
|
||||
osmbasedmap: ["osmbasedmap"]
|
||||
}
|
||||
|
||||
function availableForCategory(type: CategoryType): Store<RasterLayerPolygon[]> {
|
||||
|
@ -45,27 +42,31 @@
|
|||
const otherLayers = availableForCategory("other")
|
||||
|
||||
function onApply() {
|
||||
visible.setData(false)
|
||||
shown.setData(false)
|
||||
}
|
||||
|
||||
function getPref(type: CategoryType): undefined | UIEventSource<string> {
|
||||
return userstate?.osmConnection?.GetPreference("preferred-layer-" + type)
|
||||
}
|
||||
|
||||
export let onlyLink: boolean
|
||||
|
||||
</script>
|
||||
|
||||
<TitledPanel>
|
||||
<Tr slot="title" t={Translations.t.general.backgroundMap} />
|
||||
<Page {onlyLink} shown={shown} fullscreen={true}>
|
||||
<Tr slot="header" t={Translations.t.general.backgroundMap} />
|
||||
{#if $_availableLayers?.length < 1}
|
||||
<Loading />
|
||||
{:else}
|
||||
<div class="grid h-full w-full grid-cols-1 gap-2 md:grid-cols-2">
|
||||
|
||||
<div class="flex gap-x-2 flex-col sm:flex-row gap-y-2" style="height: calc( 100% - 5rem)">
|
||||
<RasterLayerPicker
|
||||
availableLayers={$photoLayers}
|
||||
favourite={getPref("photo")}
|
||||
{map}
|
||||
{mapproperties}
|
||||
on:appliedLayer={onApply}
|
||||
{visible}
|
||||
{shown}
|
||||
/>
|
||||
<RasterLayerPicker
|
||||
availableLayers={$mapLayers}
|
||||
|
@ -73,7 +74,7 @@
|
|||
{map}
|
||||
{mapproperties}
|
||||
on:appliedLayer={onApply}
|
||||
{visible}
|
||||
{shown}
|
||||
/>
|
||||
<RasterLayerPicker
|
||||
availableLayers={$osmbasedmapLayers}
|
||||
|
@ -81,7 +82,7 @@
|
|||
{map}
|
||||
{mapproperties}
|
||||
on:appliedLayer={onApply}
|
||||
{visible}
|
||||
{shown}
|
||||
/>
|
||||
<RasterLayerPicker
|
||||
availableLayers={$otherLayers}
|
||||
|
@ -89,8 +90,8 @@
|
|||
{map}
|
||||
{mapproperties}
|
||||
on:appliedLayer={onApply}
|
||||
{visible}
|
||||
{shown}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</TitledPanel>
|
||||
</Page>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
export let mapproperties: MapProperties
|
||||
export let map: Store<MlMap>
|
||||
|
||||
export let visible: Store<boolean> = undefined
|
||||
export let shown: Store<boolean> = undefined
|
||||
|
||||
let dispatch = createEventDispatcher<{ appliedLayer }>()
|
||||
|
||||
|
@ -48,10 +48,10 @@
|
|||
|
||||
let rasterLayerOnMap = UIEventSource.feedFrom(rasterLayer)
|
||||
|
||||
if (visible) {
|
||||
if (shown) {
|
||||
onDestroy(
|
||||
visible?.addCallbackAndRunD((visible) => {
|
||||
if (visible) {
|
||||
shown?.addCallbackAndRunD((shown) => {
|
||||
if (shown) {
|
||||
rasterLayerOnMap.setData(rasterLayer.data ?? availableLayers[0])
|
||||
} else {
|
||||
rasterLayerOnMap.setData(undefined)
|
||||
|
@ -85,7 +85,7 @@
|
|||
rasterLayer={rasterLayerOnMap}
|
||||
placedOverMap={map}
|
||||
placedOverMapProperties={mapproperties}
|
||||
{visible}
|
||||
visible={shown}
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
|
|
|
@ -13,65 +13,40 @@
|
|||
import type { MapProperties } from "../Models/MapProperties"
|
||||
import Geosearch from "./BigComponents/Geosearch.svelte"
|
||||
import Translations from "./i18n/Translations"
|
||||
import usersettings from "../assets/generated/layers/usersettings.json"
|
||||
import {
|
||||
CogIcon,
|
||||
EyeIcon,
|
||||
HeartIcon,
|
||||
MenuIcon,
|
||||
XCircleIcon,
|
||||
MenuIcon
|
||||
} from "@rgossiaux/svelte-heroicons/solid"
|
||||
import Tr from "./Base/Tr.svelte"
|
||||
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
|
||||
import FloatOver from "./Base/FloatOver.svelte"
|
||||
import Constants from "../Models/Constants"
|
||||
import TabbedGroup from "./Base/TabbedGroup.svelte"
|
||||
import UserRelatedState from "../Logic/State/UserRelatedState"
|
||||
import LoginToggle from "./Base/LoginToggle.svelte"
|
||||
import LoginButton from "./Base/LoginButton.svelte"
|
||||
import CopyrightPanel from "./BigComponents/CopyrightPanel.svelte"
|
||||
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
|
||||
import ModalRight from "./Base/ModalRight.svelte"
|
||||
import LevelSelector from "./BigComponents/LevelSelector.svelte"
|
||||
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
|
||||
import type { RasterLayerPolygon } from "../Models/RasterLayers"
|
||||
import { AvailableRasterLayers } from "../Models/RasterLayers"
|
||||
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
|
||||
import IfHidden from "./Base/IfHidden.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
|
||||
import StateIndicator from "./BigComponents/StateIndicator.svelte"
|
||||
import ShareScreen from "./BigComponents/ShareScreen.svelte"
|
||||
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"
|
||||
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"
|
||||
import Cross from "../assets/svg/Cross.svelte"
|
||||
import LanguagePicker from "./InputElement/LanguagePicker.svelte"
|
||||
import Min from "../assets/svg/Min.svelte"
|
||||
import Plus from "../assets/svg/Plus.svelte"
|
||||
import Filter from "../assets/svg/Filter.svelte"
|
||||
import Community from "../assets/svg/Community.svelte"
|
||||
import Favourites from "./Favourites/Favourites.svelte"
|
||||
import ImageOperations from "./Image/ImageOperations.svelte"
|
||||
import VisualFeedbackPanel from "./BigComponents/VisualFeedbackPanel.svelte"
|
||||
import { Orientation } from "../Sensors/Orientation"
|
||||
import GeolocationIndicator from "./BigComponents/GeolocationIndicator.svelte"
|
||||
import Compass_arrow from "../assets/svg/Compass_arrow.svelte"
|
||||
import ReverseGeocoding from "./BigComponents/ReverseGeocoding.svelte"
|
||||
import FilterPanel from "./BigComponents/FilterPanel.svelte"
|
||||
import PrivacyPolicy from "./BigComponents/PrivacyPolicy.svelte"
|
||||
import { BBox } from "../Logic/BBox"
|
||||
import ReviewsOverview from "./Reviews/ReviewsOverview.svelte"
|
||||
import ExtraLinkButton from "./BigComponents/ExtraLinkButton.svelte"
|
||||
import CloseAnimation from "./Base/CloseAnimation.svelte"
|
||||
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
|
||||
import ArrowDownTray from "@babeard/svelte-heroicons/mini/ArrowDownTray"
|
||||
import Share from "@babeard/svelte-heroicons/solid/Share"
|
||||
import ChevronRight from "@babeard/svelte-heroicons/solid/ChevronRight"
|
||||
import Marker from "./Map/Marker.svelte"
|
||||
import AboutMapComplete from "./BigComponents/AboutMapComplete.svelte"
|
||||
import HotkeyTable from "./BigComponents/HotkeyTable.svelte"
|
||||
import SelectedElementPanel from "./Base/SelectedElementPanel.svelte"
|
||||
import type { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
|
||||
import MenuDrawer from "./BigComponents/MenuDrawer.svelte"
|
||||
import DrawerLeft from "./Base/DrawerLeft.svelte"
|
||||
|
||||
export let state: ThemeViewState
|
||||
let layout = state.layout
|
||||
|
@ -120,7 +95,6 @@
|
|||
let visualFeedback = state.visualFeedback
|
||||
let viewport: UIEventSource<HTMLDivElement> = new UIEventSource<HTMLDivElement>(undefined)
|
||||
let mapproperties: MapProperties = state.mapProperties
|
||||
let usersettingslayer = new LayerConfig(<LayerConfigJson>usersettings, "usersettings", true)
|
||||
state.mapProperties.installCustomKeyboardHandler(viewport)
|
||||
let canZoomIn = mapproperties.maxzoom.map(
|
||||
(mz) => mapproperties.zoom.data < mz,
|
||||
|
@ -144,7 +118,7 @@
|
|||
const bottomRight = mlmap.unproject([rect.right, rect.bottom])
|
||||
const bbox = new BBox([
|
||||
[topLeft.lng, topLeft.lat],
|
||||
[bottomRight.lng, bottomRight.lat],
|
||||
[bottomRight.lng, bottomRight.lat]
|
||||
])
|
||||
state.visualFeedbackViewportBounds.setData(bbox)
|
||||
}
|
||||
|
@ -156,7 +130,6 @@
|
|||
updateViewport()
|
||||
})
|
||||
let featureSwitches: FeatureSwitchState = state.featureSwitches
|
||||
let availableLayers = state.availableLayers
|
||||
let currentViewLayer: LayerConfig = layout.layers.find((l) => l.id === "current_view")
|
||||
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
|
||||
let rasterLayerName =
|
||||
|
@ -168,8 +141,13 @@
|
|||
})
|
||||
)
|
||||
let previewedImage = state.previewedImage
|
||||
|
||||
let addNewFeatureMode = state.userRelatedState.addNewFeatureMode
|
||||
let gpsAvailable = state.geolocation.geolocationState.gpsAvailable
|
||||
let gpsButtonAriaLabel = state.geolocation.geolocationState.gpsStateExplanation
|
||||
let debug = state.featureSwitches.featureSwitchIsDebugging
|
||||
|
||||
|
||||
|
||||
debug.addCallbackAndRun((dbg) => {
|
||||
if (dbg) {
|
||||
document.body.classList.add("debug")
|
||||
|
@ -190,26 +168,7 @@
|
|||
animation?.cameraAnimation(mlmap)
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed for the animations
|
||||
*/
|
||||
let openMapButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||
let openMenuButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||
let openCurrentViewLayerButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(
|
||||
undefined
|
||||
)
|
||||
let _openNewElementButton: HTMLButtonElement
|
||||
let openNewElementButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||
|
||||
$: {
|
||||
openNewElementButton.setData(_openNewElementButton)
|
||||
}
|
||||
let openFilterButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||
let openBackgroundButton: UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||
let addNewFeatureMode = state.userRelatedState.addNewFeatureMode
|
||||
|
||||
let gpsAvailable = state.geolocation.geolocationState.gpsAvailable
|
||||
let gpsButtonAriaLabel = state.geolocation.geolocationState.gpsStateExplanation
|
||||
</script>
|
||||
|
||||
<main>
|
||||
|
@ -255,9 +214,8 @@
|
|||
<div class="float-left m-1 flex flex-col sm:mt-2">
|
||||
<If condition={state.featureSwitches.featureSwitchWelcomeMessage}>
|
||||
<MapControlButton
|
||||
on:click={() => state.guistate.themeIsOpened.setData(true)}
|
||||
on:click={() => state.guistate.pageStates.about_theme.set(true)}
|
||||
on:keydown={forwardEventToMap}
|
||||
htmlElem={openMapButton}
|
||||
>
|
||||
<div
|
||||
class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2"
|
||||
|
@ -272,9 +230,8 @@
|
|||
|
||||
<MapControlButton
|
||||
arialabel={Translations.t.general.labels.menu}
|
||||
on:click={() => state.guistate.menuIsOpened.setData(true)}
|
||||
on:click={() => {console.log("Opening...."); state.guistate.menuIsOpened.setData(true)}}
|
||||
on:keydown={forwardEventToMap}
|
||||
htmlElem={openMenuButton}
|
||||
>
|
||||
<MenuIcon class="h-8 w-8 cursor-pointer" />
|
||||
</MapControlButton>
|
||||
|
@ -285,7 +242,6 @@
|
|||
state.selectCurrentView()
|
||||
}}
|
||||
on:keydown={forwardEventToMap}
|
||||
htmlElem={openCurrentViewLayerButton}
|
||||
>
|
||||
<div class="h-8 w-8 cursor-pointer">
|
||||
<ToSvelte construct={() => currentViewLayer.defaultIcon()} />
|
||||
|
@ -322,7 +278,6 @@
|
|||
<button
|
||||
class="low-interaction pointer-events-auto w-fit"
|
||||
class:disabled={$currentZoom < Constants.minZoomLevelToAddNewPoint}
|
||||
bind:this={_openNewElementButton}
|
||||
on:click={() => {
|
||||
state.openNewDialog()
|
||||
}}
|
||||
|
@ -346,7 +301,6 @@
|
|||
arialabel={Translations.t.general.labels.filter}
|
||||
on:click={() => state.guistate.openFilterView()}
|
||||
on:keydown={forwardEventToMap}
|
||||
htmlElem={openFilterButton}
|
||||
>
|
||||
<Filter class="h-6 w-6" />
|
||||
</MapControlButton>
|
||||
|
@ -355,25 +309,17 @@
|
|||
<OpenBackgroundSelectorButton
|
||||
hideTooltip={true}
|
||||
{state}
|
||||
htmlElem={openBackgroundButton}
|
||||
/>
|
||||
</If>
|
||||
<a
|
||||
class="bg-black-transparent pointer-events-auto ml-1 h-fit max-h-12 cursor-pointer self-end self-center overflow-hidden rounded-2xl px-1 text-white opacity-50 hover:opacity-100"
|
||||
on:click={() => {
|
||||
if (featureSwitches.featureSwitchWelcomeMessage.data) {
|
||||
state.guistate.themeViewTab.setData("copyright")
|
||||
state.guistate.themeIsOpened.setData(true)
|
||||
} else {
|
||||
state.guistate.copyrightPanelIsOpened.setData(true)
|
||||
}
|
||||
}}
|
||||
<button
|
||||
class="as-link bg-black-transparent pointer-events-auto ml-1 h-fit max-h-12 cursor-pointer self-end self-center overflow-hidden rounded-2xl px-1 text-white opacity-50 hover:opacity-100"
|
||||
on:click={() => {state.guistate.pageStates.copyright.set(true)}}
|
||||
>
|
||||
© <span class="hidden sm:inline sm:pr-2">
|
||||
OpenStreetMap
|
||||
<span class="hidden w-24 md:inline md:pr-2">, {rasterLayerName}</span>
|
||||
</span>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -443,6 +389,11 @@
|
|||
<svelte:fragment slot="error" />
|
||||
</LoginToggle>
|
||||
|
||||
<DrawerLeft shown={state.guistate.menuIsOpened}>
|
||||
<MenuDrawer onlyLink={true} {state} />
|
||||
</DrawerLeft>
|
||||
<MenuDrawer onlyLink={false} {state} />
|
||||
|
||||
{#if $selectedElement !== undefined && $selectedLayer !== undefined && !$selectedLayer.popupInFloatover}
|
||||
<!-- right modal with the selected element view -->
|
||||
<ModalRight
|
||||
|
@ -485,225 +436,5 @@
|
|||
</FloatOver>
|
||||
</If>
|
||||
|
||||
<!-- big theme menu -->
|
||||
<If condition={state.guistate.themeIsOpened}>
|
||||
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
|
||||
<span slot="close-button"><!-- Disable the close button --></span>
|
||||
<TabbedGroup
|
||||
condition1={state.featureSwitches.featureSwitchEnableExport}
|
||||
tab={state.guistate.themeViewTabIndex}
|
||||
>
|
||||
<div slot="post-tablist">
|
||||
<XCircleIcon
|
||||
class="mr-2 h-8 w-8"
|
||||
on:click={() => state.guistate.themeIsOpened.setData(false)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex" slot="title0">
|
||||
<Marker icons={layout.icon} size="h-4 w-4" />
|
||||
<Tr t={layout.title} />
|
||||
</div>
|
||||
|
||||
<div class="m-4 h-full" slot="content0">
|
||||
<ThemeIntroPanel {state} />
|
||||
</div>
|
||||
|
||||
<div class="flex" slot="title1">
|
||||
<If condition={state.featureSwitches.featureSwitchEnableExport}>
|
||||
<ArrowDownTray class="h-4 w-4" />
|
||||
<Tr t={Translations.t.general.download.title} />
|
||||
</If>
|
||||
</div>
|
||||
<div class="m-4" slot="content1">
|
||||
<DownloadPanel {state} />
|
||||
</div>
|
||||
|
||||
<div slot="title2">
|
||||
<Tr t={Translations.t.general.attribution.title} />
|
||||
</div>
|
||||
|
||||
<div slot="content2" class="m-2 flex flex-col">
|
||||
<CopyrightPanel {state} />
|
||||
</div>
|
||||
|
||||
<div class="flex" slot="title3">
|
||||
<Share class="h-4 w-4" />
|
||||
<Tr t={Translations.t.general.sharescreen.title} />
|
||||
</div>
|
||||
<div class="m-2" slot="content3">
|
||||
<ShareScreen {state} />
|
||||
</div>
|
||||
</TabbedGroup>
|
||||
</FloatOver>
|
||||
</If>
|
||||
|
||||
<!-- Filterpane -->
|
||||
<If condition={state.guistate.filtersPanelIsOpened}>
|
||||
<FloatOver on:close={() => state.guistate.filtersPanelIsOpened.setData(false)}>
|
||||
<FilterPanel {state} />
|
||||
</FloatOver>
|
||||
</If>
|
||||
|
||||
<!-- background layer selector -->
|
||||
<IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}>
|
||||
<FloatOver
|
||||
on:close={() => {
|
||||
state.guistate.backgroundLayerSelectionIsOpened.setData(false)
|
||||
}}
|
||||
>
|
||||
<RasterLayerOverview
|
||||
{availableLayers}
|
||||
map={state.map}
|
||||
mapproperties={state.mapProperties}
|
||||
userstate={state.userRelatedState}
|
||||
visible={state.guistate.backgroundLayerSelectionIsOpened}
|
||||
/>
|
||||
</FloatOver>
|
||||
</IfHidden>
|
||||
|
||||
<!-- Menu page -->
|
||||
<If condition={state.guistate.menuIsOpened}>
|
||||
<FloatOver on:close={() => state.guistate.menuIsOpened.setData(false)}>
|
||||
<span slot="close-button"><!-- Hide the default close button --></span>
|
||||
<TabbedGroup
|
||||
condition1={featureSwitches.featureSwitchEnableLogin}
|
||||
condition2={state.featureSwitches.featureSwitchCommunityIndex}
|
||||
tab={state.guistate.menuViewTabIndex}
|
||||
>
|
||||
<div slot="post-tablist">
|
||||
<XCircleIcon
|
||||
class="mr-2 h-8 w-8"
|
||||
on:click={() => state.guistate.menuIsOpened.setData(false)}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex" slot="title0">
|
||||
<Tr t={Translations.t.general.menu.aboutMapComplete} />
|
||||
</div>
|
||||
|
||||
<div slot="content0" class="flex flex-col">
|
||||
<AboutMapComplete {state} />
|
||||
<div class="m-2 flex flex-col">
|
||||
<HotkeyTable />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex" slot="title1">
|
||||
<CogIcon class="h-6 w-6" />
|
||||
<Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})} />
|
||||
</div>
|
||||
|
||||
<div class="links-as-button py-8" slot="content1">
|
||||
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
|
||||
<LoginToggle {state}>
|
||||
<div class="flex flex-col" slot="not-logged-in">
|
||||
<LanguagePicker availableLanguages={layout.language} />
|
||||
<Tr cls="alert" t={Translations.t.userinfo.notLoggedIn} />
|
||||
<LoginButton clss="primary" osmConnection={state.osmConnection} />
|
||||
</div>
|
||||
<SelectedElementView
|
||||
highlightedRendering={state.guistate.highlightedUserSetting}
|
||||
layer={usersettingslayer}
|
||||
selectedElement={{
|
||||
type: "Feature",
|
||||
properties: { id: "settings" },
|
||||
geometry: { type: "Point", coordinates: [0, 0] },
|
||||
}}
|
||||
{state}
|
||||
tags={state.userRelatedState.preferencesAsTags}
|
||||
/>
|
||||
</LoginToggle>
|
||||
</div>
|
||||
|
||||
<div class="flex" slot="title2">
|
||||
<HeartIcon class="h-6 w-6" />
|
||||
<Tr t={Translations.t.favouritePoi.tab} />
|
||||
</div>
|
||||
|
||||
<div class="m-2 flex flex-col" slot="content2">
|
||||
<h3>
|
||||
<Tr t={Translations.t.favouritePoi.title} />
|
||||
</h3>
|
||||
<Favourites {state} />
|
||||
<h3>
|
||||
<Tr t={Translations.t.reviews.your_reviews} />
|
||||
</h3>
|
||||
<ReviewsOverview {state} />
|
||||
</div>
|
||||
</TabbedGroup>
|
||||
</FloatOver>
|
||||
</If>
|
||||
|
||||
<!-- Privacy policy -->
|
||||
<If condition={state.guistate.privacyPanelIsOpened}>
|
||||
<FloatOver on:close={() => state.guistate.privacyPanelIsOpened.setData(false)}>
|
||||
<div class="flex h-full flex-col overflow-hidden">
|
||||
<h2 class="low-interaction m-0 flex items-center p-4 drop-shadow-md">
|
||||
<EyeIcon class="w-6 pr-2" />
|
||||
<Tr t={Translations.t.privacy.title} />
|
||||
</h2>
|
||||
<div class="overflow-auto p-4">
|
||||
<PrivacyPolicy {state} />
|
||||
</div>
|
||||
</div>
|
||||
</FloatOver>
|
||||
</If>
|
||||
|
||||
<!-- Attribution, copyright and about MapComplete (no menu case) -->
|
||||
<If condition={state.guistate.copyrightPanelIsOpened}>
|
||||
<FloatOver on:close={() => state.guistate.copyrightPanelIsOpened.setData(false)}>
|
||||
<div class="flex h-full flex-col overflow-hidden">
|
||||
<h1 class="low-interaction m-0 flex items-center p-4 drop-shadow-md">
|
||||
<Tr t={Translations.t.general.attribution.title} />
|
||||
</h1>
|
||||
<div class="overflow-auto p-4">
|
||||
<h2>
|
||||
<Tr t={Translations.t.general.menu.aboutMapComplete} />
|
||||
</h2>
|
||||
<AboutMapComplete {state} />
|
||||
<CopyrightPanel {state} />
|
||||
</div>
|
||||
</div>
|
||||
</FloatOver>
|
||||
</If>
|
||||
|
||||
<!-- Community index -->
|
||||
<If condition={state.guistate.communityIndexPanelIsOpened}>
|
||||
<FloatOver on:close={() => state.guistate.communityIndexPanelIsOpened.setData(false)}>
|
||||
<div class="flex h-full flex-col overflow-hidden">
|
||||
<h2 class="low-interaction m-0 flex items-center p-4">
|
||||
<Community class="h-6 w-6" />
|
||||
<Tr t={Translations.t.communityIndex.title} />
|
||||
</h2>
|
||||
<div class="overflow-auto p-4">
|
||||
<CommunityIndexView location={state.mapProperties.location} />
|
||||
</div>
|
||||
</div>
|
||||
</FloatOver>
|
||||
</If>
|
||||
|
||||
<CloseAnimation isOpened={state.guistate.themeIsOpened} moveTo={openMapButton} debug="theme" />
|
||||
<CloseAnimation isOpened={state.guistate.menuIsOpened} moveTo={openMenuButton} debug="menu" />
|
||||
<CloseAnimation
|
||||
isOpened={selectedLayer.map((sl) => sl !== undefined && sl === currentViewLayer)}
|
||||
moveTo={openCurrentViewLayerButton}
|
||||
debug="currentViewLayer"
|
||||
/>
|
||||
<CloseAnimation
|
||||
isOpened={selectedElement.map(
|
||||
(sl) => sl !== undefined && sl?.properties?.id === LastClickFeatureSource.newPointElementId
|
||||
)}
|
||||
moveTo={openNewElementButton}
|
||||
debug="newElement"
|
||||
/>
|
||||
<CloseAnimation
|
||||
isOpened={state.guistate.filtersPanelIsOpened}
|
||||
moveTo={openFilterButton}
|
||||
debug="filter"
|
||||
/>
|
||||
<CloseAnimation
|
||||
isOpened={state.guistate.backgroundLayerSelectionIsOpened}
|
||||
moveTo={openBackgroundButton}
|
||||
debug="bg"
|
||||
/>
|
||||
</main>
|
||||
|
|
|
@ -461,6 +461,10 @@ code {
|
|||
color: var(--background-color);
|
||||
}
|
||||
|
||||
.h-full-child > div {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bg-black-transparent {
|
||||
background-color: #00000088;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue