Porównaj commity

...

19 Commity

Autor SHA1 Wiadomość Data
Candid Dauth b2bbe1c2a2 Rename "Pad" to "Map" in HistoryEntry.type 2024-04-22 16:45:30 +02:00
Candid Dauth 7a5d74dc74 Rename Pads table to Maps 2024-04-22 13:05:17 +02:00
Candid Dauth 7fa2208079 Rename PadNotFoundError to MapNotFoundError 2024-04-21 19:55:09 +02:00
Candid Dauth fe74eab306 Rename pad to map in socket methods 2024-04-21 19:50:44 +02:00
Candid Dauth 24b4c3bf96 Fix error when removing attribution control 2024-04-21 05:03:31 +02:00
Candid Dauth 3f7d64a305 Listen to viewreset event in bbox handler 2024-04-21 05:03:31 +02:00
Candid Dauth 39f3df852f Rename padData/deletePad events to mapData/deleteMap 2024-04-21 05:03:30 +02:00
Candid Dauth daa1257a60 Start renaming "pad" to "map" 2024-04-21 05:03:30 +02:00
Candid Dauth 0592d0bca7 Upgrade facilmap-client-v3 dependency 2024-04-21 05:03:30 +02:00
Candid Dauth db31dc8a2a Support history for socket v2 and add tests 2024-04-21 05:03:29 +02:00
Candid Dauth a547401e08 Import client v4 in integration tests 2024-04-21 05:03:29 +02:00
Candid Dauth 3589ec3336 Rename "symbol" to "marker", introduce v3 socket version 2024-04-21 05:03:29 +02:00
Candid Dauth e080834696 Fix applying hash query on page load (#268) 2024-04-21 04:53:30 +02:00
Candid Dauth c9d20cea88 Improve error stack traces in client 2024-04-19 23:54:13 +02:00
Candid Dauth 78d36ff2fa
Merge pull request #265 from weblate/weblate-facilmap-facilmap-frontend
Translations update from Hosted Weblate
2024-04-19 23:53:28 +02:00
Roman Deev 15f2d91d23
Translated using Weblate (Russian)
Currently translated at 38.6% (73 of 189 strings)

Translation: FacilMap/FacilMap Leaflet
Translate-URL: https://hosted.weblate.org/projects/facilmap/facilmap-leaflet/ru/
2024-04-18 12:10:28 +02:00
Roman Deev 19eda4b29f
Translated using Weblate (Russian)
Currently translated at 21.6% (41 of 189 strings)

Translation: FacilMap/FacilMap Leaflet
Translate-URL: https://hosted.weblate.org/projects/facilmap/facilmap-leaflet/ru/
2024-04-18 12:10:28 +02:00
Roman Deev d9be2030b7
Translated using Weblate (Russian)
Currently translated at 3.6% (26 of 720 strings)

Translation: FacilMap/FacilMap frontend
Translate-URL: https://hosted.weblate.org/projects/facilmap/facilmap-frontend/ru/
2024-04-18 12:10:28 +02:00
Candid Dauth 347edfc861 Make OsmAnd markers and lines transparent 2024-04-18 12:10:14 +02:00
157 zmienionych plików z 4253 dodań i 2900 usunięć

Wyświetl plik

@ -0,0 +1,11 @@
diff --git a/dist/client.d.ts b/dist/client.d.ts
index f441770687db57140a4c50b4a78a15b696f44177..3dbea34c932e79d6e3dbbf49b5dcafe2fb54cccd 100644
--- a/dist/client.d.ts
+++ b/dist/client.d.ts
@@ -1,5 +1,5 @@
import { Socket as SocketIO } from "socket.io-client";
-import { Bbox, BboxWithZoom, EventHandler, EventName, FindOnMapQuery, FindPadsQuery, FindPadsResult, FindQuery, GetPadQuery, HistoryEntry, ID, Line, LineCreate, LineExportRequest, LineTemplateRequest, LineToRouteCreate, LineUpdate, MapEvents, Marker, MarkerCreate, MarkerUpdate, MultipleEvents, ObjectWithId, PadData, PadDataCreate, PadDataUpdate, PadId, PagedResults, RequestData, RequestName, ResponseData, Route, RouteClear, RouteCreate, RouteExportRequest, RouteInfo, RouteRequest, SearchResult, TrackPoint, Type, TypeCreate, TypeUpdate, View, ViewCreate, ViewUpdate, Writable } from "facilmap-types";
+import { Bbox, BboxWithZoom, EventHandler, EventName, FindOnMapQuery, FindPadsQuery, FindPadsResult, FindQuery, GetPadQuery, HistoryEntry, ID, Line, LineCreate, LineExportRequest, LineTemplateRequest, LineToRouteCreate, LineUpdate, MapEvents, Marker, MarkerCreate, MarkerUpdate, MultipleEvents, ObjectWithId, PadData, PadDataCreate, PadDataUpdate, PadId, PagedResults, RequestData, RequestName, ResponseData, Route, RouteClear, RouteCreate, RouteExportRequest, RouteInfo, RouteRequest, SearchResult, TrackPoint, Type, TypeCreate, TypeUpdate, View, ViewCreate, ViewUpdate, Writable } from "facilmap-types-v3";
export interface ClientEvents<DataType = Record<string, string>> extends MapEvents<DataType> {
connect: [];
disconnect: [string];

Wyświetl plik

@ -0,0 +1,90 @@
diff --git a/dist/facilmap-client.d.ts b/dist/facilmap-client.d.ts
index 80360fe16876fe53bcad94efe2ef607c0be793ee..442708ef08dd539c393924c86f510785697130da 100644
--- a/dist/facilmap-client.d.ts
+++ b/dist/facilmap-client.d.ts
@@ -1,45 +1,45 @@
-import { Bbox } from 'facilmap-types';
-import { BboxWithZoom } from 'facilmap-types';
-import { CRU } from 'facilmap-types';
-import { EventHandler } from 'facilmap-types';
-import { EventName } from 'facilmap-types';
-import { FindOnMapQuery } from 'facilmap-types';
-import { FindPadsQuery } from 'facilmap-types';
-import { FindPadsResult } from 'facilmap-types';
-import { FindQuery } from 'facilmap-types';
-import { GetPadQuery } from 'facilmap-types';
-import { HistoryEntry } from 'facilmap-types';
-import { ID } from 'facilmap-types';
-import { Line } from 'facilmap-types';
-import { LineExportRequest } from 'facilmap-types';
-import { LineTemplate } from 'facilmap-types';
-import { LineTemplateRequest } from 'facilmap-types';
-import { LineToRouteCreate } from 'facilmap-types';
+import { Bbox } from 'facilmap-types-v4';
+import { BboxWithZoom } from 'facilmap-types-v4';
+import { CRU } from 'facilmap-types-v4';
+import { EventHandler } from 'facilmap-types-v4';
+import { EventName } from 'facilmap-types-v4';
+import { FindOnMapQuery } from 'facilmap-types-v4';
+import { FindPadsQuery } from 'facilmap-types-v4';
+import { FindPadsResult } from 'facilmap-types-v4';
+import { FindQuery } from 'facilmap-types-v4';
+import { GetPadQuery } from 'facilmap-types-v4';
+import { HistoryEntry } from 'facilmap-types-v4';
+import { ID } from 'facilmap-types-v4';
+import { Line } from 'facilmap-types-v4';
+import { LineExportRequest } from 'facilmap-types-v4';
+import { LineTemplate } from 'facilmap-types-v4';
+import { LineTemplateRequest } from 'facilmap-types-v4';
+import { LineToRouteCreate } from 'facilmap-types-v4';
import { ManagerOptions } from 'socket.io-client';
-import { Marker } from 'facilmap-types';
-import { MultipleEvents } from 'facilmap-types';
-import { ObjectWithId } from 'facilmap-types';
-import { PadData } from 'facilmap-types';
-import { PadId } from 'facilmap-types';
-import { PagedResults } from 'facilmap-types';
-import { Route } from 'facilmap-types';
-import { RouteClear } from 'facilmap-types';
-import { RouteCreate } from 'facilmap-types';
-import { RouteExportRequest } from 'facilmap-types';
-import { RouteInfo } from 'facilmap-types';
-import { RouteRequest } from 'facilmap-types';
-import { SearchResult } from 'facilmap-types';
-import { SetLanguageRequest } from 'facilmap-types';
-import { SocketEvents } from 'facilmap-types';
+import { Marker } from 'facilmap-types-v4';
+import { MultipleEvents } from 'facilmap-types-v4';
+import { ObjectWithId } from 'facilmap-types-v4';
+import { PadData } from 'facilmap-types-v4';
+import { PadId } from 'facilmap-types-v4';
+import { PagedResults } from 'facilmap-types-v4';
+import { Route } from 'facilmap-types-v4';
+import { RouteClear } from 'facilmap-types-v4';
+import { RouteCreate } from 'facilmap-types-v4';
+import { RouteExportRequest } from 'facilmap-types-v4';
+import { RouteInfo } from 'facilmap-types-v4';
+import { RouteRequest } from 'facilmap-types-v4';
+import { SearchResult } from 'facilmap-types-v4';
+import { SetLanguageRequest } from 'facilmap-types-v4';
+import { SocketEvents } from 'facilmap-types-v4';
import { SocketOptions } from 'socket.io-client';
-import { SocketRequest } from 'facilmap-types';
-import { SocketRequestName } from 'facilmap-types';
-import { SocketResponse } from 'facilmap-types';
-import { SocketVersion } from 'facilmap-types';
-import { TrackPoint } from 'facilmap-types';
-import { Type } from 'facilmap-types';
-import { View } from 'facilmap-types';
-import { Writable } from 'facilmap-types';
+import { SocketRequest } from 'facilmap-types-v4';
+import { SocketRequestName } from 'facilmap-types-v4';
+import { SocketResponse } from 'facilmap-types-v4';
+import { SocketVersion } from 'facilmap-types-v4';
+import { TrackPoint } from 'facilmap-types-v4';
+import { Type } from 'facilmap-types-v4';
+import { View } from 'facilmap-types-v4';
+import { Writable } from 'facilmap-types-v4';
declare class Client {
private socket;

Wyświetl plik

@ -1,6 +1,6 @@
{
"name": "facilmap-client",
"version": "4.1.1",
"version": "5.0.0",
"description": "A library that acts as a client to FacilMap and makes it possible to retrieve and modify objects on a collaborative map.",
"keywords": [
"maps",

Wyświetl plik

@ -1,8 +1,8 @@
import { io, type ManagerOptions, type Socket as SocketIO, type SocketOptions } from "socket.io-client";
import { type Bbox, type BboxWithZoom, type CRU, type EventHandler, type EventName, type FindOnMapQuery, type FindPadsQuery, type FindPadsResult, type FindQuery, type GetPadQuery, type HistoryEntry, type ID, type Line, type LineExportRequest, type LineTemplateRequest, type LineToRouteCreate, type SocketEvents, type Marker, type MultipleEvents, type ObjectWithId, type PadData, type PadId, type PagedResults, type SocketRequest, type SocketRequestName, type SocketResponse, type Route, type RouteClear, type RouteCreate, type RouteExportRequest, type RouteInfo, type RouteRequest, type SearchResult, type SocketVersion, type TrackPoint, type Type, type View, type Writable, type SocketClientToServerEvents, type SocketServerToClientEvents, type LineTemplate, type LinePointsEvent, PadNotFoundError, type SetLanguageRequest } from "facilmap-types";
import { deserializeError, errorConstructors } from "serialize-error";
import { type Bbox, type BboxWithZoom, type CRU, type EventHandler, type EventName, type FindOnMapQuery, type FindMapsQuery, type FindMapsResult, type FindQuery, type GetMapQuery, type HistoryEntry, type ID, type Line, type LineExportRequest, type LineTemplateRequest, type LineToRouteCreate, type SocketEvents, type Marker, type MultipleEvents, type ObjectWithId, type MapData, type MapId, type PagedResults, type SocketRequest, type SocketRequestName, type SocketResponse, type Route, type RouteClear, type RouteCreate, type RouteExportRequest, type RouteInfo, type RouteRequest, type SearchResult, type SocketVersion, type TrackPoint, type Type, type View, type Writable, type SocketClientToServerEvents, type SocketServerToClientEvents, type LineTemplate, type LinePointsEvent, MapNotFoundError, type SetLanguageRequest } from "facilmap-types";
import { deserializeError, errorConstructors, serializeError } from "serialize-error";
export interface ClientEvents extends SocketEvents<SocketVersion.V2> {
export interface ClientEventsInterface extends SocketEvents<SocketVersion.V3> {
connect: [];
disconnect: [string];
connect_error: [Error];
@ -21,11 +21,13 @@ export interface ClientEvents extends SocketEvents<SocketVersion.V2> {
route: [RouteWithTrackPoints];
clearRoute: [RouteClear];
emit: { [eventName in SocketRequestName<SocketVersion.V2>]: [eventName, SocketRequest<SocketVersion.V2, eventName>] }[SocketRequestName<SocketVersion.V2>];
emitResolve: { [eventName in SocketRequestName<SocketVersion.V2>]: [eventName, SocketResponse<SocketVersion.V2, eventName>] }[SocketRequestName<SocketVersion.V2>];
emitReject: [SocketRequestName<SocketVersion.V2>, Error];
emit: { [eventName in SocketRequestName<SocketVersion.V3>]: [eventName, SocketRequest<SocketVersion.V3, eventName>] }[SocketRequestName<SocketVersion.V3>];
emitResolve: { [eventName in SocketRequestName<SocketVersion.V3>]: [eventName, SocketResponse<SocketVersion.V3, eventName>] }[SocketRequestName<SocketVersion.V3>];
emitReject: [SocketRequestName<SocketVersion.V3>, Error];
}
export type ClientEvents = Pick<ClientEventsInterface, keyof ClientEventsInterface>; // Workaround for https://github.com/microsoft/TypeScript/issues/15300
const MANAGER_EVENTS: Array<EventName<ClientEvents>> = ['error', 'reconnect', 'reconnect_attempt', 'reconnect_error', 'reconnect_failed'];
export interface TrackPoints {
@ -45,7 +47,7 @@ export interface RouteWithTrackPoints extends Omit<Route, "trackPoints"> {
interface ClientState {
disconnected: boolean;
server: string;
padId: string | undefined;
mapId: string | undefined;
bbox: BboxWithZoom | undefined;
readonly: boolean | undefined;
writable: Writable | undefined;
@ -56,7 +58,7 @@ interface ClientState {
}
interface ClientData {
padData: (PadData & { writable: Writable }) | undefined;
mapData: (MapData & { writable: Writable }) | undefined;
markers: Record<ID, Marker>;
lines: Record<ID, LineWithTrackPoints>;
views: Record<ID, View>;
@ -66,10 +68,10 @@ interface ClientData {
routes: Record<string, RouteWithTrackPoints>;
}
errorConstructors.set("PadNotFoundError", PadNotFoundError as any);
errorConstructors.set("MapNotFoundError", MapNotFoundError as any);
class Client {
private socket: SocketIO<SocketServerToClientEvents<SocketVersion.V2>, SocketClientToServerEvents<SocketVersion.V2>>;
private socket: SocketIO<SocketServerToClientEvents<SocketVersion.V3>, SocketClientToServerEvents<SocketVersion.V3>>;
private state: ClientState;
private data: ClientData;
@ -77,11 +79,11 @@ class Client {
[E in EventName<ClientEvents>]?: Array<EventHandler<ClientEvents, E>>
} = { };
constructor(server: string, padId?: string, socketOptions?: Partial<ManagerOptions & SocketOptions>) {
constructor(server: string, mapId?: string, socketOptions?: Partial<ManagerOptions & SocketOptions>) {
this.state = this._makeReactive({
disconnected: true,
server,
padId,
mapId,
bbox: undefined,
readonly: undefined,
writable: undefined,
@ -92,7 +94,7 @@ class Client {
});
this.data = this._makeReactive({
padData: undefined,
mapData: undefined,
markers: { },
lines: { },
views: { },
@ -103,7 +105,7 @@ class Client {
});
const serverUrl = typeof location != "undefined" ? new URL(this.state.server, location.href) : new URL(this.state.server);
const socket = io(`${serverUrl.origin}/v2`, {
const socket = io(`${serverUrl.origin}/v3`, {
forceNew: true,
path: serverUrl.pathname.replace(/\/$/, "") + "/socket.io",
...socketOptions
@ -141,7 +143,7 @@ class Client {
return result;
}
private _fixResponseObject<T>(requestName: SocketRequestName<SocketVersion.V2>, obj: T): T {
private _fixResponseObject<T>(requestName: SocketRequestName<SocketVersion.V3>, obj: T): T {
if (typeof obj != "object" || !(obj as any)?.data || !["getMarker", "addMarker", "editMarker", "deleteMarker", "getLineTemplate", "addLine", "editLine", "deleteLine"].includes(requestName))
return obj;
@ -188,16 +190,18 @@ class Client {
}
}
private async _emit<R extends SocketRequestName<SocketVersion.V2>>(eventName: R, ...[data]: SocketRequest<SocketVersion.V2, R> extends undefined | null ? [ ] : [ SocketRequest<SocketVersion.V2, R> ]): Promise<SocketResponse<SocketVersion.V2, R>> {
private async _emit<R extends SocketRequestName<SocketVersion.V3>>(eventName: R, ...[data]: SocketRequest<SocketVersion.V3, R> extends undefined | null ? [ ] : [ SocketRequest<SocketVersion.V3, R> ]): Promise<SocketResponse<SocketVersion.V3, R>> {
try {
this._simulateEvent("loadStart");
this._simulateEvent("emit", eventName as any, data as any);
const outerError = new Error();
return await new Promise((resolve, reject) => {
this.socket.emit(eventName as any, data, (err: any, data: SocketResponse<SocketVersion.V2, R>) => {
this.socket.emit(eventName as any, data, (err: any, data: SocketResponse<SocketVersion.V3, R>) => {
if(err) {
reject(deserializeError(err));
const cause = deserializeError(err);
reject(deserializeError({ ...serializeError(outerError), message: cause.message, cause }));
this._simulateEvent("emitReject", eventName as any, err);
} else {
const fixedData = this._fixResponseObject(eventName, data);
@ -214,8 +218,8 @@ class Client {
private _handlers: {
[E in EventName<ClientEvents>]?: EventHandler<ClientEvents, E>
} = {
padData: (data) => {
this._set(this.data, 'padData', data);
mapData: (data) => {
this._set(this.data, 'mapData', data);
if(data.writable != null) {
this._set(this.state, 'readonly', data.writable == 0);
@ -224,10 +228,10 @@ class Client {
const id = this.state.writable == 2 ? data.adminId : this.state.writable == 1 ? data.writeId : data.id;
if(id != null)
this._set(this.state, 'padId', id);
this._set(this.state, 'mapId', id);
},
deletePad: () => {
deleteMap: () => {
this._set(this.state, 'readonly', true);
this._set(this.state, 'writable', 0);
this._set(this.state, 'deleted', true);
@ -285,9 +289,9 @@ class Client {
deleteView: (data) => {
this._delete(this.data.views, data.id);
if (this.data.padData) {
if(this.data.padData.defaultViewId == data.id)
this._set(this.data.padData, 'defaultViewId', null);
if (this.data.mapData) {
if(this.data.mapData.defaultViewId == data.id)
this._set(this.data.mapData, 'defaultViewId', null);
}
},
@ -308,17 +312,17 @@ class Client {
},
connect: () => {
this._set(this.state, 'disconnected', false); // Otherwise it gets set when padData arrives
this._set(this.state, 'disconnected', false); // Otherwise it gets set when mapData arrives
if(this.state.padId)
this._setPadId(this.state.padId).catch(() => undefined);
if(this.state.mapId)
this._setMapId(this.state.mapId).catch(() => undefined);
// TODO: Handle errors
if(this.state.bbox)
this.updateBbox(this.state.bbox).catch((err) => { console.error("Error updating bbox.", err); });
if(this.state.listeningToHistory) // TODO: Execute after setPadId() returns
if(this.state.listeningToHistory) // TODO: Execute after setMapId() returns
this.listenToHistory().catch(function(err) { console.error("Error listening to history", err); });
if(this.data.route)
@ -348,18 +352,18 @@ class Client {
}
};
async setPadId(padId: PadId): Promise<MultipleEvents<SocketEvents<SocketVersion.V2>>> {
if(this.state.padId != null)
throw new Error("Pad ID already set.");
async setMapId(mapId: MapId): Promise<MultipleEvents<SocketEvents<SocketVersion.V3>>> {
if(this.state.mapId != null)
throw new Error("Map ID already set.");
return await this._setPadId(padId);
return await this._setMapId(mapId);
}
async setLanguage(language: SetLanguageRequest): Promise<void> {
await this._emit("setLanguage", language);
}
async updateBbox(bbox: BboxWithZoom): Promise<MultipleEvents<SocketEvents<SocketVersion.V2>>> {
async updateBbox(bbox: BboxWithZoom): Promise<MultipleEvents<SocketEvents<SocketVersion.V3>>> {
const isZoomChange = this.bbox && bbox.zoom !== this.bbox.zoom;
this._set(this.state, 'bbox', bbox);
@ -390,16 +394,16 @@ class Client {
return obj;
}
async getPad(data: GetPadQuery): Promise<FindPadsResult | null> {
return await this._emit("getPad", data);
async getMap(data: GetMapQuery): Promise<FindMapsResult | null> {
return await this._emit("getMap", data);
}
async findPads(data: FindPadsQuery): Promise<PagedResults<FindPadsResult>> {
return await this._emit("findPads", data);
async findMaps(data: FindMapsQuery): Promise<PagedResults<FindMapsResult>> {
return await this._emit("findMaps", data);
}
async createPad(data: PadData<CRU.CREATE>): Promise<MultipleEvents<SocketEvents<SocketVersion.V2>>> {
const obj = await this._emit("createPad", data);
async createMap(data: MapData<CRU.CREATE>): Promise<MultipleEvents<SocketEvents<SocketVersion.V3>>> {
const obj = await this._emit("createMap", data);
this._set(this.state, 'serverError', undefined);
this._set(this.state, 'readonly', false);
this._set(this.state, 'writable', 2);
@ -407,15 +411,15 @@ class Client {
return obj;
}
async editPad(data: PadData<CRU.UPDATE>): Promise<PadData> {
return await this._emit("editPad", data);
async editMap(data: MapData<CRU.UPDATE>): Promise<MapData> {
return await this._emit("editMap", data);
}
async deletePad(): Promise<void> {
await this._emit("deletePad");
async deleteMap(): Promise<void> {
await this._emit("deleteMap");
}
async listenToHistory(): Promise<MultipleEvents<SocketEvents<SocketVersion.V2>>> {
async listenToHistory(): Promise<MultipleEvents<SocketEvents<SocketVersion.V3>>> {
const obj = await this._emit("listenToHistory");
this._set(this.state, 'listeningToHistory', true);
this._receiveMultiple(obj);
@ -427,7 +431,7 @@ class Client {
await this._emit("stopListeningToHistory");
}
async revertHistoryEntry(data: ObjectWithId): Promise<MultipleEvents<SocketEvents<SocketVersion.V2>>> {
async revertHistoryEntry(data: ObjectWithId): Promise<MultipleEvents<SocketEvents<SocketVersion.V3>>> {
const obj = await this._emit("revertHistoryEntry", data);
this._set(this.data, 'history', {});
this._receiveMultiple(obj);
@ -481,7 +485,7 @@ class Client {
return await this._emit("find", data);
}
async findOnMap(data: FindOnMapQuery): Promise<SocketResponse<SocketVersion.V2, 'findOnMap'>> {
async findOnMap(data: FindOnMapQuery): Promise<SocketResponse<SocketVersion.V3, 'findOnMap'>> {
return await this._emit("findOnMap", data);
}
@ -578,11 +582,11 @@ class Client {
this.socket.disconnect();
}
private async _setPadId(padId: string): Promise<MultipleEvents<SocketEvents<SocketVersion.V2>>> {
private async _setMapId(mapId: string): Promise<MultipleEvents<SocketEvents<SocketVersion.V3>>> {
this._set(this.state, 'serverError', undefined);
this._set(this.state, 'padId', padId);
this._set(this.state, 'mapId', mapId);
try {
const obj = await this._emit("setPadId", padId);
const obj = await this._emit("setMapId", mapId);
this._receiveMultiple(obj);
return obj;
} catch(err: any) {
@ -634,8 +638,8 @@ class Client {
return this.state.server;
}
get padId(): string | undefined {
return this.state.padId;
get mapId(): string | undefined {
return this.state.mapId;
}
get bbox(): BboxWithZoom | undefined {
@ -666,8 +670,8 @@ class Client {
return this.state.listeningToHistory;
}
get padData(): PadData | undefined {
return this.data.padData;
get mapData(): (MapData & { writable: Writable }) | undefined {
return this.data.mapData;
}
get markers(): Record<ID, Marker> {

Wyświetl plik

@ -1,6 +1,6 @@
{
"name": "facilmap-docs",
"version": "4.1.1",
"version": "5.0.0",
"description": "Documentation for FacilMap.",
"author": "Candid Dauth <cdauth@cdauth.eu>",
"repository": {

Wyświetl plik

@ -6,8 +6,6 @@ The FacilMap client makes a connection to the FacilMap server using [socket.io](
* Modify the objects on a collaborative map (only if opened through its writable or admin ID)
* Be notified live about changes that other people are making to the collaborative map.
Note that in the context of the client, a collaborative map will be referred to as __pad__. This is because the collaborative part of FacilMap used to be a separate software called FacilPad.
The socket on the server side maintains different API versions in an attempt to stay backwards compatible with older versions of the client. Have a look at the [./changelog.md](changelog) to find out what has changed when upgrading to a new version of the client.
@ -42,7 +40,7 @@ One instance of the client class represents one connection to one specific colla
* Map ID set: All methods are available. Events are received when the map settings, types, views and lines (only metadata, not track points) are created/updated/deleted.
* Map ID and bbox set: All methods are available. In addition to the other events, events are received when markers and lines in the specified bounding box are created/updated/deleted.
It is possible to initialize a client without a map ID and later open a map using [`setPadId`](./methods.md#setpadid-padid) or [`createPad`](./methods.md#createpad-data). Once a specific map is loaded, it is not possible to close it or switch to another map anymore. To do that, a new client instance has to be created.
It is possible to initialize a client without a map ID and later open a map using [`setMapId`](./methods.md#setmapid-mapid) or [`createMap`](./methods.md#createmap-data). Once a specific map is loaded, it is not possible to close it or switch to another map anymore. To do that, a new client instance has to be created.
The bbox can be updated continuously. In the official FacilMap UI, the bbox is updated every time the user pans the map, causing the server to send the markers within that bbox and a simplified version of the line track points and active routes fit to the bbox and zoom level.
@ -52,15 +50,15 @@ The bbox can be updated continuously. In the official FacilMap UI, the bbox is u
import Client from "facilmap-client";
const client = new Client("https://facilmap.org/");
await client.setPadId("myMapId");
console.log(client.padData, client.types, client.lines);
await client.setMapId("myMapId");
console.log(client.mapData, client.types, client.lines);
```
The client [constructor](./methods.md#constructor-server-padid) takes the URL where the FacilMap server is running and opens a socket.io connection to the server.
The client [constructor](./methods.md#constructor-server-mapid) takes the URL where the FacilMap server is running and opens a socket.io connection to the server.
When opening a collaborative map using [`setPadId`](./methods.md#setpadid-padid), the server sends [events](./events.md) for the map settings, types, views and lines (without track points). The same types of events will be received later if the respective objects are changed while the connection is open. The client has some default listeners registered that will store the data received as events in its [properties](./properties.md). For example, a `padData` event contains the map settings and is emitted the first time the map ID is set and every time the map settings are changed while the connection is open. The `client.padData` property always contains the latest state of the map settings.
When opening a collaborative map using [`setMapId`](./methods.md#setmapid-mapid), the server sends [events](./events.md) for the map settings, types, views and lines (without track points). The same types of events will be received later if the respective objects are changed while the connection is open. The client has some default listeners registered that will store the data received as events in its [properties](./properties.md). For example, a `mapData` event contains the map settings and is emitted the first time the map ID is set and every time the map settings are changed while the connection is open. The `client.mapData` property always contains the latest state of the map settings.
Note that most methods of the client are asynchronous. Events that the server fires in response to a method call are always fired before the method returns. This is why in the above example, `client.padData` and the other properties are available right after the `setPadId` call.
Note that most methods of the client are asynchronous. Events that the server fires in response to a method call are always fired before the method returns. This is why in the above example, `client.mapData` and the other properties are available right after the `setMapId` call.
### Set a bbox

Wyświetl plik

@ -20,9 +20,9 @@ these two objects consistently in the following way:
You can override the `_makeReactive`, `_set` and `_delete` methods to make the private properties (and as a consequence the public
getters) of facilmap-client reactive. This way your UI framework will detect changes to any properties on the client, and you can
reference values like `client.padData.name`, `client.disconnected` and `client.loading` in your UI components.
reference values like `client.mapData.name`, `client.disconnected` and `client.loading` in your UI components.
Note that client always replaces whole objects rather than updating individual properties. For example, when a new version of the map settings arrives, `client.padData` is replaced with a new object, or when a new marker arrives, `client.markers[markerId]` is replaced with a new object. This makes deep watches unnecessary in most cases.
Note that client always replaces whole objects rather than updating individual properties. For example, when a new version of the map settings arrives, `client.mapData` is replaced with a new object, or when a new marker arrives, `client.markers[markerId]` is replaced with a new object. This makes deep watches unnecessary in most cases.
### Vue.js 3
@ -117,5 +117,5 @@ Keep in mind that Reacts `useSyncExternalStore` will rerender the component i
This means one the one hand that you cannot use this example implementation on a higher up object of the client (such as
`client` itself or `client.markers`), as their identity never changes, causing your component to never rerender. And on
the other hand that you should avoid using it on objects created in the selector (such as returning
`[client.padData.id, client.padData.name]` in order to get multiple values at once), as it will cause your component to
`[client.mapData.id, client.mapData.name]` in order to get multiple values at once), as it will cause your component to
rerender every time the selector is called.

Wyświetl plik

@ -2,6 +2,11 @@
The websocket on the FacilMap server provides different API versions (implemented as socket.io namespaces such as `/` for version 1, `/v2` for version 2, etc.) in order to stay backwards compatible with older clients. Each release of facilmap-client is adapted to a particular API version of the server. When upgrading to a new version of the client, have a look at this page to find out what has changed.
## v5.0.0 (API v3)
* “symbol” was renamed to “icon” everywhere. This applies to `Marker.symbol`, `Type.defaultSymbol`, `Type.symbolFixed`, `Type.fields[].controlSymbol` and `Type.fields[].options[].symbol`.
* “pad” was renamed “map” everywhere. This applies to the `padData` and `deletePad` socket events and `getPad` (including its `padId` request property), the `findPads`, `createPad`, `editPad`, `deletePad`, `setPadId` client/socket methods, the `PadNotFoundError`.
## v4.0.0 (API v2)
* Before, creating a map with an empty name resulted in `padData.name` set to `"Unnamed map"`. Now, an empty name will result in `""` and the UI is responsible for displaying that in an appropriate way.

Wyświetl plik

@ -7,9 +7,9 @@ Note that events are always fired _before_ the method causing them returns. For
Subscribe to events using the [`on(eventName, function)`](./methods.md#on-eventname-function) method. Example:
```js
const client = new FacilMap.Client("https://facilmap.org/", "testPad");
client.on("padData", (padData) => {
document.title = padData.name;
const client = new FacilMap.Client("https://facilmap.org/", "testMap");
client.on("mapData", (mapData) => {
document.title = mapData.name;
});
```
@ -17,18 +17,18 @@ client.on("padData", (padData) => {
These events come from socket.io and are [documented there](https://socket.io/docs/v4/client-api/#events).
## `padData`
## `mapData`
The settings of the map have changed or are retrieved for the first time.
Note that when this event is fired, the read-only and/or the read-write ID of the map might have changed. The [`padId`](./properties.md#padid)
Note that when this event is fired, the read-only and/or the read-write ID of the map might have changed. The [`mapId`](./properties.md#mapid)
property is updated automatically.
_Type:_ [PadData](./types.md#paddata)
_Type:_ [MapData](./types.md#mapdata)
## `serverError`
[`setPadId()`](./methods.md#setpadid-padid) failed and the map could not be opened.
[`setMapId()`](./methods.md#setmapid-mapid) failed and the map could not be opened.
_Type:_ Error

Wyświetl plik

@ -1,18 +1,18 @@
# Methods
## `constructor(server, padId, socketOptions)`
## `constructor(server, mapId, socketOptions)`
Connects to the FacilMap server `server` and optionally opens the collaborative map with the ID `padId`. If the pad ID
is not set, it can be set later using [`setPadId(padId)`](#setpadid-padid) or using [`createPad(data)`](#createpad-data).
Connects to the FacilMap server `server` and optionally opens the collaborative map with the ID `mapId`. If the map ID
is not set, it can be set later using [`setMapId(mapId)`](#setmapid-mapid) or using [`createMap(data)`](#createmap-data).
The connection is established in the background, and a `connect` event is fired when it is successful. If a `padId` is specified, a [`padData`](./events.md#paddata) or [`serverError`](./events.md#servererror) event will indicate when the map has been opened successfully or unsuccessfully. Note that you can already call methods immediately after constructing the client, causing them to be delayed until the connection is established.
The connection is established in the background, and a `connect` event is fired when it is successful. If a `mapId` is specified, a [`mapData`](./events.md#mapdata) or [`serverError`](./events.md#servererror) event will indicate when the map has been opened successfully or unsuccessfully. Note that you can already call methods immediately after constructing the client, causing them to be delayed until the connection is established.
If the connection to the server breaks down, a `disconnect` event will be emitted and socket.io will attempt to reconnect. On successful reconnection, a `reconnect` and `connect` event will be fired. During the interruption, you can still call methods, causing them to be delayed until the connection is reestablished.
* `server` (string): The URL of the FacilMap server, for example `https://facilmap.org/`.
* `padId` (string, optional): The ID of the collaborative map to open.
* `mapId` (string, optional): The ID of the collaborative map to open.
* `socketOptions` (object, optional): Any additional [Socket.io client options](https://socket.io/docs/v4/client-options/).
* **Events:** Causes a `connect` event to be fired when the connection is established. If `padId` is defined, causes events to be fired with the map settings, all views, all types and all lines (without line points) of the map. If the map with `padId` could not be opened, causes a [`serverError`](./events.md#servererror) event.
* **Events:** Causes a `connect` event to be fired when the connection is established. If `mapId` is defined, causes events to be fired with the map settings, all views, all types and all lines (without line points) of the map. If the map with `mapId` could not be opened, causes a [`serverError`](./events.md#servererror) event.
## `on(eventName, function)`
@ -28,15 +28,15 @@ Unregisters an event handler previously assigned using `on(eventName, function)`
* `eventName` (string): The name of the event.
* `function` (function): The function that was passed to `on(eventName, function)` when registering the handler.
## `setPadId(padId)`
## `setMapId(mapId)`
Opens the collaborative map with the ID `padId`.
Opens the collaborative map with the ID `mapId`.
This method can only be called once, and only if no `padId` was passed to the constructor. If you want to open a different map, you need to create a new instance of the client.
This method can only be called once, and only if no `mapId` was passed to the constructor. If you want to open a different map, you need to create a new instance of the client.
Setting the padId causes the server to send several objects, such as the map settings, all views, and all lines (just metadata, without line points). Each of these objects is sent as an individual [`event`](./events.md).
Setting the mapId causes the server to send several objects, such as the map settings, all views, and all lines (just metadata, without line points). Each of these objects is sent as an individual [`event`](./events.md).
* `padId` (string): The ID of the collaborative map to open. Can be a read-only ID, writable ID or admin ID of a map.
* `mapId` (string): The ID of the collaborative map to open. Can be a read-only ID, writable ID or admin ID of a map.
* **Returns:** A promise that is resolved empty when all objects have been received.
* **Events:** Causes events to be fired with the map settings, all views, all types and all lines (without line points) of the map. If the map could not be opened, causes a [`serverError`](./events.md#servererror) event.
* **Availability:** Only available if no map is opened yet on this client instance.
@ -61,19 +61,19 @@ Updates the bbox. This will cause all markers, line points and route points with
* **Events:** Causes events to be fired with the markers, line points and route points within the bbox.
* **Availability:** Always.
## `getPad(data)`
## `getMap(data)`
Finds a collaborative map by ID. This can be used to check if a map with a certain ID exists.
* `data`: An object with the following properties:
* `padId`: The read-only, writable or admin ID of the map.
* `mapId`: The read-only, writable or admin ID of the map.
* **Returns:** A promise that is resolved with undefined (if no map with that ID exists) or with an object with an `id` (read-only ID), `name` and `description` property.
* **Events:** None.
* **Availability:** Always.
## `findPads(data)`
## `findMaps(data)`
Finds collaborative maps by a search term. Only finds maps that have been made public by setting [`searchEngines`](./types.md#paddata) to `true`.
Finds collaborative maps by a search term. Only finds maps that have been made public by setting [`searchEngines`](./types.md#mapdata) to `true`.
* `data`: An object with the following properties:
* `query` (string): A search term. `*` can be used as a wildcard and `?` as a single-character wildcard.
@ -84,30 +84,30 @@ Finds collaborative maps by a search term. Only finds maps that have been made p
* **Events:** None.
* **Availability:** Always.
## `createPad(data)`
## `createMap(data)`
Creates a new collaborative map and opens it.
* `data` ([padData](./types.md#paddata)): The data of the new map, including the desired read-only, writable and admin ID.
* **Returns:** A promise that is resolved with the new padData when the map has been created.
* **Events:** Causes a [`padData`](./events.md#paddata) event and other events for objects that have been created on the map (such as the default Marker and Line types).
* `data` ([mapData](./types.md#mapdata)): The data of the new map, including the desired read-only, writable and admin ID.
* **Returns:** A promise that is resolved with the new mapData when the map has been created.
* **Events:** Causes a [`mapData`](./events.md#mapdata) event and other events for objects that have been created on the map (such as the default Marker and Line types).
* **Availability:** Only if no collaborative map is opened yet.
## `editPad(data)`
## `editMap(data)`
Update the map settings of the current map.
* `data` ([PadData](./types.md#paddata)): The data of the map that should be modified. Fields that are not defined will not be modified. To change the default view, set the `defaultViewId` property. The `defaultView` property is ignored.
* **Returns:** A promise that is resolved with the new padData.
* **Events:** Causes a [`padData`](./events.md#paddata) event.
* `data` ([MapData](./types.md#mapdata)): The data of the map that should be modified. Fields that are not defined will not be modified. To change the default view, set the `defaultViewId` property. The `defaultView` property is ignored.
* **Returns:** A promise that is resolved with the new mapData.
* **Events:** Causes a [`mapData`](./events.md#mapdata) event.
* **Availability:** Only if a collaborative map is opened through its admin ID.
## `deletePad()`
## `deleteMap()`
Delete the current map irrevocably.
* **Returns:** A promise that is resolved empty when the map has been deleted.
* **Events:** Causes a [`deletePad`](./events.md#deletepad) event.
* **Events:** Causes a [`deleteMap`](./events.md#deletemap) event.
* **Availability:** Only if a collaborative map is opened through its admin ID.
## `listenToHistory()`

Wyświetl plik

@ -25,27 +25,27 @@ client.on("marker", (marker) => {
});
```
## `padId`
## `mapId`
The ID of the collaborative map that the client is connected to. Can be the read-only, writable or admin ID of an existing map.
Note that the ID can be changed in the settings. If in case of a [`padData`](./events.md#paddata) event, the ID of the pad has changed, this property is updated automatically.
Note that the ID can be changed in the settings. If in case of a [`mapData`](./events.md#mapdata) event, the ID of the map has changed, this property is updated automatically.
_Set:_ when calling [`setPadId`](./methods.md#setpadid-padid) and in response to a [`padData`](./events.md#paddata) event.\
_Set:_ when calling [`setMapId`](./methods.md#setmapid-mapid) and in response to a [`mapData`](./events.md#mapdata) event.\
_Type:_ string
## `readonly`
`true` if the map has been opened using its read-only ID. `false` if the map is writable.
_Set:_ during [`setPadId`](./methods.md#setpadid-padid).\
_Set:_ during [`setMapId`](./methods.md#setmapid-mapid).\
_Type:_ boolean
## `writable`
`2` if the map has been opened using its admin ID, `1` if if has been opened using the writable ID, `0` if the map is read-only.
_Set:_ during [`setPadId`](./methods.md#setpadid-padid).\
_Set:_ during [`setMapId`](./methods.md#setmapid-mapid).\
_Type:_ number
@ -53,15 +53,15 @@ _Type:_ number
`true` if the map was deleted while this client was connected to it.
_Set:_ in response to a [`deletePad`](./events.md#deletepad) event.\
_Set:_ in response to a [`deleteMap`](./events.md#deletemap) event.\
_Type:_ boolean
## `padData`
## `mapData`
The current settings of the map. `writeId` and/or `adminId` is null if if has been opened using another ID than the admin ID.
_Set:_ in response to a [`padData`](./events.md#paddata) event.\
_Type:_ [PadData](./types.md#paddata)
_Set:_ in response to a [`mapData`](./events.md#mapdata) event.\
_Type:_ [MapData](./types.md#mapdata)
## `markers`
@ -114,9 +114,9 @@ _Type:_ [<code>{ &#91;routeId: string&#93;: Route }</code>](./types.md#route)
## `serverError`
If the opening the map failed ([`setPadId(padId)`](./methods.md#setpadid-padid) promise got rejected), the error message is stored in this property.
If the opening the map failed ([`setMapId(mapId)`](./methods.md#setmapid-mapid) promise got rejected), the error message is stored in this property.
_Set:_ in response to a [`serverError`](./events.md#servererror) event (fired during [`setPadId`](./methods.md#setpadid-padid)).\
_Set:_ in response to a [`serverError`](./events.md#servererror) event (fired during [`setMapId`](./methods.md#setmapid-mapid)).\
_Type:_ Error
## `loading`

Wyświetl plik

@ -20,7 +20,7 @@ A bounding box that describes which part of the map the user is currently viewin
* `name` (string): The name of this marker
* `colour` (string): The colour of this marker as a 6-digit hex value, for example `ff0000`
* `size` (number, min: 15): The height of the marker in pixels
* `symbol` (string): The symbol name for the marker. Default is an empty string.
* `icon` (string): The icon name for the marker. Default is an empty string.
* `shape` (string): The shape name for the marker. Default is an empty string (equivalent to `"drop"`).
* `ele` (number or null): The elevation of this marker in metres (set by the server)
* `typeId` (number): The ID of the type of this marker
@ -71,7 +71,7 @@ their `idx` property.
* `zoom` (number, min: 1, max: 20): The miminum zoom level from which this track point makes sense to show
* `ele` (number or null): The elevation of this track point in metres (set by the server). Not set for high zoom levels.
## PadData
## MapData
* `id` (string): The read-only ID of this map
* `writeId` (string): The read-write ID of this map (not set when opened through its read-only ID)
@ -110,20 +110,20 @@ their `idx` property.
* `name` (string): The name of this type. Note that the if the name is "Marker" or "Line", the FacilMap UI will translate the name to other languages even though the underlying name is in English.
* `type` (string): `marker` or `line`
* `idx` (number): The sorting position of this type. When a list of types is shown to the user, it must be ordered by this value. If types were deleted or reordered, there may be gaps in the sequence of indexes, but no two types on the same map can ever have the same index. When setting this as part of a type creation/update, other types with a same/higher index will have their index increased to be moved down the list.
* `defaultColour`, `defaultSize`, `defaultSymbol`, `defaultShape`, `defaultWidth`, `defaultStroke`, `defaultMode` (string/number): Default values for the
* `defaultColour`, `defaultSize`, `defaultIcon`, `defaultShape`, `defaultWidth`, `defaultStroke`, `defaultMode` (string/number): Default values for the
different object properties
* `colourFixed`, `sizeFixed`, `symbolFixed`, `shapeFixed`, `widthFixed`, `strokeFixed`, `modeFixed` (boolean): Whether those values are fixed and
* `colourFixed`, `sizeFixed`, `iconFixed`, `shapeFixed`, `widthFixed`, `strokeFixed`, `modeFixed` (boolean): Whether those values are fixed and
cannot be changed for an individual object
* `fields` ([object]): The form fields for this type. Each field has the following properties:
* `name` (string): The name of the field. This is at the same time the key in the `data` properties of markers and lines. Note that the if the name is "Description", the FacilMap UI will translate the name to other languages even though the underlying name is in English.
* `oldName` (string): When renaming a field (using [`editType(data)`](./methods.md#edittype-data)), specify the former name here
* `type` (string): The type of field, one of `textarea`, `dropdown`, `checkbox`, `input`
* `controlColour`, `controlSize`, `controlSymbol`, `controlShape`, `controlWidth`, `controlStroke` (boolean): If this field is a dropdown, whether the different options set a specific property on the object
* `controlColour`, `controlSize`, `controlIcon`, `controlShape`, `controlWidth`, `controlStroke` (boolean): If this field is a dropdown, whether the different options set a specific property on the object
* `default` (string/boolean): The default value of this field
* `options` ([object]): If this field is a dropdown or a checkbox, an array of objects with the following properties. For a checkbox, the array has to have 2 items, the first representing the unchecked and the second the checked state.
* `value` (string): The value of this option.
* `oldValue` (string): When renaming a dropdown option (using [`editType(data)`](./methods.md#edittype-data)), specify the former value here
* `colour`, `size`, `shape`, `symbol`, `width`, `stroke` (string/number): The property value if this field controls that property
* `colour`, `size`, `shape`, `icon`, `width`, `stroke` (string/number): The property value if this field controls that property
## SearchResult
@ -135,7 +135,7 @@ their `idx` property.
* `zoom` (number): Zoom level at which there is a good view onto the result. Might be null if `boundingbox` is set.
* `extratags` (object): Extra OSM tags that might be useful
* `geojson` (object): GeoJSON if the result has a shape
* `icon` (string): Symbol key of the result
* `icon` (string): Icon key of the result
* `type` (string): Type of the result
* `id` (string): If the result is an OSM object, the ID of the OSM object, prefixed by `n` (node), `w` (way) or `r` (relation)
* `ele` (number): Elevation in meters

Wyświetl plik

@ -32,7 +32,7 @@ The [`<FacilMap>`](./facilmap.md) component renders the whole frontend, includin
<FacilMap
baseUrl="https://facilmap.org/"
serverUrl="https://facilmap.org/"
:padId="undefined"
:mapId="undefined"
></FacilMap>
```

Wyświetl plik

@ -11,7 +11,7 @@ The `FacilMap` component renders a complete FacilMap UI. It can be used like thi
<FacilMap
baseUrl="https://facilmap.org/"
serverUrl="https://facilmap.org/"
padId="my-map"
mapId="my-map"
></FacilMap>
</template>
```
@ -27,7 +27,7 @@ createApp(defineComponent({
return () => h(FacilMap, {
baseUrl: "https://facilmap.org/",
serverUrl: "https://facilmap.org/",
padId: "my-map"
mapId: "my-map"
});
}
})).mount(document.getElementById("facilmap")!); // A DOM element that be replaced with the FacilMap component
@ -39,7 +39,7 @@ Note that all of these props are reactive and can be changed while the map is op
* `baseUrl` (string, required): Collaborative maps should be reachable under `${baseUrl}${mapId}`, while the general map should be available under `${baseUrl}`. For the default FacilMap installation, `baseUrl` would be `https://facilmap.org/`. It needs to end with a slash. It is used to create the map URL for example in the map settings or when switching between different maps (only in interactive mode).
* `serverUrl` (string, required): The URL under which the FacilMap server is running, for example `https://facilmap.org/`. This is invisible to the user.
* `padId` (string or undefined, required): The ID of the collaborative map that should be opened. If this is undefined, no map is opened. This is reactive, when a new value is passed, a new map is opened. Note that the map ID may change as the map is open, either because the ID of the map is changed in the map settings, or because the user navigates to a different map (only in interactive mode). Use `v-model:padId` to get a [two-way binding](https://vuejs.org/guide/essentials/forms.html) (or listen to the `update:padId` event).
* `mapId` (string or undefined, required): The ID of the collaborative map that should be opened. If this is undefined, no map is opened. This is reactive, when a new value is passed, a new map is opened. Note that the map ID may change as the map is open, either because the ID of the map is changed in the map settings, or because the user navigates to a different map (only in interactive mode). Use `v-model:mapId` to get a [two-way binding](https://vuejs.org/guide/essentials/forms.html) (or listen to the `update:mapId` event).
* `settings` (object, optional): An object with the following properties:
* `toolbox` (boolean, optional): Whether the toolbox should be shown. Default is `true`.
* `search` (boolean, optional): Whether the search box should be shown. Default is `true`.
@ -51,8 +51,8 @@ Note that all of these props are reactive and can be changed while the map is op
## Events
* `update:padId`: When the ID of the currently open map is changed, either because the ID of the map was changed in the map settings or because the user navigated to another map. The parameter is a string or undefined (if no map is opened).
* `update:padName`: When the name of the currently open map is changed, either because it was changed in the map settings or because the user navigated to another map. The parameter is a string or undefined (if no map is opened).
* `update:mapId`: When the ID of the currently open map is changed, either because the ID of the map was changed in the map settings or because the user navigated to another map. The parameter is a string or undefined (if no map is opened).
* `update:mapName`: When the name of the currently open map is changed, either because it was changed in the map settings or because the user navigated to another map. The parameter is a string or undefined (if no map is opened).
## Slots

Wyświetl plik

@ -1,6 +1,6 @@
# Icons
FacilMap comes with a large selection of icons (called “symbols” in the code) and marker shapes. The icons come from the following sources:
FacilMap comes with a large selection of icons and marker shapes. The icons come from the following sources:
* All the [Open SVG Map Icons](https://github.com/twain47/Open-SVG-Map-Icons/) (these are the ones used by Nominatim for search results)
* A selection of [Glyphicons](https://getbootstrap.com/docs/3.4/components/#glyphicons-glyphs) from Bootstrap 3.
* A few icons from [Material Design Iconic Font](https://zavoloklom.github.io/material-design-iconic-font/).
@ -10,45 +10,45 @@ FacilMap uses these icons as part of markers on the map and in regular UI elemen
facilmap-leaflet includes all the icons and marker shapes and provides some helper methods to access them in different sizes and styles.
To make the bundle size smaller, the symbols are separated into two sets:
* The *core* symbols are included in the main facilmap-leaflet bundle. This includes all all symbols that are used by FacilMap as UI elements.
* The *extra* symbols are included in a separate file. When you call any of the methods below for the first time for an extra symbol, this separate file is loaded using an async import. You can also explicitly load the extra symbols at any point of time by calling `preloadExtraSymbols()`.
To make the bundle size smaller, the icons are separated into two sets:
* The *core* icons are included in the main facilmap-leaflet bundle. This includes all all icons that are used by FacilMap as UI elements.
* The *extra* icons are included in a separate file. When you call any of the methods below for the first time for an extra icon, this separate file is loaded using an async import. You can also explicitly load the extra icons at any point of time by calling `preloadExtraIcons()`.
## Available symbols and shapes
## Available icons and shapes
`symbolList` and `shapeList` are arrays of strings that contain the names of all the available symbols (core and extra) and marker shapes. The `coreSymbolList` array contains only the names of the core symbols.
`iconList` and `shapeList` are arrays of strings that contain the names of all the available icons (core and extra) and marker shapes. The `coreIconList` array contains only the names of the core icons.
In addition to the symbols listed in `symbolList`, any single character can be used as a symbol. Single-character symbols are rendered in the browser, they dont require loading the extra symbols.
In addition to the icons listed in `iconList`, any single character can be used as an icon. Single-character icons are rendered in the browser, they dont require loading the extra icons.
## Get a symbol
## Get an icon
The following methods returns a simple monochrome icon.
* `async getSymbolCode(colour, size, symbol)`: Returns a raw SVG object with the code of the symbol as a string.
* `async getSymbolUrl(colour, size, symbol)`: Returns the symbol as a `data:` URL (that can be used as the `src` of an `img` for example)
* `async getSymbolHtml(colour, size, symbol)`: Returns the symbol as an SVG element source code (as a string) that can be embedded straight into a HTML page.
* `async getIconCode(colour, size, icon)`: Returns a raw SVG object with the code of the icon as a string.
* `async getIconUrl(colour, size, icon)`: Returns the icon as a `data:` URL (that can be used as the `src` of an `img` for example)
* `async getIconHtml(colour, size, icon)`: Returns the icon as an SVG element source code (as a string) that can be embedded straight into a HTML page.
The following arguments are expected:
* `colour`: Any colour that would be acceptable in SVG, for example `#000000` or `currentColor`.
* `size`: The height/width in pixels that the symbol should have (symbols are square). For `getSymbolHtml()`, the size can also be a string (for example `1em`).
* `symbol`: Either one of the symbol name that is listed in `symbolList`, or a single letter, or an empty string or undefined to render the default symbol (a dot).
* `size`: The height/width in pixels that the icon should have (icons are square). For `getIconHtml()`, the size can also be a string (for example `1em`).
* `icon`: Either one of the icon name that is listed in `iconList`, or a single letter, or an empty string or undefined to render the default icon (a dot).
## Get a marker icon
The following methods returns a marker icon with the specified shape and the specified symbol inside.
The following methods returns a marker icon with the specified shape and the specified icon inside.
* `async getMarkerCode(colour, height, symbol, shape, highlight)`: Returns a raw SVG object with the code of the marker as a string.
* `async getMarkerUrl(colour, height, symbol, shape, highlight)`: Returns the marker as a `data:` URL (that can be used as the `src` of an `img` for example)
* `async getMarkerHtml(colour, height, symbol, shape, highlight)`: Returns the marker as an SVG element source code (as a string) that can be embedded straight into a HTML page.
* `getMarkerIcon(colour, height, symbol, shape, highlight)`: Returns the marker as a [Leaflet Icon](https://leafletjs.com/reference.html#icon) that can be used for Leaflet markers. The anchor point is set correctly. The Icon object is returned synchronously and updates its `src` automatically as soon as it is loaded.
* `async getMarkerCode(colour, height, icon, shape, highlight)`: Returns a raw SVG object with the code of the marker as a string.
* `async getMarkerUrl(colour, height, icon, shape, highlight)`: Returns the marker as a `data:` URL (that can be used as the `src` of an `img` for example)
* `async getMarkerHtml(colour, height, icon, shape, highlight)`: Returns the marker as an SVG element source code (as a string) that can be embedded straight into a HTML page.
* `getMarkerIcon(colour, height, icon, shape, highlight)`: Returns the marker as a [Leaflet Icon](https://leafletjs.com/reference.html#icon) that can be used for Leaflet markers. The anchor point is set correctly. The Icon object is returned synchronously and updates its `src` automatically as soon as it is loaded.
The following arguments are expected:
* `colour`: A colour in hex format, for example `#000000`.
* `height`: The height of the marker in pixels. Different marker shapes have different aspect ratios, so the width will differ depending on which shape is used. Note that the height is approximate, it is scaled down for some shapes with the aim that two markers with different shapes but the same `height` should visually appear roughly the same size.
* `symbol`: Either one of the symbol name that is listed in `symbolList`, or a single letter, or an empty string or undefined to render the default symbol (a dot).
* `icon`: Either one of the icon name that is listed in `iconList`, or a single letter, or an empty string or undefined to render the default icon (a dot).
* `shape`: A shape name that is listed in `shapeList`, or an empty string or undefined to render the default shape (`drop`).
* `highlight`: If this is set to true, the marker is rendered as highlighted (with an increased border width).
## Get a symbol for an OSM element
## Get an icon for an OSM element
Calling `getSymbolForTags(tags)` will make a best attempt to return a symbol that is appropriate for a given OSM element. `tags` should be an object that maps OSM tag keys to values. The function returns a string representing the name of a symbol. If no fitting symbol can be found, an empty string is returned.
Calling `getIconForTags(tags)` will make a best attempt to return an icon that is appropriate for a given OSM element. `tags` should be an object that maps OSM tag keys to values. The function returns a string representing the name of an icon. If no fitting icon can be found, an empty string is returned.

Wyświetl plik

@ -55,7 +55,7 @@ If you want to make sure that a marker with a particular ID is shown (regardless
`MarkerLayer` (in singular, as opposed to `MarkersLayer`) makes it possible to render an individual marker object with its appropriate style. It does not automatically update when the marker changes and can also be used for markers that are not saved on the map.
`MarkerLayer` is based on regular [Leaflet markers](https://leafletjs.com/reference.html#marker), but accepts the following additional options:
* `marker`: A marker object that the marker style will be based on. Only the properties relevant for the style (`colour`, `size`, `symbol` and `shape`) need to be set.
* `marker`: A marker object that the marker style will be based on. Only the properties relevant for the style (`colour`, `size`, `icon` and `shape`) need to be set.
* `highlight`: If this is `true`, the marker will be shown with a thicker border.
* `raised`: If this is `true`, the marker will be rendered above other map objects.
@ -65,7 +65,7 @@ import { MarkerLayer } from "facilmap-leaflet";
const map = L.map('map');
new MarkerLayer([52.5295, 13.3840], {
marker: { colour: "00ff00", size: 40, symbol: "alert", shape: "pentagon" }
marker: { colour: "00ff00", size: 40, icon: "alert", shape: "pentagon" }
}).addTo(map);
```

Wyświetl plik

@ -10,7 +10,7 @@
* `markerShape`: The shape of search result markers, defaults to `drop`
* `pathOptions`: [Path options](https://leafletjs.com/reference.html#path-option) for lines and polygons.
Note that the marker symbol is determined by the type of result.
Note that the marker icon is determined by the type of result.
Example usage:
```javascript

Wyświetl plik

@ -1,6 +1,6 @@
{
"name": "facilmap-frontend",
"version": "4.1.1",
"version": "5.0.0",
"description": "A fully-featured OpenStreetMap-based map where markers and lines can be added with live collaboration.",
"keywords": [
"maps",

Wyświetl plik

@ -4,10 +4,10 @@
import { ref } from "vue";
const serverUrl = "http://localhost:40829/";
const padId1 = ref("test");
const padName1 = ref<string>();
const padId2 = ref("test");
const padName2 = ref<string>();
const mapId1 = ref("test");
const mapName1 = ref<string>();
const mapId2 = ref("test");
const mapName2 = ref<string>();
const map1Ref = ref<InstanceType<typeof FacilMap>>();
const map2Ref = ref<InstanceType<typeof FacilMap>>();
@ -17,12 +17,12 @@
<FacilMap
:baseUrl="serverUrl"
:serverUrl="serverUrl"
v-model:padId="padId1"
@update:padName="padName1 = $event"
v-model:mapId="mapId1"
@update:mapName="mapName1 = $event"
ref="map1Ref"
>
<template #before>
<div>{{padId1}} | {{padName1}}</div>
<div>{{mapId1}} | {{mapName1}}</div>
<div>
#{{map1Ref?.context?.components.map?.hash}}
</div>
@ -32,12 +32,12 @@
<FacilMap
:baseUrl="serverUrl"
:serverUrl="serverUrl"
v-model:padId="padId2"
@update:padName="padName2 = $event"
v-model:mapId="mapId2"
@update:mapName="mapName2 = $event"
ref="map2Ref"
>
<template #before>
<div>{{padId2}} | {{padName2}}</div>
<div>{{mapId2}} | {{mapName2}}</div>
<div>
#{{map2Ref?.context?.components.map?.hash}}
</div>

Wyświetl plik

@ -128,7 +128,7 @@
"lon-lat-description": "Koordinaten des Markers",
"colour-description": "Farbe des Markers oder der Linie",
"size-description": "Größe des Markers",
"symbol-description": "Symbol des Markers",
"icon-description": "Symbol des Markers",
"shape-description": "Form des Markers",
"ele-description": "Höhe über NN des Markers",
"mode-description": "Routenmodus der Linie (z.B. {{straight}} (Luftlinie) / {{car}} (Auto) / {{bicycle}} (Fahrrad) / {{pedestrian}} (zu Fuß) / {{track}} (importierter GPX-Track))",
@ -572,7 +572,7 @@
"save": "Speichern"
},
"open-map-dialog": {
"find-pads-error": "Fehler bei der Suche nach öffentlichen Karten",
"find-maps-error": "Fehler bei der Suche nach öffentlichen Karten",
"map-id-format-error": "Bitte geben Sie eine gültige Karten-ID oder -URL ein.",
"map-not-found-error": "Es konnte keine Karte mit dieser ID gefunden werden.",
"title": "Kollaborative Karte öffnen",
@ -611,10 +611,10 @@
"details": "Details",
"zoom-to-object-label": "Zur Auswahl zoomen"
},
"pad-id-edit": {
"map-id-edit": {
"unique-id-error": "Die selbe ID kann nicht für unterschiedliche Zugangsrechte verwendet werden."
},
"pad-settings-dialog": {
"map-settings-dialog": {
"create-map-error": "Fehler beim Erstellen der Karte",
"save-map-error": "Fehler beim Speichern der Karte",
"delete-map-title": "Karte löschen",
@ -715,7 +715,7 @@
"dashed": "Gestrichelt",
"dotted": "Gepunktet"
},
"symbol-picker": {
"icon-picker": {
"unknown-icon-error": "Unbekanntes Symbol",
"filter-placeholder": "Filtern",
"no-icons-found-error": "Es konnten keine Symbole gefunden werden."
@ -831,12 +831,12 @@
},
"toolbox-collab-maps-dropdown": {
"label": "Kollaborative Karten",
"bookmark": "Karte „{{padName}}“ zu Favoriten hinzufügen",
"bookmark": "Karte „{{mapName}}“ zu Favoriten hinzufügen",
"manage-bookmarks": "Favoriten verwalten",
"create-map": "Neue Karte erstellen",
"open-map": "Existierende Karte öffnen",
"open-other-map": "Andere Karte öffnen",
"close-map": "Karte „{{padName}}“ schließen"
"close-map": "Karte „{{mapName}}“ schließen"
},
"toolbox-help-dropdown": {
"label": "Hilfe",

Wyświetl plik

@ -130,7 +130,7 @@
"lon-lat-description": "Marker coordinates",
"colour-description": "Marker/line colour",
"size-description": "Marker size",
"symbol-description": "Marker icon",
"icon-description": "Marker icon",
"shape-description": "Marker shape",
"ele-description": "Marker elevation",
"mode-description": "Line routing mode ({{straight}} / {{car}} / {{bicycle}} / {{pedestrian}} / {{track}})",
@ -574,7 +574,7 @@
"save": "Save"
},
"open-map-dialog": {
"find-pads-error": "Error searching for public maps",
"find-maps-error": "Error searching for public maps",
"map-id-format-error": "Please enter a valid map ID or URL.",
"map-not-found-error": "No map with this ID could be found.",
"title": "Open collaborative map",
@ -613,10 +613,10 @@
"details": "Details",
"zoom-to-object-label": "Zoom to selection"
},
"pad-id-edit": {
"map-id-edit": {
"unique-id-error": "The same link cannot be used for different access levels."
},
"pad-settings-dialog": {
"map-settings-dialog": {
"create-map-error": "Error creating map",
"save-map-error": "Error saving map settings",
"delete-map-title": "Delete map",
@ -717,7 +717,7 @@
"dashed": "Dashed",
"dotted": "Dotted"
},
"symbol-picker": {
"icon-picker": {
"unknown-icon-error": "Unknown icon",
"filter-placeholder": "Filter",
"no-icons-found-error": "No icons could be found."
@ -834,12 +834,12 @@
},
"toolbox-collab-maps-dropdown": {
"label": "Collaborative maps",
"bookmark": "Bookmark map “{{padName}}”",
"bookmark": "Bookmark map “{{mapName}}”",
"manage-bookmarks": "Manage bookmarks",
"create-map": "Create a new map",
"open-map": "Open an existing map",
"open-other-map": "Open another map",
"close-map": "Close map “{{padName}}”"
"close-map": "Close map “{{mapName}}”"
},
"toolbox-help-dropdown": {
"label": "Help",

Wyświetl plik

@ -26,7 +26,7 @@
"apply": "Bruk",
"typeId-description-separator": " / ",
"size-description": "Pekerstørrelse",
"symbol-description": "Pekerikon",
"icon-description": "Pekerikon",
"shape-description": "Pekerform",
"width-description": "Linjebredde"
},

Wyświetl plik

@ -32,5 +32,11 @@
},
"zoom-to-object-button": {
"fallback-label": "Приблизить объект"
},
"toolbox-collab-maps-dropdown": {
"label": "Совместные карты"
},
"open-map-dialog": {
"search-alt": "Поиск"
}
}

Wyświetl plik

@ -1,8 +1,8 @@
<script lang="ts">
import { computed, onBeforeUnmount, reactive, ref, toRaw, watch } from "vue";
import Client from "facilmap-client";
import { PadNotFoundError, type PadData, type PadId } from "facilmap-types";
import PadSettingsDialog from "./pad-settings-dialog/pad-settings-dialog.vue";
import { MapNotFoundError, type MapData, type MapId } from "facilmap-types";
import MapSettingsDialog from "./map-settings-dialog/map-settings-dialog.vue";
import storage from "../utils/storage";
import { type ToastAction } from "./ui/toasts/toasts.vue";
import Toast from "./ui/toasts/toast.vue";
@ -11,8 +11,8 @@
import { isLanguageExplicit, isUnitsExplicit, useI18n } from "../utils/i18n";
import { getCurrentLanguage, getCurrentUnits } from "facilmap-utils";
function isPadNotFoundError(serverError: Client["serverError"]): boolean {
return !!serverError && serverError instanceof PadNotFoundError;
function isMapNotFoundError(serverError: Client["serverError"]): boolean {
return !!serverError && serverError instanceof MapNotFoundError;
}
</script>
@ -24,24 +24,24 @@
const connectingClient = ref<ClientContext>();
const props = defineProps<{
padId: string | undefined;
mapId: string | undefined;
serverUrl: string;
}>();
const emit = defineEmits<{
"update:padId": [padId: string | undefined];
"update:mapId": [mapId: string | undefined];
}>();
function openPad(padId: string | undefined): void {
emit("update:padId", padId);
function openMap(mapId: string | undefined): void {
emit("update:mapId", mapId);
}
watch([
() => props.padId,
() => props.mapId,
() => props.serverUrl
], async () => {
const existingClient = connectingClient.value || client.value;
if (existingClient && existingClient.server == props.serverUrl && existingClient.padId == props.padId)
if (existingClient && existingClient.server == props.serverUrl && existingClient.mapId == props.mapId)
return;
class CustomClient extends Client implements ClientContext {
@ -49,16 +49,16 @@
return reactive(obj) as O;
}
openPad(padId: string | undefined) {
openPad(padId);
openMap(mapId: string | undefined) {
openMap(mapId);
}
get isCreatePad() {
return context.settings.interactive && isPadNotFoundError(super.serverError);
get isCreateMap() {
return context.settings.interactive && isMapNotFoundError(super.serverError);
}
}
const newClient = new CustomClient(props.serverUrl, props.padId, {
const newClient = new CustomClient(props.serverUrl, props.mapId, {
query: {
...isLanguageExplicit() ? { lang: getCurrentLanguage() } : {},
...isUnitsExplicit() ? { units: getCurrentUnits() } : {}
@ -66,27 +66,27 @@
});
connectingClient.value = newClient;
let lastPadId: PadId | undefined = undefined;
let lastPadData: PadData | undefined = undefined;
let lastMapId: MapId | undefined = undefined;
let lastMapData: MapData | undefined = undefined;
newClient.on("padData", () => {
newClient.on("mapData", () => {
for (const bookmark of storage.bookmarks) {
if (lastPadId && bookmark.id == lastPadId)
bookmark.id = newClient.padId!;
if (lastMapId && bookmark.id == lastMapId)
bookmark.id = newClient.mapId!;
if (lastPadData && lastPadData.id == bookmark.padId)
bookmark.padId = newClient.padData!.id;
if (lastMapData && lastMapData.id == bookmark.padId)
bookmark.padId = newClient.mapData!.id;
if (bookmark.padId == newClient.padData!.id)
bookmark.name = newClient.padData!.name;
if (bookmark.padId == newClient.mapData!.id)
bookmark.name = newClient.mapData!.name;
}
lastPadId = newClient.padId;
lastPadData = newClient.padData;
lastMapId = newClient.mapId;
lastMapData = newClient.mapData;
});
await new Promise<void>((resolve) => {
newClient.once(props.padId ? "padData" : "connect", () => { resolve(); });
newClient.once(props.mapId ? "mapData" : "connect", () => { resolve(); });
newClient.on("serverError", () => { resolve(); });
});
@ -108,8 +108,8 @@
context.provideComponent("client", client);
function handleCreateDialogHide() {
if (client.value?.isCreatePad) {
client.value.openPad(undefined);
if (client.value?.isCreateMap) {
client.value.openMap(undefined);
}
}
@ -119,7 +119,7 @@
onClick: (e) => {
if (!e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey) {
e.preventDefault();
openPad(undefined);
openMap(undefined);
}
}
}));
@ -129,20 +129,20 @@
<template v-if="connectingClient">
<Toast
:id="`fm${context.id}-client-connecting`"
:title="props.padId ? i18n.t('client-provider.loading-map-header') : i18n.t('client-provider.connecting-header')"
:message="props.padId ? i18n.t('client-provider.loading-map') : i18n.t('client-provider.connecting')"
:title="props.mapId ? i18n.t('client-provider.loading-map-header') : i18n.t('client-provider.connecting-header')"
:message="props.mapId ? i18n.t('client-provider.loading-map') : i18n.t('client-provider.connecting')"
spinner
noCloseButton
/>
</template>
<template v-else-if="client">
<template v-if="client.serverError && !client.isCreatePad">
<template v-if="client.disconnected || !props.padId">
<template v-if="client.serverError && !client.isCreateMap">
<template v-if="client.disconnected || !props.mapId">
<Toast
:id="`fm${context.id}-client-error`"
:title="i18n.t('client-provider.connection-error')"
:message="client.serverError"
:noCloseButton="!!props.padId"
:noCloseButton="!!props.mapId"
/>
</template>
<template v-else>
@ -177,10 +177,10 @@
</template>
</template>
<PadSettingsDialog
v-if="client?.isCreatePad"
<MapSettingsDialog
v-if="client?.isCreateMap"
isCreate
:proposedAdminId="client.padId"
:proposedAdminId="client.mapId"
@hide="handleCreateDialogHide"
></PadSettingsDialog>
></MapSettingsDialog>
</template>

Wyświetl plik

@ -166,9 +166,9 @@
</tr>
<tr>
<td><code>symbol</code></td>
<td>{{i18n.t("edit-filter-dialog.symbol-description")}}</td>
<td><code>symbol == &quot;accommodation_camping&quot;</code></td>
<td><code>icon</code></td>
<td>{{i18n.t("edit-filter-dialog.icon-description")}}</td>
<td><code>icon == &quot;accommodation_camping&quot;</code></td>
</tr>
<tr>

Wyświetl plik

@ -5,7 +5,7 @@
import { cloneDeep, isEqual } from "lodash-es";
import ModalDialog from "./ui/modal-dialog.vue";
import ColourPicker from "./ui/colour-picker.vue";
import SymbolPicker from "./ui/symbol-picker.vue";
import IconPicker from "./ui/icon-picker.vue";
import ShapePicker from "./ui/shape-picker.vue";
import FieldInput from "./ui/field-input.vue";
import SizePicker from "./ui/size-picker.vue";
@ -118,11 +118,11 @@
</div>
</template>
<template v-if="resolvedCanControl.includes('symbol')">
<template v-if="resolvedCanControl.includes('icon')">
<div class="row mb-3">
<label :for="`${id}-symbol-input`" class="col-sm-3 col-form-label">{{i18n.t("edit-marker-dialog.icon")}}</label>
<label :for="`${id}-icon-input`" class="col-sm-3 col-form-label">{{i18n.t("edit-marker-dialog.icon")}}</label>
<div class="col-sm-9">
<SymbolPicker :id="`${id}-symbol-input`" v-model="marker.symbol"></SymbolPicker>
<IconPicker :id="`${id}-icon-input`" v-model="marker.icon"></IconPicker>
</div>
</div>
</template>

Wyświetl plik

@ -7,7 +7,7 @@
import { useToasts } from "../ui/toasts/toasts.vue";
import ColourPicker from "../ui/colour-picker.vue";
import ShapePicker from "../ui/shape-picker.vue";
import SymbolPicker from "../ui/symbol-picker.vue";
import IconPicker from "../ui/icon-picker.vue";
import RouteMode from "../ui/route-mode.vue";
import Draggable from "vuedraggable";
import FieldInput from "../ui/field-input.vue";
@ -252,26 +252,26 @@
</div>
</template>
<template v-if="resolvedCanControl.includes('symbol')">
<template v-if="resolvedCanControl.includes('icon')">
<div class="row mb-3">
<label :for="`${id}-default-symbol-input`" class="col-sm-3 col-form-label">{{i18n.t("edit-type-dialog.default-icon")}}</label>
<label :for="`${id}-default-icon-input`" class="col-sm-3 col-form-label">{{i18n.t("edit-type-dialog.default-icon")}}</label>
<div class="col-sm-9">
<div class="row align-items-center">
<div class="col-sm-9">
<SymbolPicker
:id="`${id}-default-symbol-input`"
v-model="type.defaultSymbol"
></SymbolPicker>
<IconPicker
:id="`${id}-default-icon-input`"
v-model="type.defaultIcon"
></IconPicker>
</div>
<div class="col-sm-3">
<div class="form-check">
<input
type="checkbox"
class="form-check-input"
:id="`${id}-default-symbol-fixed`"
v-model="type.symbolFixed"
:id="`${id}-default-icon-fixed`"
v-model="type.iconFixed"
/>
<label :for="`${id}-default-symbol-fixed`" class="form-check-label">{{i18n.t("edit-type-dialog.fixed")}}</label>
<label :for="`${id}-default-icon-fixed`" class="form-check-label">{{i18n.t("edit-type-dialog.fixed")}}</label>
</div>
</div>
</div>

Wyświetl plik

@ -9,7 +9,7 @@
import ModalDialog from "../ui/modal-dialog.vue";
import ShapePicker from "../ui/shape-picker.vue";
import SizePicker from "../ui/size-picker.vue";
import SymbolPicker from "../ui/symbol-picker.vue";
import IconPicker from "../ui/icon-picker.vue";
import WidthPicker from "../ui/width-picker.vue";
import { useToasts } from "../ui/toasts/toasts.vue";
import { computed, ref, watch } from "vue";
@ -24,7 +24,7 @@
field.controlColour,
...(type.type == "marker" ? [
field.controlSize,
field.controlSymbol,
field.controlIcon,
field.controlShape
] : []),
...(type.type == "line" ? [
@ -196,15 +196,15 @@
<div v-if="type.type == 'marker'" class="form-check">
<input
:id="`${id}-control-symbol`"
:id="`${id}-control-icon`"
class="form-check-input"
type="checkbox"
v-model="fieldValue.controlSymbol"
:disabled="!resolvedCanControl.includes('symbol')"
v-model="fieldValue.controlIcon"
:disabled="!resolvedCanControl.includes('icon')"
/>
<label
class="form-check-label"
:for="`${id}-control-symbol`"
:for="`${id}-control-icon`"
>
{{i18n.t("edit-type-dropdown-dialog.control-icon", { type: typeInterpolation })}}
</label>
@ -266,7 +266,7 @@
<th v-if="fieldValue.type == 'checkbox'">{{i18n.t("edit-type-dropdown-dialog.label")}}</th>
<th v-if="fieldValue.controlColour">{{i18n.t("edit-type-dropdown-dialog.colour")}}</th>
<th v-if="fieldValue.controlSize">{{i18n.t("edit-type-dropdown-dialog.size")}}</th>
<th v-if="fieldValue.controlSymbol">{{i18n.t("edit-type-dropdown-dialog.icon")}}</th>
<th v-if="fieldValue.controlIcon">{{i18n.t("edit-type-dropdown-dialog.icon")}}</th>
<th v-if="fieldValue.controlShape">{{i18n.t("edit-type-dropdown-dialog.shape")}}</th>
<th v-if="fieldValue.controlWidth">{{i18n.t("edit-type-dropdown-dialog.width")}}</th>
<th v-if="fieldValue.controlStroke">{{i18n.t("edit-type-dropdown-dialog.stroke")}}</th>
@ -315,11 +315,11 @@
class="fm-custom-range-with-label"
></SizePicker>
</td>
<td v-if="fieldValue.controlSymbol" class="field">
<SymbolPicker
:modelValue="option.symbol ?? type.defaultSymbol"
@update:modelValue="option.symbol = $event"
></SymbolPicker>
<td v-if="fieldValue.controlIcon" class="field">
<IconPicker
:modelValue="option.icon ?? type.defaultIcon"
@update:modelValue="option.icon = $event"
></IconPicker>
</td>
<td v-if="fieldValue.controlShape" class="field">
<ShapePicker

Wyświetl plik

@ -113,7 +113,7 @@
}
return (
context.baseUrl
+ client.value.padData!.id
+ client.value.mapData!.id
+ `/rawTable`
+ `/${resolvedTypeId.value}`
+ (paramsStr ? `?${paramsStr}` : '')
@ -121,7 +121,7 @@
} else {
return (
context.baseUrl
+ client.value.padData!.id
+ client.value.mapData!.id
+ `/table`
+ (paramsStr ? `?${paramsStr}` : '')
);
@ -134,7 +134,7 @@
}
return (
context.baseUrl
+ client.value.padData!.id
+ client.value.mapData!.id
+ `/csv`
+ `/${resolvedTypeId.value}`
+ (paramsStr ? `?${paramsStr}` : '')
@ -144,7 +144,7 @@
case "gpx": {
return (
context.baseUrl
+ client.value.padData!.id
+ client.value.mapData!.id
+ `/${format.value}`
+ (routeType.value === "zip" ? `/zip` : "")
+ (paramsStr ? `?${paramsStr}` : '')
@ -154,7 +154,7 @@
default: {
return (
context.baseUrl
+ client.value.padData!.id
+ client.value.mapData!.id
+ `/${format.value}`
+ (paramsStr ? `?${paramsStr}` : '')
);

Wyświetl plik

@ -1,7 +1,7 @@
import type Client from "facilmap-client";
export type ClientContext = Client & {
/** If this is a true, it means that the current pad ID was not found and a create dialog is shown for it. */
get isCreatePad(): boolean;
openPad(padId: string | undefined): void;
/** If this is a true, it means that the current map ID was not found and a create dialog is shown for it. */
get isCreateMap(): boolean;
openMap(mapId: string | undefined): void;
};

Wyświetl plik

@ -17,7 +17,7 @@ export interface MapComponents {
bboxHandler: BboxHandler;
container: HTMLElement;
graphicScale: any;
hashHandler: HashHandler;
hashHandler: HashHandler & { _fmActivate: () => Promise<void> };
linesLayer: LinesLayer;
locateControl?: L.Control.Locate;
map: Map;

Wyświetl plik

@ -1,5 +1,5 @@
export interface WritableSearchFormTabContext {
setQuery(query: string, zoom?: boolean, smooth?: boolean, autofocus?: boolean): void;
setQuery(query: string, zoom?: boolean, smooth?: boolean, autofocus?: boolean): Promise<void>;
}
export type SearchFormTabContext = Readonly<WritableSearchFormTabContext>;

Wyświetl plik

@ -16,26 +16,26 @@
import FacilMapContextProvider from "./facil-map-context-provider/facil-map-context-provider.vue";
import type { FacilMapSettings } from "./facil-map-context-provider/facil-map-context";
import ClientProvider from "./client-provider.vue";
import { preloadExtraSymbols } from "facilmap-leaflet";
import { preloadExtraIcons } from "facilmap-leaflet";
const props = defineProps<{
baseUrl: string;
serverUrl: string;
padId: string | undefined;
mapId: string | undefined;
appName?: string;
hideCommercialMapLinks?: boolean;
settings?: Partial<FacilMapSettings>;
}>();
const emit = defineEmits<{
"update:padId": [padId: string | undefined];
"update:padName": [padName: string | undefined];
"update:mapId": [mapId: string | undefined];
"update:mapName": [mapName: string | undefined];
}>();
const padId = computed({
get: () => props.padId,
set: (padId) => {
emit("update:padId", padId);
const mapId = computed({
get: () => props.mapId,
set: (mapId) => {
emit("update:mapId", mapId);
}
});
@ -43,15 +43,15 @@
const context = toRef(() => contextRef.value?.context);
const client = toRef(() => context.value?.components.client);
watch(() => client.value?.padId, () => {
if (client.value && client.value.padId !== props.padId) {
emit("update:padId", client.value.padId);
watch(() => client.value?.mapId, () => {
if (client.value && client.value.mapId !== props.mapId) {
emit("update:mapId", client.value.mapId);
}
});
watch(() => client.value?.padData?.name, () => {
watch(() => client.value?.mapData?.name, () => {
if (client.value) {
emit("update:padName", client.value.padData?.name);
emit("update:mapName", client.value.mapData?.name);
}
});
@ -59,7 +59,7 @@
context
});
preloadExtraSymbols().catch((err) => {
preloadExtraIcons().catch((err) => {
console.error("Error preloading extra icons", err);
});
</script>
@ -73,7 +73,7 @@
:settings="props.settings"
ref="contextRef"
>
<ClientProvider v-model:padId="padId" :serverUrl="serverUrl"></ClientProvider>
<ClientProvider v-model:mapId="mapId" :serverUrl="serverUrl"></ClientProvider>
<LeafletMap v-if="context?.components.client">
<Toolbox v-if="context.settings.toolbox" :interactive="context.settings.interactive"></Toolbox>

Wyświetl plik

@ -100,7 +100,7 @@
<template v-if="isAddingView.has(view)">
<div class="spinner-border spinner-border-sm"></div>
</template>
<template v-else-if="client.padData && client.writable == 2 && !existingViews.get(view)">
<template v-else-if="client.mapData && client.writable == 2 && !existingViews.get(view)">
<a
href="javascript:"
@click="addView(view)"
@ -129,7 +129,7 @@
<template v-if="isAddingType.has(type)">
<div class="spinner-border spinner-border-sm"></div>
</template>
<template v-else-if="client.padData && client.writable == 2 && !existingTypes.get(type)">
<template v-else-if="client.mapData && client.writable == 2 && !existingTypes.get(type)">
<a
href="javascript:"
@click="addType(type)"

Wyświetl plik

@ -35,7 +35,7 @@ export interface HistoryEntryLabels {
export function getLabelsForHistoryEntry(client: ClientContext, entry: HistoryEntry): HistoryEntryLabels {
const i18n = getI18n();
if(entry.type == "Pad") {
if(entry.type == "Map") {
return {
description: i18n.t("history-utils.description-update-map"),
revert: {

Wyświetl plik

@ -32,7 +32,6 @@ export class AttributionControl extends Control {
onRemove(map: Map): void {
map.off("layeradd", this.update, this);
map.off("layerremove", this.update, this);
delete this._map;
}
update(): void {

Wyświetl plik

@ -1,7 +1,7 @@
import { type Ref, ref, watch, markRaw, reactive, watchEffect, shallowRef, shallowReadonly, type Raw } from "vue";
import { type Ref, ref, watch, markRaw, reactive, watchEffect, shallowRef, shallowReadonly, type Raw, nextTick } from "vue";
import { type Control, latLng, latLngBounds, type Map, map as leafletMap, DomUtil, control } from "leaflet";
import "leaflet/dist/leaflet.css";
import { BboxHandler, getSymbolHtml, getVisibleLayers, HashHandler, LinesLayer, MarkersLayer, SearchResultsLayer, OverpassLayer, OverpassLoadStatus, displayView, getInitialView, coreSymbolList } from "facilmap-leaflet";
import { BboxHandler, getIconHtml, getVisibleLayers, HashHandler, LinesLayer, MarkersLayer, SearchResultsLayer, OverpassLayer, OverpassLoadStatus, displayView, getInitialView, coreIconList } from "facilmap-leaflet";
import "leaflet.locatecontrol";
import "leaflet.locatecontrol/dist/L.Control.Locate.css";
import "leaflet-graphicscale";
@ -15,7 +15,7 @@ import type { MapComponents, MapContextData, MapContextEvents, WritableMapContex
import type { ClientContext } from "../facil-map-context-provider/client-context";
import type { FacilMapContext } from "../facil-map-context-provider/facil-map-context";
import { requireClientContext } from "../facil-map-context-provider/facil-map-context-provider.vue";
import { type Optional, sleep } from "facilmap-utils";
import { type Optional } from "facilmap-utils";
import { getI18n, i18nResourceChangeCounter } from "../../utils/i18n";
import { AttributionControl } from "./attribution";
import { fixOnCleanup } from "../../utils/vue";
@ -179,11 +179,11 @@ function useLocateControl(map: Ref<Map>, context: FacilMapContext): Ref<Raw<Cont
if (locateControl) {
locateControl.addTo(map.value);
if (!coreSymbolList.includes("screenshot")) {
if (!coreIconList.includes("screenshot")) {
console.warn(`Icon "screenshot" is not in core icons.`);
}
getSymbolHtml("currentColor", "1.5em", "screenshot").then((html) => {
getIconHtml("currentColor", "1.5em", "screenshot").then((html) => {
locateControl._container.querySelector("a")?.insertAdjacentHTML("beforeend", html);
}).catch((err) => {
console.error("Error loading locate control icon", err);
@ -310,31 +310,36 @@ function useSelectionHandler(map: Ref<Map>, context: FacilMapContext, mapContext
);
}
function useHashHandler(map: Ref<Map>, client: Ref<ClientContext>, context: FacilMapContext, mapContext: MapContextWithoutComponents, overpassLayer: Ref<OverpassLayer>): Ref<Raw<HashHandler>> {
function useHashHandler(map: Ref<Map>, client: Ref<ClientContext>, context: FacilMapContext, mapContext: MapContextWithoutComponents, overpassLayer: Ref<OverpassLayer>): Ref<Raw<HashHandler & { _fmActivate: () => Promise<void> }>> {
return useMapComponent(
map,
() => markRaw(new HashHandler(map.value, client.value, { overpassLayer: overpassLayer.value, simulate: !context.settings.updateHash }))
.on("fmQueryChange", async (e: any) => {
let smooth = true;
let autofocus = false;
if (!mapContext.components) {
// This is called while the hash handler is being enabled, so it is the initial view
smooth = false;
autofocus = context.settings.autofocus;
await sleep(0); // Wait for components to be initialized (needed by openSpecialQuery())
}
() => {
let queryChangePromise: Promise<void> | undefined;
const hashHandler = markRaw(new HashHandler(map.value, client.value, { overpassLayer: overpassLayer.value, simulate: !context.settings.updateHash }))
.on("fmQueryChange", async (e: any) => {
let smooth = true;
let autofocus = false;
const searchFormTab = context.components.searchFormTab;
if (!e.query)
searchFormTab?.setQuery("", false, false);
else if (!await openSpecialQuery(e.query, context, e.zoom, smooth))
searchFormTab?.setQuery(e.query, e.zoom, smooth, autofocus);
})
.on("fmHash", (e: any) => {
mapContext.hash = e.hash;
}),
const searchFormTab = context.components.searchFormTab;
queryChangePromise = (async () => {
if (!e.query)
await searchFormTab?.setQuery("", false, false);
else if (!await openSpecialQuery(e.query, context, e.zoom, smooth))
await searchFormTab?.setQuery(e.query, e.zoom, smooth, autofocus);
})();
await queryChangePromise;
})
.on("fmHash", (e: any) => {
mapContext.hash = e.hash;
});
return Object.assign(hashHandler, {
_fmActivate: async () => {
hashHandler.enable();
await queryChangePromise;
}
});
},
(hashHandler, onCleanup) => {
hashHandler.enable();
onCleanup(() => {
hashHandler.disable();
});
@ -420,32 +425,43 @@ export async function useMapContext(context: FacilMapContext, mapRef: Ref<HTMLEl
const client = requireClientContext(context);
if (!map._loaded) {
try {
// Initial view was not set by hash handler
displayView(map, await getInitialView(client.value), { overpassLayer });
} catch (error) {
console.error(error);
displayView(map, undefined, { overpassLayer });
}
}
(async () => {
await nextTick(); // useMapContext() return promise is resolved, setting mapContext.value in <LeafletMap>
await nextTick(); // <LeafletMap> rerenders with its slot, search box tabs are now available and can receive the query from the hash handler
watchEffect(() => {
mapContext.activeQuery = getHashQuery(mapContext.components.map, client.value, mapContext.selection) || mapContext.fallbackQuery;
mapContext.components.hashHandler.setQuery(mapContext.activeQuery);
});
await mapContext.components.hashHandler._fmActivate();
if (!map._loaded) {
try {
// Initial view was not set by hash handler
displayView(map, await getInitialView(client.value), { overpassLayer });
} catch (error) {
console.error(error);
displayView(map, undefined, { overpassLayer });
}
}
watch(() => mapContext.components.hashHandler, async (hashHandler) => {
await hashHandler._fmActivate();
});
watchEffect(() => {
mapContext.activeQuery = getHashQuery(mapContext.components.map, client.value, mapContext.selection) || mapContext.fallbackQuery;
mapContext.components.hashHandler.setQuery(mapContext.activeQuery);
});
})().catch(console.error);
return mapContext;
}
/* function createButton(symbol: string, onClick: () => void): Control {
/* function createButton(icon: Icon, onClick: () => void): Control {
return Object.assign(new Control(), {
onAdd() {
const div = document.createElement('div');
div.className = "leaflet-bar";
const a = document.createElement('a');
a.href = "javascript:";
a.innerHTML = createSymbolHtml("currentColor", "1.5em", symbol);
a.innerHTML = createIconHtml("currentColor", "1.5em", icon);
a.addEventListener("click", (e) => {
e.preventDefault();
onClick();

Wyświetl plik

@ -65,8 +65,8 @@
<slot v-if="mapContext" name="after"></slot>
</div>
<div class="fm-leaflet-map-disabled-cover" v-show="client.padId && (client.disconnected || (client.serverError && !client.isCreatePad) || client.deleted)"></div>
<div class="fm-leaflet-map-loading" v-show="!loaded && !client.serverError && !client.isCreatePad" :class="{ 'fatal-error': !!fatalError }">
<div class="fm-leaflet-map-disabled-cover" v-show="client.mapId && (client.disconnected || (client.serverError && !client.isCreateMap) || client.deleted)"></div>
<div class="fm-leaflet-map-loading" v-show="!loaded && !client.serverError && !client.isCreateMap" :class="{ 'fatal-error': !!fatalError }">
{{fatalError || i18n.t("leaflet-map.loading")}}
</div>
</div>

Wyświetl plik

@ -1,5 +1,5 @@
<script setup lang="ts">
import { getMarkerHtml, getSymbolHtml } from "facilmap-leaflet";
import { getMarkerHtml, getIconHtml } from "facilmap-leaflet";
import { makeTypeFilter, markdownBlock } from "facilmap-utils";
import type { LegendItem, LegendType } from "./legend-utils";
import { createLinePlaceholderHtml } from "../../utils/ui";
@ -54,13 +54,13 @@
mapContext.value.components.map.setFmFilter(makeTypeFilter(mapContext.value.components.map.fmFilter, typeInfo.typeId, filters));
}
async function makeSymbol(typeInfo: LegendType, item: LegendItem, height = 15): Promise<string> {
async function makeIcon(typeInfo: LegendType, item: LegendItem, height = 15): Promise<string> {
if(typeInfo.type == "line")
return createLinePlaceholderHtml(item.colour || "rainbow", item.width || 5, 50, item.stroke ?? "");
else if (item.colour || item.shape != null)
return await getMarkerHtml(item.colour || "rainbow", height, item.symbol, item.shape);
return await getMarkerHtml(item.colour || "rainbow", height, item.icon, item.shape);
else
return await getSymbolHtml("#000000", height, item.symbol);
return await getIconHtml("#000000", height, item.icon);
}
function togglePopover(itemKey: string, show: boolean) {
@ -84,9 +84,9 @@
<dl>
<template v-for="(item, idx) in type.items" :key="item.key">
<dt
:class="[ 'fm-legend-symbol', 'fm-' + type.type, { filtered: item.filtered, first: (item.first && idx !== 0), bright: item.bright } ]"
:class="[ 'fm-legend-icon', 'fm-' + type.type, { filtered: item.filtered, first: (item.first && idx !== 0), bright: item.bright } ]"
@click="toggleFilter(type, item)"
v-html-async="makeSymbol(type, item)"
v-html-async="makeIcon(type, item)"
@mouseenter="togglePopover(item.key, true)"
@mouseleave="togglePopover(item.key, false)"
:ref="mapRef(itemIconRefs, item.key)"
@ -112,14 +112,14 @@
>
<div
:class="[
'fm-legend-symbol',
'fm-legend-icon',
`fm-${type.type}`,
{
filtered: item.filtered,
bright: item.bright
}
]"
v-html-async="makeSymbol(type, item, 40)"
v-html-async="makeIcon(type, item, 40)"
></div>
<p>
<span class="text-break" :style="item.strikethrough ? {'text-decoration': 'line-through'} : {}">{{item.label}}</span>

Wyświetl plik

@ -1,5 +1,5 @@
import type { ID, Shape, Stroke, Symbol, Type } from "facilmap-types";
import { symbolList } from "facilmap-leaflet";
import type { ID, Shape, Stroke, Icon, Type } from "facilmap-types";
import { iconList } from "facilmap-leaflet";
import { formatTypeName, getOrderedTypes, isBright } from "facilmap-utils";
import type { FacilMapContext } from "../facil-map-context-provider/facil-map-context";
import { requireClientContext, requireMapContext } from "../facil-map-context-provider/facil-map-context-provider.vue";
@ -22,7 +22,7 @@ export interface LegendItem {
first?: boolean;
strikethrough?: boolean;
colour?: string;
symbol?: Symbol;
icon?: Icon;
shape?: Shape;
width?: number;
stroke?: Stroke;
@ -43,7 +43,7 @@ export function getLegendItems(context: FacilMapContext): LegendType[] {
if (
type.colourFixed ||
(type.type == "marker" && type.symbolFixed && type.defaultSymbol && (symbolList.includes(type.defaultSymbol) || type.defaultSymbol.length == 1)) ||
(type.type == "marker" && type.iconFixed && type.defaultIcon && (iconList.includes(type.defaultIcon) || type.defaultIcon.length == 1)) ||
(type.type == "marker" && type.shapeFixed) ||
(type.type == "line" && type.widthFixed) ||
(type.type === "line" && type.strokeFixed)
@ -58,8 +58,8 @@ export function getLegendItems(context: FacilMapContext): LegendType[] {
if (type.colourFixed) {
item.colour = type.defaultColour ? `#${type.defaultColour}` : undefined;
}
if (type.type == "marker" && type.symbolFixed && type.defaultSymbol && (symbolList.includes(type.defaultSymbol) || type.defaultSymbol.length == 1)) {
item.symbol = type.defaultSymbol;
if (type.type == "marker" && type.iconFixed && type.defaultIcon && (iconList.includes(type.defaultIcon) || type.defaultIcon.length == 1)) {
item.icon = type.defaultIcon;
}
if (type.type == "marker" && type.shapeFixed) {
item.shape = type.defaultShape ?? "";
@ -82,7 +82,7 @@ export function getLegendItems(context: FacilMapContext): LegendType[] {
(field.type != "dropdown" && field.type != "checkbox") ||
(
!field.controlColour &&
!(type.type === "marker" && field.controlSymbol) &&
!(type.type === "marker" && field.controlIcon) &&
!(type.type === "marker" && field.controlShape) &&
!(type.type === "line" && field.controlWidth) &&
!(type.type === "line" && field.controlStroke)
@ -119,10 +119,10 @@ export function getLegendItems(context: FacilMapContext): LegendType[] {
}
if (type.type == "marker") {
if (field.controlSymbol) {
item.symbol = option.symbol ?? type.defaultSymbol;
} else if (type.symbolFixed) {
item.symbol = type.defaultSymbol;
if (field.controlIcon) {
item.icon = option.icon ?? type.defaultIcon;
} else if (type.iconFixed) {
item.icon = type.defaultIcon;
}
if (field.controlShape) {

Wyświetl plik

@ -38,11 +38,11 @@
}
const legend1 = computed(() => {
return client.value.padData?.legend1?.trim() || "";
return client.value.mapData?.legend1?.trim() || "";
});
const legend2 = computed(() => {
return client.value.padData?.legend2?.trim() || "";
return client.value.mapData?.legend2?.trim() || "";
});
const legendItems = computed(() => {

Wyświetl plik

@ -16,7 +16,7 @@
}>();
const isBookmarked = computed(() => {
return !!client.value.padId && storage.bookmarks.some((bookmark) => bookmark.id == client.value.padId);
return !!client.value.mapId && storage.bookmarks.some((bookmark) => bookmark.id == client.value.mapId);
});
function deleteBookmark(bookmark: Bookmark): void {
@ -26,7 +26,7 @@
}
function addBookmark(): void {
storage.bookmarks.push({ id: client.value.padId!, padId: client.value.padData!.id, name: client.value.padData!.name });
storage.bookmarks.push({ id: client.value.mapId!, padId: client.value.mapData!.id, name: client.value.mapData!.name });
}
</script>
@ -54,7 +54,7 @@
>
<template #item="{ element: bookmark }">
<tr>
<td class="align-middle text-break" :class="{ 'font-weight-bold': bookmark.id == client.padId }">
<td class="align-middle text-break" :class="{ 'font-weight-bold': bookmark.id == client.mapId }">
{{bookmark.id}}
</td>
<td class="align-middle">
@ -67,7 +67,7 @@
</tr>
</template>
</Draggable>
<tfoot v-if="client.padData && !isBookmarked">
<tfoot v-if="client.mapData && !isBookmarked">
<tr>
<td colspan="3">
<button type="button" class="btn btn-secondary" @click="addBookmark()">{{i18n.t("manage-bookmarks-dialog.bookmark-current")}}</button>

Wyświetl plik

@ -38,7 +38,7 @@
toasts.hideToast(`fm${context.id}-save-view-error-default`);
try {
await client.value.editPad({ defaultViewId: view.id });
await client.value.editMap({ defaultViewId: view.id });
} catch (err) {
toasts.showErrorToast(`fm${context.id}-save-view-error-default`, () => i18n.t("manage-views-dialog.default-view-error"), err);
} finally {
@ -114,7 +114,7 @@
<td
class="text-break align-middle"
:class="{
'font-weight-bold': client.padData?.defaultView && view.id == client.padData.defaultView.id
'font-weight-bold': client.mapData?.defaultView && view.id == client.mapData.defaultView.id
}"
>
<a href="javascript:" @click="display(view)">{{view.name}}</a>
@ -123,7 +123,7 @@
<button
type="button"
class="btn btn-secondary"
v-show="!client.padData?.defaultView || view.id !== client.padData.defaultView.id"
v-show="!client.mapData?.defaultView || view.id !== client.mapData.defaultView.id"
@click="makeDefault(view)"
:disabled="!!isSavingDefaultView || isDeleting.has(view.id)"
>

Wyświetl plik

@ -1,5 +1,5 @@
<script setup lang="ts">
import { padIdValidator, type CRU, type PadData } from "facilmap-types";
import { mapIdValidator, type CRU, type MapData } from "facilmap-types";
import { computed } from "vue";
import { getUniqueId, getZodValidator, validateRequired } from "../../utils/utils";
import { injectContextRequired } from "../facil-map-context-provider/facil-map-context-provider.vue";
@ -13,7 +13,7 @@
const i18n = useI18n();
const props = defineProps<{
padData: PadData<CRU.CREATE>;
mapData: MapData<CRU.CREATE>;
idProp: IdProp;
modelValue: string;
label: string;
@ -24,7 +24,7 @@
"update:modelValue": [string];
}>();
const id = getUniqueId("fm-pad-settings-pad-id-edit");
const id = getUniqueId("fm-map-settings-map-id-edit");
const value = computed({
get: () => props.modelValue,
@ -33,9 +33,9 @@
}
});
function validateDistinctPadId(id: string) {
if (idProps.some((p) => p !== props.idProp && props.padData[p] === id)) {
return i18n.t("pad-id-edit.unique-id-error");
function validateDistinctMapId(id: string) {
if (idProps.some((p) => p !== props.idProp && props.mapData[p] === id)) {
return i18n.t("map-id-edit.unique-id-error");
}
}
</script>
@ -50,7 +50,7 @@
successTitle="Map link copied"
successMessage="The map link was copied to the clipboard."
:fullUrl="`${context.baseUrl}${encodeURIComponent(value)}`"
:validators="[validateRequired, getZodValidator(padIdValidator), validateDistinctPadId]"
:validators="[validateRequired, getZodValidator(mapIdValidator), validateDistinctMapId]"
></CopyToClipboardInput>
<div class="form-text">
@ -58,12 +58,4 @@
</div>
</div>
</div>
</template>
<style lang="scss">
.fm-pad-settings-pad-id-edit {
input {
min-width: 11rem;
}
}
</style>
</template>

Wyświetl plik

@ -1,13 +1,13 @@
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { padDataValidator, type CRU, type PadData } from "facilmap-types";
import { generateRandomPadId, mergeObject } from "facilmap-utils";
import { mapDataValidator, type CRU, type MapData } from "facilmap-types";
import { generateRandomMapId, mergeObject } from "facilmap-utils";
import { getUniqueId, getZodValidator } from "../../utils/utils";
import { cloneDeep, isEqual } from "lodash-es";
import ModalDialog from "../ui/modal-dialog.vue";
import { useToasts } from "../ui/toasts/toasts.vue";
import { showConfirm } from "../ui/alert.vue";
import PadIdEdit from "./pad-id-edit.vue";
import MapIdEdit from "./map-id-edit.vue";
import { injectContextRequired, requireClientContext } from "../facil-map-context-provider/facil-map-context-provider.vue";
import ValidatedField from "../ui/validated-form/validated-field.vue";
import { T, useI18n } from "../../utils/i18n";
@ -29,59 +29,59 @@
hidden: [];
}>();
const id = getUniqueId("fm-pad-settings");
const id = getUniqueId("fm-map-settings");
const isDeleting = ref(false);
const deleteConfirmation = ref("");
const expectedDeleteConfirmation = computed(() => i18n.t('pad-settings-dialog.delete-code'));
const expectedDeleteConfirmation = computed(() => i18n.t('map-settings-dialog.delete-code'));
const initialPadData: PadData<CRU.CREATE> | undefined = props.isCreate ? {
const initialMapData: MapData<CRU.CREATE> | undefined = props.isCreate ? {
name: "",
searchEngines: false,
description: "",
clusterMarkers: false,
adminId: (props.proposedAdminId || generateRandomPadId(16)),
writeId: generateRandomPadId(14),
id: generateRandomPadId(12),
adminId: (props.proposedAdminId || generateRandomMapId(16)),
writeId: generateRandomMapId(14),
id: generateRandomMapId(12),
legend1: "",
legend2: "",
defaultViewId: null
} : undefined;
const originalPadData = computed(() => props.isCreate ? initialPadData! : client.value.padData as PadData<CRU.CREATE>);
const originalMapData = computed(() => props.isCreate ? initialMapData! : client.value.mapData as MapData<CRU.CREATE>);
const padData = ref(cloneDeep(originalPadData.value));
const mapData = ref(cloneDeep(originalMapData.value));
const modalRef = ref<InstanceType<typeof ModalDialog>>();
const isModified = computed(() => !isEqual(padData.value, originalPadData.value));
const isModified = computed(() => !isEqual(mapData.value, originalMapData.value));
watch(() => client.value.padData, (newPadData, oldPadData) => {
if (!props.isCreate && padData.value && newPadData)
mergeObject(oldPadData, newPadData, padData.value as PadData);
watch(() => client.value.mapData, (newMapData, oldMapData) => {
if (!props.isCreate && mapData.value && newMapData)
mergeObject(oldMapData, newMapData, mapData.value as MapData);
}, { deep: true });
async function save(): Promise<void> {
toasts.hideToast(`fm${context.id}-pad-settings-error`);
toasts.hideToast(`fm${context.id}-map-settings-error`);
try {
if(props.isCreate)
await client.value.createPad(padData.value as PadData<CRU.CREATE>);
await client.value.createMap(mapData.value as MapData<CRU.CREATE>);
else
await client.value.editPad(padData.value);
await client.value.editMap(mapData.value);
modalRef.value?.modal.hide();
} catch (err) {
toasts.showErrorToast(`fm${context.id}-pad-settings-error`, () => (props.isCreate ? i18n.t("pad-settings-dialog.create-map-error") : i18n.t("pad-settings-dialog.save-map-error")), err);
toasts.showErrorToast(`fm${context.id}-map-settings-error`, () => (props.isCreate ? i18n.t("map-settings-dialog.create-map-error") : i18n.t("map-settings-dialog.save-map-error")), err);
}
};
async function deletePad(): Promise<void> {
toasts.hideToast(`fm${context.id}-pad-settings-error`);
async function deleteMap(): Promise<void> {
toasts.hideToast(`fm${context.id}-map-settings-error`);
if (!await showConfirm({
title: i18n.t("pad-settings-dialog.delete-map-title"),
message: i18n.t("pad-settings-dialog.delete-map-message", { name: padData.value.name }),
title: i18n.t("map-settings-dialog.delete-map-title"),
message: i18n.t("map-settings-dialog.delete-map-message", { name: mapData.value.name }),
variant: "danger",
okLabel: i18n.t("pad-settings-dialog.delete-map-ok")
okLabel: i18n.t("map-settings-dialog.delete-map-ok")
})) {
return;
}
@ -89,10 +89,10 @@
isDeleting.value = true;
try {
await client.value.deletePad();
await client.value.deleteMap();
modalRef.value?.modal.hide();
} catch (err) {
toasts.showErrorToast(`fm${context.id}-pad-settings-error`, () => i18n.t("pad-settings-dialog.delete-map-error"), err);
toasts.showErrorToast(`fm${context.id}-map-settings-error`, () => i18n.t("map-settings-dialog.delete-map-error"), err);
} finally {
isDeleting.value = false;
}
@ -101,56 +101,56 @@
<template>
<ModalDialog
:title="props.isCreate ? i18n.t('pad-settings-dialog.title-create') : i18n.t('pad-settings-dialog.title-edit')"
class="fm-pad-settings"
:title="props.isCreate ? i18n.t('map-settings-dialog.title-create') : i18n.t('map-settings-dialog.title-edit')"
class="fm-map-settings fm-pad-settings"
:noCancel="props.noCancel"
:isBusy="isDeleting"
:isCreate="props.isCreate"
:isModified="isModified"
:okLabel="props.isCreate ? i18n.t('pad-settings-dialog.create-button') : undefined"
:okLabel="props.isCreate ? i18n.t('map-settings-dialog.create-button') : undefined"
ref="modalRef"
@submit="$event.waitUntil(save())"
@hide="emit('hide')"
@hidden="emit('hidden')"
>
<template v-if="padData">
<PadIdEdit
:padData="padData"
<template v-if="mapData">
<MapIdEdit
:mapData="mapData"
idProp="adminId"
v-model="padData.adminId"
:label="i18n.t('pad-settings-dialog.admin-link-label')"
:description="i18n.t('pad-settings-dialog.admin-link-description')"
></PadIdEdit>
v-model="mapData.adminId"
:label="i18n.t('map-settings-dialog.admin-link-label')"
:description="i18n.t('map-settings-dialog.admin-link-description')"
></MapIdEdit>
<PadIdEdit
:padData="padData"
<MapIdEdit
:mapData="mapData"
idProp="writeId"
v-model="padData.writeId"
:label="i18n.t('pad-settings-dialog.write-link-label')"
:description="i18n.t('pad-settings-dialog.write-link-description')"
></PadIdEdit>
v-model="mapData.writeId"
:label="i18n.t('map-settings-dialog.write-link-label')"
:description="i18n.t('map-settings-dialog.write-link-description')"
></MapIdEdit>
<PadIdEdit
:padData="padData"
<MapIdEdit
:mapData="mapData"
idProp="id"
v-model="padData.id"
:label="i18n.t('pad-settings-dialog.read-link-label')"
:description="i18n.t('pad-settings-dialog.read-link-description')"
></PadIdEdit>
v-model="mapData.id"
:label="i18n.t('map-settings-dialog.read-link-label')"
:description="i18n.t('map-settings-dialog.read-link-description')"
></MapIdEdit>
<ValidatedField
class="row mb-3"
:value="padData.name"
:validators="[getZodValidator(padDataValidator.update.shape.name)]"
:value="mapData.name"
:validators="[getZodValidator(mapDataValidator.update.shape.name)]"
>
<template #default="slotProps">
<label :for="`${id}-pad-name-input`" class="col-sm-3 col-form-label">{{i18n.t("pad-settings-dialog.map-name")}}</label>
<label :for="`${id}-map-name-input`" class="col-sm-3 col-form-label">{{i18n.t("map-settings-dialog.map-name")}}</label>
<div class="col-sm-9 position-relative">
<input
:id="`${id}-pad-name-input`"
:id="`${id}-map-name-input`"
class="form-control"
type="text"
v-model="padData.name"
v-model="mapData.name"
:ref="slotProps.inputRef"
/>
<div class="invalid-tooltip">
@ -161,79 +161,79 @@
</ValidatedField>
<div class="row mb-3">
<label :for="`${id}-search-engines-input`" class="col-sm-3 col-form-label">{{i18n.t("pad-settings-dialog.search-engines")}}</label>
<label :for="`${id}-search-engines-input`" class="col-sm-3 col-form-label">{{i18n.t("map-settings-dialog.search-engines")}}</label>
<div class="col-sm-9">
<div class="form-check fm-form-check-with-label">
<input
:id="`${id}-search-engines-input`"
class="form-check-input"
type="checkbox"
v-model="padData.searchEngines"
v-model="mapData.searchEngines"
/>
<label :for="`${id}-search-engines-input`" class="form-check-label">
{{i18n.t("pad-settings-dialog.search-engines-label")}}
{{i18n.t("map-settings-dialog.search-engines-label")}}
</label>
</div>
<div class="form-text">
{{i18n.t("pad-settings-dialog.search-engines-description")}}
{{i18n.t("map-settings-dialog.search-engines-description")}}
</div>
</div>
</div>
<div class="row mb-3">
<label :for="`${id}-description-input`" class="col-sm-3 col-form-label">{{i18n.t("pad-settings-dialog.map-description")}}</label>
<label :for="`${id}-description-input`" class="col-sm-3 col-form-label">{{i18n.t("map-settings-dialog.map-description")}}</label>
<div class="col-sm-9">
<input
:id="`${id}-description-input`"
class="form-control"
type="text"
v-model="padData.description"
v-model="mapData.description"
/>
<div class="form-text">
{{i18n.t("pad-settings-dialog.map-description-description")}}
{{i18n.t("map-settings-dialog.map-description-description")}}
</div>
</div>
</div>
<div class="row mb-3">
<label :for="`${id}-cluster-markers-input`" class="col-sm-3 col-form-label">{{i18n.t("pad-settings-dialog.cluster-markers")}}</label>
<label :for="`${id}-cluster-markers-input`" class="col-sm-3 col-form-label">{{i18n.t("map-settings-dialog.cluster-markers")}}</label>
<div class="col-sm-9">
<div class="form-check fm-form-check-with-label">
<input
:id="`${id}-cluster-markers-input`"
class="form-check-input"
type="checkbox"
v-model="padData.clusterMarkers"
v-model="mapData.clusterMarkers"
/>
<label :for="`${id}-cluster-markers-input`" class="form-check-label">
{{i18n.t("pad-settings-dialog.cluster-markers-label")}}
{{i18n.t("map-settings-dialog.cluster-markers-label")}}
</label>
</div>
<div class="form-text">
{{i18n.t("pad-settings-dialog.cluster-markers-description")}}
{{i18n.t("map-settings-dialog.cluster-markers-description")}}
</div>
</div>
</div>
<div class="row mb-3">
<label :for="`${id}-legend1-input`" class="col-sm-3 col-form-label">{{i18n.t("pad-settings-dialog.legend-text")}}</label>
<label :for="`${id}-legend1-input`" class="col-sm-3 col-form-label">{{i18n.t("map-settings-dialog.legend-text")}}</label>
<div class="col-sm-9">
<textarea
:id="`${id}-legend1-input`"
class="form-control"
type="text"
v-model="padData.legend1"
v-model="mapData.legend1"
></textarea>
<textarea
:id="`${id}-legend2-input`"
class="form-control mt-1"
type="text"
v-model="padData.legend2"
v-model="mapData.legend2"
></textarea>
<div class="form-text">
<T k="pad-settings-dialog.legend-text-description">
<T k="map-settings-dialog.legend-text-description">
<template #markdown>
<a href="http://commonmark.org/help/" target="_blank">{{i18n.t("pad-settings-dialog.legend-text-description-interpolation-markdown")}}</a>
<a href="http://commonmark.org/help/" target="_blank">{{i18n.t("map-settings-dialog.legend-text-description-interpolation-markdown")}}</a>
</template>
</T>
</div>
@ -241,11 +241,11 @@
</div>
</template>
<template v-if="padData && !props.isCreate">
<template v-if="mapData && !props.isCreate">
<hr/>
<div class="row mb-3">
<label :for="`${id}-delete-input`" class="col-sm-3 col-form-label">{{i18n.t("pad-settings-dialog.delete-map")}}</label>
<label :for="`${id}-delete-input`" class="col-sm-3 col-form-label">{{i18n.t("map-settings-dialog.delete-map")}}</label>
<div class="col-sm-9">
<div class="input-group">
<input
@ -262,11 +262,11 @@
:disabled="isDeleting || modalRef?.formData?.isSubmitting || deleteConfirmation != expectedDeleteConfirmation"
>
<div v-if="isDeleting" class="spinner-border spinner-border-sm"></div>
{{i18n.t("pad-settings-dialog.delete-map-button")}}
{{i18n.t("map-settings-dialog.delete-map-button")}}
</button>
</div>
<div class="form-text">
<T k="pad-settings-dialog.delete-description">
<T k="map-settings-dialog.delete-description">
<template #code>
<code>{{expectedDeleteConfirmation}}</code>
</template>
@ -277,6 +277,6 @@
</template>
</ModalDialog>
<form :id="`${id}-delete-form`" @submit.prevent="deleteConfirmation == expectedDeleteConfirmation && deletePad()">
<form :id="`${id}-delete-form`" @submit.prevent="deleteConfirmation == expectedDeleteConfirmation && deleteMap()">
</form>
</template>

Wyświetl plik

@ -1,6 +1,6 @@
<script setup lang="ts">
import Icon from "./ui/icon.vue";
import type { FindPadsResult } from "facilmap-types";
import type { FindMapsResult } from "facilmap-types";
import { computed, ref } from "vue";
import { useToasts } from "./ui/toasts/toasts.vue";
import Pagination from "./ui/pagination.vue";
@ -10,7 +10,7 @@
import { injectContextRequired, requireClientContext, requireMapContext } from "./facil-map-context-provider/facil-map-context-provider.vue";
import type { FacilMapContext } from "./facil-map-context-provider/facil-map-context";
import ValidatedField from "./ui/validated-form/validated-field.vue";
import { parsePadUrl } from "facilmap-utils";
import { parseMapUrl } from "facilmap-utils";
import { useI18n } from "../utils/i18n";
const toasts = useToasts();
@ -22,9 +22,9 @@
const ITEMS_PER_PAGE = 20;
function parsePadId(val: string, context: FacilMapContext): { padId: string; hash: string } | undefined {
function parseMapId(val: string, context: FacilMapContext): { mapId: string; hash: string } | undefined {
const url = val.startsWith(context.baseUrl) ? val : `${context.baseUrl}${val}`;
return parsePadUrl(url, context.baseUrl);
return parseMapUrl(url, context.baseUrl);
}
const context = injectContextRequired();
@ -33,11 +33,11 @@
const id = getUniqueId("fm-open-map");
const padId = ref("");
const mapId = ref("");
const searchQuery = ref("");
const submittedSearchQuery = ref<string>();
const isSearching = ref(false);
const results = ref<FindPadsResult[]>([]);
const results = ref<FindMapsResult[]>([]);
const pages = ref(0);
const activePage = ref(0);
@ -45,16 +45,16 @@
const openFormRef = ref<InstanceType<typeof ValidatedForm>>();
const url = computed(() => {
const parsed = parsePadId(padId.value, context);
const parsed = parseMapId(mapId.value, context);
if (parsed) {
return context.baseUrl + encodeURIComponent(parsed.padId) + parsed.hash;
return context.baseUrl + encodeURIComponent(parsed.mapId) + parsed.hash;
}
});
function handleSubmit(): void {
const parsed = parsePadId(padId.value, context);
const parsed = parseMapId(mapId.value, context);
if (parsed) {
client.value.openPad(parsed.padId);
client.value.openMap(parsed.mapId);
modalRef.value!.modal.hide();
setTimeout(() => {
@ -64,8 +64,8 @@
}
}
function openResult(result: FindPadsResult): void {
client.value.openPad(result.id);
function openResult(result: FindMapsResult): void {
client.value.openMap(result.id);
modalRef.value!.modal.hide();
setTimeout(() => {
@ -87,7 +87,7 @@
toasts.hideToast(`fm${context.id}-open-map-search-error`);
try {
const newResults = await client.value.findPads({
const newResults = await client.value.findMaps({
query,
start: page * ITEMS_PER_PAGE,
limit: ITEMS_PER_PAGE
@ -97,26 +97,26 @@
results.value = newResults.results;
pages.value = Math.ceil(newResults.totalLength / ITEMS_PER_PAGE);
} catch (err) {
toasts.showErrorToast(`fm${context.id}-open-map-search-error`, () => i18n.t("open-map-dialog.find-pads-error"), err);
toasts.showErrorToast(`fm${context.id}-open-map-search-error`, () => i18n.t("open-map-dialog.find-maps-error"), err);
} finally {
isSearching.value = false;
}
}
function validatePadIdFormat(padId: string) {
const parsed = parsePadId(padId, context);
function validateMapIdFormat(mapId: string) {
const parsed = parseMapId(mapId, context);
if (!parsed) {
return i18n.t("open-map-dialog.map-id-format-error");
}
}
async function validatePadExistence(padId: string) {
const parsed = parsePadId(padId, context);
async function validateMapExistence(mapId: string) {
const parsed = parseMapId(mapId, context);
if (parsed) {
const padInfo = await client.value.getPad({ padId: parsed.padId });
if (!padInfo) {
const mapInfo = await client.value.getMap({ mapId: parsed.mapId });
if (!mapInfo) {
return i18n.t("open-map-dialog.map-not-found-error");
}
}
@ -133,26 +133,26 @@
>
<p>{{i18n.t("open-map-dialog.introduction")}}</p>
<ValidatedField
:value="padId"
:validators="padId ? [
validatePadIdFormat,
validatePadExistence
:value="mapId"
:validators="mapId ? [
validateMapIdFormat,
validateMapExistence
] : []"
:reportValid="!!padId"
:reportValid="!!mapId"
:debounceMs="300"
class="input-group has-validation position-relative"
>
<template #default="slotProps">
<input
class="form-control"
v-model="padId"
v-model="mapId"
:form="`${id}-open-form`"
:ref="slotProps.inputRef"
/>
<button
type="submit"
class="btn btn-primary"
:disabled="!padId"
:disabled="!mapId"
:form="`${id}-open-form`"
>
<div v-if="openFormRef?.formData.isValidating" class="spinner-border spinner-border-sm"></div>

Wyświetl plik

@ -65,7 +65,7 @@
</li>
</ul>
<div v-if="client.padData && !client.readonly" class="btn-toolbar mt-2">
<div v-if="client.mapData && !client.readonly" class="btn-toolbar mt-2">
<ZoomToObjectButton
v-if="zoomDestination"
:label="i18n.t('overpass-multiple-info.zoom-to-object-label')"

Wyświetl plik

@ -310,7 +310,7 @@
const [searchResults, mapResults] = await Promise.all([
client.value.find({ query: query }),
(async () => {
if (client.value.padData) {
if (client.value.mapData) {
const m = query.match(/^m(\d+)$/);
if (m) {
const marker = await client.value.getMarker({ id: Number(m[1]) });
@ -366,7 +366,7 @@
marker: {
colour: dragMarkerColour,
size: 35,
symbol: "",
icon: "",
shape: "drop"
}
})).addTo(mapContext.value.components.map));

Wyświetl plik

@ -52,7 +52,7 @@
});
if (makeDefault.value) {
await client.value.editPad({ defaultViewId: view.id });
await client.value.editMap({ defaultViewId: view.id });
}
modalRef.value?.modal.hide();

Wyświetl plik

@ -27,10 +27,10 @@
}
const searchFormTabContext: WritableSearchFormTabContext = {
setQuery(query, zoom = false, smooth = true, autofocus = false): void {
async setQuery(query, zoom = false, smooth = true, autofocus = false): Promise<void> {
searchForm.value!.setSearchString(query);
searchForm.value!.search(zoom, undefined, smooth);
searchBoxContext.value.activateTab(`fm${context.id}-search-form-tab`, { expand: !!query, autofocus });
await searchForm.value!.search(zoom, undefined, smooth);
}
};

Wyświetl plik

@ -99,7 +99,7 @@
lang: isLanguageExplicit() ? getCurrentLanguage() : undefined
}))
),
client.value.padData ? client.value.findOnMap({ query }) : undefined
client.value.mapData ? client.value.findOnMap({ query }) : undefined
]);
if (counter != searchCounter.value)

Wyświetl plik

@ -214,7 +214,7 @@
<slot name="after"></slot>
</div>
<div v-if="client.padData && !client.readonly && searchResults && searchResults.length > 0" class="btn-toolbar mt-2">
<div v-if="client.mapData && !client.readonly && searchResults && searchResults.length > 0" class="btn-toolbar mt-2">
<button
type="button"
class="btn btn-secondary btn-sm"

Wyświetl plik

@ -28,7 +28,7 @@
const showPois = ref(true);
const showLegend = ref(true);
const showLocate = ref(true);
const padIdType = ref<Writable>(2);
const mapIdType = ref<Writable>(2);
const activeShareTab = ref(0);
const layers = computed(() => {
@ -40,10 +40,10 @@
});
const hasLegend = computed(() => {
return !!client.value.padData && getLegendItems(context).length > 0;
return !!client.value.mapData && getLegendItems(context).length > 0;
});
const padIdTypes = computed(() => {
const mapIdTypes = computed(() => {
return [
{ value: 2, text: i18n.t("share-dialog.type-admin") },
{ value: 1, text: i18n.t("share-dialog.type-write") },
@ -75,7 +75,7 @@
const paramsStr = params.toString();
return context.baseUrl
+ (client.value.padData ? encodeURIComponent((padIdType.value == 2 && client.value.padData.adminId) || (padIdType.value == 1 && client.value.padData.writeId) || client.value.padData.id) : '')
+ (client.value.mapData ? encodeURIComponent((mapIdType.value == 2 && client.value.mapData.adminId) || (mapIdType.value == 1 && client.value.mapData.writeId) || client.value.mapData.id) : '')
+ (paramsStr ? `?${paramsStr}` : '')
+ (includeMapView.value && mapContext.value.hash ? `#${mapContext.value.hash}` : '');
});
@ -101,7 +101,7 @@
class="form-check-input"
:id="`${id}-include-map-view-input`"
v-model="includeMapView"
:disabled="!client.padData"
:disabled="!client.mapData"
/>
<label :for="`${id}-include-map-view-input`" class="form-check-label">
<T k="share-dialog.include-view">
@ -230,12 +230,12 @@
</div>
</div>
<template v-if="client.padData">
<template v-if="client.mapData">
<div class="row mb-3">
<label :for="`${id}-padIdType-input`" class="col-sm-3 col-form-label">{{i18n.t("share-dialog.link-type")}}</label>
<label :for="`${id}-mapIdType-input`" class="col-sm-3 col-form-label">{{i18n.t("share-dialog.link-type")}}</label>
<div class="col-sm-9">
<select :id="`${id}-padIdType-input`" class="form-select" v-model="padIdType">
<option v-for="type in padIdTypes" :key="type.value" :value="type.value">{{type.text}}</option>
<select :id="`${id}-mapIdType-input`" class="form-select" v-model="mapIdType">
<option v-for="type in mapIdTypes" :key="type.value" :value="type.value">{{type.text}}</option>
</select>
</div>
</div>

Wyświetl plik

@ -72,7 +72,7 @@
</DropdownMenu>
<ManageTypesDialog
v-if="dialog === 'manage-types' && client.padData"
v-if="dialog === 'manage-types' && client.mapData"
@hidden="dialog = undefined"
></ManageTypesDialog>
</template>

Wyświetl plik

@ -1,12 +1,12 @@
<script setup lang="ts">
import PadSettingsDialog from "../pad-settings-dialog/pad-settings-dialog.vue";
import MapSettingsDialog from "../map-settings-dialog/map-settings-dialog.vue";
import storage from "../../utils/storage";
import ManageBookmarksDialog from "../manage-bookmarks-dialog.vue";
import OpenMapDialog from "../open-map-dialog.vue";
import { computed, ref } from "vue";
import DropdownMenu from "../ui/dropdown-menu.vue";
import { injectContextRequired, requireClientContext, requireMapContext } from "../facil-map-context-provider/facil-map-context-provider.vue";
import { normalizePadName } from "facilmap-utils";
import { normalizeMapName } from "facilmap-utils";
import { useI18n } from "../../utils/i18n";
const context = injectContextRequired();
@ -21,7 +21,7 @@
const dialog = ref<
| "open-map"
| "manage-bookmarks"
| "create-pad"
| "create-map"
>();
const hash = computed(() => {
@ -30,11 +30,11 @@
});
const isBookmarked = computed(() => {
return !!client.value.padId && storage.bookmarks.some((bookmark) => bookmark.id == client.value.padId);
return !!client.value.mapId && storage.bookmarks.some((bookmark) => bookmark.id == client.value.mapId);
});
function addBookmark(): void {
storage.bookmarks.push({ id: client.value.padId!, padId: client.value.padData!.id, name: client.value.padData!.name });
storage.bookmarks.push({ id: client.value.mapId!, padId: client.value.mapData!.id, name: client.value.mapData!.name });
}
</script>
@ -51,24 +51,24 @@
<li v-for="bookmark in storage.bookmarks" :key="bookmark.id">
<a
class="dropdown-item"
:class="{ active: bookmark.id == client.padId }"
:class="{ active: bookmark.id == client.mapId }"
:href="`${context.baseUrl}${encodeURIComponent(bookmark.id)}#${hash}`"
@click.exact.prevent="client.openPad(bookmark.id); emit('hide-sidebar')"
@click.exact.prevent="client.openMap(bookmark.id); emit('hide-sidebar')"
draggable="false"
>{{bookmark.customName || normalizePadName(bookmark.name)}}</a>
>{{bookmark.customName || normalizeMapName(bookmark.name)}}</a>
</li>
<li v-if="storage.bookmarks.length > 0">
<hr class="dropdown-divider">
</li>
<li v-if="client.padData && !isBookmarked">
<li v-if="client.mapData && !isBookmarked">
<a
class="dropdown-item"
href="javascript:"
@click="addBookmark()"
draggable="false"
>{{i18n.t('toolbox-collab-maps-dropdown.bookmark', { padName: normalizePadName(client.padData.name) })}}</a>
>{{i18n.t('toolbox-collab-maps-dropdown.bookmark', { mapName: normalizeMapName(client.mapData.name) })}}</a>
</li>
<li v-if="storage.bookmarks.length > 0">
@ -80,15 +80,15 @@
>{{i18n.t('toolbox-collab-maps-dropdown.manage-bookmarks')}}</a>
</li>
<li v-if="(client.padData && !isBookmarked) || storage.bookmarks.length > 0">
<li v-if="(client.mapData && !isBookmarked) || storage.bookmarks.length > 0">
<hr class="dropdown-divider">
</li>
<li v-if="!client.padId">
<li v-if="!client.mapId">
<a
class="dropdown-item"
href="javascript:"
@click="dialog = 'create-pad'; emit('hide-sidebar')"
@click="dialog = 'create-map'; emit('hide-sidebar')"
draggable="false"
>{{i18n.t('toolbox-collab-maps-dropdown.create-map')}}</a>
</li>
@ -99,16 +99,16 @@
href="javascript:"
@click="dialog = 'open-map'; emit('hide-sidebar')"
draggable="false"
>{{client.padId ? i18n.t("toolbox-collab-maps-dropdown.open-other-map") : i18n.t("toolbox-collab-maps-dropdown.open-map")}}</a>
>{{client.mapId ? i18n.t("toolbox-collab-maps-dropdown.open-other-map") : i18n.t("toolbox-collab-maps-dropdown.open-map")}}</a>
</li>
<li v-if="client.padData">
<li v-if="client.mapData">
<a
class="dropdown-item"
:href="`${context.baseUrl}#${hash}`"
@click.exact.prevent="client.openPad(undefined)"
@click.exact.prevent="client.openMap(undefined)"
draggable="false"
>{{i18n.t("toolbox-collab-maps-dropdown.close-map", { padName: normalizePadName(client.padData.name) })}}</a>
>{{i18n.t("toolbox-collab-maps-dropdown.close-map", { mapName: normalizeMapName(client.mapData.name) })}}</a>
</li>
</DropdownMenu>
@ -122,9 +122,9 @@
@hidden="dialog = undefined"
></ManageBookmarksDialog>
<PadSettingsDialog
v-if="dialog === 'create-pad'"
<MapSettingsDialog
v-if="dialog === 'create-map'"
@hidden="dialog = undefined"
:isCreate="true"
></PadSettingsDialog>
></MapSettingsDialog>
</template>

Wyświetl plik

@ -1,5 +1,5 @@
<script setup lang="ts">
import PadSettingsDialog from "../pad-settings-dialog/pad-settings-dialog.vue";
import MapSettingsDialog from "../map-settings-dialog/map-settings-dialog.vue";
import EditFilterDialog from "../edit-filter-dialog.vue";
import HistoryDialog from "../history-dialog/history-dialog.vue";
import ShareDialog from "../share-dialog.vue";
@ -24,7 +24,7 @@
}>();
const dialog = ref<
| "edit-pad"
| "edit-map"
| "share"
| "export"
| "edit-filter"
@ -60,7 +60,7 @@
>{{i18n.t("toolbox-tools-dropdown.open-file")}}</a>
</li>
<li v-if="client.padData">
<li v-if="client.mapData">
<a
class="dropdown-item"
href="javascript:"
@ -69,11 +69,11 @@
>{{i18n.t("toolbox-tools-dropdown.export")}}</a>
</li>
<li v-if="client.padData">
<li v-if="client.mapData">
<hr class="dropdown-divider">
</li>
<li v-if="client.padData">
<li v-if="client.mapData">
<a
class="dropdown-item"
href="javascript:"
@ -82,16 +82,16 @@
>{{i18n.t("toolbox-tools-dropdown.filter")}}</a>
</li>
<li v-if="client.writable == 2 && client.padData">
<li v-if="client.writable == 2 && client.mapData">
<a
class="dropdown-item"
href="javascript:"
@click="dialog = 'edit-pad'; emit('hide-sidebar')"
@click="dialog = 'edit-map'; emit('hide-sidebar')"
draggable="false"
>{{i18n.t("toolbox-tools-dropdown.settings")}}</a>
</li>
<li v-if="!client.readonly && client.padData">
<li v-if="!client.readonly && client.mapData">
<a
class="dropdown-item"
href="javascript:"
@ -114,10 +114,10 @@
</li>
</DropdownMenu>
<PadSettingsDialog
v-if="dialog === 'edit-pad' && client.padData"
<MapSettingsDialog
v-if="dialog === 'edit-map' && client.mapData"
@hidden="dialog = undefined"
></PadSettingsDialog>
></MapSettingsDialog>
<ShareDialog
v-if="dialog === 'share'"
@ -132,12 +132,12 @@
</KeepAlive>
<EditFilterDialog
v-if="dialog === 'edit-filter' && client.padData"
v-if="dialog === 'edit-filter' && client.mapData"
@hidden="dialog = undefined"
></EditFilterDialog>
<HistoryDialog
v-if="dialog === 'history' && client.padData"
v-if="dialog === 'history' && client.mapData"
@hidden="dialog = undefined"
></HistoryDialog>

Wyświetl plik

@ -72,12 +72,12 @@
</DropdownMenu>
<SaveViewDialog
v-if="dialog === 'save-view' && client.padData"
v-if="dialog === 'save-view' && client.mapData"
@hidden="dialog = undefined"
></SaveViewDialog>
<ManageViewsDialog
v-if="dialog === 'manage-views' && client.padData"
v-if="dialog === 'manage-views' && client.mapData"
@hidden="dialog = undefined"
></ManageViewsDialog>
</template>

Wyświetl plik

@ -40,19 +40,19 @@
></ToolboxCollabMapsDropdown>
<ToolboxAddDropdown
v-if="!client.readonly && client.padData"
v-if="!client.readonly && client.mapData"
@hide-sidebar="sidebarVisible = false"
></ToolboxAddDropdown>
<ToolboxViewsDropdown
v-if="client.padData && (!client.readonly || Object.keys(client.views).length > 0)"
v-if="client.mapData && (!client.readonly || Object.keys(client.views).length > 0)"
@hide-sidebar="sidebarVisible = false"
></ToolboxViewsDropdown>
<ToolboxMapStyleDropdown></ToolboxMapStyleDropdown>
<ToolboxToolsDropdown
v-if="props.interactive || client.padData"
v-if="props.interactive || client.mapData"
:interactive="props.interactive"
@hide-sidebar="sidebarVisible = false"
></ToolboxToolsDropdown>

Wyświetl plik

@ -73,7 +73,7 @@
<template>
<DropdownMenu
v-if="client.padData && !client.readonly && ((props.markers && markerTypes.length > 0) || (props.lines && lineTypes.length > 0))"
v-if="client.mapData && !client.readonly && ((props.markers && markerTypes.length > 0) || (props.lines && lineTypes.length > 0))"
:label="props.label ?? i18n.t('add-to-map-dropdown.fallback-label')"
:isDisabled="(props.markers ?? []).length === 0 && (props.lines ?? []).length === 0"
:isBusy="isAdding"

Wyświetl plik

@ -1,5 +1,5 @@
<script lang="ts">
import { getSymbolHtml, symbolList } from "facilmap-leaflet";
import { getIconHtml, iconList } from "facilmap-leaflet";
import Icon from "./icon.vue";
import Picker from "./picker.vue";
import { arrowNavigation } from "../../utils/ui";
@ -12,8 +12,8 @@
let allItemsP: Promise<Record<string, string>>;
async function getAllItems(): Promise<Record<string, string>> {
if (!allItemsP) { // eslint-disable-line @typescript-eslint/no-misused-promises
allItemsP = Promise.all(symbolList.map(async (s) => (
[s, await getSymbolHtml("currentColor", "1.5em", s)] as const
allItemsP = Promise.all(iconList.map(async (s) => (
[s, await getIconHtml("currentColor", "1.5em", s)] as const
))).then((l) => Object.fromEntries(l));
}
return await allItemsP;
@ -49,14 +49,14 @@
return Object.fromEntries<string>((await Promise.all([
(async (): Promise<Array<[string, string]>> => {
if (filter.value.length == 1) {
return [[filter.value, await getSymbolHtml("currentColor", "1.5em", filter.value)]];
return [[filter.value, await getIconHtml("currentColor", "1.5em", filter.value)]];
} else {
return [];
}
})(),
(async (): Promise<Array<[string, string]>> => {
if (props.modelValue?.length == 1 && props.modelValue != filter.value) {
return [[props.modelValue, await getSymbolHtml("currentColor", "1.5em", props.modelValue)]];
return [[props.modelValue, await getIconHtml("currentColor", "1.5em", props.modelValue)]];
} else {
return [];
}
@ -75,7 +75,7 @@
});
function validateSymbol(symbol: string) {
if (symbol && symbol.length !== 1 && !symbolList.includes(symbol)) {
if (symbol && symbol.length !== 1 && !iconList.includes(symbol)) {
return i18n.t("symbol-picker.unknown-icon-error");
}
}

Wyświetl plik

@ -1,5 +1,5 @@
<script setup lang="ts">
import { coreSymbolList, getSymbolHtml } from "facilmap-leaflet";
import { coreIconList, getIconHtml } from "facilmap-leaflet";
import { vHtmlAsync } from "../../utils/vue";
import { computed, watchEffect } from "vue";
@ -13,12 +13,12 @@
});
watchEffect(() => {
if (props.icon && !props.async && !coreSymbolList.includes(props.icon)) {
if (props.icon && !props.async && !coreIconList.includes(props.icon)) {
console.warn(`Icon "${props.icon}" is not in core icons.`);
}
});
const iconCodeP = computed(() => getSymbolHtml("currentColor", props.size, props.icon));
const iconCodeP = computed(() => getIconHtml("currentColor", props.size, props.icon));
</script>
<template>

Wyświetl plik

@ -53,8 +53,8 @@ export { default as MultipleInfoTab } from "./components/multiple-info/multiple-
export { default as MultipleInfo } from "./components/multiple-info/multiple-info.vue";
export { default as OverpassFormTab } from "./components/overpass-form/overpass-form-tab.vue";
export { default as OverpassForm } from "./components/overpass-form/overpass-form.vue";
export { default as PadIdEdit } from "./components/pad-settings-dialog/pad-settings-dialog.vue";
export { default as PadSettingsDialog } from "./components/pad-settings-dialog/pad-settings-dialog.vue";
export { default as MapIdEdit } from "./components/map-settings-dialog/map-id-edit.vue";
export { default as MapSettingsDialog } from "./components/map-settings-dialog/map-settings-dialog.vue";
export { default as RouteFormTab } from "./components/route-form/route-form-tab.vue";
export { default as RouteForm } from "./components/route-form/route-form.vue";
export { default as SearchBoxTab } from "./components/search-box/search-box-tab.vue";
@ -100,7 +100,7 @@ export { default as ShapePicker } from "./components/ui/shape-picker.vue";
export { default as Sidebar } from "./components/ui/sidebar.vue";
export { default as SizePicker } from "./components/ui/size-picker.vue";
export { default as StrokePicker } from "./components/ui/stroke-picker.vue";
export { default as SymbolPicker } from "./components/ui/symbol-picker.vue";
export { default as IconPicker } from "./components/ui/icon-picker.vue";
export { default as UseAsDropdown } from "./components/ui/use-as-dropdown.vue";
export { default as WidthPicker } from "./components/ui/width-picker.vue";
export { default as ZoomToObjectButton } from "./components/ui/zoom-to-object-button.vue";

Wyświetl plik

@ -1,5 +1,5 @@
import type { Feature, Geometry } from "geojson";
import type { GeoJsonExport, LineFeature, MarkerFeature, SearchResult } from "facilmap-types";
import { legacyV2MarkerToCurrent, legacyV2TypeToCurrent, type GeoJsonExport, type LineFeature, type MarkerFeature, type SearchResult } from "facilmap-types";
import { flattenObject } from "facilmap-utils";
import { getI18n } from "./i18n";
@ -71,7 +71,7 @@ export async function parseFiles(files: string[]): Promise<FileResultObject> {
if (geojson.facilmap.types) {
for (const i of Object.keys(geojson.facilmap.types)) {
typeMapping[i] = nextTypeIdx++;
ret.types[typeMapping[i]] = geojson.facilmap.types[i];
ret.types[typeMapping[i]] = legacyV2TypeToCurrent(geojson.facilmap.types[i]);
}
}
@ -122,7 +122,7 @@ export async function parseFiles(files: string[]): Promise<FileResultObject> {
if(geojson.facilmap) {
if(feature.properties.typeId && typeMapping[feature.properties.typeId])
f.fmTypeId = typeMapping[feature.properties.typeId];
f.fmProperties = feature.properties;
f.fmProperties = legacyV2MarkerToCurrent(feature.properties);
}
ret.features.push(f);

Wyświetl plik

@ -5,7 +5,7 @@ import type { FileResult, FileResultObject } from "./files";
import type { ClientContext } from "../components/facil-map-context-provider/client-context";
const VIEW_KEYS: Array<keyof FileResultObject["views"][0]> = ["name", "baseLayer", "layers", "top", "bottom", "left", "right", "filter"];
const TYPE_KEYS: Array<keyof FileResultObject["types"][0]> = ["name", "type", "defaultColour", "colourFixed", "defaultSize", "sizeFixed", "defaultSymbol", "symbolFixed", "defaultShape", "shapeFixed", "defaultWidth", "widthFixed", "defaultStroke", "strokeFixed", "defaultMode", "modeFixed", "fields"];
const TYPE_KEYS: Array<keyof FileResultObject["types"][0]> = ["name", "type", "defaultColour", "colourFixed", "defaultSize", "sizeFixed", "defaultIcon", "iconFixed", "defaultShape", "shapeFixed", "defaultWidth", "widthFixed", "defaultStroke", "strokeFixed", "defaultMode", "modeFixed", "fields"];
export function isSearchResult(result: SearchResult | FindOnMapResult | FileResult): result is SearchResult {
return !isMapResult(result) && !isFileResult(result);

Wyświetl plik

@ -1,12 +1,12 @@
import type { PadId } from "facilmap-types";
import type { MapId } from "facilmap-types";
import { isEqual } from "lodash-es";
import { reactive, watch } from "vue";
export interface Bookmark {
/** ID used to open the map */
id: PadId;
id: MapId;
/** Read-only ID of the map */
padId: PadId;
padId: MapId;
/** Last known name of the map */
name: string;
/** If this is defined, it is shown instead of the map name. */

Wyświetl plik

@ -2,9 +2,9 @@
<html class="fm-facilmap-map">
<head>
<meta charset="utf-8">
<title><%=normalizePageTitle(padData?.name != null ? normalizePadName(padData.name) : undefined, appName)%></title>
<meta name="description" content="<%=normalizePageDescription(padData?.description)%>" />
<% if(!padData || (isReadOnly && padData.searchEngines)) { -%>
<title><%=normalizePageTitle(mapData?.name != null ? normalizeMapName(mapData.name) : undefined, appName)%></title>
<meta name="description" content="<%=normalizePageDescription(mapData?.description)%>" />
<% if(!mapData || (isReadOnly && mapData.searchEngines)) { -%>
<meta name="robots" content="index,nofollow" />
<% } else { -%>
<meta name="robots" content="noindex,nofollow" />
@ -16,7 +16,7 @@
<link rel="apple-touch-icon" href="<%=paths.base%>static/app-180.png">
<link rel="manifest" href="<%=paths.base%>manifest.json">
<link rel="search" type="application/opensearchdescription+xml" title="<%=appName%>" href="<%=paths.base%>opensearch.xml">
<link rel="alternate" type="application/json+oembed" href="<%=paths.base%>oembed?url=<%=encodeURIComponent(url)%>&amp;format=json" title="<%=normalizePageTitle(padData ? normalizePadName(padData.name) : undefined, appName)%>" />
<link rel="alternate" type="application/json+oembed" href="<%=paths.base%>oembed?url=<%=encodeURIComponent(url)%>&amp;format=json" title="<%=normalizePageTitle(mapData ? normalizeMapName(mapData.name) : undefined, appName)%>" />
<link rel="iframely geolocation web-share resizable" href="<%=url%>">
<style type="text/css">
html, body, #app {

Wyświetl plik

@ -1,6 +1,6 @@
import { computed, createApp, defineComponent, h, ref, watch } from "vue";
import { FacilMap } from "../lib";
import { decodeQueryString, encodeQueryString, normalizePadName } from "facilmap-utils";
import { decodeQueryString, encodeQueryString, normalizeMapName } from "facilmap-utils";
import decodeURIComponent from "decode-uri-component";
import "../lib/bootstrap.scss"; // Not imported in lib/index.ts because we don't want it to be bundled
import { setLayerOptions } from "facilmap-leaflet";
@ -28,7 +28,7 @@ const queryParams = decodeQueryString(location.search);
const toBoolean = (val: string, def: boolean) => (val == null ? def : val != "0" && val != "false" && val != "no");
const baseUrl = location.protocol + "//" + location.host + location.pathname.replace(/[^/]*$/, "");
const initialPadId = decodeURIComponent(location.pathname.match(/[^/]*$/)![0]) || undefined;
const initialMapId = decodeURIComponent(location.pathname.match(/[^/]*$/)![0]) || undefined;
registerDereferrerHandler(baseUrl);
@ -44,20 +44,20 @@ if(!location.hash || location.hash == "#") {
const query = encodeQueryString(queryParams);
const hash = encodeQueryString(hashParams);
history.replaceState(null, "", baseUrl + encodeURIComponent(initialPadId || "") + (query ? "?" + query : "") + "#" + hash);
history.replaceState(null, "", baseUrl + encodeURIComponent(initialMapId || "") + (query ? "?" + query : "") + "#" + hash);
}
}
const Root = defineComponent({
setup() {
const padId = ref(initialPadId);
const padName = ref<string | undefined>(undefined);
const mapId = ref(initialMapId);
const mapName = ref<string | undefined>(undefined);
watch(padId, () => {
history.replaceState(null, "", baseUrl + (padId.value ? encodeURIComponent(padId.value) : "") + location.search + location.hash);
watch(mapId, () => {
history.replaceState(null, "", baseUrl + (mapId.value ? encodeURIComponent(mapId.value) : "") + location.search + location.hash);
});
const pageTitle = computed(() => padName.value != null ? `${normalizePadName(padName.value)}${config.appName}` : config.appName);
const pageTitle = computed(() => mapName.value != null ? `${normalizeMapName(mapName.value)}${config.appName}` : config.appName);
watch(pageTitle, () => {
// We have to call history.replaceState() in order for the new title to end up in the browser history
@ -68,7 +68,7 @@ const Root = defineComponent({
return () => h(FacilMap, {
baseUrl,
serverUrl: baseUrl,
padId: padId.value,
mapId: mapId.value,
appName: config.appName,
hideCommercialMapLinks: config.hideCommercialMapLinks,
settings: {
@ -85,8 +85,8 @@ const Root = defineComponent({
routing: config.supportsRoutes,
advancedRouting: config.supportsAdvancedRoutes
},
"onUpdate:padId": (v) => padId.value = v,
"onUpdate:padName": (v) => padName.value = v
"onUpdate:mapId": (v) => mapId.value = v,
"onUpdate:mapName": (v) => mapName.value = v
});
}
});

Wyświetl plik

@ -2,14 +2,14 @@
<html class="fm-facilmap-table">
<head>
<meta charset="utf-8">
<title><%=normalizePageTitle(normalizePadName(padData.name), appName)%></title>
<title><%=normalizePageTitle(normalizeMapName(mapData.name), appName)%></title>
<base href="../" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<%
if(!padData || padData.searchEngines) {
if(!mapData || mapData.searchEngines) {
-%>
<meta name="robots" content="index,nofollow" />
<meta name="description" content="<%=normalizePageDescription(padData.description)%>" />
<meta name="description" content="<%=normalizePageDescription(mapData.description)%>" />
<%
} else {
-%>
@ -21,7 +21,7 @@
<link rel="mask-icon" href="<%=paths.base%>favicon.svg" color="#00272a">
<link rel="apple-touch-icon" href="<%=paths.base%>static/app-180.png">
<link rel="manifest" href="<%=paths.base%>static/manifest.json">
<link rel="alternate" type="application/json+oembed" href="<%=paths.base%>oembed?url=<%=encodeURIComponent(url)%>&amp;format=json" title="<%=normalizePageTitle(normalizePadName(padData.name), appName)%>" />
<link rel="alternate" type="application/json+oembed" href="<%=paths.base%>oembed?url=<%=encodeURIComponent(url)%>&amp;format=json" title="<%=normalizePageTitle(normalizeMapName(mapData.name), appName)%>" />
<link rel="iframely resizable" href="<%=url%>">
<style type="text/css">
@ -41,7 +41,7 @@
</head>
<body>
<div class="container-fluid">
<h1><%=normalizePageTitle(normalizePadName(padData.name), appName)%></h1>
<h1><%=normalizePageTitle(normalizeMapName(mapData.name), appName)%></h1>
<%
for (const type of types) {
-%>

Wyświetl plik

@ -1,6 +1,6 @@
{
"name": "facilmap-integration-tests",
"version": "4.1.1",
"version": "5.0.0",
"private": true,
"type": "module",
"homepage": "https://github.com/FacilMap/facilmap",
@ -21,7 +21,11 @@
},
"dependencies": {
"facilmap-client": "workspace:^",
"facilmap-client-v3": "patch:facilmap-client@npm%3A3#../.yarn/patches/facilmap-client-npm-3.4.1-0b872958ee.patch",
"facilmap-client-v4": "patch:facilmap-client@npm%3A4#../.yarn/patches/facilmap-client-npm-4.1.1-841eedca5b.patch",
"facilmap-types": "workspace:^",
"facilmap-types-v3": "npm:facilmap-types@3",
"facilmap-types-v4": "npm:facilmap-types@4",
"facilmap-utils": "workspace:^",
"lodash-es": "^4.17.21",
"socket.io-client": "^4.7.5",

Wyświetl plik

@ -1,384 +0,0 @@
import { expect, test, vi } from "vitest";
import { createTemporaryPad, emit, generateTestPadId, getFacilMapUrl, getTemporaryPadData, openClient, openSocket } from "./utils";
import { Writable, type PadData, SocketVersion, CRU, type FindPadsResult, type PagedResults } from "facilmap-types";
import { pick } from "lodash-es";
import Client from "facilmap-client";
test("Create pad (using default values)", async () => {
const client = await openClient();
const onPadData = vi.fn();
client.on("padData", onPadData);
await createTemporaryPad(client, {}, async (createPadData, padData) => {
const expectedPadData: PadData & { writable: Writable } = {
...createPadData,
name: "",
searchEngines: false,
description: "",
clusterMarkers: false,
legend1: "",
legend2: "",
defaultViewId: null,
defaultView: null,
writable: Writable.ADMIN
};
expect(client.padId).toBe(createPadData.adminId);
expect(client.readonly).toBe(false);
expect(client.writable).toBe(Writable.ADMIN);
expect(client.serverError).toBe(undefined);
expect(padData).toEqual(expectedPadData);
expect(client.padData).toEqual(expectedPadData);
expect(onPadData).toBeCalledTimes(1);
expect(onPadData).toHaveBeenCalledWith(expectedPadData);
expect(await client.getPad({ padId: createPadData.id })).toEqual(pick(expectedPadData, ["id", "name", "description"]));
});
});
test("Create pad (using custom values)", async () => {
const client = await openClient();
const onPadData = vi.fn();
client.on("padData", onPadData);
await createTemporaryPad(client, {
name: "Test pad",
searchEngines: true,
description: "Test description",
clusterMarkers: true,
legend1: "Legend 1",
legend2: "Legend 1",
defaultViewId: null
}, async (createPadData, padData) => {
const expectedPadData: PadData & { writable: Writable } = {
...createPadData,
defaultView: null,
writable: Writable.ADMIN
};
expect(client.padId).toBe(createPadData.adminId);
expect(client.readonly).toBe(false);
expect(client.writable).toBe(Writable.ADMIN);
expect(client.serverError).toBe(undefined);
expect(padData).toEqual(expectedPadData);
expect(client.padData).toEqual(expectedPadData);
expect(onPadData).toBeCalledTimes(1);
expect(onPadData).toHaveBeenCalledWith(expectedPadData);
expect(await client.getPad({ padId: createPadData.id })).toEqual(pick(expectedPadData, ["id", "name", "description"]));
});
});
test("Create pad (ID already taken)", async () => {
const client1 = await openClient();
const client2 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData1) => {
await expect(async () => {
await createTemporaryPad(client2, {
id: createPadData1.id
})
}).rejects.toThrowError("already taken");
await expect(async () => {
await createTemporaryPad(client2, {
writeId: createPadData1.id
})
}).rejects.toThrowError("already taken");
await expect(async () => {
await createTemporaryPad(client2, {
adminId: createPadData1.id
})
}).rejects.toThrowError("already taken");
});
});
test("Create pad (duplicate IDs)", async () => {
const client = await openClient();
const newId = generateTestPadId();
await expect(async () => {
await createTemporaryPad(client, {
id: newId,
writeId: newId
});
}).rejects.toThrowError("have to be different");
await expect(async () => {
await createTemporaryPad(client, {
id: newId,
adminId: newId
});
}).rejects.toThrowError("have to be different");
await expect(async () => {
await createTemporaryPad(client, {
writeId: newId,
adminId: newId
});
}).rejects.toThrowError("have to be different");
});
test("Edit pad", async () => {
const client = await openClient();
const onPadData = vi.fn();
client.on("padData", onPadData);
await createTemporaryPad(client, {}, async (createPadData, padData) => {
const update = {
name: "Test pad",
searchEngines: true,
description: "Test description",
clusterMarkers: true,
legend1: "Legend 1",
legend2: "Legend 1"
} satisfies PadData<CRU.UPDATE>;
const updatedPadData = await client.editPad(update);
const expectedPadData: PadData & { writable: Writable } = {
...createPadData,
...update,
defaultViewId: null,
defaultView: null,
writable: Writable.ADMIN
};
expect(updatedPadData).toEqual(expectedPadData);
expect(client.padData).toEqual(expectedPadData);
expect(onPadData).toHaveBeenLastCalledWith(expectedPadData);
expect(await client.getPad({ padId: createPadData.id })).toEqual(pick(expectedPadData, ["id", "name", "description"]));
});
});
test("Rename pad", async () => {
const client = await openClient();
const onPadData = vi.fn();
client.on("padData", onPadData);
await createTemporaryPad(client, {}, async (createPadData, padData) => {
const update = {
id: generateTestPadId(),
writeId: generateTestPadId(),
adminId: generateTestPadId()
} satisfies PadData<CRU.UPDATE>;
const updatedPadData = await client.editPad(update);
const expectedPadData: PadData & { writable: Writable } = {
...padData,
...update
};
expect(updatedPadData).toEqual(expectedPadData);
expect(client.padData).toEqual(expectedPadData);
expect(onPadData).toHaveBeenLastCalledWith(expectedPadData);
expect(await client.getPad({ padId: createPadData.id })).toBeNull();
expect(await client.getPad({ padId: createPadData.writeId })).toBeNull();
expect(await client.getPad({ padId: createPadData.adminId })).toBeNull();
expect(await client.getPad({ padId: update.id })).toEqual(pick(expectedPadData, ["id", "name", "description"]));
expect(await client.getPad({ padId: update.writeId })).toEqual(pick(expectedPadData, ["id", "name", "description"]));
expect(await client.getPad({ padId: update.adminId })).toEqual(pick(expectedPadData, ["id", "name", "description"]));
});
});
test("Rename pad (ID already taken)", async () => {
const client1 = await openClient();
const client2 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData1) => {
await createTemporaryPad(client2, {}, async (createPadData2) => {
await expect(async () => {
await client2.editPad({
id: createPadData1.id
});
}).rejects.toThrowError("already taken");
await expect(async () => {
await client2.editPad({
writeId: createPadData1.id
});
}).rejects.toThrowError("already taken");
await expect(async () => {
await client2.editPad({
adminId: createPadData1.id
});
}).rejects.toThrowError("already taken");
});
});
});
test("Rename pad (duplicate IDs)", async () => {
const client = await openClient();
await createTemporaryPad(client, {}, async () => {
const newId = generateTestPadId();
await expect(async () => {
await client.editPad({
id: newId,
writeId: newId
});
}).rejects.toThrowError("cannot be the same");
await expect(async () => {
await client.editPad({
id: newId,
adminId: newId
});
}).rejects.toThrowError("cannot be the same");
await expect(async () => {
await client.editPad({
writeId: newId,
adminId: newId
});
}).rejects.toThrowError("cannot be the same");
});
});
test("Delete pad", async () => {
const client = await openClient();
const padData = getTemporaryPadData({});
await createTemporaryPad(client, padData, async () => {
expect(client.deleted).toBe(false);
const result = await client.getPad({ padId: padData.id });
expect(result).toBeTruthy();
});
expect(client.deleted).toBe(true);
const result = await client.getPad({ padId: padData.id });
expect(result).toBeNull();
});
test("Open existing pad", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(createPadData.id);
expect(client2.padData).toEqual({ ...padData, writeId: undefined, adminId: undefined, writable: Writable.READ });
const client3 = await openClient(createPadData.writeId);
expect(client3.padData).toEqual({ ...padData, adminId: undefined, writable: Writable.WRITE });
const client4 = await openClient(createPadData.adminId);
expect(client4.padData).toEqual({ ...padData, writable: Writable.ADMIN });
const client5 = await openClient();
const onPadData5 = vi.fn();
client5.on("padData", onPadData5);
const result5 = await client5.setPadId(createPadData.id);
expect(result5.padData![0]).toEqual({ ...padData, writeId: undefined, adminId: undefined, writable: Writable.READ });
expect(onPadData5).toBeCalledTimes(1);
expect(onPadData5).toBeCalledWith({ ...padData, writeId: undefined, adminId: undefined, writable: Writable.READ });
expect(client5.padData).toEqual({ ...padData, writeId: undefined, adminId: undefined, writable: Writable.READ });
const client6 = await openClient();
const onPadData6 = vi.fn();
client6.on("padData", onPadData6);
const result6 = await client6.setPadId(createPadData.writeId);
expect(result6.padData![0]).toEqual({ ...padData, adminId: undefined, writable: Writable.WRITE });
expect(onPadData6).toBeCalledTimes(1);
expect(onPadData6).toBeCalledWith({ ...padData, adminId: undefined, writable: Writable.WRITE });
expect(client6.padData).toEqual({ ...padData, adminId: undefined, writable: Writable.WRITE });
const client7 = await openClient();
const onPadData7 = vi.fn();
client7.on("padData", onPadData7);
const result7 = await client7.setPadId(createPadData.adminId);
expect(result7.padData![0]).toEqual({ ...padData, writable: Writable.ADMIN });
expect(onPadData7).toBeCalledTimes(1);
expect(onPadData7).toBeCalledWith({ ...padData, writable: Writable.ADMIN });
expect(client7.padData).toEqual({ ...padData, writable: Writable.ADMIN });
});
});
test("Open non-existing pad", async () => {
const id = generateTestPadId();
const client1 = new Client(getFacilMapUrl(), id, { reconnection: false });
await expect(new Promise<any>((resolve, reject) => {
client1.on("padData", resolve);
client1.on("serverError", reject);
client1.on("connect_error", reject);
})).rejects.toThrowError("does not exist");
expect(client1.serverError?.message).toMatch("does not exist");
const client2 = await openClient();
await expect(async () => {
await client2.setPadId(id);
}).rejects.toThrowError("does not exist");
expect(client2.serverError?.message).toMatch("does not exist");
});
test("Find pads", async () => {
const uniqueId = generateTestPadId();
const client = await openClient();
await createTemporaryPad(client, {
name: `Test ${uniqueId} pad`,
searchEngines: true
}, async (createPadData) => {
const expectedFound: PagedResults<FindPadsResult> = {
results: [{ id: createPadData.id, name: `Test ${uniqueId} pad`, description: "" }],
totalLength: 1
};
const expectedNotFound: PagedResults<FindPadsResult> = {
results: [],
totalLength: 0
};
expect(await client.findPads({ query: `Test ${uniqueId} pad` })).toEqual(expectedFound);
expect(await client.findPads({ query: `test ${uniqueId} pad` })).toEqual(expectedFound);
expect(await client.findPads({ query: `Te?t ${uniqueId} pad` })).toEqual(expectedFound);
expect(await client.findPads({ query: `Te* ${uniqueId} pad` })).toEqual(expectedFound);
expect(await client.findPads({ query: uniqueId })).toEqual(expectedFound);
expect(await client.findPads({ query: `Te ${uniqueId} pad` })).toEqual(expectedNotFound);
expect(await client.findPads({ query: `Te? ${uniqueId} pad` })).toEqual(expectedNotFound);
expect(await client.findPads({ query: `Te% ${uniqueId} pad` })).toEqual(expectedNotFound);
await client.editPad({ searchEngines: false });
expect(await client.findPads({ query: `Test ${uniqueId} pad` })).toEqual(expectedNotFound);
});
});
test("Socket v1 pad name", async () => {
const socket = await openSocket(SocketVersion.V1);
const onPadData = vi.fn();
socket.on("padData", onPadData);
try {
const padData = getTemporaryPadData({});
const result = await emit(socket, "createPad", padData);
expect(result.padData![0].name).toBe("Unnamed map");
const result2 = await emit(socket, "editPad", { name: "New name" });
expect(result2.name).toBe("New name");
expect(onPadData).toBeCalledTimes(1);
expect(onPadData.mock.calls[0][0].name).toBe("New name");
const result3 = await emit(socket, "editPad", { name: "" });
expect(result3.name).toBe("Unnamed map");
expect(onPadData).toBeCalledTimes(2);
expect(onPadData.mock.calls[1][0].name).toBe("Unnamed map");
} finally {
await emit(socket, "deletePad", undefined);
}
});

Wyświetl plik

@ -0,0 +1,73 @@
import { expect, test, vi } from "vitest";
import { createTemporaryMapV2, openClient, retry } from "../utils";
import { SocketVersion, type Line } from "facilmap-types";
test("Socket v1 line name", async () => {
// client1: Creates the line and has it in its bbox
// client2: Has the line in its bbox
// client3: Does not have the line in its bbox
const client1 = await openClient(undefined, SocketVersion.V1);
await createTemporaryMapV2(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.adminId, SocketVersion.V1);
const client3 = await openClient(mapData.adminId, SocketVersion.V1);
const onLine1 = vi.fn();
client1.on("line", onLine1);
const onLine2 = vi.fn();
client2.on("line", onLine2);
const onLine3 = vi.fn();
client3.on("line", onLine3);
const lineType = Object.values(client1.types).find((t) => t.type === "line")!;
await client1.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client2.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client3.updateBbox({ top: 5, bottom: 0, left: 0, right: 5, zoom: 1 });
const line = await client1.addLine({
routePoints: [
{ lat: 6, lon: 6 },
{ lat: 14, lon: 14 }
],
typeId: lineType.id
});
const expectedLine = {
id: line.id,
routePoints: [
{ lat: 6, lon: 6 },
{ lat: 14, lon: 14 }
],
typeId: lineType.id,
padId: mapData.id,
name: "Untitled line",
mode: "",
colour: "0000ff",
width: 4,
stroke: "",
distance: 1247.95,
time: null,
ascent: null,
descent: null,
extraInfo: null,
top: 14,
right: 14,
bottom: 6,
left: 6,
data: {}
} satisfies Line;
expect(line).toEqual(expectedLine);
await retry(() => {
expect(onLine1).toHaveBeenCalledTimes(1);
expect(onLine2).toHaveBeenCalledTimes(1);
expect(onLine3).toHaveBeenCalledTimes(1);
});
expect(onLine1).toHaveBeenCalledWith(expectedLine);
expect(onLine2).toHaveBeenCalledWith(expectedLine);
expect(onLine3).toHaveBeenCalledWith(expectedLine);
});
});

Wyświetl plik

@ -0,0 +1,62 @@
import { expect, test, vi } from "vitest";
import { createTemporaryMapV2, openClient, retry } from "../utils";
import { SocketVersion } from "facilmap-types";
import type { LegacyV2Marker } from "facilmap-types";
test("Socket v1 marker name", async () => {
// client1: Creates the marker and has it in its bbox
// client2: Has the marker in its bbox
// client3: Does not have the marker in its bbox
const client1 = await openClient(undefined, SocketVersion.V1);
await createTemporaryMapV2(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.adminId, SocketVersion.V1);
const client3 = await openClient(mapData.adminId, SocketVersion.V1);
const onMarker1 = vi.fn();
client1.on("marker", onMarker1);
const onMarker2 = vi.fn();
client2.on("marker", onMarker2);
const onMarker3 = vi.fn();
client3.on("marker", onMarker3);
const markerType = Object.values(client1.types).find((t) => t.type === "marker")!;
await client1.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client2.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client3.updateBbox({ top: 5, bottom: 0, left: 0, right: 5, zoom: 1 });
const marker = await client1.addMarker({
lat: 10,
lon: 10,
typeId: markerType.id,
ele: null as any
});
const expectedMarker = {
id: marker.id,
lat: 10,
lon: 10,
typeId: markerType.id,
padId: mapData.id,
name: "Untitled marker",
colour: "ff0000",
size: 30,
symbol: "",
shape: "",
data: {},
ele: null
} satisfies LegacyV2Marker;
expect(marker).toEqual(expectedMarker);
await retry(() => {
expect(onMarker1).toHaveBeenCalledTimes(1);
expect(onMarker2).toHaveBeenCalledTimes(1);
expect(onMarker3).toHaveBeenCalledTimes(0);
});
expect(onMarker1).toHaveBeenCalledWith(expectedMarker);
expect(onMarker2).toHaveBeenCalledWith(expectedMarker);
});
});

Wyświetl plik

@ -0,0 +1,25 @@
import { expect, test, vi } from "vitest";
import { createTemporaryMapV2, openClient } from "../utils";
import { SocketVersion } from "facilmap-types";
test("Socket v1 pad name", async () => {
const client = await openClient(undefined, SocketVersion.V1);
const onPadData = vi.fn();
client.on("padData", onPadData);
await createTemporaryMapV2(client, {}, async (createMapData, mapData) => {
expect(onPadData).toBeCalledTimes(1);
expect(onPadData.mock.calls[0][0].name).toBe("Unnamed map");
const result2 = await client.editPad({ name: "New name" });
expect(result2.name).toBe("New name");
expect(onPadData).toBeCalledTimes(2);
expect(onPadData.mock.calls[1][0].name).toBe("New name");
const result3 = await client.editPad({ name: "" });
expect(result3.name).toBe("Unnamed map");
expect(onPadData).toBeCalledTimes(3);
expect(onPadData.mock.calls[2][0].name).toBe("Unnamed map");
});
});

Wyświetl plik

@ -0,0 +1,162 @@
import { expect, test, vi } from "vitest";
import { createTemporaryMapV2, openClient, retry } from "../utils";
import { CRU, SocketVersion, type ID, type LegacyV2Type } from "facilmap-types";
test("Marker update history (socket v2)", async () => {
const client1 = await openClient(undefined, SocketVersion.V2);
await createTemporaryMapV2(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.adminId);
const markerType = Object.values(client1.types).find((t) => t.type === "marker")!;
const createdMarker = await client1.addMarker({
lat: 10,
lon: 10,
typeId: markerType.id,
ele: null,
symbol: "icon1"
});
await Promise.all([
client1.listenToHistory(),
client2.listenToHistory()
]);
const onHistory1 = vi.fn();
client1.on("history", onHistory1);
const onHistory2 = vi.fn();
client2.on("history", onHistory2);
const newData = {
id: createdMarker.id,
symbol: "icon2"
};
await client1.editMarker(newData);
await retry(() => {
expect(onHistory1).toHaveBeenCalledTimes(1);
expect(onHistory2).toHaveBeenCalledTimes(1);
});
expect(onHistory1).toHaveBeenCalledWith(expect.objectContaining({
objectBefore: expect.objectContaining({
symbol: "icon1"
}),
objectAfter: expect.objectContaining({
symbol: "icon2"
})
}));
expect(onHistory2).toHaveBeenCalledWith(expect.objectContaining({
objectBefore: expect.objectContaining({
icon: "icon1"
}),
objectAfter: expect.objectContaining({
icon: "icon2"
})
}));
});
});
test("Type update history (socket v2)", async () => {
const client1 = await openClient(undefined, SocketVersion.V2);
await createTemporaryMapV2(client1, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const client2 = await openClient(mapData.adminId);
const createdType = await client1.addType({
name: "Test type",
type: "marker",
defaultSymbol: "icon1",
symbolFixed: false,
fields: [{
type: "dropdown",
name: "Test",
controlSymbol: false,
options: [{
value: "Test",
symbol: "test1"
}]
}]
});
await Promise.all([
client1.listenToHistory(),
client2.listenToHistory()
]);
const onHistory1 = vi.fn();
client1.on("history", onHistory1);
const onHistory2 = vi.fn();
client2.on("history", onHistory2);
const update = {
id: createdType.id,
defaultSymbol: "icon2",
symbolFixed: true,
fields: [{
type: "dropdown",
name: "Test",
controlSymbol: true,
options: [{
value: "Test",
symbol: "test2"
}]
}]
} satisfies LegacyV2Type<CRU.UPDATE> & { id: ID };
await client1.editType(update);
await retry(() => {
expect(onHistory1).toHaveBeenCalledTimes(1);
expect(onHistory2).toHaveBeenCalledTimes(1);
});
expect(onHistory1).toHaveBeenCalledWith(expect.objectContaining({
objectBefore: expect.objectContaining({
defaultSymbol: "icon1",
symbolFixed: false,
fields: [expect.objectContaining({
controlSymbol: false,
options: [expect.objectContaining({
symbol: "test1"
})]
})]
}),
objectAfter: expect.objectContaining({
defaultSymbol: "icon2",
symbolFixed: true,
fields: [expect.objectContaining({
controlSymbol: true,
options: [expect.objectContaining({
symbol: "test2"
})]
})]
})
}));
expect(onHistory2).toHaveBeenCalledWith(expect.objectContaining({
objectBefore: expect.objectContaining({
defaultIcon: "icon1",
iconFixed: false,
fields: [expect.objectContaining({
controlIcon: false,
options: [expect.objectContaining({
icon: "test1"
})]
})]
}),
objectAfter: expect.objectContaining({
defaultIcon: "icon2",
iconFixed: true,
fields: [expect.objectContaining({
controlIcon: true,
options: [expect.objectContaining({
icon: "test2"
})]
})]
})
}));
});
});

Wyświetl plik

@ -0,0 +1,169 @@
import { expect, test, vi } from "vitest";
import { createTemporaryMapV2, openClient, retry } from "../utils";
import { SocketVersion } from "facilmap-types";
import { cloneDeep } from "lodash-es";
test("Create marker (Socket v2)", async () => {
// client1: Creates the marker and has it in its bbox
// client2: Has the marker in its bbox
// client3: Opens the map later
// client4: Opens the map later (Socket v3)
const client1 = await openClient(undefined, SocketVersion.V2);
await createTemporaryMapV2(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.id, SocketVersion.V2);
const onMarker1 = vi.fn();
client1.on("marker", onMarker1);
const onMarker2 = vi.fn();
client2.on("marker", onMarker2);
const markerType = Object.values(client1.types).find((t) => t.type === "marker")!;
await client1.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client2.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
const marker = await client1.addMarker({
lat: 10,
lon: 10,
typeId: markerType.id,
ele: null,
symbol: "test"
});
const expectedMarker = {
symbol: "test"
};
expect(marker).toMatchObject(expectedMarker);
await retry(() => {
expect(onMarker1).toHaveBeenCalledTimes(1);
expect(onMarker2).toHaveBeenCalledTimes(1);
});
expect(onMarker1).toHaveBeenCalledWith(expect.objectContaining(expectedMarker));
expect(onMarker2).toHaveBeenCalledWith(expect.objectContaining(expectedMarker));
const expectedMarkerRecord = { [marker.id]: expectedMarker };
expect(cloneDeep(client1.markers)).toMatchObject(expectedMarkerRecord);
expect(cloneDeep(client2.markers)).toMatchObject(expectedMarkerRecord);
const client3 = await openClient(mapData.id, SocketVersion.V2);
await client3.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
expect(cloneDeep(client3.markers)).toMatchObject(expectedMarkerRecord);
const client4 = await openClient(mapData.id, SocketVersion.V3);
await client4.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
expect(cloneDeep(client4.markers)).toMatchObject({ [marker.id]: { icon: "test" } });
});
});
test("Edit marker (socket v2)", async () => {
// client1: Creates the marker and has it in its bbox
// client2: Has the marker in its bbox
// client3: Has the marker in its bbox (Socket v3)
const client1 = await openClient(undefined, SocketVersion.V2);
await createTemporaryMapV2(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.id, SocketVersion.V2);
const client3 = await openClient(mapData.id, SocketVersion.V3);
const markerType = Object.values(client1.types).find((t) => t.type === "marker")!;
const createdMarker = await client1.addMarker({
lat: 10,
lon: 10,
typeId: markerType.id,
ele: null
});
await client1.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client2.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client3.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
const onMarker1 = vi.fn();
client1.on("marker", onMarker1);
const onMarker2 = vi.fn();
client2.on("marker", onMarker2);
const onMarker3 = vi.fn();
client3.on("marker", onMarker3);
const newData = {
id: createdMarker.id,
symbol: "icon"
};
const marker = await client1.editMarker(newData);
expect(marker).toMatchObject({ symbol: "icon" });
await retry(() => {
expect(onMarker1).toHaveBeenCalledTimes(1);
expect(onMarker2).toHaveBeenCalledTimes(1);
expect(onMarker3).toHaveBeenCalledTimes(1);
});
expect(onMarker1).toHaveBeenCalledWith(expect.objectContaining({ symbol: "icon" }));
expect(onMarker2).toHaveBeenCalledWith(expect.objectContaining({ symbol: "icon" }));
expect(onMarker3).toHaveBeenCalledWith(expect.objectContaining({ icon: "icon" }));
});
});
test("Delete marker (socket v2)", async () => {
const client = await openClient(undefined, SocketVersion.V2);
await createTemporaryMapV2(client, {}, async (createMapData, mapData) => {
const markerType = Object.values(client.types).find((t) => t.type === "marker")!;
const createdMarker = await client.addMarker({
lat: 10,
lon: 10,
typeId: markerType.id,
ele: null,
symbol: "icon"
});
const deletedMarker = await client.deleteMarker({ id: createdMarker.id });
expect(deletedMarker).toEqual(createdMarker);
});
});
test("Get marker (socket v2)", async () => {
const client = await openClient(undefined, SocketVersion.V2);
await createTemporaryMapV2(client, {}, async (createMapData, mapData) => {
const markerType = Object.values(client.types).find((t) => t.type === "marker")!;
const marker = await client.addMarker({
lat: 10,
lon: 10,
typeId: markerType.id,
ele: null,
symbol: "icon"
});
expect(await client.getMarker({ id: marker.id })).toMatchObject({ symbol: "icon" });
});
});
test("Find marker", async () => {
const client = await openClient(undefined, SocketVersion.V2);
await createTemporaryMapV2(client, {}, async (createMapData, mapData) => {
const markerType = Object.values(client.types).find((t) => t.type === "marker")!;
await client.addMarker({
name: "Marker test",
lat: 10,
lon: 10,
typeId: markerType.id,
symbol: "icon",
ele: null
});
expect(await client.findOnMap({ query: "Test" })).toMatchObject([{ symbol: "icon" }]);
});
});

Wyświetl plik

@ -0,0 +1,169 @@
import { expect, test, vi } from "vitest";
import { createTemporaryMapV2, openClient, retry } from "../utils";
import { CRU, SocketVersion, type ID, type LegacyV2Type } from "facilmap-types";
import { cloneDeep } from "lodash-es";
test("Create type (socket v2)", async () => {
const client1 = await openClient(undefined, SocketVersion.V2);
await createTemporaryMapV2(client1, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const client2 = await openClient(mapData.id, SocketVersion.V2);
const onType1 = vi.fn();
client1.on("type", onType1);
const onType2 = vi.fn();
client2.on("type", onType2);
const type = {
name: "Test type",
type: "marker",
defaultSymbol: "icon",
symbolFixed: true,
fields: [{
type: "dropdown",
name: "Test",
controlSymbol: true,
options: [{
value: "Test",
symbol: "test"
}]
}]
} satisfies LegacyV2Type<CRU.CREATE>;
const typeResult = await client1.addType(type);
expect(typeResult).toMatchObject(type);
await retry(async () => {
expect(onType1).toBeCalledTimes(1);
expect(onType2).toBeCalledTimes(1);
});
expect(onType1).toHaveBeenNthCalledWith(1, expect.objectContaining(type));
expect(cloneDeep(client1.types)).toEqual({
[typeResult.id]: expect.objectContaining(type)
});
expect(onType2).toHaveBeenNthCalledWith(1, expect.objectContaining(type));
expect(cloneDeep(client2.types)).toEqual({
[typeResult.id]: expect.objectContaining(type)
});
const client3 = await openClient(mapData.id, SocketVersion.V2);
expect(cloneDeep(client3.types)).toEqual({
[typeResult.id]: expect.objectContaining(type)
});
const client4 = await openClient(mapData.id);
expect(cloneDeep(client4.types)).toEqual({
[typeResult.id]: expect.objectContaining({
defaultIcon: "icon",
iconFixed: true,
fields: [expect.objectContaining({
controlIcon: true,
options: [expect.objectContaining({
value: "Test",
icon: "test"
})]
})]
})
});
});
});
test("Update type (socket v2)", async () => {
const client1 = await openClient(undefined, SocketVersion.V2);
const onType = vi.fn();
client1.on("type", onType);
await createTemporaryMapV2(client1, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const createdType = await client1.addType({
name: "Test type",
type: "marker"
});
const client2 = await openClient(mapData.id, SocketVersion.V2);
const onType1 = vi.fn();
client1.on("type", onType1);
const onType2 = vi.fn();
client2.on("type", onType2);
const update = {
id: createdType.id,
defaultSymbol: "icon",
symbolFixed: true,
fields: [{
type: "dropdown",
name: "Test",
controlSymbol: true,
options: [{
value: "Test",
symbol: "test"
}]
}]
} satisfies LegacyV2Type<CRU.UPDATE> & { id: ID };
const typeResult = await client1.editType(update);
expect(typeResult).toMatchObject(update);
await retry(async () => {
expect(onType1).toBeCalledTimes(1);
expect(onType2).toBeCalledTimes(1);
});
expect(onType1).toHaveBeenNthCalledWith(1, expect.objectContaining(update));
expect(cloneDeep(client1.types)).toEqual({
[createdType.id]: expect.objectContaining(update)
});
expect(onType2).toHaveBeenNthCalledWith(1, expect.objectContaining(update));
expect(cloneDeep(client2.types)).toEqual({
[createdType.id]: expect.objectContaining(update)
});
const client3 = await openClient(mapData.id);
expect(cloneDeep(client3.types)).toEqual({
[createdType.id]: expect.objectContaining({
defaultIcon: "icon",
iconFixed: true,
fields: [expect.objectContaining({
controlIcon: true,
options: [expect.objectContaining({
icon: "test"
})]
})]
})
});
});
});
test("Delete type (socket v2)", async () => {
const client = await openClient(undefined, SocketVersion.V2);
await createTemporaryMapV2(client, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const type = await client.addType({
name: "Test type",
type: "marker",
defaultSymbol: "icon",
symbolFixed: true,
fields: [{
type: "dropdown",
name: "Test",
controlSymbol: true,
options: [{
value: "Test",
symbol: "test"
}]
}]
});
const deletedType = await client.deleteType({ id: type.id });
expect(deletedType).toEqual(type);
});
});

Wyświetl plik

@ -0,0 +1,22 @@
// listenToHistory returns all past history events and transmits all future history events
// stopListeningToHistory stops transmitting future history events
// revertHistoryEntry reverts a marker creation
// revertHistoryEntry reverts a marker update
// revertHistoryEntry reverts a marker deletion, updating the ID in the history
// revertHistoryEntry reverts a line creation
// revertHistoryEntry reverts a line update
// revertHistoryEntry reverts a line deletion, updating the ID in the history
// revertHistoryEntry reverts a view creation
// revertHistoryEntry reverts a view update
// revertHistoryEntry reverts a view deletion, updating the ID in the history
// revertHistoryEntry cannot revert view creation in non-admin mode
// revertHistoryEntry cannot revert view update in non-admin mode
// revertHistoryEntry cannot revert view deletion in non-admin mode
// revertHistoryEntry reverts a type creation
// revertHistoryEntry reverts a type update
// revertHistoryEntry reverts a type deletion, updating the ID in the history
// revertHistoryEntry cannot revert type creation in non-admin mode
// revertHistoryEntry cannot revert type update in non-admin mode
// revertHistoryEntry cannot revert type deletion in non-admin mode
// revertHistoryEntry reverts a pad data update
// revertHistoryEntry cannot revert a pad data update in non-admin mode

Wyświetl plik

@ -1,6 +1,6 @@
import { expect, test, vi } from "vitest";
import { createTemporaryPad, emit, getTemporaryPadData, openClient, openSocket, retry } from "./utils";
import { SocketVersion, CRU, type Line, type LinePointsEvent, type FindOnMapLine, type ID } from "facilmap-types";
import { createTemporaryMap, openClient, retry } from "../utils";
import { CRU, type Line, type LinePointsEvent, type FindOnMapLine, type ID } from "facilmap-types";
import type { LineWithTrackPoints } from "facilmap-client";
import { cloneDeep, omit } from "lodash-es";
@ -11,9 +11,9 @@ test("Create line (using default values)", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(padData.id);
const client3 = await openClient(padData.id);
await createTemporaryMap(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.id);
const client3 = await openClient(mapData.id);
const onLine1 = vi.fn();
client1.on("line", onLine1);
@ -49,7 +49,7 @@ test("Create line (using default values)", async () => {
{ lat: 14, lon: 14 }
],
typeId: lineType.id,
padId: padData.id,
padId: mapData.id,
name: "",
mode: "",
colour: "0000ff",
@ -118,7 +118,7 @@ test("Create line (using custom values)", async () => {
const client = await openClient();
await client.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await createTemporaryPad(client, {}, async (createPadData, padData) => {
await createTemporaryMap(client, {}, async (createMapData, mapData) => {
const lineType = Object.values(client.types).find((t) => t.type === "line")!;
const data: Line<CRU.CREATE> = {
@ -146,7 +146,7 @@ test("Create line (using custom values)", async () => {
const expectedLine = {
id: line.id,
padId: padData.id,
padId: mapData.id,
...omit(data, ["trackPoints"]),
top: 14,
right: 14,
@ -185,9 +185,9 @@ test("Edit line", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(padData.id);
const client3 = await openClient(padData.id);
await createTemporaryMap(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.id);
const client3 = await openClient(mapData.id);
const lineType = Object.values(client1.types).find((t) => t.type === "line")!;
@ -245,7 +245,7 @@ test("Edit line", async () => {
const line = await client1.editLine(newData);
const expectedLine = {
padId: padData.id,
padId: mapData.id,
...omit(newData, ["trackPoints"]),
top: 14,
right: 14,
@ -314,9 +314,9 @@ test("Delete line", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(padData.id);
const client3 = await openClient(padData.id);
await createTemporaryMap(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.id);
const client3 = await openClient(mapData.id);
const lineType = Object.values(client1.types).find((t) => t.type === "line")!;
@ -363,8 +363,8 @@ test("Delete line", async () => {
test("Find line", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(padData.id);
await createTemporaryMap(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.id);
const lineType = Object.values(client1.types).find((t) => t.type === "line")!;
@ -399,7 +399,7 @@ test("Find line", async () => {
test("Try to create line with marker type", async () => {
const client = await openClient();
await createTemporaryPad(client, {}, async (createPadData) => {
await createTemporaryMap(client, {}, async (createMapData) => {
const lineType = Object.values(client.types).find((t) => t.type === "marker")!;
await expect(async () => {
@ -412,7 +412,7 @@ test("Try to create line with marker type", async () => {
});
}).rejects.toThrowError("Cannot use marker type for line");
const client3 = await openClient(createPadData.adminId);
const client3 = await openClient(createMapData.adminId);
await client3.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
expect(cloneDeep(client3.lines)).toEqual({});
});
@ -421,7 +421,7 @@ test("Try to create line with marker type", async () => {
test("Try to update line with line type", async () => {
const client = await openClient();
await createTemporaryPad(client, {}, async (createPadData) => {
await createTemporaryMap(client, {}, async (createMapData) => {
const markerType = Object.values(client.types).find((t) => t.type === "marker")!;
const lineType = Object.values(client.types).find((t) => t.type === "line")!;
@ -440,7 +440,7 @@ test("Try to update line with line type", async () => {
});
}).rejects.toThrowError("Cannot use marker type for line");
const client3 = await openClient(createPadData.adminId);
const client3 = await openClient(createMapData.adminId);
expect(cloneDeep(client3.lines)).toEqual({
[line.id]: {
...line,
@ -452,12 +452,12 @@ test("Try to update line with line type", async () => {
});
});
test("Try to create line with line type from other pad", async () => {
test("Try to create line with line type from other map", async () => {
const client1 = await openClient();
const client2 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData) => {
await createTemporaryPad(client2, {}, async () => {
await createTemporaryMap(client1, {}, async (createMapData) => {
await createTemporaryMap(client2, {}, async () => {
const lineType2 = Object.values(client2.types).find((t) => t.type === "line")!;
await expect(async () => {
@ -470,19 +470,19 @@ test("Try to create line with line type from other pad", async () => {
});
}).rejects.toThrowError("could not be found");
const client3 = await openClient(createPadData.adminId);
const client3 = await openClient(createMapData.adminId);
await client3.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
expect(cloneDeep(client3.lines)).toEqual({});
});
});
});
test("Try to update line with line type from other pad", async () => {
test("Try to update line with line type from other map", async () => {
const client1 = await openClient();
const client2 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData) => {
await createTemporaryPad(client2, {}, async () => {
await createTemporaryMap(client1, {}, async (createMapData) => {
await createTemporaryMap(client2, {}, async () => {
const lineType1 = Object.values(client1.types).find((t) => t.type === "line")!;
const lineType2 = Object.values(client2.types).find((t) => t.type === "line")!;
@ -501,7 +501,7 @@ test("Try to update line with line type from other pad", async () => {
});
}).rejects.toThrowError("could not be found");
const client3 = await openClient(createPadData.adminId);
const client3 = await openClient(createMapData.adminId);
await client3.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
expect(cloneDeep(client3.lines)).toEqual({
[line.id]: {
@ -517,86 +517,10 @@ test("Try to update line with line type from other pad", async () => {
});
});
test("Socket v1 line name", async () => {
// socket1: Creates the line and has it in its bbox
// socket2: Has the line in its bbox
// socket3: Does not have the line in its bbox
const socket1 = await openSocket(SocketVersion.V1);
const socket2 = await openSocket(SocketVersion.V1);
const socket3 = await openSocket(SocketVersion.V1);
const onLine1 = vi.fn();
socket1.on("line", onLine1);
const onLine2 = vi.fn();
socket2.on("line", onLine2);
const onLine3 = vi.fn();
socket3.on("line", onLine3);
try {
const padData = getTemporaryPadData({});
const padResult = await emit(socket1, "createPad", padData);
await emit(socket2, "setPadId", padData.adminId);
await emit(socket3, "setPadId", padData.adminId);
const lineType = padResult.type!.find((t) => t.type === "line")!;
await emit(socket1, "updateBbox", { top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await emit(socket2, "updateBbox", { top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await emit(socket3, "updateBbox", { top: 5, bottom: 0, left: 0, right: 5, zoom: 1 });
const line = await emit(socket1, "addLine", {
routePoints: [
{ lat: 6, lon: 6 },
{ lat: 14, lon: 14 }
],
typeId: lineType.id
});
const expectedLine = {
id: line.id,
routePoints: [
{ lat: 6, lon: 6 },
{ lat: 14, lon: 14 }
],
typeId: lineType.id,
padId: padData.id,
name: "Untitled line",
mode: "",
colour: "0000ff",
width: 4,
stroke: "",
distance: 1247.95,
time: null,
ascent: null,
descent: null,
extraInfo: null,
top: 14,
right: 14,
bottom: 6,
left: 6,
data: {}
} satisfies Line;
expect(line).toEqual(expectedLine);
await retry(() => {
expect(onLine1).toHaveBeenCalledTimes(1);
expect(onLine2).toHaveBeenCalledTimes(1);
expect(onLine3).toHaveBeenCalledTimes(1);
});
expect(onLine1).toHaveBeenCalledWith(expectedLine);
expect(onLine2).toHaveBeenCalledWith(expectedLine);
expect(onLine3).toHaveBeenCalledWith(expectedLine);
} finally {
await emit(socket1, "deletePad", undefined);
}
});
test("Export line", async () => {
const client = await openClient();
await createTemporaryPad(client, {}, async (createPadData, padData) => {
await createTemporaryMap(client, {}, async (createMapData, mapData) => {
const lineType = Object.values(client.types).find((t) => t.type === "line")!;
const line = await client.addLine({
@ -618,7 +542,7 @@ test("Export line", async () => {
</extensions>
</metadata>
<extensions>
<osmand:color>#0000ff</osmand:color>
<osmand:color>#aa0000ff</osmand:color>
<osmand:width>4</osmand:width>
</extensions>
<trk>
@ -642,7 +566,7 @@ test("Export line", async () => {
</extensions>
</metadata>
<extensions>
<osmand:color>#0000ff</osmand:color>
<osmand:color>#aa0000ff</osmand:color>
<osmand:width>4</osmand:width>
</extensions>
<rte>
@ -658,7 +582,7 @@ test("Export line", async () => {
test("Export line (track)", async () => {
const client = await openClient();
await createTemporaryPad(client, {}, async (createPadData, padData) => {
await createTemporaryMap(client, {}, async (createMapData, mapData) => {
const lineType = Object.values(client.types).find((t) => t.type === "line")!;
const line = await client.addLine({
@ -693,7 +617,7 @@ test("Export line (track)", async () => {
</extensions>
</metadata>
<extensions>
<osmand:color>#00ff00</osmand:color>
<osmand:color>#aa00ff00</osmand:color>
<osmand:width>10</osmand:width>
</extensions>
<trk>
@ -718,7 +642,7 @@ test("Export line (track)", async () => {
</extensions>
</metadata>
<extensions>
<osmand:color>#00ff00</osmand:color>
<osmand:color>#aa00ff00</osmand:color>
<osmand:width>10</osmand:width>
</extensions>
<rte>

Wyświetl plik

@ -0,0 +1,358 @@
import { expect, test, vi } from "vitest";
import { createTemporaryMap, generateTestMapId, getFacilMapUrl, getTemporaryMapData, openClient } from "../utils";
import { Writable, type MapData, CRU, type FindMapsResult, type PagedResults, SocketVersion } from "facilmap-types";
import { pick } from "lodash-es";
import Client from "facilmap-client";
test("Create map (using default values)", async () => {
const client = await openClient();
const onMapData = vi.fn();
client.on("mapData", onMapData);
await createTemporaryMap(client, {}, async (createMapData, mapData) => {
const expectedMapData: MapData & { writable: Writable } = {
...createMapData,
name: "",
searchEngines: false,
description: "",
clusterMarkers: false,
legend1: "",
legend2: "",
defaultViewId: null,
defaultView: null,
writable: Writable.ADMIN
};
expect(client.mapId).toBe(createMapData.adminId);
expect(client.readonly).toBe(false);
expect(client.writable).toBe(Writable.ADMIN);
expect(client.serverError).toBe(undefined);
expect(mapData).toEqual(expectedMapData);
expect(client.mapData).toEqual(expectedMapData);
expect(onMapData).toBeCalledTimes(1);
expect(onMapData).toHaveBeenCalledWith(expectedMapData);
expect(await client.getMap({ mapId: createMapData.id })).toEqual(pick(expectedMapData, ["id", "name", "description"]));
});
});
test("Create map (using custom values)", async () => {
const client = await openClient();
const onMapData = vi.fn();
client.on("mapData", onMapData);
await createTemporaryMap(client, {
name: "Test map",
searchEngines: true,
description: "Test description",
clusterMarkers: true,
legend1: "Legend 1",
legend2: "Legend 1",
defaultViewId: null
}, async (createMapData, mapData) => {
const expectedMapData: MapData & { writable: Writable } = {
...createMapData,
defaultView: null,
writable: Writable.ADMIN
};
expect(client.mapId).toBe(createMapData.adminId);
expect(client.readonly).toBe(false);
expect(client.writable).toBe(Writable.ADMIN);
expect(client.serverError).toBe(undefined);
expect(mapData).toEqual(expectedMapData);
expect(client.mapData).toEqual(expectedMapData);
expect(onMapData).toBeCalledTimes(1);
expect(onMapData).toHaveBeenCalledWith(expectedMapData);
expect(await client.getMap({ mapId: createMapData.id })).toEqual(pick(expectedMapData, ["id", "name", "description"]));
});
});
test("Create map (ID already taken)", async () => {
const client1 = await openClient();
const client2 = await openClient();
await createTemporaryMap(client1, {}, async (createMapData1) => {
await expect(async () => {
await createTemporaryMap(client2, {
id: createMapData1.id
})
}).rejects.toThrowError("already taken");
await expect(async () => {
await createTemporaryMap(client2, {
writeId: createMapData1.id
})
}).rejects.toThrowError("already taken");
await expect(async () => {
await createTemporaryMap(client2, {
adminId: createMapData1.id
})
}).rejects.toThrowError("already taken");
});
});
test("Create map (duplicate IDs)", async () => {
const client = await openClient();
const newId = generateTestMapId();
await expect(async () => {
await createTemporaryMap(client, {
id: newId,
writeId: newId
});
}).rejects.toThrowError("have to be different");
await expect(async () => {
await createTemporaryMap(client, {
id: newId,
adminId: newId
});
}).rejects.toThrowError("have to be different");
await expect(async () => {
await createTemporaryMap(client, {
writeId: newId,
adminId: newId
});
}).rejects.toThrowError("have to be different");
});
test("Edit map", async () => {
const client = await openClient();
const onMapData = vi.fn();
client.on("mapData", onMapData);
await createTemporaryMap(client, {}, async (createMapData, mapData) => {
const update = {
name: "Test map",
searchEngines: true,
description: "Test description",
clusterMarkers: true,
legend1: "Legend 1",
legend2: "Legend 1"
} satisfies MapData<CRU.UPDATE>;
const updatedMapData = await client.editMap(update);
const expectedMapData: MapData & { writable: Writable } = {
...createMapData,
...update,
defaultViewId: null,
defaultView: null,
writable: Writable.ADMIN
};
expect(updatedMapData).toEqual(expectedMapData);
expect(client.mapData).toEqual(expectedMapData);
expect(onMapData).toHaveBeenLastCalledWith(expectedMapData);
expect(await client.getMap({ mapId: createMapData.id })).toEqual(pick(expectedMapData, ["id", "name", "description"]));
});
});
test("Rename map", async () => {
const client = await openClient();
const onMapData = vi.fn();
client.on("mapData", onMapData);
await createTemporaryMap(client, {}, async (createMapData, mapData) => {
const update = {
id: generateTestMapId(),
writeId: generateTestMapId(),
adminId: generateTestMapId()
} satisfies MapData<CRU.UPDATE>;
const updatedMapData = await client.editMap(update);
const expectedMapData: MapData & { writable: Writable } = {
...mapData,
...update
};
expect(updatedMapData).toEqual(expectedMapData);
expect(client.mapData).toEqual(expectedMapData);
expect(onMapData).toHaveBeenLastCalledWith(expectedMapData);
expect(await client.getMap({ mapId: createMapData.id })).toBeNull();
expect(await client.getMap({ mapId: createMapData.writeId })).toBeNull();
expect(await client.getMap({ mapId: createMapData.adminId })).toBeNull();
expect(await client.getMap({ mapId: update.id })).toEqual(pick(expectedMapData, ["id", "name", "description"]));
expect(await client.getMap({ mapId: update.writeId })).toEqual(pick(expectedMapData, ["id", "name", "description"]));
expect(await client.getMap({ mapId: update.adminId })).toEqual(pick(expectedMapData, ["id", "name", "description"]));
});
});
test("Rename map (ID already taken)", async () => {
const client1 = await openClient();
const client2 = await openClient();
await createTemporaryMap(client1, {}, async (createMapData1) => {
await createTemporaryMap(client2, {}, async (createMapData2) => {
await expect(async () => {
await client2.editMap({
id: createMapData1.id
});
}).rejects.toThrowError("already taken");
await expect(async () => {
await client2.editMap({
writeId: createMapData1.id
});
}).rejects.toThrowError("already taken");
await expect(async () => {
await client2.editMap({
adminId: createMapData1.id
});
}).rejects.toThrowError("already taken");
});
});
});
test("Rename map (duplicate IDs)", async () => {
const client = await openClient();
await createTemporaryMap(client, {}, async () => {
const newId = generateTestMapId();
await expect(async () => {
await client.editMap({
id: newId,
writeId: newId
});
}).rejects.toThrowError("cannot be the same");
await expect(async () => {
await client.editMap({
id: newId,
adminId: newId
});
}).rejects.toThrowError("cannot be the same");
await expect(async () => {
await client.editMap({
writeId: newId,
adminId: newId
});
}).rejects.toThrowError("cannot be the same");
});
});
test("Delete map", async () => {
const client = await openClient();
const mapData = getTemporaryMapData(SocketVersion.V3, {});
await createTemporaryMap(client, mapData, async () => {
expect(client.deleted).toBe(false);
const result = await client.getMap({ mapId: mapData.id });
expect(result).toBeTruthy();
});
expect(client.deleted).toBe(true);
const result = await client.getMap({ mapId: mapData.id });
expect(result).toBeNull();
});
test("Open existing map", async () => {
const client1 = await openClient();
await createTemporaryMap(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(createMapData.id);
expect(client2.mapData).toEqual({ ...mapData, writeId: undefined, adminId: undefined, writable: Writable.READ });
const client3 = await openClient(createMapData.writeId);
expect(client3.mapData).toEqual({ ...mapData, adminId: undefined, writable: Writable.WRITE });
const client4 = await openClient(createMapData.adminId);
expect(client4.mapData).toEqual({ ...mapData, writable: Writable.ADMIN });
const client5 = await openClient();
const onMapData5 = vi.fn();
client5.on("mapData", onMapData5);
const result5 = await client5.setMapId(createMapData.id);
expect(result5.mapData![0]).toEqual({ ...mapData, writeId: undefined, adminId: undefined, writable: Writable.READ });
expect(onMapData5).toBeCalledTimes(1);
expect(onMapData5).toBeCalledWith({ ...mapData, writeId: undefined, adminId: undefined, writable: Writable.READ });
expect(client5.mapData).toEqual({ ...mapData, writeId: undefined, adminId: undefined, writable: Writable.READ });
const client6 = await openClient();
const onMapData6 = vi.fn();
client6.on("mapData", onMapData6);
const result6 = await client6.setMapId(createMapData.writeId);
expect(result6.mapData![0]).toEqual({ ...mapData, adminId: undefined, writable: Writable.WRITE });
expect(onMapData6).toBeCalledTimes(1);
expect(onMapData6).toBeCalledWith({ ...mapData, adminId: undefined, writable: Writable.WRITE });
expect(client6.mapData).toEqual({ ...mapData, adminId: undefined, writable: Writable.WRITE });
const client7 = await openClient();
const onMapData7 = vi.fn();
client7.on("mapData", onMapData7);
const result7 = await client7.setMapId(createMapData.adminId);
expect(result7.mapData![0]).toEqual({ ...mapData, writable: Writable.ADMIN });
expect(onMapData7).toBeCalledTimes(1);
expect(onMapData7).toBeCalledWith({ ...mapData, writable: Writable.ADMIN });
expect(client7.mapData).toEqual({ ...mapData, writable: Writable.ADMIN });
});
});
test("Open non-existing map", async () => {
const id = generateTestMapId();
const client1 = new Client(getFacilMapUrl(), id, { reconnection: false });
await expect(new Promise<any>((resolve, reject) => {
client1.on("mapData", resolve);
client1.on("serverError", reject);
client1.on("connect_error", reject);
})).rejects.toThrowError("does not exist");
expect(client1.serverError?.message).toMatch("does not exist");
const client2 = await openClient();
await expect(async () => {
await client2.setMapId(id);
}).rejects.toThrowError("does not exist");
expect(client2.serverError?.message).toMatch("does not exist");
});
test("Find maps", async () => {
const uniqueId = generateTestMapId();
const client = await openClient();
await createTemporaryMap(client, {
name: `Test ${uniqueId} map`,
searchEngines: true
}, async (createMapData) => {
const expectedFound: PagedResults<FindMapsResult> = {
results: [{ id: createMapData.id, name: `Test ${uniqueId} map`, description: "" }],
totalLength: 1
};
const expectedNotFound: PagedResults<FindMapsResult> = {
results: [],
totalLength: 0
};
expect(await client.findMaps({ query: `Test ${uniqueId} map` })).toEqual(expectedFound);
expect(await client.findMaps({ query: `test ${uniqueId} map` })).toEqual(expectedFound);
expect(await client.findMaps({ query: `Te?t ${uniqueId} map` })).toEqual(expectedFound);
expect(await client.findMaps({ query: `Te* ${uniqueId} map` })).toEqual(expectedFound);
expect(await client.findMaps({ query: uniqueId })).toEqual(expectedFound);
expect(await client.findMaps({ query: `Te ${uniqueId} map` })).toEqual(expectedNotFound);
expect(await client.findMaps({ query: `Te? ${uniqueId} map` })).toEqual(expectedNotFound);
expect(await client.findMaps({ query: `Te% ${uniqueId} map` })).toEqual(expectedNotFound);
await client.editMap({ searchEngines: false });
expect(await client.findMaps({ query: `Test ${uniqueId} map` })).toEqual(expectedNotFound);
});
});

Wyświetl plik

@ -1,6 +1,6 @@
import { expect, test, vi } from "vitest";
import { createTemporaryPad, emit, getTemporaryPadData, openClient, openSocket, retry } from "./utils";
import { SocketVersion, CRU, type Marker, type FindOnMapMarker, type ID } from "facilmap-types";
import { createTemporaryMap, openClient, retry } from "../utils";
import { CRU, type Marker, type FindOnMapMarker, type ID } from "facilmap-types";
import { cloneDeep } from "lodash-es";
test("Create marker (using default values)", async () => {
@ -10,9 +10,9 @@ test("Create marker (using default values)", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(padData.id);
const client3 = await openClient(padData.id);
await createTemporaryMap(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.id);
const client3 = await openClient(mapData.id);
const onMarker1 = vi.fn();
client1.on("marker", onMarker1);
@ -39,11 +39,11 @@ test("Create marker (using default values)", async () => {
lat: 10,
lon: 10,
typeId: markerType.id,
padId: padData.id,
padId: mapData.id,
name: "",
colour: "ff0000",
size: 30,
symbol: "",
icon: "",
shape: "",
data: {},
ele: null
@ -70,7 +70,7 @@ test("Create marker (using default values)", async () => {
test("Create marker (using custom values)", async () => {
const client = await openClient();
await createTemporaryPad(client, {}, async (createPadData, padData) => {
await createTemporaryMap(client, {}, async (createMapData, mapData) => {
const markerType = Object.values(client.types).find((t) => t.type === "marker")!;
const data: Marker<CRU.CREATE> = {
@ -80,7 +80,7 @@ test("Create marker (using custom values)", async () => {
name: "Test marker",
colour: "0000ff",
size: 40,
symbol: "symbol",
icon: "icon",
shape: "shape",
data: {
test: "value"
@ -92,7 +92,7 @@ test("Create marker (using custom values)", async () => {
const expectedMarker = {
id: marker.id,
padId: padData.id,
padId: mapData.id,
...data
};
@ -107,16 +107,12 @@ test("Edit marker", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(padData.id);
const client3 = await openClient(padData.id);
await createTemporaryMap(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.id);
const client3 = await openClient(mapData.id);
const markerType = Object.values(client1.types).find((t) => t.type === "marker")!;
await client1.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client2.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client3.updateBbox({ top: 5, bottom: 0, left: 0, right: 5, zoom: 1 });
const createdMarker = await client1.addMarker({
lat: 10,
lon: 10,
@ -129,6 +125,10 @@ test("Edit marker", async () => {
name: "Second type"
});
await client1.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client2.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await client3.updateBbox({ top: 5, bottom: 0, left: 0, right: 5, zoom: 1 });
const onMarker1 = vi.fn();
client1.on("marker", onMarker1);
const onMarker2 = vi.fn();
@ -144,7 +144,7 @@ test("Edit marker", async () => {
name: "Test marker",
colour: "0000ff",
size: 40,
symbol: "symbol",
icon: "icon",
shape: "shape",
data: {
test: "value"
@ -154,7 +154,7 @@ test("Edit marker", async () => {
const marker = await client1.editMarker(newData);
const expectedMarker = {
padId: padData.id,
padId: mapData.id,
...newData
};
@ -183,9 +183,9 @@ test("Delete marker", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(padData.id);
const client3 = await openClient(padData.id);
await createTemporaryMap(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.id);
const client3 = await openClient(mapData.id);
const markerType = Object.values(client1.types).find((t) => t.type === "marker")!;
@ -231,8 +231,8 @@ test("Delete marker", async () => {
test("Get marker", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(padData.id);
await createTemporaryMap(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.id);
const markerType = Object.values(client1.types).find((t) => t.type === "marker")!;
@ -248,11 +248,11 @@ test("Get marker", async () => {
lat: 10,
lon: 10,
typeId: markerType.id,
padId: padData.id,
padId: mapData.id,
name: "",
colour: "ff0000",
size: 30,
symbol: "",
icon: "",
shape: "",
data: {},
ele: null
@ -265,8 +265,8 @@ test("Get marker", async () => {
test("Find marker", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData, padData) => {
const client2 = await openClient(padData.id);
await createTemporaryMap(client1, {}, async (createMapData, mapData) => {
const client2 = await openClient(mapData.id);
const markerType = Object.values(client1.types).find((t) => t.type === "marker")!;
@ -275,7 +275,7 @@ test("Find marker", async () => {
lat: 10,
lon: 10,
typeId: markerType.id,
symbol: "a",
icon: "a",
ele: null
});
@ -287,7 +287,7 @@ test("Find marker", async () => {
lon: 10,
typeId: markerType.id,
name: "Marker test",
symbol: "a"
icon: "a"
};
expect(await client2.findOnMap({ query: "Test" })).toEqual([{ ...expectedResult, similarity: 0.3333333333333333 }]);
@ -300,7 +300,7 @@ test("Find marker", async () => {
test("Try to create marker with line type", async () => {
const client = await openClient();
await createTemporaryPad(client, {}, async (createPadData) => {
await createTemporaryMap(client, {}, async (createMapData) => {
const lineType = Object.values(client.types).find((t) => t.type === "line")!;
await expect(async () => {
@ -311,7 +311,7 @@ test("Try to create marker with line type", async () => {
});
}).rejects.toThrowError("Cannot use line type for marker");
const client3 = await openClient(createPadData.adminId);
const client3 = await openClient(createMapData.adminId);
await client3.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
expect(cloneDeep(client3.markers)).toEqual({});
});
@ -320,7 +320,7 @@ test("Try to create marker with line type", async () => {
test("Try to update marker with line type", async () => {
const client = await openClient();
await createTemporaryPad(client, {}, async (createPadData) => {
await createTemporaryMap(client, {}, async (createMapData) => {
const markerType = Object.values(client.types).find((t) => t.type === "marker")!;
const lineType = Object.values(client.types).find((t) => t.type === "line")!;
@ -338,7 +338,7 @@ test("Try to update marker with line type", async () => {
});
}).rejects.toThrowError("Cannot use line type for marker");
const client3 = await openClient(createPadData.adminId);
const client3 = await openClient(createMapData.adminId);
await client3.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
expect(cloneDeep(client3.markers)).toEqual({
[marker.id]: marker
@ -346,12 +346,12 @@ test("Try to update marker with line type", async () => {
});
});
test("Try to create marker with marker type from other pad", async () => {
test("Try to create marker with marker type from other map", async () => {
const client1 = await openClient();
const client2 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData) => {
await createTemporaryPad(client2, {}, async () => {
await createTemporaryMap(client1, {}, async (createMapData) => {
await createTemporaryMap(client2, {}, async () => {
const markerType2 = Object.values(client2.types).find((t) => t.type === "marker")!;
await expect(async () => {
@ -362,19 +362,19 @@ test("Try to create marker with marker type from other pad", async () => {
});
}).rejects.toThrowError("could not be found");
const client3 = await openClient(createPadData.adminId);
const client3 = await openClient(createMapData.adminId);
await client3.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
expect(cloneDeep(client3.markers)).toEqual({});
});
});
});
test("Try to update marker with marker type from other pad", async () => {
test("Try to update marker with marker type from other map", async () => {
const client1 = await openClient();
const client2 = await openClient();
await createTemporaryPad(client1, {}, async (createPadData) => {
await createTemporaryPad(client2, {}, async () => {
await createTemporaryMap(client1, {}, async (createMapData) => {
await createTemporaryMap(client2, {}, async () => {
const markerType1 = Object.values(client1.types).find((t) => t.type === "marker")!;
const markerType2 = Object.values(client2.types).find((t) => t.type === "marker")!;
@ -392,7 +392,7 @@ test("Try to update marker with marker type from other pad", async () => {
});
}).rejects.toThrowError("could not be found");
const client3 = await openClient(createPadData.adminId);
const client3 = await openClient(createMapData.adminId);
await client3.updateBbox({ top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
expect(cloneDeep(client3.markers)).toEqual({
[marker.id]: marker
@ -400,67 +400,3 @@ test("Try to update marker with marker type from other pad", async () => {
});
});
});
test("Socket v1 marker name", async () => {
// socket1: Creates the marker and has it in its bbox
// socket2: Has the marker in its bbox
// socket3: Does not have the marker in its bbox
const socket1 = await openSocket(SocketVersion.V1);
const socket2 = await openSocket(SocketVersion.V1);
const socket3 = await openSocket(SocketVersion.V1);
const onMarker1 = vi.fn();
socket1.on("marker", onMarker1);
const onMarker2 = vi.fn();
socket2.on("marker", onMarker2);
const onMarker3 = vi.fn();
socket3.on("marker", onMarker3);
try {
const padData = getTemporaryPadData({});
const padResult = await emit(socket1, "createPad", padData);
await emit(socket2, "setPadId", padData.adminId);
await emit(socket3, "setPadId", padData.adminId);
const markerType = padResult.type!.find((t) => t.type === "marker")!;
await emit(socket1, "updateBbox", { top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await emit(socket2, "updateBbox", { top: 20, bottom: 0, left: 0, right: 20, zoom: 1 });
await emit(socket3, "updateBbox", { top: 5, bottom: 0, left: 0, right: 5, zoom: 1 });
const marker = await emit(socket1, "addMarker", {
lat: 10,
lon: 10,
typeId: markerType.id,
ele: null
});
const expectedMarker = {
id: marker.id,
lat: 10,
lon: 10,
typeId: markerType.id,
padId: padData.id,
name: "Untitled marker",
colour: "ff0000",
size: 30,
symbol: "",
shape: "",
data: {},
ele: null
} satisfies Marker;
expect(marker).toEqual(expectedMarker);
await retry(() => {
expect(onMarker1).toHaveBeenCalledTimes(1);
expect(onMarker2).toHaveBeenCalledTimes(1);
expect(onMarker3).toHaveBeenCalledTimes(0);
});
expect(onMarker1).toHaveBeenCalledWith(expectedMarker);
expect(onMarker2).toHaveBeenCalledWith(expectedMarker);
} finally {
await emit(socket1, "deletePad", undefined);
}
});

Wyświetl plik

@ -0,0 +1,7 @@
// getRoute returns calculated route
// With route ID and without route ID:
// setRoute calculates route and transmits track points for initial bbox and for updated bbox
// setRoute updates route and transmits track points for initial bbox and for updated bbox
// clearRoute clears route and does not transmit track points on bbox update anymore
// lineToRoute converts a line to a route
// exportRoute exports a route to GPX

Wyświetl plik

@ -1,10 +1,10 @@
import { expect, test, vi } from "vitest";
import { createTemporaryPad, openClient } from "../utils";
import { createTemporaryMap, openClient } from "../../utils";
test("New marker is created with dropdown styles", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "marker",
@ -14,11 +14,11 @@ test("New marker is created with dropdown styles", async () => {
type: "dropdown",
controlColour: true,
controlSize: true,
controlSymbol: true,
controlIcon: true,
controlShape: true,
options: [
{ value: "Value 1", colour: "00ffff", size: 60, symbol: "z", shape: "rectangle" },
{ value: "Value 2", colour: "00ff00", size: 50, symbol: "a", shape: "circle" }
{ value: "Value 1", colour: "00ffff", size: 60, icon: "z", shape: "rectangle" },
{ value: "Value 2", colour: "00ff00", size: 50, icon: "a", shape: "circle" }
],
default: "Value 2"
}
@ -31,14 +31,14 @@ test("New marker is created with dropdown styles", async () => {
typeId: type.id,
colour: "ffffff",
size: 20,
symbol: "b",
icon: "b",
shape: "drop"
});
expect(marker).toMatchObject({
colour: "00ff00",
size: 50,
symbol: "a",
icon: "a",
shape: "circle",
});
});
@ -47,7 +47,7 @@ test("New marker is created with dropdown styles", async () => {
test("New line is created with dropdown styles", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "line",
@ -89,7 +89,7 @@ test("New line is created with dropdown styles", async () => {
test("Line template uses dropdown styles", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "line",
@ -128,7 +128,7 @@ test("Line template uses dropdown styles", async () => {
test("Marker update is overridden by dropdown styles", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "marker",
@ -138,11 +138,11 @@ test("Marker update is overridden by dropdown styles", async () => {
type: "dropdown",
controlColour: true,
controlSize: true,
controlSymbol: true,
controlIcon: true,
controlShape: true,
options: [
{ value: "Value 1", colour: "00ffff", size: 60, symbol: "z", shape: "rectangle" },
{ value: "Value 2", colour: "00ff00", size: 50, symbol: "a", shape: "circle" }
{ value: "Value 1", colour: "00ffff", size: 60, icon: "z", shape: "rectangle" },
{ value: "Value 2", colour: "00ff00", size: 50, icon: "a", shape: "circle" }
],
default: "Value 2"
}
@ -162,14 +162,14 @@ test("Marker update is overridden by dropdown styles", async () => {
id: marker.id,
colour: "ffffff",
size: 20,
symbol: "b",
icon: "b",
shape: "drop"
});
expect(markerUpdate).toMatchObject({
colour: "00ffff",
size: 60,
symbol: "z",
icon: "z",
shape: "rectangle",
});
});
@ -178,7 +178,7 @@ test("Marker update is overridden by dropdown styles", async () => {
test("Line update is overridden by dropdown styles", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "line",
@ -227,7 +227,7 @@ test("Line update is overridden by dropdown styles", async () => {
test("New dropdown styles are applied to existing markers", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "marker"
@ -241,7 +241,7 @@ test("New dropdown styles are applied to existing markers", async () => {
typeId: type.id,
colour: "ffffff",
size: 20,
symbol: "b",
icon: "b",
shape: "drop"
});
@ -256,11 +256,11 @@ test("New dropdown styles are applied to existing markers", async () => {
type: "dropdown",
controlColour: true,
controlSize: true,
controlSymbol: true,
controlIcon: true,
controlShape: true,
options: [
{ value: "Value 1", colour: "00ffff", size: 60, symbol: "z", shape: "rectangle" },
{ value: "Value 2", colour: "00ff00", size: 50, symbol: "a", shape: "circle" }
{ value: "Value 1", colour: "00ffff", size: 60, icon: "z", shape: "rectangle" },
{ value: "Value 2", colour: "00ff00", size: 50, icon: "a", shape: "circle" }
],
default: "Value 2"
}
@ -272,7 +272,7 @@ test("New dropdown styles are applied to existing markers", async () => {
expect(client.markers[marker.id]).toMatchObject({
colour: "00ff00",
size: 50,
symbol: "a",
icon: "a",
shape: "circle",
});
});
@ -281,7 +281,7 @@ test("New dropdown styles are applied to existing markers", async () => {
test("New dropdown styles are applied to existing lines", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "line"

Wyświetl plik

@ -1,10 +1,10 @@
import { expect, test, vi } from "vitest";
import { createTemporaryPad, openClient } from "../utils";
import { createTemporaryMap, openClient } from "../../utils";
test("Rename field (marker type)", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async (createPadData, padData, result) => {
await createTemporaryMap(client, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const type = await client.addType({
name: "Test type",
type: "marker",
@ -49,7 +49,7 @@ test("Rename field (marker type)", async () => {
test("Rename field (line type)", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async (createPadData, padData, result) => {
await createTemporaryMap(client, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const type = await client.addType({
name: "Test type",
type: "line",
@ -94,7 +94,7 @@ test("Rename field (line type)", async () => {
test("Rename dropdown option (marker type)", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async (createPadData, padData, result) => {
await createTemporaryMap(client, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const type = await client.addType({
name: "Test type",
type: "marker",
@ -147,7 +147,7 @@ test("Rename dropdown option (marker type)", async () => {
test("Rename dropdown option (line type)", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async (createPadData, padData, result) => {
await createTemporaryMap(client, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const type = await client.addType({
name: "Test type",
type: "line",
@ -202,7 +202,7 @@ test("Rename dropdown option (line type)", async () => {
test("Create type with duplicate fields", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
await expect(async () => {
await client.addType({
name: "Test type",
@ -219,7 +219,7 @@ test("Create type with duplicate fields", async () => {
test("Update type with duplicate fields", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "marker",
@ -244,7 +244,7 @@ test("Update type with duplicate fields", async () => {
test("Create type with duplicate dropdown values", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
await expect(async () => {
await client.addType({
name: "Test type",
@ -267,7 +267,7 @@ test("Create type with duplicate dropdown values", async () => {
test("Update type with duplicate dropdown values", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "marker"

Wyświetl plik

@ -1,10 +1,10 @@
import { expect, test } from "vitest";
import { createTemporaryPad, openClient } from "../utils";
import { createTemporaryMap, openClient } from "../../utils";
test("Reorder types", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async (padData) => {
await createTemporaryMap(client, { createDefaultTypes: false }, async (mapData) => {
const type1 = await client.addType({
name: "Test type 1",
type: "marker"

Wyświetl plik

@ -1,16 +1,16 @@
import { expect, test, vi } from "vitest";
import { createTemporaryPad, openClient } from "../utils";
import { createTemporaryMap, openClient } from "../../utils";
test("New marker is created with default settings", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "marker",
defaultColour: "00ff00",
defaultSize: 50,
defaultSymbol: "a",
defaultIcon: "a",
defaultShape: "circle"
});
@ -23,7 +23,7 @@ test("New marker is created with default settings", async () => {
expect(marker).toMatchObject({
colour: "00ff00",
size: 50,
symbol: "a",
icon: "a",
shape: "circle",
});
});
@ -32,7 +32,7 @@ test("New marker is created with default settings", async () => {
test("New line is created with default settings", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "line",
@ -62,7 +62,7 @@ test("New line is created with default settings", async () => {
test("Line template uses default settings", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "line",
@ -91,7 +91,7 @@ test("Line template uses default settings", async () => {
test("New marker is created with fixed settings", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "marker",
@ -99,8 +99,8 @@ test("New marker is created with fixed settings", async () => {
colourFixed: true,
defaultSize: 50,
sizeFixed: true,
defaultSymbol: "a",
symbolFixed: true,
defaultIcon: "a",
iconFixed: true,
defaultShape: "circle",
shapeFixed: true
});
@ -111,14 +111,14 @@ test("New marker is created with fixed settings", async () => {
typeId: type.id,
colour: "ffffff",
size: 20,
symbol: "b",
icon: "b",
shape: "drop"
});
expect(marker).toMatchObject({
colour: "00ff00",
size: 50,
symbol: "a",
icon: "a",
shape: "circle",
});
});
@ -127,7 +127,7 @@ test("New marker is created with fixed settings", async () => {
test("New line is created with fixed settings", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "line",
@ -165,7 +165,7 @@ test("New line is created with fixed settings", async () => {
test("Marker update is overridden by fixed settings", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "marker",
@ -173,8 +173,8 @@ test("Marker update is overridden by fixed settings", async () => {
colourFixed: true,
defaultSize: 50,
sizeFixed: true,
defaultSymbol: "a",
symbolFixed: true,
defaultIcon: "a",
iconFixed: true,
defaultShape: "circle",
shapeFixed: true
});
@ -189,14 +189,14 @@ test("Marker update is overridden by fixed settings", async () => {
id: marker.id,
colour: "ffffff",
size: 20,
symbol: "b",
icon: "b",
shape: "drop"
});
expect(markerUpdate).toMatchObject({
colour: "00ff00",
size: 50,
symbol: "a",
icon: "a",
shape: "circle",
});
});
@ -205,7 +205,7 @@ test("Marker update is overridden by fixed settings", async () => {
test("Line is overridden by fixed settings", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "line",
@ -247,7 +247,7 @@ test("Line is overridden by fixed settings", async () => {
test("New fixed marker styles are applied to existing markers", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "marker"
@ -261,7 +261,7 @@ test("New fixed marker styles are applied to existing markers", async () => {
typeId: type.id,
colour: "ffffff",
size: 20,
symbol: "b",
icon: "b",
shape: "drop"
});
@ -274,8 +274,8 @@ test("New fixed marker styles are applied to existing markers", async () => {
colourFixed: true,
defaultSize: 50,
sizeFixed: true,
defaultSymbol: "a",
symbolFixed: true,
defaultIcon: "a",
iconFixed: true,
defaultShape: "circle",
shapeFixed: true
});
@ -285,7 +285,7 @@ test("New fixed marker styles are applied to existing markers", async () => {
expect(client.markers[marker.id]).toMatchObject({
colour: "00ff00",
size: 50,
symbol: "a",
icon: "a",
shape: "circle",
});
});
@ -294,7 +294,7 @@ test("New fixed marker styles are applied to existing markers", async () => {
test("New fixed line styles are applied to existing lines", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async () => {
await createTemporaryMap(client, { createDefaultTypes: false }, async () => {
const type = await client.addType({
name: "Test type",
type: "line"

Wyświetl plik

@ -1,5 +1,5 @@
import { expect, test, vi } from "vitest";
import { createTemporaryPad, openClient, retry } from "../utils";
import { createTemporaryMap, openClient, retry } from "../../utils";
import { CRU, type ID, type Type } from "facilmap-types";
import { cloneDeep } from "lodash-es";
@ -9,7 +9,7 @@ test("Default types are added", async () => {
const onType = vi.fn();
client.on("type", onType);
await createTemporaryPad(client, {}, async (createPadData, padData, result) => {
await createTemporaryMap(client, {}, async (createMapData, mapData, result) => {
expect(result.type?.length).toBe(2);
const expectedTypes = [
@ -25,8 +25,8 @@ test("Default types are added", async () => {
colourFixed: false,
defaultSize: 30,
sizeFixed: false,
defaultSymbol: '',
symbolFixed: false,
defaultIcon: '',
iconFixed: false,
defaultShape: '',
shapeFixed: false,
defaultWidth: 4,
@ -36,7 +36,7 @@ test("Default types are added", async () => {
defaultMode: '',
modeFixed: false,
showInLegend: false,
padId: padData.id
padId: mapData.id
},
{
fields: [
@ -50,8 +50,8 @@ test("Default types are added", async () => {
colourFixed: false,
defaultSize: 30,
sizeFixed: false,
defaultSymbol: '',
symbolFixed: false,
defaultIcon: '',
iconFixed: false,
defaultShape: '',
shapeFixed: false,
defaultWidth: 4,
@ -61,7 +61,7 @@ test("Default types are added", async () => {
defaultMode: '',
modeFixed: false,
showInLegend: false,
padId: padData.id
padId: mapData.id
}
] satisfies Array<Type>;
@ -79,7 +79,7 @@ test("Default types are added", async () => {
test("Default types are not added", async () => {
const client = await openClient();
await createTemporaryPad(client, { createDefaultTypes: false }, async (createPadData, padData, result) => {
await createTemporaryMap(client, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
expect(result.type).toEqual([]);
expect(client.types).toEqual({});
});
@ -88,8 +88,8 @@ test("Default types are not added", async () => {
test("Create type (marker, default settings)", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, { createDefaultTypes: false }, async (createPadData, padData, result) => {
const client2 = await openClient(padData.id);
await createTemporaryMap(client1, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const client2 = await openClient(mapData.id);
const onType1 = vi.fn();
client1.on("type", onType1);
@ -107,14 +107,14 @@ test("Create type (marker, default settings)", async () => {
const expectedType: Type = {
...type,
id: typeResult.id,
padId: padData.id,
padId: mapData.id,
idx: 0,
defaultColour: "ff0000",
colourFixed: false,
defaultSize: 30,
sizeFixed: false,
defaultSymbol: "",
symbolFixed: false,
defaultIcon: "",
iconFixed: false,
defaultShape: "",
shapeFixed: false,
defaultWidth: 4,
@ -146,7 +146,7 @@ test("Create type (marker, default settings)", async () => {
[expectedType.id]: expectedType
});
const client3 = await openClient(padData.id);
const client3 = await openClient(mapData.id);
expect(cloneDeep(client3.types)).toEqual({
[expectedType.id]: expectedType
});
@ -159,8 +159,8 @@ test("Create type (line, default settings)", async () => {
const onType = vi.fn();
client1.on("type", onType);
await createTemporaryPad(client1, { createDefaultTypes: false }, async (createPadData, padData, result) => {
const client2 = await openClient(padData.id);
await createTemporaryMap(client1, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const client2 = await openClient(mapData.id);
const onType1 = vi.fn();
client1.on("type", onType1);
@ -178,14 +178,14 @@ test("Create type (line, default settings)", async () => {
const expectedType: Type = {
...type,
id: typeResult.id,
padId: padData.id,
padId: mapData.id,
idx: 0,
defaultColour: "0000ff",
colourFixed: false,
defaultSize: 30,
sizeFixed: false,
defaultSymbol: "",
symbolFixed: false,
defaultIcon: "",
iconFixed: false,
defaultShape: "",
shapeFixed: false,
defaultWidth: 4,
@ -217,7 +217,7 @@ test("Create type (line, default settings)", async () => {
[expectedType.id]: expectedType
});
const client3 = await openClient(padData.id);
const client3 = await openClient(mapData.id);
expect(cloneDeep(client3.types)).toEqual({
[expectedType.id]: expectedType
});
@ -230,7 +230,7 @@ test("Create type (custom settings)", async () => {
const onType = vi.fn();
client.on("type", onType);
await createTemporaryPad(client, { createDefaultTypes: false }, async (createPadData, padData, result) => {
await createTemporaryMap(client, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const onType = vi.fn();
client.on("type", onType);
@ -242,8 +242,8 @@ test("Create type (custom settings)", async () => {
colourFixed: true,
defaultSize: 35,
sizeFixed: true,
defaultSymbol: "a",
symbolFixed: true,
defaultIcon: "a",
iconFixed: true,
defaultShape: "star",
shapeFixed: true,
defaultWidth: 10,
@ -263,7 +263,7 @@ test("Create type (custom settings)", async () => {
const expectedType: Type = {
...type,
id: typeResult.id,
padId: padData.id
padId: mapData.id
};
expect(typeResult).toEqual(expectedType);
@ -285,13 +285,13 @@ test("Update type", async () => {
const onType = vi.fn();
client1.on("type", onType);
await createTemporaryPad(client1, { createDefaultTypes: false }, async (createPadData, padData, result) => {
await createTemporaryMap(client1, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const createdType = await client1.addType({
name: "Test type",
type: "marker"
});
const client2 = await openClient(padData.id);
const client2 = await openClient(mapData.id);
const onType1 = vi.fn();
client1.on("type", onType1);
@ -307,8 +307,8 @@ test("Update type", async () => {
colourFixed: true,
defaultSize: 35,
sizeFixed: true,
defaultSymbol: "a",
symbolFixed: true,
defaultIcon: "a",
iconFixed: true,
defaultShape: "star",
shapeFixed: true,
defaultWidth: 10,
@ -327,7 +327,7 @@ test("Update type", async () => {
const expectedType: Type = {
...update,
padId: padData.id,
padId: mapData.id,
type: "marker"
};
@ -348,7 +348,7 @@ test("Update type", async () => {
[expectedType.id]: expectedType
});
const client3 = await openClient(padData.id);
const client3 = await openClient(mapData.id);
expect(cloneDeep(client3.types)).toEqual({
[expectedType.id]: expectedType
});
@ -358,13 +358,13 @@ test("Update type", async () => {
test("Delete type", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, { createDefaultTypes: false }, async (createPadData, padData, result) => {
await createTemporaryMap(client1, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const type = await client1.addType({
name: "Test type",
type: "marker"
});
const client2 = await openClient(padData.id);
const client2 = await openClient(mapData.id);
const onDeleteType1 = vi.fn();
client1.on("deleteType", onDeleteType1);
@ -387,7 +387,7 @@ test("Delete type", async () => {
expect(onDeleteType2).toHaveBeenNthCalledWith(1, { id: type.id });
expect(cloneDeep(client2.types)).toEqual({});
const client3 = await openClient(padData.id);
const client3 = await openClient(mapData.id);
expect(cloneDeep(client3.types)).toEqual({});
});
});
@ -395,7 +395,7 @@ test("Delete type", async () => {
test("Delete type (existing markers)", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, { createDefaultTypes: false }, async (createPadData, padData, result) => {
await createTemporaryMap(client1, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const type = await client1.addType({
name: "Test type",
type: "marker"
@ -407,7 +407,7 @@ test("Delete type (existing markers)", async () => {
typeId: type.id
});
const client2 = await openClient(padData.id);
const client2 = await openClient(mapData.id);
const onDeleteType1 = vi.fn();
client1.on("deleteType", onDeleteType1);
@ -428,7 +428,7 @@ test("Delete type (existing markers)", async () => {
[type.id]: type
});
const client3 = await openClient(padData.id);
const client3 = await openClient(mapData.id);
expect(cloneDeep(client3.types)).toEqual({
[type.id]: type
});
@ -438,7 +438,7 @@ test("Delete type (existing markers)", async () => {
test("Delete type (existing lines)", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, { createDefaultTypes: false }, async (createPadData, padData, result) => {
await createTemporaryMap(client1, { createDefaultTypes: false }, async (createMapData, mapData, result) => {
const type = await client1.addType({
name: "Test type",
type: "line"
@ -452,7 +452,7 @@ test("Delete type (existing lines)", async () => {
typeId: type.id
});
const client2 = await openClient(padData.id);
const client2 = await openClient(mapData.id);
const onDeleteType1 = vi.fn();
client1.on("deleteType", onDeleteType1);
@ -473,7 +473,7 @@ test("Delete type (existing lines)", async () => {
[type.id]: type
});
const client3 = await openClient(padData.id);
const client3 = await openClient(mapData.id);
expect(cloneDeep(client3.types)).toEqual({
[type.id]: type
});

Wyświetl plik

@ -1,13 +1,13 @@
import { expect, test, vi } from "vitest";
import { createTemporaryPad, openClient, retry } from "./utils";
import { createTemporaryMap, openClient, retry } from "../utils";
import { type CRU, type ID, type View } from "facilmap-types";
import { cloneDeep } from "lodash-es";
test("Create view (default values)", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, {}, async (padData) => {
const client2 = await openClient(padData.id);
await createTemporaryMap(client1, {}, async (mapData) => {
const client2 = await openClient(mapData.id);
const onView1 = vi.fn();
client1.on("view", onView1);
@ -33,7 +33,7 @@ test("Create view (default values)", async () => {
idx: 0,
filter: null,
id: viewResult.id,
padId: padData.id
padId: mapData.id
};
expect(viewResult).toEqual(expectedView);
@ -53,7 +53,7 @@ test("Create view (default values)", async () => {
[expectedView.id]: expectedView
});
const client3 = await openClient(padData.id);
const client3 = await openClient(mapData.id);
expect(client3.views).toEqual({
[expectedView.id]: expectedView
});
@ -66,7 +66,7 @@ test("Create view (custom values)", async () => {
const onView = vi.fn();
client.on("view", onView);
await createTemporaryPad(client, {}, async (padData) => {
await createTemporaryMap(client, {}, async (mapData) => {
const view = {
name: "Test view 1",
left: -10,
@ -84,7 +84,7 @@ test("Create view (custom values)", async () => {
const expectedView: View = {
...view,
id: viewResult.id,
padId: padData.id
padId: mapData.id
};
expect(viewResult).toEqual(expectedView);
@ -99,7 +99,7 @@ test("Create view (custom values)", async () => {
test("Update view", async () => {
const client1 = await openClient();
await createTemporaryPad(client1, {}, async (padData) => {
await createTemporaryMap(client1, {}, async (mapData) => {
const createdView = await client1.addView({
name: "Test view 1",
left: -10,
@ -110,7 +110,7 @@ test("Update view", async () => {
layers: []
});
const client2 = await openClient(padData.id);
const client2 = await openClient(mapData.id);
const onView1 = vi.fn();
client1.on("view", onView1);
@ -134,7 +134,7 @@ test("Update view", async () => {
const expectedView: View = {
...update,
padId: padData.id
padId: mapData.id
};
expect(view).toEqual(expectedView);
@ -153,7 +153,7 @@ test("Update view", async () => {
[expectedView.id]: expectedView
});
const client3 = await openClient(padData.id);
const client3 = await openClient(mapData.id);
expect(client3.views).toEqual({
[expectedView.id]: expectedView
});
@ -163,10 +163,10 @@ test("Update view", async () => {
test("Set default view", async () => {
const client = await openClient();
const onPadData = vi.fn();
client.on("padData", onPadData);
const onMapData = vi.fn();
client.on("mapData", onMapData);
await createTemporaryPad(client, {}, async (padData) => {
await createTemporaryMap(client, {}, async (mapData) => {
await client.addView({
name: "Test view 1",
left: -10,
@ -189,18 +189,18 @@ test("Set default view", async () => {
filter: "name == \"\""
});
const padResult = await client.editPad({
const mapResult = await client.editMap({
defaultViewId: view2.id
});
expect(padResult.defaultView).toEqual(view2);
expect(onPadData.mock.lastCall[0].defaultView).toEqual(view2);
expect(mapResult.defaultView).toEqual(view2);
expect(onMapData.mock.lastCall[0].defaultView).toEqual(view2);
});
});
test("Delete view", async () => {
const client = await openClient();
await createTemporaryPad(client, {}, async (padData) => {
await createTemporaryMap(client, {}, async (mapData) => {
const view = await client.addView({
name: "Test view 1",
left: -10,
@ -226,7 +226,7 @@ test("Delete view", async () => {
test("Reorder views", async () => {
const client = await openClient();
await createTemporaryPad(client, {}, async (padData) => {
await createTemporaryMap(client, {}, async (mapData) => {
const viewSettings = {
name: "Test view",
left: -10,

Wyświetl plik

@ -1,7 +1,10 @@
import { io, Socket } from "socket.io-client";
import Client from "facilmap-client";
import { type CRU, type PadData, SocketVersion, type SocketClientToServerEvents, type SocketServerToClientEvents, Writable, type MultipleEvents, type SocketEvents } from "facilmap-types";
import { generateRandomPadId, sleep } from "facilmap-utils";
import ClientV3 from "facilmap-client-v3";
import ClientV4 from "facilmap-client-v4";
import { type CRU, type MapData, SocketVersion, type SocketClientToServerEvents, type SocketServerToClientEvents } from "facilmap-types";
import { generateRandomMapId, sleep } from "facilmap-utils";
export function getFacilMapUrl(): string {
if (!process.env.FACILMAP_URL) {
@ -23,10 +26,21 @@ export async function openSocket<V extends SocketVersion>(version: V): Promise<S
return socket;
}
export async function openClient(id?: string): Promise<Client> {
const client = new Client(getFacilMapUrl(), id, { reconnection: false });
const clientConstructors = {
[SocketVersion.V1]: ClientV3,
[SocketVersion.V2]: ClientV4,
[SocketVersion.V3]: Client
};
type ClientInstance<V extends SocketVersion> = InstanceType<typeof clientConstructors[V]> & { _version: V };
export async function openClient<V extends SocketVersion = SocketVersion.V3>(id?: string, version: V = SocketVersion.V3 as any): Promise<ClientInstance<V>> {
const client = Object.assign(new clientConstructors[version](getFacilMapUrl(), id, { reconnection: false }) as any, { _version: version });
await new Promise<void>((resolve, reject) => {
if (id != null) {
client.on("mapData", () => {
resolve();
});
client.on("padData", () => {
resolve();
});
@ -39,28 +53,42 @@ export async function openClient(id?: string): Promise<Client> {
return client;
}
export function generateTestPadId(): string {
return `integration-test-${generateRandomPadId()}`;
export function generateTestMapId(): string {
return `integration-test-${generateRandomMapId()}`;
}
export function getTemporaryPadData<D extends Partial<PadData<CRU.CREATE>>>(data: D): D & Pick<PadData<CRU.CREATE>, "id" | "writeId" | "adminId"> {
export function getTemporaryMapData<V extends SocketVersion, D extends Partial<MapData<CRU.CREATE>>>(version: V, data: D): D & Pick<MapData<CRU.CREATE>, "id" | "writeId" | "adminId"> {
return {
id: generateTestPadId(),
writeId: generateTestPadId(),
adminId: generateTestPadId(),
id: generateTestMapId(),
writeId: generateTestMapId(),
adminId: generateTestMapId(),
...data
};
}
export async function createTemporaryPad<D extends Partial<PadData<CRU.CREATE>>>(
client: Client,
export async function createTemporaryMap<V extends SocketVersion.V3, D extends Partial<MapData<CRU.CREATE>>>(
client: ClientInstance<V>,
data: D,
callback?: (createPadData: ReturnType<typeof getTemporaryPadData<D>>, padData: PadData & { writable: Writable }, result: MultipleEvents<SocketEvents<SocketVersion.V2>>) => Promise<void>
callback?: (createMapData: ReturnType<typeof getTemporaryMapData<V, D>>, mapData: NonNullable<ClientInstance<V>["mapData"]>, result: Awaited<ReturnType<ClientInstance<V>["createMap"]>>) => Promise<void>
): Promise<void> {
const createPadData = getTemporaryPadData(data);
const result = await client.createPad(createPadData);
const createMapData = getTemporaryMapData(client._version, data);
const result = await client.createMap(createMapData as any);
try {
await callback?.(createPadData, result.padData![0], result);
await callback?.(createMapData, client.mapData!, result as any);
} finally {
await client.deleteMap();
}
}
export async function createTemporaryMapV2<V extends SocketVersion.V1 | SocketVersion.V2, D extends Partial<MapData<CRU.CREATE>>>(
client: ClientInstance<V>,
data: D,
callback?: (createMapData: ReturnType<typeof getTemporaryMapData<V, D>>, mapData: NonNullable<ClientInstance<V>["padData"]>, result: Awaited<ReturnType<ClientInstance<V>["createPad"]>>) => Promise<void>
): Promise<void> {
const createMapData = getTemporaryMapData(client._version, data);
const result = await client.createPad(createMapData as any);
try {
await callback?.(createMapData, client.padData!, result as any);
} finally {
await client.deletePad();
}

Wyświetl plik

@ -41,7 +41,7 @@
<input
type="button"
value="Open map wqxygV4R506PlBlZ"
onclick="client.setPadId('wqxygV4R506PlBlZ').catch(log('setPadId error'))"
onclick="client.setMapId('wqxygV4R506PlBlZ').catch(log('setMapId error'))"
/>
<a href="http://localhost:40829/wqxygV4R506PlBlZ" target="_blank"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg=="></a>

Wyświetl plik

@ -25,7 +25,7 @@
const el = document.createElement("div");
el.id = id;
document.body.appendChild(el);
for (const icon of ["", "O", "g", "j", "a", "*", ...FacilMap.symbolList]) {
for (const icon of ["", "O", "g", "j", "a", "*", ...FacilMap.iconList]) {
const span = document.createElement('span');
span.title = icon;
createIcon(icon).then((html) => {
@ -35,7 +35,7 @@
}
}
createIcons("icons", (icon) => FacilMap.getSymbolHtml("#000000", 50, icon));
createIcons("icons", (icon) => FacilMap.getIconHtml("#000000", 50, icon));
for (const shape of ["drop", "rectangle-marker", "circle", "rectangle", "diamond", "pentagon", "hexagon", "triangle", "triangle-down", "star"]) {
createIcons(`icons-${shape}`, (icon) => FacilMap.getMarkerHtml("#ccffcc", 50, icon, shape));
createIcons(`icons-${shape}-highlight`, (icon) => FacilMap.getMarkerHtml("#ccffcc", 50, icon, shape, true));

Wyświetl plik

@ -1,6 +1,6 @@
{
"name": "facilmap-leaflet",
"version": "4.1.1",
"version": "5.0.0",
"description": "Utilities to show FacilMap objects on a Leaflet map.",
"keywords": [
"maps",
@ -47,7 +47,7 @@
"leaflet-auto-graticule": "^2.0.0",
"leaflet-draggable-lines": "^2.0.0",
"leaflet-freie-tonne": "^2.0.1",
"leaflet-highlightable-layers": "^3.0.0",
"leaflet-highlightable-layers": "^3.0.1",
"leaflet.markercluster": "^1.5.3",
"lodash-es": "^4.17.21"
},

Wyświetl plik

@ -39,15 +39,28 @@ export default class BboxHandler extends Handler {
void this.client.updateBbox(leafletToFmBbox(bounds ?? this._map.getBounds(), zoom ?? this._map.getZoom()));
}
handleMoveEnd = (): void => {
if (this.client.padData || this.client.route || Object.keys(this.client.routes).length > 0) {
this.updateBbox();
}
shouldUpdateBbox(): boolean {
console.log(!!this.client.mapData, !!this.client.route, Object.keys(this.client.routes).length > 0);
return !!this.client.mapData || !!this.client.route || Object.keys(this.client.routes).length > 0;
}
handleMoveEnd = (): void => {
if (this.shouldUpdateBbox()) {
this.updateBbox();
}
};
handleViewReset = (): void => {
if (this.shouldUpdateBbox()) {
this.updateBbox();
}
};
handleFlyTo = ({ latlng, zoom }: any): void => {
this.updateBbox(latlng, zoom);
}
if (this.shouldUpdateBbox()) {
this.updateBbox(latlng, zoom);
}
};
handleEmitResolve: EventHandler<ClientEvents, "emitResolve"> = (name, data) => {
if (["createPad", "setPadId", "setRoute"].includes(name)) {
@ -57,12 +70,14 @@ export default class BboxHandler extends Handler {
addHooks(): void {
this._map.on("moveend", this.handleMoveEnd);
this._map.on("viewreset", this.handleViewReset);
this._map.on("fmFlyTo", this.handleFlyTo);
this.client.on("emitResolve", this.handleEmitResolve);
}
removeHooks(): void {
this._map.off("moveend", this.handleMoveEnd);
this._map.off("viewreset", this.handleViewReset);
this._map.off("fmFlyTo", this.handleFlyTo);
this.client.removeListener("emitResolve", this.handleEmitResolve);
}

Wyświetl plik

@ -1 +1,83 @@
{}
{
"layers": {
"topo-name": "OpenTopoMap",
"ocyc-name": "OpenCycleMap",
"mpnk-attribution": "© [Участники OSM](https://www.openstreetmap.org/copyright)",
"topl-name": "TopPlus",
"mpnk-name": "Mapnik",
"map1-name": "Map1.eu",
"cyco-name": "CyclOSM"
},
"overpass-presets": {
"bank": "Банки",
"bench": "Скамейки",
"bicycleparking": "Велопарковки",
"bicyclerental": "Велопрокаты",
"cinema": "Кинотеатры",
"clinic": "Клиники",
"embassy": "Посольства",
"fuel": "Заправки",
"hospital": "Больницы",
"pharmacy": "Аптеки",
"postbox": "Почтовые ящики",
"postoffice": "Почта",
"theatre": "Театры",
"church": "Церкви",
"mosque": "Мечети",
"buddhist": "Буддийские храмы",
"hindu": "Индуистские храмы",
"synagogue": "Синагоги",
"category-restaurants": "Рестораны",
"category-various": "Прочее",
"firestation": "Пожарные части",
"library": "Библиотеки",
"police": "Полиция",
"musicschool": "Музыкальные школы",
"toilets": "Туалеты",
"worship": "Религиозные объекты",
"parking": "Парковки",
"school": "Школы/колледжы",
"shower": "Душ",
"university": "Университеты",
"taxi": "Такси",
"cemetery": "Кладбища",
"category-tourism": "Туризм",
"category-sports": "Спорт",
"category-shops": "Магазины",
"shops": "Все магазины",
"atm": "Банкоматы",
"drinkingwater": "Питьевая вода",
"statue": "Статуи",
"abandoned": "Заброшенное",
"artwork": "Произведения искусства",
"attraction": "Достопримечательности",
"castle": "Замки",
"gallery": "Галереи",
"heritage": "Культурное наследие",
"historic": "Исторические объекты",
"museum": "Музеи",
"picnic": "Места для пикников",
"sauna": "Сауны",
"zoo": "Зоопарки",
"spa": "Спа",
"themepark": "Парки развлечений",
"viewpoint": "Смотровые площадки",
"vineyard": "Виноградники",
"alpinehut": "Альпийские хижины",
"apartment": "Апартаменты",
"campsite": "Кемпинги",
"guesthouse": "Гостевые дома",
"hotel": "Отели",
"golfcourse": "Гольф",
"touristinformation": "Информация для туристов",
"monument": "Монументы/мемориалы",
"chalet": "Шале",
"tourism": "Все туристические места",
"hostel": "Хостелы",
"motel": "Мотели",
"artscentre": "Центры искусств",
"casino": "Казино",
"windmill": "Ветряные мельницы",
"watermill": "Водяные мельницы"
}
}

Wyświetl plik

@ -142,13 +142,13 @@ export default class LinesLayer extends FeatureGroup {
highlightLine(id: ID): void {
this.highlightedLinesIds.add(id);
if (this.client.lines[id])
if (this._map && this.client.lines[id])
this.handleLine(this.client.lines[id]);
}
unhighlightLine(id: ID): void {
this.highlightedLinesIds.delete(id);
if (this.client.lines[id])
if (this._map && this.client.lines[id])
this.handleLine(this.client.lines[id]);
}

Wyświetl plik

@ -1,6 +1,6 @@
import { Map, MarkerClusterGroup, type MarkerClusterGroupOptions } from "leaflet";
import type Client from "facilmap-client";
import type { PadData } from "facilmap-types";
import type { MapData } from "facilmap-types";
import "leaflet.markercluster";
import "leaflet.markercluster/dist/MarkerCluster.css";
import "leaflet.markercluster/dist/MarkerCluster.Default.css";
@ -26,11 +26,11 @@ export default class MarkerCluster extends MarkerClusterGroup {
this.client = client;
}
protected handlePadData = (padData: PadData): void => {
protected handleMapData = (mapData: MapData): void => {
const isClusterEnabled = this._maxClusterRadiusBkp == null;
if (!!padData.clusterMarkers !== isClusterEnabled) {
if (padData.clusterMarkers) {
if (!!mapData.clusterMarkers !== isClusterEnabled) {
if (mapData.clusterMarkers) {
this.options.maxClusterRadius = this._maxClusterRadiusBkp;
this._maxClusterRadiusBkp = undefined;
} else {
@ -47,9 +47,9 @@ export default class MarkerCluster extends MarkerClusterGroup {
onAdd(map: Map): this {
super.onAdd(map);
this.client.on("padData", this.handlePadData);
if (this.client.padData)
this.handlePadData(this.client.padData);
this.client.on("mapData", this.handleMapData);
if (this.client.mapData)
this.handleMapData(this.client.mapData);
return this;
}
@ -57,7 +57,7 @@ export default class MarkerCluster extends MarkerClusterGroup {
onRemove(map: Map): this {
super.onRemove(map);
this.client.removeListener("padData", this.handlePadData);
this.client.removeListener("mapData", this.handleMapData);
return this;
}

Wyświetl plik

@ -9,7 +9,7 @@ Map.addInitHook(function (this: Map) {
});
export interface MarkerLayerOptions extends MarkerOptions {
marker?: Partial<Marker> & Pick<Marker, 'colour' | 'size' | 'symbol' | 'shape'>;
marker?: Partial<Marker> & Pick<Marker, 'colour' | 'size' | 'icon' | 'shape'>;
highlight?: boolean;
raised?: boolean;
}
@ -70,7 +70,7 @@ export default class MarkerLayer extends LeafletMarker {
_initIcon(): void {
if (this.options.marker)
this.options.icon = getMarkerIcon(`#${this.options.marker.colour}`, this.options.marker.size, this.options.marker.symbol ?? undefined, this.options.marker.shape ?? undefined, this.options.highlight);
this.options.icon = getMarkerIcon(`#${this.options.marker.colour}`, this.options.marker.size, this.options.marker.icon ?? undefined, this.options.marker.shape ?? undefined, this.options.highlight);
super._initIcon();

Wyświetl plik

@ -104,13 +104,13 @@ export default class MarkersLayer extends MarkerCluster {
highlightMarker(id: ID): void {
this.highlightedMarkerIds.add(id);
if (this.client.markers[id])
if (this._map && this.client.markers[id])
this.handleMarker(this.client.markers[id]);
}
unhighlightMarker(id: ID): void {
this.highlightedMarkerIds.delete(id);
if (this.client.markers[id])
if (this._map && this.client.markers[id])
this.handleMarker(this.client.markers[id]);
}

Wyświetl plik

@ -1,7 +1,7 @@
import type { Colour, Shape } from "facilmap-types";
import { FeatureGroup, latLng, Layer, type LayerOptions } from "leaflet";
import MarkerLayer from "../markers/marker-layer";
import { getSymbolForTags } from "../utils/icons";
import { getIconForTags } from "../utils/icons";
import { tooltipOptions } from "../utils/leaflet";
import type { OverpassPreset } from "./overpass-presets";
import { getOverpassElements, isOverpassQueryEmpty, type OverpassElement } from "./overpass-utils";
@ -114,7 +114,7 @@ export default class OverpassLayer extends FeatureGroup {
marker: {
colour: this.options.markerColour!,
size: this.options.markerSize!,
symbol: getSymbolForTags(element.tags),
icon: getIconForTags(element.tags),
shape: this.options.markerShape!
},
raised: isHighlighted,

Wyświetl plik

@ -78,7 +78,7 @@ export default class SearchResultsLayer extends FeatureGroup {
marker: {
colour: this.options.markerColour!,
size: this.options.markerSize!,
symbol: result.icon || '',
icon: result.icon || '',
shape: this.options.markerShape!
},
pathOptions: this.options.pathOptions
@ -97,7 +97,7 @@ export default class SearchResultsLayer extends FeatureGroup {
marker: {
colour: this.options.markerColour!,
size: this.options.markerSize!,
symbol: result.icon || '',
icon: result.icon || '',
shape: this.options.markerShape!
}
}).bindTooltip(result.display_name, { ...tooltipOptions, offset: [ 20, 0 ] })

Wyświetl plik

@ -1,13 +1,13 @@
import type { Shape, Symbol } from "facilmap-types";
import type { Shape, Icon } from "facilmap-types";
import { makeTextColour, quoteHtml } from "facilmap-utils";
import { Icon, type IconOptions } from "leaflet";
import { Icon as LeafletIcon, type IconOptions } from "leaflet";
import { memoize } from "lodash-es";
import iconKeys, { coreIconKeys } from "virtual:icons:keys";
import rawIconsCore from "virtual:icons:core";
export { coreIconKeys as coreSymbolList };
export { coreIconKeys as coreIconList };
export const symbolList: string[] = Object.values(iconKeys).flat();
export const iconList: string[] = Object.values(iconKeys).flat();
export const RAINBOW_STOPS = `<stop offset="0" stop-color="red"/><stop offset="33%" stop-color="#ff0"/><stop offset="50%" stop-color="#0f0"/><stop offset="67%" stop-color="cyan"/><stop offset="100%" stop-color="blue"/>`;
@ -22,12 +22,12 @@ interface ShapeInfo {
* For a square marker, the bottom center would be [36, 18] and the center would be [18, 18]. */
base: [number, number];
/** The X and Y coordinates of the center of the marker. This is where the symbol will be inserted.
/** The X and Y coordinates of the center of the marker. This is where the icon will be inserted.
* For a square marker, the center would be [18, 18]. */
center: [number, number];
/** The height/width that inserted symbols should have. */
symbolSize: number;
/** The height/width that inserted icons should have. */
iconSize: number;
/** Scale factor for the shape itself. If this is 1, a markers that has its size set to 25 will be 25px high. */
scale: number;
@ -42,7 +42,7 @@ const MARKER_SHAPES: Record<Shape, ShapeInfo> = {
width: 26,
base: [13, 36],
center: [13, 13],
symbolSize: 18,
iconSize: 18,
scale: 1
},
"rectangle-marker": {
@ -50,7 +50,7 @@ const MARKER_SHAPES: Record<Shape, ShapeInfo> = {
width: 30,
base: [15, 36],
center: [15, 15],
symbolSize: 24,
iconSize: 24,
scale: 0.95
},
"circle": {
@ -58,7 +58,7 @@ const MARKER_SHAPES: Record<Shape, ShapeInfo> = {
width: 36,
base: [18, 18],
center: [18, 18],
symbolSize: 24,
iconSize: 24,
scale: 0.85
},
"rectangle": {
@ -66,7 +66,7 @@ const MARKER_SHAPES: Record<Shape, ShapeInfo> = {
width: 36,
base: [18, 18],
center: [18, 18],
symbolSize: 28,
iconSize: 28,
scale: 0.8
},
"diamond": {
@ -74,7 +74,7 @@ const MARKER_SHAPES: Record<Shape, ShapeInfo> = {
width: 36,
base: [18, 18],
center: [18, 18],
symbolSize: 18,
iconSize: 18,
scale: 0.9
},
"pentagon": {
@ -82,7 +82,7 @@ const MARKER_SHAPES: Record<Shape, ShapeInfo> = {
width: 38,
base: [19, 20],
center: [19, 20],
symbolSize: 20,
iconSize: 20,
scale: 0.85
},
"hexagon": {
@ -90,7 +90,7 @@ const MARKER_SHAPES: Record<Shape, ShapeInfo> = {
width: 32,
base: [16, 18],
center: [16, 18],
symbolSize: 22,
iconSize: 22,
scale: 0.9
},
"triangle": {
@ -98,7 +98,7 @@ const MARKER_SHAPES: Record<Shape, ShapeInfo> = {
width: 42,
base: [21, 24],
center: [21, 24],
symbolSize: 16,
iconSize: 16,
scale: 0.85
},
"triangle-down": {
@ -106,7 +106,7 @@ const MARKER_SHAPES: Record<Shape, ShapeInfo> = {
width: 40,
base: [20, 36],
center: [20, 12],
symbolSize: 16,
iconSize: 16,
scale: 0.85
},
"star": {
@ -114,7 +114,7 @@ const MARKER_SHAPES: Record<Shape, ShapeInfo> = {
width: 38,
base: [19, 18],
center: [19, 20],
symbolSize: 14,
iconSize: 14,
scale: 0.9
}
};
@ -147,41 +147,41 @@ let rawIconsExtra: (typeof import("virtual:icons:extra"))["default"];
/**
* Downloads the icons chunk to have them already downloaded the first time the icon code is needed.
*/
export async function preloadExtraSymbols(): Promise<void> {
export async function preloadExtraIcons(): Promise<void> {
if (!rawIconsExtra) {
rawIconsExtra = (await import("virtual:icons:extra")).default;
}
}
function symbolNeedsPreload(symbol: Symbol | undefined): boolean {
return !!symbol && symbolList.includes(symbol) && !coreIconKeys.includes(symbol);
function iconNeedsPreload(icon: Icon | undefined): boolean {
return !!icon && iconList.includes(icon) && !coreIconKeys.includes(icon);
}
export async function preloadSymbol(symbol: Symbol | undefined): Promise<void> {
if (symbolNeedsPreload(symbol)) {
await preloadExtraSymbols();
export async function preloadIcon(icon: Icon | undefined): Promise<void> {
if (iconNeedsPreload(icon)) {
await preloadExtraIcons();
}
}
export function isSymbolPreloaded(symbol: Symbol | undefined): boolean {
return !symbolNeedsPreload(symbol) || !!rawIconsExtra;
export function isIconPreloaded(icon: Icon | undefined): boolean {
return !iconNeedsPreload(icon) || !!rawIconsExtra;
}
function getRawSymbolCodeSync(symbol: Symbol): [set: string, code: string] {
if (coreIconKeys.includes(symbol)) {
const set = Object.keys(rawIconsCore).filter((i) => (rawIconsCore[i][symbol] != null))[0];
return [set, rawIconsCore[set][symbol]];
} else if (symbolList.includes(symbol)) {
const set = Object.keys(rawIconsExtra).filter((i) => (rawIconsExtra[i][symbol] != null))[0];
return [set, rawIconsExtra[set][symbol]];
function getRawIconCodeSync(icon: Icon): [set: string, code: string] {
if (coreIconKeys.includes(icon)) {
const set = Object.keys(rawIconsCore).filter((i) => (rawIconsCore[i][icon] != null))[0];
return [set, rawIconsCore[set][icon]];
} else if (iconList.includes(icon)) {
const set = Object.keys(rawIconsExtra).filter((i) => (rawIconsExtra[i][icon] != null))[0];
return [set, rawIconsExtra[set][icon]];
} else {
throw new Error(`Unknown icon ${symbol}.`);
throw new Error(`Unknown icon ${icon}.`);
}
}
export function getSymbolCodeSync(colour: string, size: number, symbol?: Symbol): string {
if(symbol && symbolList.includes(symbol)) {
const [set, code] = getRawSymbolCodeSync(symbol);
export function getIconCodeSync(colour: string, size: number, icon?: Icon): string {
if(icon && iconList.includes(icon)) {
const [set, code] = getRawIconCodeSync(icon);
if(set == "osmi") {
return `<g transform="scale(${size / sizes.osmi})">${code.replace(/#000/g, colour)}</g>`;
@ -199,107 +199,107 @@ export function getSymbolCodeSync(colour: string, size: number, symbol?: Symbol)
const content = code.replace(/^<svg [^>]*>/, "").replace(/<\/svg>$/, "");
return `<g transform="scale(${scale}) translate(${moveX}, ${moveY})" fill="${colour}">${content}</g>`;
} else if (symbol && symbol.length == 1) {
} else if (icon && icon.length == 1) {
try {
const offset = getLetterOffset(symbol);
const offset = getLetterOffset(icon);
return (
`<g transform="scale(${size / 25}) translate(${offset.x}, ${offset.y})">` +
`<text style="font-size: 25px; font-family: sans-serif; fill: ${colour}">${quoteHtml(symbol)}</text>` +
`<text style="font-size: 25px; font-family: sans-serif; fill: ${colour}">${quoteHtml(icon)}</text>` +
`</g>`
);
} catch (e) {
console.error("Error creating letter symbol.", e);
console.error("Error creating letter icon.", e);
}
}
return `<circle style="fill:${colour}" cx="${Math.floor(size / 2)}" cy="${Math.floor(size / 2)}" r="${Math.floor(size / 6)}" />`;
}
export async function getSymbolCode(colour: string, size: number, symbol?: Symbol): Promise<string> {
await preloadSymbol(symbol);
return getSymbolCodeSync(colour, size, symbol);
export async function getIconCode(colour: string, size: number, icon?: Icon): Promise<string> {
await preloadIcon(icon);
return getIconCodeSync(colour, size, icon);
}
export function getSymbolUrlSync(colour: string, height: number, symbol?: Symbol): string {
export function getIconUrlSync(colour: string, height: number, icon?: Icon): string {
const svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>` +
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${height}" height="${height}" viewbox="0 0 24 24" version="1.1">` +
getSymbolCodeSync(colour, 24, symbol) +
getIconCodeSync(colour, 24, icon) +
`</svg>`;
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
}
export async function getSymbolUrl(colour: string, height: number, symbol?: Symbol): Promise<string> {
await preloadSymbol(symbol);
return getSymbolUrlSync(colour, height, symbol);
export async function getIconUrl(colour: string, height: number, icon?: Icon): Promise<string> {
await preloadIcon(icon);
return getIconUrlSync(colour, height, icon);
}
export function getSymbolHtmlSync(colour: string, height: number | string, symbol?: Symbol): string {
export function getIconHtmlSync(colour: string, height: number | string, icon?: Icon): string {
return `<svg width="${height}" height="${height}" viewbox="0 0 24 24">` +
getSymbolCodeSync(colour, 24, symbol) +
getIconCodeSync(colour, 24, icon) +
`</svg>`;
}
export async function getSymbolHtml(colour: string, height: number | string, symbol?: Symbol): Promise<string> {
await preloadSymbol(symbol);
return getSymbolHtmlSync(colour, height, symbol);
export async function getIconHtml(colour: string, height: number | string, icon?: Icon): Promise<string> {
await preloadIcon(icon);
return getIconHtmlSync(colour, height, icon);
}
let idCounter = 0;
export function getMarkerCodeSync(colour: string, height: number, symbol?: Symbol, shape?: Shape, highlight = false): string {
export function getMarkerCodeSync(colour: string, height: number, icon?: Icon, shape?: Shape, highlight = false): string {
const borderColour = makeTextColour(colour, 0.3);
const id = `${idCounter++}`;
const colourCode = colour == "rainbow" ? `url(#fm-rainbow-${id})` : colour;
const shapeObj = (shape && MARKER_SHAPES[shape]) || MARKER_SHAPES[DEFAULT_SHAPE];
const symbolCode = getSymbolCodeSync(borderColour, shapeObj.symbolSize, symbol);
const translateX = `${Math.floor(shapeObj.center[0] - shapeObj.symbolSize / 2)}`;
const translateY = `${Math.floor(shapeObj.center[1] - shapeObj.symbolSize / 2)}`;
const iconCode = getIconCodeSync(borderColour, shapeObj.iconSize, icon);
const translateX = `${Math.floor(shapeObj.center[0] - shapeObj.iconSize / 2)}`;
const translateY = `${Math.floor(shapeObj.center[1] - shapeObj.iconSize / 2)}`;
return (
`<g transform="scale(${height / SHAPE_HEIGHT})">` +
(colour == "rainbow" ? `<defs><linearGradient id="fm-rainbow-${id}" x2="0" y2="100%">${RAINBOW_STOPS}</linearGradient></defs>` : '') +
`<path id="shape-${id}" style="stroke: ${borderColour}; stroke-width: ${highlight ? 6 : 2}; stroke-linecap: round; fill: ${colourCode}; clip-path: url(#clip-${id})" d="${quoteHtml(shapeObj.path)}"/>"/>` +
`<clipPath id="clip-${id}"><use xlink:href="#shape-${id}"/></clipPath>` + // Don't increase the size by increasing the border: https://stackoverflow.com/a/32162431/242365
`<g transform="translate(${translateX}, ${translateY})">${symbolCode}</g>` +
`<g transform="translate(${translateX}, ${translateY})">${iconCode}</g>` +
`</g>`
);
}
export async function getMarkerCode(colour: string, height: number, symbol?: Symbol, shape?: Shape, highlight = false): Promise<string> {
await preloadSymbol(symbol);
return getMarkerCodeSync(colour, height, symbol, shape, highlight);
export async function getMarkerCode(colour: string, height: number, icon?: Icon, shape?: Shape, highlight = false): Promise<string> {
await preloadIcon(icon);
return getMarkerCodeSync(colour, height, icon, shape, highlight);
}
export function getMarkerUrlSync(colour: string, height: number, symbol?: Symbol, shape?: Shape, highlight = false): string {
export function getMarkerUrlSync(colour: string, height: number, icon?: Icon, shape?: Shape, highlight = false): string {
const shapeObj = (shape && MARKER_SHAPES[shape]) || MARKER_SHAPES[DEFAULT_SHAPE];
const width = Math.ceil(height * shapeObj.width / SHAPE_HEIGHT);
return "data:image/svg+xml,"+encodeURIComponent(
`<?xml version="1.0" encoding="UTF-8" standalone="no"?>` +
`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}" viewBox="0 0 ${shapeObj.width} ${SHAPE_HEIGHT}" version="1.1">` +
getMarkerCodeSync(colour, SHAPE_HEIGHT, symbol, shape, highlight) +
getMarkerCodeSync(colour, SHAPE_HEIGHT, icon, shape, highlight) +
`</svg>`
);
}
export async function getMarkerUrl(colour: string, height: number, symbol?: Symbol, shape?: Shape, highlight = false): Promise<string> {
await preloadSymbol(symbol);
return getMarkerUrlSync(colour, height, symbol, shape, highlight);
export async function getMarkerUrl(colour: string, height: number, icon?: Icon, shape?: Shape, highlight = false): Promise<string> {
await preloadIcon(icon);
return getMarkerUrlSync(colour, height, icon, shape, highlight);
}
export function getMarkerHtmlSync(colour: string, height: number, symbol?: Symbol, shape?: Shape, highlight = false): string {
export function getMarkerHtmlSync(colour: string, height: number, icon?: Icon, shape?: Shape, highlight = false): string {
const shapeObj = (shape && MARKER_SHAPES[shape]) || MARKER_SHAPES[DEFAULT_SHAPE];
const width = Math.ceil(height * shapeObj.width / SHAPE_HEIGHT);
return (
`<svg width="${width}" height="${height}" viewBox="0 0 ${shapeObj.width} ${SHAPE_HEIGHT}">` +
getMarkerCodeSync(colour, SHAPE_HEIGHT, symbol, shape, highlight) +
getMarkerCodeSync(colour, SHAPE_HEIGHT, icon, shape, highlight) +
`</svg>`
);
}
export async function getMarkerHtml(colour: string, height: number, symbol?: Symbol, shape?: Shape, highlight = false): Promise<string> {
await preloadSymbol(symbol);
return getMarkerHtmlSync(colour, height, symbol, shape, highlight);
export async function getMarkerHtml(colour: string, height: number, icon?: Icon, shape?: Shape, highlight = false): Promise<string> {
await preloadIcon(icon);
return getMarkerHtmlSync(colour, height, icon, shape, highlight);
}
export const TRANSPARENT_IMAGE_URL = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E";
@ -313,7 +313,7 @@ declare global {
/**
* A Leaflet icon that accepts a promise for its URL and will update the image src when the promise is resolved.
*/
export class AsyncIcon extends Icon {
export class AsyncIcon extends LeafletIcon {
private _asyncIconUrl?: Promise<string>;
constructor(options: Omit<IconOptions, "iconUrl"> & { iconUrl: string | Promise<string> }) {
@ -351,13 +351,13 @@ export class AsyncIcon extends Icon {
}
}
export function getMarkerIcon(colour: string, height: number, symbol?: Symbol, shape?: Shape, highlight = false): Icon {
export function getMarkerIcon(colour: string, height: number, icon?: Icon, shape?: Shape, highlight = false): LeafletIcon {
const shapeObj = (shape && MARKER_SHAPES[shape]) || MARKER_SHAPES[DEFAULT_SHAPE];
const scale = shapeObj.scale * height / SHAPE_HEIGHT;
const result = new AsyncIcon({
iconUrl: (
isSymbolPreloaded(symbol) ? getMarkerUrlSync(colour, height, symbol, shape, highlight)
: getMarkerUrl(colour, height, symbol, shape, highlight)
isIconPreloaded(icon) ? getMarkerUrlSync(colour, height, icon, shape, highlight)
: getMarkerUrl(colour, height, icon, shape, highlight)
),
iconSize: [Math.round(shapeObj.width*scale), Math.round(SHAPE_HEIGHT*scale)],
iconAnchor: [Math.round(shapeObj.base[0]*scale), Math.round(shapeObj.base[1]*scale)],
@ -376,9 +376,9 @@ const RELEVANT_TAGS = [
"plant:source"
];
export function getSymbolForTags(tags: Record<string, string>): Symbol {
export function getIconForTags(tags: Record<string, string>): Icon {
const tagWords = Object.entries(tags).flatMap(([k, v]) => (RELEVANT_TAGS.includes(k) ? v.split(/_:/) : []));
let result: Symbol = "";
let result: Icon = "";
let resultMatch: number = 0;
for (const icon of iconKeys.osmi) {
const iconWords = icon.split("_");

Some files were not shown because too many files have changed in this diff Show More