forked from MapComplete/MapComplete
		
	Refactoring: fix rendering of new roads, generated by a split
This commit is contained in:
		
							parent
							
								
									840990c08b
								
							
						
					
					
						commit
						8eb2c68f79
					
				
					 34 changed files with 443 additions and 333 deletions
				
			
		|  | @ -1,6 +1,9 @@ | |||
| import { Changes } from "../Osm/Changes" | ||||
| import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore"; | ||||
| import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesStore" | ||||
| 
 | ||||
| /** | ||||
|  * Applies tag changes onto the featureStore | ||||
|  */ | ||||
| export default class ChangeToElementsActor { | ||||
|     constructor(changes: Changes, allElements: FeaturePropertiesStore) { | ||||
|         changes.pendingChanges.addCallbackAndRun((changes) => { | ||||
|  |  | |||
|  | @ -55,12 +55,13 @@ class SingleTileSaver { | |||
|  */ | ||||
| export default class SaveFeatureSourceToLocalStorage { | ||||
|     constructor( | ||||
|         backend: string, | ||||
|         layername: string, | ||||
|         zoomlevel: number, | ||||
|         features: FeatureSource, | ||||
|         featureProperties: FeaturePropertiesStore | ||||
|     ) { | ||||
|         const storage = TileLocalStorage.construct<Feature[]>(layername) | ||||
|         const storage = TileLocalStorage.construct<Feature[]>(backend, layername) | ||||
|         const singleTileSavers: Map<number, SingleTileSaver> = new Map<number, SingleTileSaver>() | ||||
|         features.features.addCallbackAndRunD((features) => { | ||||
|             const sliced = GeoOperations.slice(zoomlevel, features) | ||||
|  |  | |||
|  | @ -17,14 +17,15 @@ export default class TileLocalStorage<T> { | |||
|         this._layername = layername | ||||
|     } | ||||
| 
 | ||||
|     public static construct<T>(layername: string): TileLocalStorage<T> { | ||||
|         const cached = TileLocalStorage.perLayer[layername] | ||||
|     public static construct<T>(backend: string, layername: string): TileLocalStorage<T> { | ||||
|         const key = backend + "_" + layername | ||||
|         const cached = TileLocalStorage.perLayer[key] | ||||
|         if (cached) { | ||||
|             return cached | ||||
|         } | ||||
| 
 | ||||
|         const tls = new TileLocalStorage<T>(layername) | ||||
|         TileLocalStorage.perLayer[layername] = tls | ||||
|         const tls = new TileLocalStorage<T>(key) | ||||
|         TileLocalStorage.perLayer[key] = tls | ||||
|         return tls | ||||
|     } | ||||
| 
 | ||||
|  | @ -46,7 +47,7 @@ export default class TileLocalStorage<T> { | |||
|         return src | ||||
|     } | ||||
| 
 | ||||
|     private async SetIdb(tileIndex: number, data): Promise<void> { | ||||
|     private async SetIdb(tileIndex: number, data: any): Promise<void> { | ||||
|         try { | ||||
|             await IdbLocalStorage.SetDirectly(this._layername + "_" + tileIndex, data) | ||||
|         } catch (e) { | ||||
|  |  | |||
|  | @ -3,22 +3,18 @@ | |||
|  */ | ||||
| import { Changes } from "../../Osm/Changes" | ||||
| import { UIEventSource } from "../../UIEventSource" | ||||
| import { FeatureSourceForLayer, IndexedFeatureSource } from "../FeatureSource" | ||||
| import FilteredLayer from "../../../Models/FilteredLayer" | ||||
| import { FeatureSource, IndexedFeatureSource } from "../FeatureSource" | ||||
| import { ChangeDescription, ChangeDescriptionTools } from "../../Osm/Actions/ChangeDescription" | ||||
| import { Feature } from "geojson" | ||||
| 
 | ||||
| export default class ChangeGeometryApplicator implements FeatureSourceForLayer { | ||||
|     public readonly features: UIEventSource<Feature[]> = | ||||
|         new UIEventSource<Feature[]>([]) | ||||
|     public readonly layer: FilteredLayer | ||||
| export default class ChangeGeometryApplicator implements FeatureSource { | ||||
|     public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([]) | ||||
|     private readonly source: IndexedFeatureSource | ||||
|     private readonly changes: Changes | ||||
| 
 | ||||
|     constructor(source: IndexedFeatureSource & FeatureSourceForLayer, changes: Changes) { | ||||
|     constructor(source: IndexedFeatureSource, changes: Changes) { | ||||
|         this.source = source | ||||
|         this.changes = changes | ||||
|         this.layer = source.layer | ||||
| 
 | ||||
|         this.features = new UIEventSource<Feature[]>(undefined) | ||||
| 
 | ||||
|  | @ -30,10 +26,10 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer { | |||
| 
 | ||||
|     private update() { | ||||
|         const upstreamFeatures = this.source.features.data | ||||
|         const upstreamIds = this.source.containedIds.data | ||||
|         const upstreamIds = this.source.featuresById.data | ||||
|         const changesToApply = this.changes.allChanges.data?.filter( | ||||
|             (ch) => | ||||
|                 // Does upsteram have this element? If not, we skip
 | ||||
|                 // Does upstream have this element? If not, we skip
 | ||||
|                 upstreamIds.has(ch.type + "/" + ch.id) && | ||||
|                 // Are any (geometry) changes defined?
 | ||||
|                 ch.changes !== undefined && | ||||
|  | @ -61,7 +57,7 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer { | |||
|         for (const feature of upstreamFeatures) { | ||||
|             const changesForFeature = changesPerId.get(feature.properties.id) | ||||
|             if (changesForFeature === undefined) { | ||||
|                 // No changes for this element
 | ||||
|                 // No changes for this element - simply pass it along to downstream
 | ||||
|                 newFeatures.push(feature) | ||||
|                 continue | ||||
|             } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { Store, UIEventSource } from "../../UIEventSource" | ||||
| import { FeatureSource ,  IndexedFeatureSource } from "../FeatureSource" | ||||
| import { FeatureSource, IndexedFeatureSource } from "../FeatureSource" | ||||
| import { Feature } from "geojson" | ||||
| import { Utils } from "../../../Utils" | ||||
| 
 | ||||
|  | @ -19,6 +19,7 @@ export default class FeatureSourceMerger implements IndexedFeatureSource { | |||
|         this._featuresById = new UIEventSource<Map<string, Feature>>(undefined) | ||||
|         this.featuresById = this._featuresById | ||||
|         const self = this | ||||
|         sources = Utils.NoNull(sources) | ||||
|         for (let source of sources) { | ||||
|             source.features.addCallback(() => { | ||||
|                 self.addData(sources.map((s) => s.features.data)) | ||||
|  | @ -28,7 +29,7 @@ export default class FeatureSourceMerger implements IndexedFeatureSource { | |||
|         this._sources = sources | ||||
|     } | ||||
| 
 | ||||
|     protected addSource(source: FeatureSource) { | ||||
|     public addSource(source: FeatureSource) { | ||||
|         this._sources.push(source) | ||||
|         source.features.addCallbackAndRun(() => { | ||||
|             this.addData(this._sources.map((s) => s.features.data)) | ||||
|  |  | |||
|  | @ -4,13 +4,14 @@ import { FeatureSource } from "../FeatureSource" | |||
| import { Or } from "../../Tags/Or" | ||||
| import FeatureSwitchState from "../../State/FeatureSwitchState" | ||||
| import OverpassFeatureSource from "./OverpassFeatureSource" | ||||
| import { ImmutableStore, Store } from "../../UIEventSource" | ||||
| import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" | ||||
| import OsmFeatureSource from "./OsmFeatureSource" | ||||
| import FeatureSourceMerger from "./FeatureSourceMerger" | ||||
| import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource" | ||||
| import { BBox } from "../../BBox" | ||||
| import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource" | ||||
| import StaticFeatureSource from "./StaticFeatureSource" | ||||
| import { OsmPreferences } from "../../Osm/OsmPreferences" | ||||
| 
 | ||||
| /** | ||||
|  * This source will fetch the needed data from various sources for the given layout. | ||||
|  | @ -18,15 +19,14 @@ import StaticFeatureSource from "./StaticFeatureSource" | |||
|  * Note that special layers (with `source=null` will be ignored) | ||||
|  */ | ||||
| export default class LayoutSource extends FeatureSourceMerger { | ||||
|     private readonly _isLoading: UIEventSource<boolean> = new UIEventSource<boolean>(false) | ||||
|     /** | ||||
|      * Indicates if a data source is loading something | ||||
|      * TODO fixme | ||||
|      */ | ||||
|     public readonly isLoading: Store<boolean> = new ImmutableStore(false) | ||||
|     public readonly isLoading: Store<boolean> = this._isLoading | ||||
|     constructor( | ||||
|         layers: LayerConfig[], | ||||
|         featureSwitches: FeatureSwitchState, | ||||
|         newAndChangedElements: FeatureSource, | ||||
|         mapProperties: { bounds: Store<BBox>; zoom: Store<number> }, | ||||
|         backend: string, | ||||
|         isDisplayed: (id: string) => Store<boolean> | ||||
|  | @ -39,7 +39,7 @@ export default class LayoutSource extends FeatureSourceMerger { | |||
|         const osmLayers = layers.filter((layer) => layer.source.geojsonSource === undefined) | ||||
|         const fromCache = osmLayers.map( | ||||
|             (l) => | ||||
|                 new LocalStorageFeatureSource(l.id, 15, mapProperties, { | ||||
|                 new LocalStorageFeatureSource(backend, l.id, 15, mapProperties, { | ||||
|                     isActive: isDisplayed(l.id), | ||||
|                 }) | ||||
|         ) | ||||
|  | @ -56,7 +56,17 @@ export default class LayoutSource extends FeatureSourceMerger { | |||
|         ) | ||||
| 
 | ||||
|         const expiryInSeconds = Math.min(...(layers?.map((l) => l.maxAgeOfCache) ?? [])) | ||||
|         super(overpassSource, osmApiSource, newAndChangedElements, ...geojsonSources, ...fromCache) | ||||
| 
 | ||||
|         super(overpassSource, osmApiSource, ...geojsonSources, ...fromCache) | ||||
| 
 | ||||
|         const self = this | ||||
|         function setIsLoading() { | ||||
|             const loading = overpassSource?.runningQuery?.data && osmApiSource?.isRunning?.data | ||||
|             self._isLoading.setData(loading) | ||||
|         } | ||||
| 
 | ||||
|         overpassSource?.runningQuery?.addCallbackAndRun((_) => setIsLoading()) | ||||
|         osmApiSource?.isRunning?.addCallbackAndRun((_) => setIsLoading()) | ||||
|     } | ||||
| 
 | ||||
|     private static setupGeojsonSource( | ||||
|  | @ -83,9 +93,9 @@ export default class LayoutSource extends FeatureSourceMerger { | |||
|         zoom: Store<number>, | ||||
|         backend: string, | ||||
|         featureSwitches: FeatureSwitchState | ||||
|     ): FeatureSource { | ||||
|     ): OsmFeatureSource | undefined { | ||||
|         if (osmLayers.length == 0) { | ||||
|             return new StaticFeatureSource(new ImmutableStore([])) | ||||
|             return undefined | ||||
|         } | ||||
|         const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom)) | ||||
|         const isActive = zoom.mapD((z) => { | ||||
|  | @ -115,9 +125,9 @@ export default class LayoutSource extends FeatureSourceMerger { | |||
|         bounds: Store<BBox>, | ||||
|         zoom: Store<number>, | ||||
|         featureSwitches: FeatureSwitchState | ||||
|     ): FeatureSource { | ||||
|     ): OverpassFeatureSource | undefined { | ||||
|         if (osmLayers.length == 0) { | ||||
|             return new StaticFeatureSource(new ImmutableStore([])) | ||||
|             return undefined | ||||
|         } | ||||
|         const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom)) | ||||
|         const isActive = zoom.mapD((z) => { | ||||
|  |  | |||
|  | @ -1,13 +1,12 @@ | |||
| import { Changes } from "../../Osm/Changes" | ||||
| import { OsmNode, OsmObject, OsmRelation, OsmWay } from "../../Osm/OsmObject" | ||||
| import { FeatureSource } from "../FeatureSource" | ||||
| import { IndexedFeatureSource, WritableFeatureSource } from "../FeatureSource" | ||||
| import { UIEventSource } from "../../UIEventSource" | ||||
| import { ChangeDescription } from "../../Osm/Actions/ChangeDescription" | ||||
| import { ElementStorage } from "../../ElementStorage" | ||||
| import { OsmId, OsmTags } from "../../../Models/OsmFeature" | ||||
| import { Feature } from "geojson" | ||||
| 
 | ||||
| export class NewGeometryFromChangesFeatureSource implements FeatureSource { | ||||
| export class NewGeometryFromChangesFeatureSource implements WritableFeatureSource { | ||||
|     // This class name truly puts the 'Java' into 'Javascript'
 | ||||
| 
 | ||||
|     /** | ||||
|  | @ -18,7 +17,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { | |||
|      */ | ||||
|     public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([]) | ||||
| 
 | ||||
|     constructor(changes: Changes, allElementStorage: ElementStorage, backendUrl: string) { | ||||
|     constructor(changes: Changes, allElementStorage: IndexedFeatureSource, backendUrl: string) { | ||||
|         const seenChanges = new Set<ChangeDescription>() | ||||
|         const features = this.features.data | ||||
|         const self = this | ||||
|  | @ -53,7 +52,7 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { | |||
|                     // In _most_ of the cases, this means that this _isn't_ a new object
 | ||||
|                     // However, when a point is snapped to an already existing point, we have to create a representation for this point!
 | ||||
|                     // For this, we introspect the change
 | ||||
|                     if (allElementStorage.has(change.type + "/" + change.id)) { | ||||
|                     if (allElementStorage.featuresById.data.has(change.type + "/" + change.id)) { | ||||
|                         // The current point already exists, we don't have to do anything here
 | ||||
|                         continue | ||||
|                     } | ||||
|  | @ -65,7 +64,6 @@ export class NewGeometryFromChangesFeatureSource implements FeatureSource { | |||
|                             feat.tags[kv.k] = kv.v | ||||
|                         } | ||||
|                         const geojson = feat.asGeoJson() | ||||
|                         allElementStorage.addOrGetElement(geojson) | ||||
|                         self.features.data.push(geojson) | ||||
|                         self.features.ping() | ||||
|                     }) | ||||
|  |  | |||
|  | @ -41,7 +41,6 @@ export default class OsmFeatureSource extends FeatureSourceMerger { | |||
|         this.isActive = options.isActive ?? new ImmutableStore(true) | ||||
|         this._backend = options.backend ?? "https://www.openstreetmap.org" | ||||
|         this._bounds.addCallbackAndRunD((bbox) => this.loadData(bbox)) | ||||
|         console.log("Allowed tags are:", this.allowedTags) | ||||
|     } | ||||
| 
 | ||||
|     private async loadData(bbox: BBox) { | ||||
|  | @ -108,7 +107,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger { | |||
|     } | ||||
| 
 | ||||
|     private async LoadTile(z, x, y): Promise<void> { | ||||
|         console.log("OsmFeatureSource: loading ", z, x, y) | ||||
|         console.log("OsmFeatureSource: loading ", z, x, y, "from", this._backend) | ||||
|         if (z >= 22) { | ||||
|             throw "This is an absurd high zoom level" | ||||
|         } | ||||
|  |  | |||
|  | @ -22,13 +22,18 @@ export interface SnappingOptions { | |||
|      * The resulting snap coordinates will be written into this UIEventSource | ||||
|      */ | ||||
|     snapLocation?: UIEventSource<{ lon: number; lat: number }> | ||||
| 
 | ||||
|     /** | ||||
|      * If the projected point is within `reusePointWithin`-meter of an already existing point | ||||
|      */ | ||||
|     reusePointWithin?: number | ||||
| } | ||||
| 
 | ||||
| export default class SnappingFeatureSource implements FeatureSource { | ||||
|     public readonly features: Store<Feature<Point>[]> | ||||
| 
 | ||||
|     private readonly _snappedTo: UIEventSource<string> | ||||
|     /*Contains the id of the way it snapped to*/ | ||||
|     public readonly snappedTo: Store<string> | ||||
|     private readonly _snappedTo: UIEventSource<string> | ||||
| 
 | ||||
|     constructor( | ||||
|         snapTo: FeatureSource, | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ import StaticFeatureSource from "../Sources/StaticFeatureSource" | |||
| 
 | ||||
| export default class LocalStorageFeatureSource extends DynamicTileSource { | ||||
|     constructor( | ||||
|         backend: string, | ||||
|         layername: string, | ||||
|         zoomlevel: number, | ||||
|         mapProperties: { | ||||
|  | @ -17,7 +18,7 @@ export default class LocalStorageFeatureSource extends DynamicTileSource { | |||
|             isActive?: Store<boolean> | ||||
|         } | ||||
|     ) { | ||||
|         const storage = TileLocalStorage.construct<Feature[]>(layername) | ||||
|         const storage = TileLocalStorage.construct<Feature[]>(backend, layername) | ||||
|         super( | ||||
|             zoomlevel, | ||||
|             (tileIndex) => | ||||
|  |  | |||
|  | @ -294,6 +294,10 @@ export class GeoOperations { | |||
|      * Mostly used as helper for 'nearestPoint' | ||||
|      * @param way | ||||
|      */ | ||||
|     public static forceLineString(way: Feature<LineString | Polygon>): Feature<LineString> | ||||
|     public static forceLineString( | ||||
|         way: Feature<MultiLineString | MultiPolygon> | ||||
|     ): Feature<MultiLineString> | ||||
|     public static forceLineString( | ||||
|         way: Feature<LineString | MultiLineString | Polygon | MultiPolygon> | ||||
|     ): Feature<LineString | MultiLineString> { | ||||
|  | @ -972,4 +976,9 @@ export class GeoOperations { | |||
|             }, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static centerpointCoordinatesObj(geojson: Feature) { | ||||
|         const [lon, lat] = GeoOperations.centerpointCoordinates(geojson) | ||||
|         return { lon, lat } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ import { GeoOperations } from "../../GeoOperations" | |||
| import OsmChangeAction from "./OsmChangeAction" | ||||
| import { ChangeDescription } from "./ChangeDescription" | ||||
| import RelationSplitHandler from "./RelationSplitHandler" | ||||
| import { Feature, LineString } from "geojson" | ||||
| 
 | ||||
| interface SplitInfo { | ||||
|     originalIndex?: number // or negative for new elements
 | ||||
|  | @ -14,9 +15,9 @@ interface SplitInfo { | |||
| export default class SplitAction extends OsmChangeAction { | ||||
|     private readonly wayId: string | ||||
|     private readonly _splitPointsCoordinates: [number, number][] // lon, lat
 | ||||
|     private _meta: { theme: string; changeType: "split" } | ||||
|     private _toleranceInMeters: number | ||||
|     private _withNewCoordinates: (coordinates: [number, number][]) => void | ||||
|     private readonly _meta: { theme: string; changeType: "split" } | ||||
|     private readonly _toleranceInMeters: number | ||||
|     private readonly _withNewCoordinates: (coordinates: [number, number][]) => void | ||||
| 
 | ||||
|     /** | ||||
|      * Create a changedescription for splitting a point. | ||||
|  | @ -197,7 +198,7 @@ export default class SplitAction extends OsmChangeAction { | |||
|      * If another point is closer then ~5m, we reuse that point | ||||
|      */ | ||||
|     private CalculateSplitCoordinates(osmWay: OsmWay, toleranceInM = 5): SplitInfo[] { | ||||
|         const wayGeoJson = osmWay.asGeoJson() | ||||
|         const wayGeoJson = <Feature<LineString>>osmWay.asGeoJson() | ||||
|         // Should be [lon, lat][]
 | ||||
|         const originalPoints: [number, number][] = osmWay.coordinates.map((c) => [c[1], c[0]]) | ||||
|         const allPoints: { | ||||
|  |  | |||
|  | @ -18,10 +18,6 @@ import FeaturePropertiesStore from "../FeatureSource/Actors/FeaturePropertiesSto | |||
|  * Needs an authenticator via OsmConnection | ||||
|  */ | ||||
| export class Changes { | ||||
|     /** | ||||
|      * All the newly created features as featureSource + all the modified features | ||||
|      */ | ||||
|     public readonly features = new UIEventSource<{ feature: any; freshness: Date }[]>([]) | ||||
|     public readonly pendingChanges: UIEventSource<ChangeDescription[]> = | ||||
|         LocalStorageSource.GetParsed<ChangeDescription[]>("pending-changes", []) | ||||
|     public readonly allChanges = new UIEventSource<ChangeDescription[]>(undefined) | ||||
|  |  | |||
|  | @ -213,7 +213,7 @@ class RewriteMetaInfoTags extends SimpleMetaTagger { | |||
|         move("changeset", "_last_edit:changeset") | ||||
|         move("timestamp", "_last_edit:timestamp") | ||||
|         move("version", "_version_number") | ||||
|         feature.properties._backend = "https://openstreetmap.org" | ||||
|         feature.properties._backend = feature.properties._backend ?? "https://openstreetmap.org" | ||||
|         return movedSomething | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -38,8 +38,9 @@ export class IdbLocalStorage { | |||
|         return src | ||||
|     } | ||||
| 
 | ||||
|     public static SetDirectly(key: string, value): Promise<void> { | ||||
|         return idb.set(key, value) | ||||
|     public static SetDirectly(key: string, value: any): Promise<void> { | ||||
|         const copy = Utils.Clone(value) | ||||
|         return idb.set(key, copy) | ||||
|     } | ||||
| 
 | ||||
|     static GetDirectly(key: string): Promise<void> { | ||||
|  |  | |||
|  | @ -43,6 +43,7 @@ export default class Constants { | |||
|     public static readonly no_include = [ | ||||
|         "conflation", | ||||
|         "split_point", | ||||
|         "split_road", | ||||
|         "current_view", | ||||
|         "matchpoint", | ||||
|         "import_candidate", | ||||
|  |  | |||
|  | @ -596,6 +596,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> { | |||
|                 id: "split-button", | ||||
|                 render: { "*": "{split_button()}" }, | ||||
|             }) | ||||
|             delete json.allowSplit | ||||
|         } | ||||
| 
 | ||||
|         if (json.allowMove && !ValidationUtils.hasSpecialVisualisation(json, "move_button")) { | ||||
|  | @ -611,7 +612,16 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> { | |||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         if (json.deletion && !ValidationUtils.hasSpecialVisualisation(json, "all_tags")) { | ||||
|         if ( | ||||
|             json.source !== "special" && | ||||
|             json.source !== "special:library" && | ||||
|             json.tagRenderings && | ||||
|             !json.tagRenderings.some((tr) => tr["id"] === "last_edit") | ||||
|         ) { | ||||
|             json.tagRenderings.push(this._desugaring.tagRenderings.get("last_edit")) | ||||
|         } | ||||
| 
 | ||||
|         if (!ValidationUtils.hasSpecialVisualisation(json, "all_tags")) { | ||||
|             const trc: TagRenderingConfigJson = { | ||||
|                 id: "all-tags", | ||||
|                 render: { "*": "{all_tags()}" }, | ||||
|  | @ -623,16 +633,7 @@ export class AddEditingElements extends DesugaringStep<LayerConfigJson> { | |||
|                     ], | ||||
|                 }, | ||||
|             } | ||||
|             json.tagRenderings.push(trc) | ||||
|         } | ||||
| 
 | ||||
|         if ( | ||||
|             json.source !== "special" && | ||||
|             json.source !== "special:library" && | ||||
|             json.tagRenderings && | ||||
|             !json.tagRenderings.some((tr) => tr["id"] === "last_edit") | ||||
|         ) { | ||||
|             json.tagRenderings.push(this._desugaring.tagRenderings.get("last_edit")) | ||||
|             json.tagRenderings?.push(trc) | ||||
|         } | ||||
| 
 | ||||
|         return { result: json } | ||||
|  |  | |||
|  | @ -26,7 +26,6 @@ import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource" | |||
| import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource" | ||||
| import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore" | ||||
| import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter" | ||||
| import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" | ||||
| import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource" | ||||
| import ShowDataLayer from "../UI/Map/ShowDataLayer" | ||||
| import TitleHandler from "../Logic/Actors/TitleHandler" | ||||
|  | @ -39,9 +38,10 @@ import Hotkeys from "../UI/Base/Hotkeys" | |||
| import Translations from "../UI/i18n/Translations" | ||||
| import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" | ||||
| import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource" | ||||
| import SimpleFeatureSource from "../Logic/FeatureSource/Sources/SimpleFeatureSource" | ||||
| import { MenuState } from "./MenuState" | ||||
| import MetaTagging from "../Logic/MetaTagging" | ||||
| import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator" | ||||
| import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" | ||||
| 
 | ||||
| /** | ||||
|  * | ||||
|  | @ -65,7 +65,7 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|     readonly selectedElement: UIEventSource<Feature> | ||||
|     readonly mapProperties: MapProperties & ExportableMap | ||||
| 
 | ||||
|     readonly dataIsLoading: Store<boolean> // TODO
 | ||||
|     readonly dataIsLoading: Store<boolean> | ||||
|     readonly guistate: MenuState | ||||
|     readonly fullNodeDatabase?: FullNodeDatabaseSource // TODO
 | ||||
| 
 | ||||
|  | @ -80,6 +80,7 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|     readonly geolocation: GeoLocationHandler | ||||
| 
 | ||||
|     readonly lastClickObject: WritableFeatureSource | ||||
| 
 | ||||
|     constructor(layout: LayoutConfig) { | ||||
|         this.layout = layout | ||||
|         this.guistate = new MenuState(layout.id) | ||||
|  | @ -121,49 +122,69 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
| 
 | ||||
|         const self = this | ||||
|         this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) | ||||
|         this.newFeatures = new SimpleFeatureSource(undefined) | ||||
|         const layoutSource = new LayoutSource( | ||||
|             layout.layers, | ||||
|             this.featureSwitches, | ||||
|             this.newFeatures, | ||||
|             this.mapProperties, | ||||
|             this.osmConnection.Backend(), | ||||
|             (id) => self.layerState.filteredLayers.get(id).isDisplayed | ||||
|         ) | ||||
|         this.indexedFeatures = layoutSource | ||||
|         this.dataIsLoading = layoutSource.isLoading | ||||
|         const lastClick = (this.lastClickObject = new LastClickFeatureSource( | ||||
|             this.mapProperties.lastClickLocation, | ||||
|             this.layout | ||||
|         )) | ||||
|         const indexedElements = this.indexedFeatures | ||||
|         this.featureProperties = new FeaturePropertiesStore(indexedElements) | ||||
|         const perLayer = new PerLayerFeatureSourceSplitter( | ||||
|             Array.from(this.layerState.filteredLayers.values()).filter( | ||||
|                 (l) => l.layerDef?.source !== null | ||||
|             ), | ||||
|             indexedElements, | ||||
|             { | ||||
|                 constructStore: (features, layer) => new GeoIndexedStoreForLayer(features, layer), | ||||
|                 handleLeftovers: (features) => { | ||||
|                     console.warn( | ||||
|                         "Got ", | ||||
|                         features.length, | ||||
|                         "leftover features, such as", | ||||
|                         features[0].properties | ||||
|                     ) | ||||
|                 }, | ||||
|             } | ||||
|         ) | ||||
|         this.perLayer = perLayer.perLayer | ||||
| 
 | ||||
|         { | ||||
|             /* Setup the layout source | ||||
|              * A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too | ||||
|              */ | ||||
| 
 | ||||
|             const layoutSource = new LayoutSource( | ||||
|                 layout.layers, | ||||
|                 this.featureSwitches, | ||||
|                 this.mapProperties, | ||||
|                 this.osmConnection.Backend(), | ||||
|                 (id) => self.layerState.filteredLayers.get(id).isDisplayed | ||||
|             ) | ||||
|             this.indexedFeatures = layoutSource | ||||
|             this.dataIsLoading = layoutSource.isLoading | ||||
| 
 | ||||
|             const indexedElements = this.indexedFeatures | ||||
|             this.featureProperties = new FeaturePropertiesStore(indexedElements) | ||||
|             this.changes = new Changes( | ||||
|                 { | ||||
|                     dryRun: this.featureSwitches.featureSwitchIsTesting, | ||||
|                     allElements: indexedElements, | ||||
|                     featurePropertiesStore: this.featureProperties, | ||||
|                     osmConnection: this.osmConnection, | ||||
|                     historicalUserLocations: this.geolocation.historicalUserLocations, | ||||
|                 }, | ||||
|                 layout?.isLeftRightSensitive() ?? false | ||||
|             ) | ||||
|             this.newFeatures = new NewGeometryFromChangesFeatureSource( | ||||
|                 this.changes, | ||||
|                 indexedElements, | ||||
|                 this.osmConnection.Backend() | ||||
|             ) | ||||
|             layoutSource.addSource(this.newFeatures) | ||||
| 
 | ||||
|             const perLayer = new PerLayerFeatureSourceSplitter( | ||||
|                 Array.from(this.layerState.filteredLayers.values()).filter( | ||||
|                     (l) => l.layerDef?.source !== null | ||||
|                 ), | ||||
|                 new ChangeGeometryApplicator(this.indexedFeatures, this.changes), | ||||
|                 { | ||||
|                     constructStore: (features, layer) => | ||||
|                         new GeoIndexedStoreForLayer(features, layer), | ||||
|                     handleLeftovers: (features) => { | ||||
|                         console.warn( | ||||
|                             "Got ", | ||||
|                             features.length, | ||||
|                             "leftover features, such as", | ||||
|                             features[0].properties | ||||
|                         ) | ||||
|                     }, | ||||
|                 } | ||||
|             ) | ||||
|             this.perLayer = perLayer.perLayer | ||||
|         } | ||||
|         this.perLayer.forEach((fs) => { | ||||
|             new SaveFeatureSourceToLocalStorage( | ||||
|             /* TODO enable   new SaveFeatureSourceToLocalStorage( | ||||
|                 this.osmConnection.Backend(), | ||||
|                 fs.layer.layerDef.id, | ||||
|                 15, | ||||
|                 fs, | ||||
|                 this.featureProperties | ||||
|             ) | ||||
|             )//*/
 | ||||
| 
 | ||||
|             const filtered = new FilteringFeatureSource( | ||||
|                 fs.layer, | ||||
|  | @ -187,16 +208,10 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|             }) | ||||
|         }) | ||||
| 
 | ||||
|         this.changes = new Changes( | ||||
|             { | ||||
|                 dryRun: this.featureSwitches.featureSwitchIsTesting, | ||||
|                 allElements: indexedElements, | ||||
|                 featurePropertiesStore: this.featureProperties, | ||||
|                 osmConnection: this.osmConnection, | ||||
|                 historicalUserLocations: this.geolocation.historicalUserLocations, | ||||
|             }, | ||||
|             layout?.isLeftRightSensitive() ?? false | ||||
|         ) | ||||
|         const lastClick = (this.lastClickObject = new LastClickFeatureSource( | ||||
|             this.mapProperties.lastClickLocation, | ||||
|             this.layout | ||||
|         )) | ||||
| 
 | ||||
|         this.initActors() | ||||
|         this.drawSpecialLayers(lastClick) | ||||
|  | @ -211,9 +226,13 @@ export default class ThemeViewState implements SpecialVisualizationState { | |||
|     private miscSetup() { | ||||
|         this.userRelatedState.markLayoutAsVisited(this.layout) | ||||
| 
 | ||||
|         this.selectedElement.addCallbackAndRunD(() => { | ||||
|             // As soon as we have a selected element, we clear it
 | ||||
|         this.selectedElement.addCallbackAndRunD((feature) => { | ||||
|             // As soon as we have a selected element, we clear the selected element
 | ||||
|             // This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature
 | ||||
|             // The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear
 | ||||
|             if (feature.properties.id === "last_click") { | ||||
|                 return | ||||
|             } | ||||
|             this.lastClickObject.features.setData([]) | ||||
|         }) | ||||
|     } | ||||
|  |  | |||
|  | @ -11,7 +11,6 @@ import { Utils } from "../../Utils" | |||
| import { MapillaryLink } from "./MapillaryLink" | ||||
| import { OpenIdEditor, OpenJosm } from "./CopyrightPanel" | ||||
| import Toggle from "../Input/Toggle" | ||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen" | ||||
| import { DefaultGuiState } from "../DefaultGuiState" | ||||
| 
 | ||||
| export class BackToThemeOverview extends Toggle { | ||||
|  | @ -78,14 +77,6 @@ export class ActionButtons extends Combine { | |||
|             new OpenIdEditor(state, iconStyle), | ||||
|             new MapillaryLink(state, iconStyle), | ||||
|             new OpenJosm(state, iconStyle).SetClass("hidden-on-mobile"), | ||||
|             new SubtleButton( | ||||
|                 Svg.translate_ui().SetStyle(iconStyle), | ||||
|                 Translations.t.translations.activateButton | ||||
|             ).onClick(() => { | ||||
|                 ScrollableFullScreen.collapse() | ||||
|                 state.defaultGuiState.userInfoIsOpened.setData(true) | ||||
|                 state.defaultGuiState.userInfoFocusedQuestion.setData("translation-mode") | ||||
|             }), | ||||
|         ]) | ||||
|         this.SetClass("block w-full link-no-underline") | ||||
|     } | ||||
|  |  | |||
							
								
								
									
										104
									
								
								UI/BigComponents/WaySplitMap.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								UI/BigComponents/WaySplitMap.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,104 @@ | |||
| <script lang="ts"> | ||||
|   /** | ||||
|    * This component shows a map which focuses on a single OSM-Way (linestring) feature. | ||||
|    * Clicking the map will add a new 'scissor' point, projected on the linestring (and possible snapped to an already existing node within the linestring; | ||||
|    * clicking this point again will remove it. | ||||
|    * The bound 'value' will contain the location of these projected points. | ||||
|    * Points are not coalesced with already existing nodes within the way; it is up to the code actually splitting the way to decide to reuse an existing point or not | ||||
|    * | ||||
|    * This component is _not_ responsible for the rest of the flow, e.g. the confirm button | ||||
|    */ | ||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||
|   import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"; | ||||
|   import split_point from "../../assets/layers/split_point/split_point.json"; | ||||
|   import split_road from "../../assets/layers/split_road/split_road.json"; | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource"; | ||||
|   import { Map as MlMap } from "maplibre-gl"; | ||||
|   import type { MapProperties } from "../../Models/MapProperties"; | ||||
|   import { MapLibreAdaptor } from "../Map/MapLibreAdaptor"; | ||||
|   import MaplibreMap from "../Map/MaplibreMap.svelte"; | ||||
|   import { OsmWay } from "../../Logic/Osm/OsmObject"; | ||||
|   import ShowDataLayer from "../Map/ShowDataLayer"; | ||||
|   import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||
|   import { GeoOperations } from "../../Logic/GeoOperations"; | ||||
|   import { BBox } from "../../Logic/BBox"; | ||||
|   import type { Feature, LineString, Point } from "geojson"; | ||||
| 
 | ||||
|   const splitpoint_style = new LayerConfig( | ||||
|     <LayerConfigJson>split_point, | ||||
|     "(BUILTIN) SplitRoadWizard.ts", | ||||
|     true | ||||
|   ) as const; | ||||
| 
 | ||||
|   const splitroad_style = new LayerConfig( | ||||
|     <LayerConfigJson>split_road, | ||||
|     "(BUILTIN) SplitRoadWizard.ts", | ||||
|     true | ||||
|   ) as const; | ||||
| 
 | ||||
|   /** | ||||
|    * The way to focus on | ||||
|    */ | ||||
|   export let osmWay: OsmWay | ||||
|   /** | ||||
|    * How to render this layer. | ||||
|    * A default is given | ||||
|    */ | ||||
|   export let layer: LayerConfig = splitroad_style | ||||
|     /** | ||||
|      * Optional: use these properties to set e.g. background layer | ||||
|      */ | ||||
|   export let mapProperties: undefined | Partial<MapProperties> = undefined; | ||||
|    | ||||
|   let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined); | ||||
|   let adaptor = new MapLibreAdaptor(map, mapProperties); | ||||
|    | ||||
|   const wayGeojson: Feature<LineString> = GeoOperations.forceLineString( osmWay.asGeoJson()) | ||||
|   adaptor.location.setData(GeoOperations.centerpointCoordinatesObj(wayGeojson)) | ||||
|   adaptor.bounds.setData(BBox.get(wayGeojson).pad(2)) | ||||
|   adaptor.maxbounds.setData(BBox.get(wayGeojson).pad(2)) | ||||
|    | ||||
|   new ShowDataLayer(map, { | ||||
|     features: new StaticFeatureSource([wayGeojson]), | ||||
|     drawMarkers: false, | ||||
|     layer: layer | ||||
|   }) | ||||
|    | ||||
|   export let splitPoints: UIEventSource< Feature< | ||||
|     Point, | ||||
|     { | ||||
|       id: number | ||||
|       index: number | ||||
|       dist: number | ||||
|       location: number | ||||
|     } | ||||
|   >[]> = new UIEventSource([]) | ||||
|   const splitPointsFS = new StaticFeatureSource(splitPoints) | ||||
|    | ||||
|   new ShowDataLayer(map, { | ||||
|     layer: splitpoint_style, | ||||
|     features: splitPointsFS, | ||||
|     onClick: (clickedFeature: Feature) => { | ||||
|       console.log("Clicked feature is", clickedFeature, splitPoints.data) | ||||
|       const i = splitPoints.data.findIndex(f => f === clickedFeature) | ||||
|       if(i < 0){ | ||||
|         return | ||||
|       } | ||||
|       splitPoints.data.splice(i, 1) | ||||
|       splitPoints.ping() | ||||
|     } | ||||
|   }) | ||||
|   let id = 0 | ||||
|   adaptor.lastClickLocation.addCallbackD(({lon, lat}) => { | ||||
|     const projected = GeoOperations.nearestPoint(wayGeojson, [lon, lat]) | ||||
|      | ||||
|     projected.properties["id"] = id | ||||
|     id++ | ||||
|     splitPoints.data.push(<any> projected) | ||||
|     splitPoints.ping() | ||||
|   }) | ||||
|    | ||||
| </script> | ||||
| <div class="w-full h-full"> | ||||
|   <MaplibreMap {map}></MaplibreMap> | ||||
| </div> | ||||
|  | @ -90,6 +90,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|                 self.setAllowMoving(self.allowMoving.data) | ||||
|                 self.setAllowZooming(self.allowZooming.data) | ||||
|                 self.setMinzoom(self.minzoom.data) | ||||
|                 self.setBounds(self.bounds.data) | ||||
|             }) | ||||
|             self.MoveMapToCurrentLoc(self.location.data) | ||||
|             self.SetZoom(self.zoom.data) | ||||
|  | @ -97,6 +98,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|             self.setAllowMoving(self.allowMoving.data) | ||||
|             self.setAllowZooming(self.allowZooming.data) | ||||
|             self.setMinzoom(self.minzoom.data) | ||||
|             self.setBounds(self.bounds.data) | ||||
|             this.updateStores() | ||||
|             map.on("moveend", () => this.updateStores()) | ||||
|             map.on("click", (e) => { | ||||
|  | @ -238,18 +240,13 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
|                     container.style.height = document.documentElement.clientHeight + "px" | ||||
|                 } | ||||
| 
 | ||||
|                 const markerCanvas: HTMLCanvasElement = await html2canvas( | ||||
|                 await html2canvas( | ||||
|                     map.getCanvasContainer(), | ||||
|                     { | ||||
|                         backgroundColor: "#00000000", | ||||
|                         canvas: drawOn, | ||||
|                     } | ||||
|                 ) | ||||
|                 const markers = await new Promise<Blob>((resolve) => | ||||
|                     markerCanvas.toBlob((data) => resolve(data)) | ||||
|                 ) | ||||
|                 console.log("Markers:", markers, markerCanvas) | ||||
|                 // destinationCtx.drawImage(markerCanvas, 0, 0)
 | ||||
|             } catch (e) { | ||||
|                 console.error(e) | ||||
|             } finally { | ||||
|  | @ -429,7 +426,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | |||
| 
 | ||||
|     private setBounds(bounds: BBox) { | ||||
|         const map = this._maplibreMap.data | ||||
|         if (map === undefined) { | ||||
|         if (map === undefined || bounds === undefined) { | ||||
|             return | ||||
|         } | ||||
|         const oldBounds = map.getBounds() | ||||
|  |  | |||
|  | @ -24,7 +24,7 @@ | |||
|      $map.resize(); | ||||
|     }); | ||||
|   }); | ||||
|   const styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=GvoVAJgu46I5rZapJuAy"; | ||||
|   const styleUrl = "https://api.maptiler.com/maps/15cc8f61-0353-4be6-b8da-13daea5f7432/style.json?key=GvoVAJgu46I5rZapJuAy"; | ||||
| </script> | ||||
| <main> | ||||
|   <Map bind:center={center} | ||||
|  |  | |||
|  | @ -197,7 +197,7 @@ class LineRenderingLayer { | |||
|         this._fetchStore = fetchStore | ||||
|         this._onClick = onClick | ||||
|         const self = this | ||||
|         features.features.addCallbackAndRunD((features) => self.update(features)) | ||||
|         features.features.addCallbackAndRunD(() => self.update(features.features)) | ||||
|     } | ||||
| 
 | ||||
|     private calculatePropsFor( | ||||
|  | @ -229,13 +229,23 @@ class LineRenderingLayer { | |||
|         return calculatedProps | ||||
|     } | ||||
| 
 | ||||
|     private async update(features: Feature[]) { | ||||
|     private currentSourceData | ||||
|     private async update(featureSource: Store<Feature[]>) { | ||||
|         const map = this._map | ||||
|         while (!map.isStyleLoaded()) { | ||||
|             await Utils.waitFor(100) | ||||
|         } | ||||
| 
 | ||||
|         // After waiting 'till the map has loaded, the data might have changed already
 | ||||
|         // As such, we only now read the features from the featureSource and compare with the previously set data
 | ||||
|         const features = featureSource.data | ||||
|         const src = <GeoJSONSource>map.getSource(this._layername) | ||||
|         if (this.currentSourceData === features) { | ||||
|             // Already up to date
 | ||||
|             return | ||||
|         } | ||||
|         if (src === undefined) { | ||||
|             this.currentSourceData = features | ||||
|             map.addSource(this._layername, { | ||||
|                 type: "geojson", | ||||
|                 data: { | ||||
|  | @ -262,7 +272,6 @@ class LineRenderingLayer { | |||
|             }) | ||||
| 
 | ||||
|             map.on("click", linelayer, (e) => { | ||||
|                 console.log("Click", e) | ||||
|                 e.originalEvent["consumed"] = true | ||||
|                 this._onClick(e.features[0]) | ||||
|             }) | ||||
|  | @ -297,9 +306,10 @@ class LineRenderingLayer { | |||
|                 } | ||||
|             }) | ||||
|         } else { | ||||
|             this.currentSourceData = features | ||||
|             src.setData({ | ||||
|                 type: "FeatureCollection", | ||||
|                 features, | ||||
|                 features: this.currentSourceData, | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|  | @ -345,10 +355,21 @@ export default class ShowDataLayer { | |||
|         "ShowDataLayer.ts:range.json" | ||||
|     ) | ||||
|     private readonly _map: Store<MlMap> | ||||
|     private readonly _options: ShowDataLayerOptions & { layer: LayerConfig } | ||||
|     private readonly _options: ShowDataLayerOptions & { | ||||
|         layer: LayerConfig | ||||
|         drawMarkers?: true | boolean | ||||
|         drawLines?: true | boolean | ||||
|     } | ||||
|     private readonly _popupCache: Map<string, ScrollableFullScreen> | ||||
| 
 | ||||
|     constructor(map: Store<MlMap>, options: ShowDataLayerOptions & { layer: LayerConfig }) { | ||||
|     constructor( | ||||
|         map: Store<MlMap>, | ||||
|         options: ShowDataLayerOptions & { | ||||
|             layer: LayerConfig | ||||
|             drawMarkers?: true | boolean | ||||
|             drawLines?: true | boolean | ||||
|         } | ||||
|     ) { | ||||
|         this._map = map | ||||
|         this._options = options | ||||
|         this._popupCache = new Map() | ||||
|  | @ -405,28 +426,31 @@ export default class ShowDataLayer { | |||
|                 selectedElement?.setData(feature) | ||||
|                 selectedLayer?.setData(this._options.layer) | ||||
|             }) | ||||
|         for (let i = 0; i < this._options.layer.lineRendering.length; i++) { | ||||
|             const lineRenderingConfig = this._options.layer.lineRendering[i] | ||||
|             new LineRenderingLayer( | ||||
|                 map, | ||||
|                 features, | ||||
|                 this._options.layer.id + "_linerendering_" + i, | ||||
|                 lineRenderingConfig, | ||||
|                 doShowLayer, | ||||
|                 fetchStore, | ||||
|                 onClick | ||||
|             ) | ||||
|         if (this._options.drawLines !== false) { | ||||
|             for (let i = 0; i < this._options.layer.lineRendering.length; i++) { | ||||
|                 const lineRenderingConfig = this._options.layer.lineRendering[i] | ||||
|                 new LineRenderingLayer( | ||||
|                     map, | ||||
|                     features, | ||||
|                     this._options.layer.id + "_linerendering_" + i, | ||||
|                     lineRenderingConfig, | ||||
|                     doShowLayer, | ||||
|                     fetchStore, | ||||
|                     onClick | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (const pointRenderingConfig of this._options.layer.mapRendering) { | ||||
|             new PointRenderingLayer( | ||||
|                 map, | ||||
|                 features, | ||||
|                 pointRenderingConfig, | ||||
|                 doShowLayer, | ||||
|                 fetchStore, | ||||
|                 onClick | ||||
|             ) | ||||
|         if (this._options.drawMarkers !== false) { | ||||
|             for (const pointRenderingConfig of this._options.layer.mapRendering) { | ||||
|                 new PointRenderingLayer( | ||||
|                     map, | ||||
|                     features, | ||||
|                     pointRenderingConfig, | ||||
|                     doShowLayer, | ||||
|                     fetchStore, | ||||
|                     onClick | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|         features.features.addCallbackAndRunD((_) => this.zoomToCurrentFeatures(map)) | ||||
|     } | ||||
|  |  | |||
|  | @ -83,19 +83,9 @@ | |||
|       snapOnto: snapToWay | ||||
|     }); | ||||
|     await state.changes.applyAction(newElementAction); | ||||
|     // The 'changes' should have created a new point, which added this into the 'featureProperties' | ||||
|     const newId = newElementAction.newElementId; | ||||
|     state.newFeatures.features.data.push({ | ||||
|       type: "Feature", | ||||
|       properties: { | ||||
|         id: newId, | ||||
|         ...TagUtils.KVtoProperties(tags) | ||||
|       }, | ||||
|       geometry: { | ||||
|         type: "Point", | ||||
|         coordinates: [location.lon, location.lat] | ||||
|       } | ||||
|     }); | ||||
|     state.newFeatures.features.ping(); | ||||
|      | ||||
|     const tagsStore = state.featureProperties.getStore(newId); | ||||
|     { | ||||
|       // Set some metainfo | ||||
|  |  | |||
|  | @ -58,6 +58,7 @@ | |||
|         ]) | ||||
|       } | ||||
|     }; | ||||
|     // Normally, the 'Changes' will generate the new element. The 'notes' are an exception to this | ||||
|     state.newFeatures.features.data.push(feature); | ||||
|     state.newFeatures.features.ping(); | ||||
|     state.selectedElement?.setData(feature); | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import { OsmConnection, OsmServiceState } from "../../Logic/Osm/OsmConnection" | |||
| import { VariableUiElement } from "../Base/VariableUIElement" | ||||
| import Loading from "../Base/Loading" | ||||
| import Translations from "../i18n/Translations" | ||||
| import { Store } from "../../Logic/UIEventSource" | ||||
| import { ImmutableStore, Store } from "../../Logic/UIEventSource" | ||||
| import Combine from "../Base/Combine" | ||||
| import { Translation } from "../i18n/Translation" | ||||
| 
 | ||||
|  | @ -13,13 +13,13 @@ class LoginButton extends SubtleButton { | |||
|     constructor( | ||||
|         text: BaseUIElement | string, | ||||
|         state: { | ||||
|             osmConnection: OsmConnection | ||||
|             osmConnection?: OsmConnection | ||||
|         }, | ||||
|         icon?: BaseUIElement | string | ||||
|     ) { | ||||
|         super(icon ?? Svg.login_ui(), text) | ||||
|         this.onClick(() => { | ||||
|             state.osmConnection.AttemptLogin() | ||||
|             state.osmConnection?.AttemptLogin() | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | @ -32,13 +32,16 @@ export class LoginToggle extends VariableUiElement { | |||
|      * If logging in is not possible for some reason, an appropriate error message is shown | ||||
|      * | ||||
|      * State contains the 'osmConnection' to work with | ||||
|      * @param el: Element to show when logged in | ||||
|      * @param text: To show on the login button. Default: nothing | ||||
|      * @param state: if no osmConnection is given, assumes test situation and will show 'el' as if logged in | ||||
|      */ | ||||
|     constructor( | ||||
|         el: BaseUIElement, | ||||
|         text: BaseUIElement | string, | ||||
|         state: { | ||||
|             readonly osmConnection: OsmConnection | ||||
|             readonly featureSwitchUserbadge: Store<boolean> | ||||
|             readonly osmConnection?: OsmConnection | ||||
|             readonly featureSwitchUserbadge?: Store<boolean> | ||||
|         } | ||||
|     ) { | ||||
|         const loading = new Loading("Trying to log in...") | ||||
|  | @ -51,14 +54,14 @@ export class LoginToggle extends VariableUiElement { | |||
|         } | ||||
| 
 | ||||
|         super( | ||||
|             state.osmConnection.loadingStatus.map( | ||||
|             state.osmConnection?.loadingStatus?.map( | ||||
|                 (osmConnectionState) => { | ||||
|                     if (state.featureSwitchUserbadge.data == false) { | ||||
|                     if (state.featureSwitchUserbadge?.data == false) { | ||||
|                         // All features to login with are disabled
 | ||||
|                         return undefined | ||||
|                     } | ||||
| 
 | ||||
|                     const apiState = state.osmConnection.apiIsOnline.data | ||||
|                     const apiState = state.osmConnection?.apiIsOnline?.data ?? "online" | ||||
|                     const apiTranslation = offlineModes[apiState] | ||||
|                     if (apiTranslation !== undefined) { | ||||
|                         return new Combine([ | ||||
|  | @ -77,15 +80,15 @@ export class LoginToggle extends VariableUiElement { | |||
|                         return el | ||||
|                     } | ||||
| 
 | ||||
|                     // Error!
 | ||||
|                     // Fallback
 | ||||
|                     return new LoginButton( | ||||
|                         Translations.t.general.loginFailed, | ||||
|                         state, | ||||
|                         Svg.invalid_svg() | ||||
|                     ) | ||||
|                 }, | ||||
|                 [state.featureSwitchUserbadge, state.osmConnection.apiIsOnline] | ||||
|             ) | ||||
|                 [state.featureSwitchUserbadge, state.osmConnection?.apiIsOnline] | ||||
|             ) ?? new ImmutableStore(el) //
 | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -2,33 +2,25 @@ import Toggle from "../Input/Toggle" | |||
| import Svg from "../../Svg" | ||||
| import { UIEventSource } from "../../Logic/UIEventSource" | ||||
| import { SubtleButton } from "../Base/SubtleButton" | ||||
| import { GeoOperations } from "../../Logic/GeoOperations" | ||||
| import Combine from "../Base/Combine" | ||||
| import { Button } from "../Base/Button" | ||||
| import Translations from "../i18n/Translations" | ||||
| import SplitAction from "../../Logic/Osm/Actions/SplitAction" | ||||
| import Title from "../Base/Title" | ||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" | ||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
| import { BBox } from "../../Logic/BBox" | ||||
| import split_point from "../../assets/layers/split_point/split_point.json" | ||||
| import { OsmConnection } from "../../Logic/Osm/OsmConnection" | ||||
| import { Changes } from "../../Logic/Osm/Changes" | ||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | ||||
| import FilteredLayer from "../../Models/FilteredLayer" | ||||
| import BaseUIElement from "../BaseUIElement" | ||||
| import { VariableUiElement } from "../Base/VariableUIElement" | ||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen" | ||||
| import { LoginToggle } from "./LoginButton" | ||||
| import { SpecialVisualizationState } from "../SpecialVisualization" | ||||
| import SvelteUIElement from "../Base/SvelteUIElement" | ||||
| import WaySplitMap from "../BigComponents/WaySplitMap.svelte" | ||||
| import { OsmObject } from "../../Logic/Osm/OsmObject" | ||||
| import { Feature, Point } from "geojson" | ||||
| import { WayId } from "../../Models/OsmFeature" | ||||
| import { OsmConnection } from "../../Logic/Osm/OsmConnection" | ||||
| import { Changes } from "../../Logic/Osm/Changes" | ||||
| import { IndexedFeatureSource } from "../../Logic/FeatureSource/FeatureSource" | ||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | ||||
| 
 | ||||
| export default class SplitRoadWizard extends Combine { | ||||
|     private static splitLayerStyling = new LayerConfig( | ||||
|         split_point, | ||||
|         "(BUILTIN) SplitRoadWizard.ts", | ||||
|         true | ||||
|     ) | ||||
| 
 | ||||
|     public dialogIsOpened: UIEventSource<boolean> | ||||
| 
 | ||||
|     /** | ||||
|  | @ -37,20 +29,34 @@ export default class SplitRoadWizard extends Combine { | |||
|      * @param id: The id of the road to remove | ||||
|      * @param state: the state of the application | ||||
|      */ | ||||
|     constructor(id: string, state: SpecialVisualizationState) { | ||||
|     constructor( | ||||
|         id: WayId, | ||||
|         state: { | ||||
|             layout?: LayoutConfig | ||||
|             osmConnection?: OsmConnection | ||||
|             changes?: Changes | ||||
|             indexedFeatures?: IndexedFeatureSource | ||||
|             selectedElement?: UIEventSource<Feature> | ||||
|         } | ||||
|     ) { | ||||
|         const t = Translations.t.split | ||||
| 
 | ||||
|         // Contains the points on the road that are selected to split on - contains geojson points with extra properties such as 'location' with the distance along the linestring
 | ||||
|         const splitPoints = new UIEventSource<{ feature: any; freshness: Date }[]>([]) | ||||
|         const splitPoints = new UIEventSource<Feature<Point>[]>([]) | ||||
| 
 | ||||
|         const hasBeenSplit = new UIEventSource(false) | ||||
| 
 | ||||
|         // Toggle variable between show split button and map
 | ||||
|         const splitClicked = new UIEventSource<boolean>(false) | ||||
| 
 | ||||
|         const leafletMap = new UIEventSource<BaseUIElement>( | ||||
|             SplitRoadWizard.setupMapComponent(id, splitPoints, state) | ||||
|         ) | ||||
|         const leafletMap = new UIEventSource<BaseUIElement>(undefined) | ||||
| 
 | ||||
|         function initMap() { | ||||
|             SplitRoadWizard.setupMapComponent(id, splitPoints).then((mapComponent) => | ||||
|                 leafletMap.setData(mapComponent.SetClass("w-full h-80")) | ||||
|             ) | ||||
|         } | ||||
|         initMap() | ||||
| 
 | ||||
|         // Toggle between splitmap
 | ||||
|         const splitButton = new SubtleButton( | ||||
|  | @ -70,23 +76,19 @@ export default class SplitRoadWizard extends Combine { | |||
|             splitClicked.setData(false) | ||||
|             const splitAction = new SplitAction( | ||||
|                 id, | ||||
|                 splitPoints.data.map((ff) => ff.feature.geometry.coordinates), | ||||
|                 splitPoints.data.map((ff) => <[number, number]>(<Point>ff.geometry).coordinates), | ||||
|                 { | ||||
|                     theme: state?.layoutToUse?.id, | ||||
|                     theme: state?.layout?.id, | ||||
|                 }, | ||||
|                 5, | ||||
|                 (coordinates) => { | ||||
|                     state.allElements.ContainingFeatures.get(id).geometry["coordinates"] = | ||||
|                         coordinates | ||||
|                 } | ||||
|                 5 | ||||
|             ) | ||||
|             await state.changes.applyAction(splitAction) | ||||
|             await state.changes?.applyAction(splitAction) | ||||
|             // We throw away the old map and splitpoints, and create a new map from scratch
 | ||||
|             splitPoints.setData([]) | ||||
|             leafletMap.setData(SplitRoadWizard.setupMapComponent(id, splitPoints, state)) | ||||
|             initMap() | ||||
| 
 | ||||
|             // Close the popup. The contributor has to select a segment again to make sure they continue editing the correct segment; see #1219
 | ||||
|             ScrollableFullScreen.collapse() | ||||
|             state.selectedElement?.setData(undefined) | ||||
|         }) | ||||
| 
 | ||||
|         saveButton.SetClass("btn btn-primary mr-3") | ||||
|  | @ -131,95 +133,14 @@ export default class SplitRoadWizard extends Combine { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     private static setupMapComponent( | ||||
|         id: string, | ||||
|         splitPoints: UIEventSource<{ feature: any; freshness: Date }[]>, | ||||
|         state: { | ||||
|             filteredLayers: UIEventSource<FilteredLayer[]> | ||||
|             backgroundLayer: UIEventSource<BaseLayer> | ||||
|             featureSwitchIsTesting: UIEventSource<boolean> | ||||
|             featureSwitchIsDebugging: UIEventSource<boolean> | ||||
|             featureSwitchShowAllQuestions: UIEventSource<boolean> | ||||
|             osmConnection: OsmConnection | ||||
|             featureSwitchUserbadge: UIEventSource<boolean> | ||||
|             changes: Changes | ||||
|             layoutToUse: LayoutConfig | ||||
|             allElements: ElementStorage | ||||
|         } | ||||
|     ): BaseUIElement { | ||||
|         // Load the road with given id on the minimap
 | ||||
|         const roadElement = state.allElements.ContainingFeatures.get(id) | ||||
| 
 | ||||
|         // Minimap on which you can select the points to be splitted
 | ||||
|         const miniMap = Minimap.createMiniMap({ | ||||
|             background: state.backgroundLayer, | ||||
|             allowMoving: true, | ||||
|             leafletOptions: { | ||||
|                 minZoom: 14, | ||||
|             }, | ||||
|     private static async setupMapComponent( | ||||
|         id: WayId, | ||||
|         splitPoints: UIEventSource<Feature[]> | ||||
|     ): Promise<BaseUIElement> { | ||||
|         const osmWay = await OsmObject.DownloadObjectAsync(id) | ||||
|         return new SvelteUIElement(WaySplitMap, { | ||||
|             osmWay, | ||||
|             splitPoints, | ||||
|         }) | ||||
|         miniMap.SetStyle("width: 100%; height: 24rem").SetClass("rounded-xl overflow-hidden") | ||||
| 
 | ||||
|         miniMap.installBounds(BBox.get(roadElement).pad(0.25), false) | ||||
| 
 | ||||
|         // Define how a cut is displayed on the map
 | ||||
| 
 | ||||
|         // Datalayer displaying the road and the cut points (if any)
 | ||||
|         new ShowDataMultiLayer({ | ||||
|             features: StaticFeatureSource.fromGeojson([roadElement]), | ||||
|             layers: state.filteredLayers, | ||||
|             leafletMap: miniMap.leafletMap, | ||||
|             zoomToFeatures: true, | ||||
|             state, | ||||
|         }) | ||||
| 
 | ||||
|         new ShowDataLayer({ | ||||
|             features: new StaticFeatureSource(splitPoints), | ||||
|             leafletMap: miniMap.leafletMap, | ||||
|             zoomToFeatures: false, | ||||
|             layerToShow: SplitRoadWizard.splitLayerStyling, | ||||
|             state, | ||||
|         }) | ||||
|         /** | ||||
|          * Handles a click on the overleaf map. | ||||
|          * Finds the closest intersection with the road and adds a point there, ready to confirm the cut. | ||||
|          * @param coordinates Clicked location, [lon, lat] | ||||
|          */ | ||||
|         function onMapClick(coordinates) { | ||||
|             // First, we check if there is another, already existing point nearby
 | ||||
|             const points = splitPoints.data | ||||
|                 .map((f, i) => [f.feature, i]) | ||||
|                 .filter( | ||||
|                     (p) => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) < 5 | ||||
|                 ) | ||||
|                 .map((p) => p[1]) | ||||
|                 .sort((a, b) => a - b) | ||||
|                 .reverse(/*Copy/derived list, inplace reverse is fine*/) | ||||
|             if (points.length > 0) { | ||||
|                 for (const point of points) { | ||||
|                     splitPoints.data.splice(point, 1) | ||||
|                 } | ||||
|                 splitPoints.ping() | ||||
|                 return | ||||
|             } | ||||
| 
 | ||||
|             // Get nearest point on the road
 | ||||
|             const pointOnRoad = GeoOperations.nearestPoint(<any>roadElement, coordinates) // pointOnRoad is a geojson
 | ||||
| 
 | ||||
|             // Update point properties to let it match the layer
 | ||||
|             pointOnRoad.properties["_split_point"] = "yes" | ||||
| 
 | ||||
|             // Add it to the list of all points and notify observers
 | ||||
|             splitPoints.data.push({ feature: pointOnRoad, freshness: new Date() }) // show the point on the data layer
 | ||||
|             splitPoints.ping() // not updated using .setData, so manually ping observers
 | ||||
|         } | ||||
| 
 | ||||
|         // When clicked, pass clicked location coordinates to onMapClick function
 | ||||
|         miniMap.leafletMap.addCallbackAndRunD((leafletMap) => | ||||
|             leafletMap.on("click", (mouseEvent: LeafletMouseEvent) => { | ||||
|                 onMapClick([mouseEvent.latlng.lng, mouseEvent.latlng.lat]) | ||||
|             }) | ||||
|         ) | ||||
|         return miniMap | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,18 +1,18 @@ | |||
| import { Store, UIEventSource } from "../Logic/UIEventSource" | ||||
| import BaseUIElement from "./BaseUIElement" | ||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig" | ||||
| import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" | ||||
| import { OsmConnection } from "../Logic/Osm/OsmConnection" | ||||
| import { Changes } from "../Logic/Osm/Changes" | ||||
| import { ExportableMap, MapProperties } from "../Models/MapProperties" | ||||
| import LayerState from "../Logic/State/LayerState" | ||||
| import { Feature, Geometry } from "geojson" | ||||
| import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" | ||||
| import { MangroveIdentity } from "../Logic/Web/MangroveReviews" | ||||
| import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore" | ||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig" | ||||
| import FeatureSwitchState from "../Logic/State/FeatureSwitchState" | ||||
| import { MenuState } from "../Models/MenuState" | ||||
| import { Store, UIEventSource } from "../Logic/UIEventSource"; | ||||
| import BaseUIElement from "./BaseUIElement"; | ||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; | ||||
| import { IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource"; | ||||
| import { OsmConnection } from "../Logic/Osm/OsmConnection"; | ||||
| import { Changes } from "../Logic/Osm/Changes"; | ||||
| import { ExportableMap, MapProperties } from "../Models/MapProperties"; | ||||
| import LayerState from "../Logic/State/LayerState"; | ||||
| import { Feature, Geometry } from "geojson"; | ||||
| import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"; | ||||
| import { MangroveIdentity } from "../Logic/Web/MangroveReviews"; | ||||
| import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"; | ||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||
| import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; | ||||
| import { MenuState } from "../Models/MenuState"; | ||||
| 
 | ||||
| /** | ||||
|  * The state needed to render a special Visualisation. | ||||
|  |  | |||
|  | @ -77,8 +77,9 @@ import Lazy from "./Base/Lazy" | |||
| import { CheckBox } from "./Input/Checkboxes" | ||||
| import Slider from "./Input/Slider" | ||||
| import DeleteWizard from "./Popup/DeleteWizard" | ||||
| import { OsmId, OsmTags } from "../Models/OsmFeature" | ||||
| import { OsmId, OsmTags, WayId } from "../Models/OsmFeature" | ||||
| import MoveWizard from "./Popup/MoveWizard" | ||||
| import SplitRoadWizard from "./Popup/SplitRoadWizard" | ||||
| 
 | ||||
| class NearbyImageVis implements SpecialVisualization { | ||||
|     // Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
 | ||||
|  | @ -532,16 +533,17 @@ export default class SpecialVisualizations { | |||
|                 args: [], | ||||
|                 constr( | ||||
|                     state: SpecialVisualizationState, | ||||
|                     tagSource: UIEventSource<Record<string, string>>, | ||||
|                     argument: string[], | ||||
|                     feature: Feature, | ||||
|                     layer: LayerConfig | ||||
|                     tagSource: UIEventSource<Record<string, string>> | ||||
|                 ): BaseUIElement { | ||||
|                     return new VariableUiElement( | ||||
|                         // TODO
 | ||||
|                         tagSource | ||||
|                             .map((tags) => tags.id) | ||||
|                             .map((id) => new FixedUiElement("TODO: enable splitting")) // new SplitRoadWizard(id, state))
 | ||||
|                             .map((id) => { | ||||
|                                 if (id.startsWith("way/")) { | ||||
|                                     return new SplitRoadWizard(<WayId>id, state) | ||||
|                                 } | ||||
|                                 return undefined | ||||
|                             }) | ||||
|                     ) | ||||
|                 }, | ||||
|             }, | ||||
|  |  | |||
							
								
								
									
										21
									
								
								assets/layers/split_road/split_road.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								assets/layers/split_road/split_road.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| { | ||||
|   "id": "split_road", | ||||
|   "description": "Layer rendering the way to split in the 'splitRoadWizard'. This one is used instead of the variable rendering by the themes themselves, as they might not always be very visible", | ||||
|   "minzoom": 1, | ||||
|   "source": "special", | ||||
|   "name": null, | ||||
|   "title": null, | ||||
|   "mapRendering": [ | ||||
|     { | ||||
|       "location": [ | ||||
|         "point" | ||||
|       ], | ||||
|       "icon": "bug", | ||||
|       "iconSize": "30,30,center" | ||||
|     }, | ||||
|     { | ||||
|       "width": "8", | ||||
|       "color": "black" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
|  | @ -379,7 +379,7 @@ | |||
|   ], | ||||
|   "overrideAll": { | ||||
|     "allowSplit": true, | ||||
|     "tagRenderings+": [ | ||||
|     "+tagRenderings": [ | ||||
|       { | ||||
|         "id": "is_cyclestreet", | ||||
|         "question": { | ||||
|  | @ -723,4 +723,4 @@ | |||
|       } | ||||
|     ] | ||||
|   } | ||||
| } | ||||
| } | ||||
|  |  | |||
|  | @ -1061,6 +1061,10 @@ video { | |||
|   height: 10rem; | ||||
| } | ||||
| 
 | ||||
| .h-80 { | ||||
|   height: 20rem; | ||||
| } | ||||
| 
 | ||||
| .max-h-20vh { | ||||
|   max-height: 20vh; | ||||
| } | ||||
|  | @ -1081,6 +1085,10 @@ video { | |||
|   min-height: 8rem; | ||||
| } | ||||
| 
 | ||||
| .w-full { | ||||
|   width: 100%; | ||||
| } | ||||
| 
 | ||||
| .w-8 { | ||||
|   width: 2rem; | ||||
| } | ||||
|  | @ -1105,10 +1113,6 @@ video { | |||
|   width: 1.5rem; | ||||
| } | ||||
| 
 | ||||
| .w-full { | ||||
|   width: 100%; | ||||
| } | ||||
| 
 | ||||
| .w-screen { | ||||
|   width: 100vw; | ||||
| } | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ | |||
| </head> | ||||
| <body> | ||||
| 
 | ||||
| <div id="maindiv">'maindiv' not attached</div> | ||||
| <div id="maindiv" class="w-full h-full">'maindiv' not attached</div> | ||||
| <div id="extradiv">'extradiv' not attached</div> | ||||
| 
 | ||||
| <script type="module" src="./test.ts"></script> | ||||
|  |  | |||
							
								
								
									
										11
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -9,6 +9,10 @@ import { UIEventSource } from "./Logic/UIEventSource" | |||
| import { VariableUiElement } from "./UI/Base/VariableUIElement" | ||||
| import { FixedUiElement } from "./UI/Base/FixedUiElement" | ||||
| import Title from "./UI/Base/Title" | ||||
| import WaySplitMap from "./UI/BigComponents/WaySplitMap.svelte" | ||||
| import SvelteUIElement from "./UI/Base/SvelteUIElement" | ||||
| import { OsmObject } from "./Logic/Osm/OsmObject" | ||||
| import SplitRoadWizard from "./UI/Popup/SplitRoadWizard" | ||||
| 
 | ||||
| function testspecial() { | ||||
|     const layout = new LayoutConfig(<any>theme, true) // qp.data === "" ?  : new AllKnownLayoutsLazy().get(qp.data)
 | ||||
|  | @ -41,5 +45,10 @@ function testinput() { | |||
|     } | ||||
|     new Combine(els).SetClass("flex flex-col").AttachTo("maindiv") | ||||
| } | ||||
| testinput() | ||||
| 
 | ||||
| async function testWaySplit() { | ||||
|     new SplitRoadWizard("way/28717919", {}).SetClass("w-full h-full").AttachTo("maindiv") | ||||
| } | ||||
| testWaySplit().then((_) => console.log("inited")) | ||||
| //testinput()
 | ||||
| // testspecial()
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue