From 6b01a2093c8b4922f43ad37465baace849c47fbc Mon Sep 17 00:00:00 2001 From: Candid Dauth Date: Fri, 7 Apr 2023 18:39:59 +0200 Subject: [PATCH 001/198] Upgrade dependencies, migrate to ESM + Vite for server/types/utils/client --- .eslintrc.js | 7 +- client/package.json | 26 +- client/src/client.ts | 2 +- client/tsconfig.json | 5 +- client/vite.config.ts | 19 + client/webpack.config.js | 53 - frontend/package.json | 69 +- .../src/lib/components/edit-line/edit-line.ts | 2 +- .../lib/components/edit-marker/edit-marker.ts | 2 +- .../edit-type/edit-type-dropdown.ts | 2 +- .../src/lib/components/edit-type/edit-type.ts | 2 +- .../src/lib/components/history/history.ts | 2 +- .../components/overpass-form/overpass-form.ts | 2 +- .../components/pad-settings/pad-settings.ts | 2 +- .../lib/components/route-form/route-form.ts | 2 +- .../search-results/search-results.ts | 2 +- .../ui/elevation-stats/elevation-stats.ts | 2 +- .../ui/elevation-stats/elevation-stats.vue | 2 +- .../ui/prerendered-list/prerendered-list.ts | 2 +- .../components/ui/route-mode/route-mode.ts | 6 +- .../components/ui/shape-field/shape-field.ts | 2 +- .../ui/symbol-field/symbol-field.ts | 2 +- frontend/src/lib/utils/box-selection.ts | 2 +- frontend/src/lib/utils/search.ts | 2 +- frontend/src/lib/utils/storage.ts | 2 +- frontend/src/lib/utils/utils.ts | 2 +- leaflet/download-icons.ts | 19 +- leaflet/example.html | 58 +- leaflet/icontest.html | 14 +- leaflet/package.json | 81 +- leaflet/rollup-icons.ts | 36 + leaflet/src/index.ts | 1 + leaflet/src/lines/lines-layer.ts | 2 +- leaflet/src/markers/marker-layer.ts | 2 +- leaflet/src/markers/markers-layer.ts | 2 +- leaflet/src/overpass/overpass-layer.ts | 4 +- leaflet/src/overpass/overpass-utils.ts | 2 +- leaflet/src/routes/route-drag-handler.ts | 2 +- leaflet/src/routes/route-layer.ts | 2 +- leaflet/src/search/search-result-geojson.ts | 2 +- leaflet/src/search/search-results-layer.ts | 2 +- leaflet/src/shims.d.ts | 4 + leaflet/src/utils/icons.ts | 23 +- leaflet/src/views/views.ts | 2 +- leaflet/tsconfig.json | 6 +- leaflet/vite.config.ts | 29 + leaflet/webpack.config.ts | 114 - package.json | 9 +- server/package.json | 87 +- server/src/database/database.ts | 30 +- server/src/database/helpers.ts | 16 +- server/src/database/history.ts | 24 +- server/src/database/line.ts | 63 +- server/src/database/marker.ts | 28 +- server/src/database/meta.ts | 6 +- server/src/database/migrations.ts | 14 +- server/src/database/pad.ts | 24 +- server/src/database/route.ts | 27 +- server/src/database/search.ts | 6 +- server/src/database/type.ts | 42 +- server/src/database/view.ts | 24 +- server/src/export/geojson.ts | 10 +- server/src/export/gpx.ts | 11 +- server/src/export/table.ts | 8 +- server/src/geoip.ts | 15 +- server/src/routing/ors.ts | 12 +- server/src/routing/osrm.ts | 6 +- server/src/routing/routing.ts | 12 +- server/src/search.ts | 12 +- server/src/server.ts | 8 +- server/src/socket.ts | 24 +- server/src/utils/__tests__/streams.test.ts | 2 +- server/src/utils/__tests__/utils.test.ts | 2 +- server/src/utils/events.ts | 26 +- server/src/utils/utils.ts | 2 +- server/src/webserver.ts | 15 +- server/tsconfig.json | 2 +- types/package.json | 7 +- types/src/events.ts | 14 +- types/src/geoJson.ts | 10 +- types/src/historyEntry.ts | 12 +- types/src/index.ts | 24 +- types/src/line.ts | 4 +- types/src/marker.ts | 4 +- types/src/padData.ts | 4 +- types/src/route.ts | 4 +- types/src/socket.ts | 18 +- types/src/type.ts | 4 +- types/src/view.ts | 4 +- types/tsconfig.json | 2 +- utils/package.json | 45 +- utils/src/dom.ts | 12 - utils/src/filter.ts | 4 +- utils/src/format.ts | 42 +- utils/src/index.ts | 13 +- utils/tsconfig.json | 2 +- yarn.lock | 8168 ++++++++--------- 97 files changed, 4532 insertions(+), 5062 deletions(-) create mode 100644 client/vite.config.ts delete mode 100644 client/webpack.config.js create mode 100644 leaflet/rollup-icons.ts create mode 100644 leaflet/src/shims.d.ts create mode 100644 leaflet/vite.config.ts delete mode 100644 leaflet/webpack.config.ts delete mode 100644 utils/src/dom.ts diff --git a/.eslintrc.js b/.eslintrc.js index a07ecbed..e5e3a9cb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,7 +8,7 @@ module.exports = { }, rules: { "@typescript-eslint/explicit-module-boundary-types": ["warn", { "allowArgumentsExplicitlyTypedAsAny": true }], - "import/no-unresolved": ["error", { "ignore": [ "geojson" ], "caseSensitive": true }], + "import/no-unresolved": ["error", { "ignore": [ "geojson", "custom:icons" ], "caseSensitive": true }], "import/no-extraneous-dependencies": ["error"], "@typescript-eslint/no-unused-vars": ["warn", { "args": "none" }], "import/no-named-as-default": ["warn"], @@ -75,5 +75,10 @@ module.exports = { "require-yield": ["error"], "use-isnan": ["error"], "valid-typeof": ["error"] + }, + "settings": { + "import/resolver": { + "typescript": {} + }, } }; \ No newline at end of file diff --git a/client/package.json b/client/package.json index 47d73d2c..72c4255b 100644 --- a/client/package.json +++ b/client/package.json @@ -14,7 +14,9 @@ }, "license": "AGPL-3.0", "author": "Candid Dauth ", - "main": "./dist/client.js", + "main": "./dist/client.mjs", + "type": "module", + "types": "./dist/client.d.ts", "repository": { "type": "git", "url": "https://github.com/FacilMap/facilmap.git" @@ -26,23 +28,21 @@ "tsconfig.json" ], "scripts": { - "build": "webpack", - "watch": "webpack --watch", + "build": "vite build", "clean": "rimraf dist", - "dev-server": "webpack-dev-server --mode development" + "dev-server": "vite" }, "dependencies": { "facilmap-types": "3.4.0", - "socket.io-client": "^4.1.2" + "socket.io-client": "^4.6.1" }, "devDependencies": { - "@types/geojson": "^7946.0.7", - "rimraf": "^3.0.2", - "source-map-loader": "^3.0.0", - "ts-loader": "^9.2.2", - "typescript": "^4.1.3", - "webpack": "^5.37.1", - "webpack-bundle-analyzer": "^4.4.2", - "webpack-cli": "^4.2.0" + "@types/geojson": "^7946.0.10", + "@types/rollup-plugin-auto-external": "^2.0.2", + "rimraf": "^4.4.1", + "rollup-plugin-auto-external": "^2.0.0", + "typescript": "^5.0.3", + "vite": "^4.2.1", + "vite-plugin-dts": "^2.2.0" } } diff --git a/client/src/client.ts b/client/src/client.ts index cefc3ac3..2fdffdd2 100644 --- a/client/src/client.ts +++ b/client/src/client.ts @@ -540,7 +540,7 @@ export default class Client> { try { const obj = await this._emit("setPadId", padId); this._receiveMultiple(obj); - } catch(err) { + } catch(err: any) { this._set(this, 'serverError', err); this._simulateEvent("serverError", err); throw err; diff --git a/client/tsconfig.json b/client/tsconfig.json index 17353e29..fa7ca2c6 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es3", + "target": "esnext", "esModuleInterop": true, "strict": true, "sourceMap": true, @@ -8,7 +8,8 @@ "outDir": "dist", "moduleResolution": "node", "noErrorTruncation": true, - "skipLibCheck": true + "skipLibCheck": true, + "module": "esnext" }, "include": ["src/**/*"] } \ No newline at end of file diff --git a/client/vite.config.ts b/client/vite.config.ts new file mode 100644 index 00000000..e79e34e5 --- /dev/null +++ b/client/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite'; +import dtsPlugin from 'vite-plugin-dts'; +import autoExternalPlugin from 'rollup-plugin-auto-external'; + +export default defineConfig({ + plugins: [ + dtsPlugin(), + autoExternalPlugin() + ], + build: { + sourcemap: true, + minify: false, + lib: { + entry: './src/client.ts', + fileName: () => 'client.mjs', + formats: ['es'] + } + } +}); diff --git a/client/webpack.config.js b/client/webpack.config.js deleted file mode 100644 index dd8012b9..00000000 --- a/client/webpack.config.js +++ /dev/null @@ -1,53 +0,0 @@ -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; - -module.exports = (env, argv) => { - const isDev = argv.mode == "development"; - - return { - entry: `${__dirname}/src/client.ts`, - output: { - filename: "client.js", - path: __dirname + "/dist/", - library: ["FacilMap", "Client"], - libraryTarget: "umd", - libraryExport: "default" - }, - resolve: { - extensions: [ ".js", ".ts" ] - }, - mode: isDev ? "development" : "production", - devtool: isDev ? "eval-source-map" : "source-map", - module: { - rules: [ - { test: /\.js$/, enforce: "pre", use: ["source-map-loader"] }, - { - resource: { and: [ /\.ts/, [ - __dirname + "/src/" - ] ] }, - loader: 'ts-loader' - }, - { - test: /\.css$/, - use: [ 'style-loader', 'css-loader' ] - } - ] - }, - externals: { - "socket.io-client": { - commonjs: 'socket.io-client', - commonjs2: 'socket.io-client', - amd: 'socket.io-client', - root: 'io' - } - }, - plugins: [ - //new BundleAnalyzerPlugin() - ], - devServer: { - publicPath: "/dist", - disableHostCheck: true, - injectClient: false, // https://github.com/webpack/webpack-dev-server/issues/2484 - port: 8083 - } - }; -}; diff --git a/frontend/package.json b/frontend/package.json index 271ef802..48dc4239 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,38 +33,38 @@ "dev-server": "webpack serve --config-name app --mode development" }, "dependencies": { - "@tmcw/togeojson": "^4.3.0", + "@tmcw/togeojson": "^5.6.0", "blob": "^0.1.0", "bootstrap": "4", - "bootstrap-touchspin": "^4.3.0", - "bootstrap-vue": "^2.21.1", - "clipboard": "^2.0.8", - "copy-to-clipboard": "^3.3.1", - "decode-uri-component": "^0.2.0", - "domutils": "^2.5.0", + "bootstrap-touchspin": "^4.6.0", + "bootstrap-vue": "^2.23.1", + "clipboard": "^2.0.11", + "copy-to-clipboard": "^3.3.3", + "decode-uri-component": "^0.4.1", + "domutils": "^3.0.1", "facilmap-client": "3.4.0", "facilmap-leaflet": "3.4.0", "facilmap-types": "3.4.0", "facilmap-utils": "3.4.0", "file-saver": "^2.0.5", "hammerjs": "^2.0.8", - "jquery": "^3.6.0", - "jquery-ui": "^1.12.1", - "leaflet": "^1.7.1", - "leaflet-draggable-lines": "^1.0.11", + "jquery": "^3.6.4", + "jquery-ui": "^1.13.2", + "leaflet": "^1.9.3", + "leaflet-draggable-lines": "^1.2.1", "leaflet-graphicscale": "^0.0.2", - "leaflet-mouse-position": "^1.0.4", + "leaflet-mouse-position": "^1.2.0", "leaflet.heightgraph": "^1.4.0", - "leaflet.locatecontrol": "^0.73.0", - "lodash": "^4.17.21", + "leaflet.locatecontrol": "^0.79.0", + "lodash-es": "^4.17.21", "markdown": "^0.5.0", - "osmtogeojson": "^3.0.0-beta.4", + "osmtogeojson": "^3.0.0-beta.5", "pluralize": "^8.0.0", "popper-max-size-modifier": "^0.2.0", "portal-vue": "^2.1.7", "tablesorter": "^2.31.3", - "vee-validate": "^3.4.5", - "vue": "^2.6.12", + "vee-validate": "^3.4.15", + "vue": "^2.7.14", "vue-class-component": "^7.2.6", "vue-color": "^2.8.1", "vue-nonreactive": "^0.1.0", @@ -74,15 +74,16 @@ "devDependencies": { "@types/copy-webpack-plugin": "^8.0.0", "@types/decode-uri-component": "^0.2.0", - "@types/file-saver": "^2.0.1", - "@types/hammerjs": "^2.0.39", - "@types/jest": "^26.0.21", - "@types/jquery": "^3.5.5", - "@types/leaflet": "^1.7.0", - "@types/leaflet-mouse-position": "^1.2.0", - "@types/leaflet.locatecontrol": "^0.60.7", + "@types/file-saver": "^2.0.5", + "@types/hammerjs": "^2.0.41", + "@types/jest": "^29.5.0", + "@types/jquery": "^3.5.16", + "@types/leaflet": "^1.9.3", + "@types/leaflet-mouse-position": "^1.2.1", + "@types/leaflet.locatecontrol": "^0.74.1", + "@types/lodash-es": "^4.17.7", "@types/pluralize": "^0.0.29", - "@types/scrollparent": "^2.0.0", + "@types/scrollparent": "^2.0.1", "@types/webpack-bundle-analyzer": "^4.4.0", "@types/webpack-dev-server": "^3.11.4", "@types/webpack-node-externals": "^2.5.1", @@ -91,18 +92,18 @@ "ejs-compiled-loader": "^3.1.0", "html-loader": "^2.1.2", "html-webpack-plugin": "^5.3.1", - "jest": "^26.6.3", - "mini-svg-data-uri": "^1.3.3", + "jest": "^29.5.0", + "mini-svg-data-uri": "^1.4.4", "sass": "^1.34.0", "sass-loader": "^11.1.1", - "source-map-loader": "^3.0.0", + "source-map-loader": "^3.0.2", "style-loader": "^2.0.0", - "svgo": "^2.2.2", - "ts-jest": "^26.5.4", - "ts-loader": "^9.2.2", - "ts-node": "^9.1.1", - "typescript": "^4.2.3", - "vue-template-compiler": "^2.6.12", + "svgo": "^3.0.2", + "ts-jest": "^29.1.0", + "ts-loader": "^9.4.2", + "ts-node": "^10.9.1", + "typescript": "^5.0.3", + "vue-template-compiler": "^2.7.14", "vue-template-loader": "^1.1.0", "webpack": "^5.37.1", "webpack-bundle-analyzer": "^4.4.2", diff --git a/frontend/src/lib/components/edit-line/edit-line.ts b/frontend/src/lib/components/edit-line/edit-line.ts index 8e522ea7..a9f6b694 100644 --- a/frontend/src/lib/components/edit-line/edit-line.ts +++ b/frontend/src/lib/components/edit-line/edit-line.ts @@ -5,7 +5,7 @@ import { Client, InjectClient, InjectContext } from "../../utils/decorators"; import { Component, Prop, Watch } from "vue-property-decorator"; import { canControl, IdType, mergeObject } from "../../utils/utils"; import { clone } from "facilmap-utils"; -import { isEqual, omit } from "lodash"; +import { isEqual, omit } from "lodash-es"; import { showErrorToast } from "../../utils/toasts"; import FormModal from "../ui/form-modal/form-modal"; import { ValidationProvider } from "vee-validate"; diff --git a/frontend/src/lib/components/edit-marker/edit-marker.ts b/frontend/src/lib/components/edit-marker/edit-marker.ts index 84045479..308159e0 100644 --- a/frontend/src/lib/components/edit-marker/edit-marker.ts +++ b/frontend/src/lib/components/edit-marker/edit-marker.ts @@ -5,7 +5,7 @@ import { Client, InjectClient, InjectContext } from "../../utils/decorators"; import { Component, Prop, Watch } from "vue-property-decorator"; import { canControl, IdType, mergeObject } from "../../utils/utils"; import { clone } from "facilmap-utils"; -import { isEqual } from "lodash"; +import { isEqual } from "lodash-es"; import { showErrorToast } from "../../utils/toasts"; import FormModal from "../ui/form-modal/form-modal"; import { ValidationProvider } from "vee-validate"; diff --git a/frontend/src/lib/components/edit-type/edit-type-dropdown.ts b/frontend/src/lib/components/edit-type/edit-type-dropdown.ts index ebf1d362..548cfff5 100644 --- a/frontend/src/lib/components/edit-type/edit-type-dropdown.ts +++ b/frontend/src/lib/components/edit-type/edit-type-dropdown.ts @@ -4,7 +4,7 @@ import { Component, Prop, Ref, Watch } from "vue-property-decorator"; import { Field, FieldOptionUpdate, FieldUpdate, Line, Marker, Type } from "facilmap-types"; import { clone } from "facilmap-utils"; import { canControl, mergeObject } from "../../utils/utils"; -import { isEqual } from "lodash"; +import { isEqual } from "lodash-es"; import { showErrorToast } from "../../utils/toasts"; import ColourField from "../ui/colour-field/colour-field"; import draggable from "vuedraggable"; diff --git a/frontend/src/lib/components/edit-type/edit-type.ts b/frontend/src/lib/components/edit-type/edit-type.ts index 756b066d..af4c45c4 100644 --- a/frontend/src/lib/components/edit-type/edit-type.ts +++ b/frontend/src/lib/components/edit-type/edit-type.ts @@ -6,7 +6,7 @@ import { Field, ID, Line, Marker, Type, TypeUpdate } from "facilmap-types"; import { clone } from "facilmap-utils"; import { canControl, IdType } from "../../utils/utils"; import { mergeTypeObject } from "./edit-type-utils"; -import { isEqual } from "lodash"; +import { isEqual } from "lodash-es"; import { showErrorToast } from "../../utils/toasts"; import FormModal from "../ui/form-modal/form-modal"; import { extend, ValidationProvider } from "vee-validate"; diff --git a/frontend/src/lib/components/history/history.ts b/frontend/src/lib/components/history/history.ts index 211e7b04..d115f34a 100644 --- a/frontend/src/lib/components/history/history.ts +++ b/frontend/src/lib/components/history/history.ts @@ -5,7 +5,7 @@ import { Client, InjectClient, InjectContext } from "../../utils/decorators"; import { showErrorToast } from "../../utils/toasts"; import { getLabelsForHistoryEntry, HistoryEntryLabels } from "./history-utils"; import { HistoryEntry } from "facilmap-types"; -import { orderBy } from "lodash"; +import { orderBy } from "lodash-es"; import Icon from "../ui/icon/icon"; import "./history.scss"; import { Context } from "../facilmap/facilmap"; diff --git a/frontend/src/lib/components/overpass-form/overpass-form.ts b/frontend/src/lib/components/overpass-form/overpass-form.ts index 4b39bb49..5bf89b42 100644 --- a/frontend/src/lib/components/overpass-form/overpass-form.ts +++ b/frontend/src/lib/components/overpass-form/overpass-form.ts @@ -5,7 +5,7 @@ import { InjectMapComponents, InjectMapContext } from "../../utils/decorators"; import { getOverpassPreset, OverpassPreset, overpassPresets, validateOverpassQuery } from "facilmap-leaflet"; import { MapComponents, MapContext } from "../leaflet-map/leaflet-map"; import "./overpass-form.scss"; -import { debounce } from "lodash"; +import { debounce } from "lodash-es"; @WithRender @Component({ }) diff --git a/frontend/src/lib/components/pad-settings/pad-settings.ts b/frontend/src/lib/components/pad-settings/pad-settings.ts index c29b25f8..60e86a21 100644 --- a/frontend/src/lib/components/pad-settings/pad-settings.ts +++ b/frontend/src/lib/components/pad-settings/pad-settings.ts @@ -6,7 +6,7 @@ import { PadData, PadDataCreate, PadDataUpdate } from "facilmap-types"; import { clone, generateRandomPadId } from "facilmap-utils"; import { Client, InjectClient, InjectContext } from "../../utils/decorators"; import { mergeObject } from "../../utils/utils"; -import { isEqual } from "lodash"; +import { isEqual } from "lodash-es"; import copyToClipboard from "copy-to-clipboard"; import FormModal from "../ui/form-modal/form-modal"; import { showErrorToast } from "../../utils/toasts"; diff --git a/frontend/src/lib/components/route-form/route-form.ts b/frontend/src/lib/components/route-form/route-form.ts index dc844423..a99f60d8 100644 --- a/frontend/src/lib/components/route-form/route-form.ts +++ b/frontend/src/lib/components/route-form/route-form.ts @@ -15,7 +15,7 @@ import { latLng, LatLng } from "leaflet"; import draggable from "vuedraggable"; import RouteMode from "../ui/route-mode/route-mode"; import DraggableLines from "leaflet-draggable-lines"; -import { throttle } from "lodash"; +import { throttle } from "lodash-es"; import ElevationStats from "../ui/elevation-stats/elevation-stats"; import ElevationPlot from "../ui/elevation-plot/elevation-plot"; import { saveAs } from 'file-saver'; diff --git a/frontend/src/lib/components/search-results/search-results.ts b/frontend/src/lib/components/search-results/search-results.ts index 3064e307..c6ae5963 100644 --- a/frontend/src/lib/components/search-results/search-results.ts +++ b/frontend/src/lib/components/search-results/search-results.ts @@ -14,7 +14,7 @@ import { showErrorToast } from "../../utils/toasts"; import { lineStringToTrackPoints, mapSearchResultToType } from "./utils"; import { isFileResult, isLineResult, isMapResult, isMarkerResult, typeExists } from "../../utils/search"; import { combineZoomDestinations, flyTo, getZoomDestinationForMapResult, getZoomDestinationForResults, getZoomDestinationForSearchResult } from "../../utils/zoom"; -import { mapValues, pickBy, uniq } from "lodash"; +import { mapValues, pickBy, uniq } from "lodash-es"; import FormModal from "../ui/form-modal/form-modal"; import StringMap from "../../utils/string-map"; import { getUniqueId } from "../../utils/utils"; diff --git a/frontend/src/lib/components/ui/elevation-stats/elevation-stats.ts b/frontend/src/lib/components/ui/elevation-stats/elevation-stats.ts index 2ffb1b7e..ff5a5bb9 100644 --- a/frontend/src/lib/components/ui/elevation-stats/elevation-stats.ts +++ b/frontend/src/lib/components/ui/elevation-stats/elevation-stats.ts @@ -1,7 +1,7 @@ import Vue from "vue"; import { Component, Prop } from "vue-property-decorator"; import WithRender from "./elevation-stats.vue"; -import { sortBy } from "lodash"; +import { sortBy } from "lodash-es"; import { LineWithTrackPoints, RouteWithTrackPoints } from "facilmap-client"; import { createElevationStats } from "../../../utils/heightgraph"; import Icon from "../icon/icon"; diff --git a/frontend/src/lib/components/ui/elevation-stats/elevation-stats.vue b/frontend/src/lib/components/ui/elevation-stats/elevation-stats.vue index e1afd351..ec9e6197 100644 --- a/frontend/src/lib/components/ui/elevation-stats/elevation-stats.vue +++ b/frontend/src/lib/components/ui/elevation-stats/elevation-stats.vue @@ -2,7 +2,7 @@ {{route.ascent}} m / {{route.descent}} m - +
Total ascent
diff --git a/frontend/src/lib/components/ui/prerendered-list/prerendered-list.ts b/frontend/src/lib/components/ui/prerendered-list/prerendered-list.ts index ceee9300..cc1c4e12 100644 --- a/frontend/src/lib/components/ui/prerendered-list/prerendered-list.ts +++ b/frontend/src/lib/components/ui/prerendered-list/prerendered-list.ts @@ -1,7 +1,7 @@ import WithRender from "./prerendered-list.vue"; import Vue from "vue"; import { Component, Prop, Watch } from "vue-property-decorator"; -import { mapValues } from "lodash"; +import { mapValues } from "lodash-es"; import { quoteHtml } from "facilmap-utils"; @WithRender diff --git a/frontend/src/lib/components/ui/route-mode/route-mode.ts b/frontend/src/lib/components/ui/route-mode/route-mode.ts index f47aa786..1db752df 100644 --- a/frontend/src/lib/components/ui/route-mode/route-mode.ts +++ b/frontend/src/lib/components/ui/route-mode/route-mode.ts @@ -25,9 +25,9 @@ const constants: { modes: ["car", "bicycle", "pedestrian", ""], modeIcon: { - car: "car-alt", - bicycle: "biking", - pedestrian: "walking", + car: "car", + bicycle: "person-biking", + pedestrian: "person-walking", "": "slash" }, diff --git a/frontend/src/lib/components/ui/shape-field/shape-field.ts b/frontend/src/lib/components/ui/shape-field/shape-field.ts index d7336f68..8d01d4e5 100644 --- a/frontend/src/lib/components/ui/shape-field/shape-field.ts +++ b/frontend/src/lib/components/ui/shape-field/shape-field.ts @@ -9,7 +9,7 @@ import Picker from "../picker/picker"; import { Shape } from "facilmap-types"; import { extend } from "vee-validate"; import { arrowNavigation } from "../../../utils/ui"; -import { keyBy, mapValues } from "lodash"; +import { keyBy, mapValues } from "lodash-es"; import PrerenderedList from "../prerendered-list/prerendered-list"; extend("shape", { diff --git a/frontend/src/lib/components/ui/symbol-field/symbol-field.ts b/frontend/src/lib/components/ui/symbol-field/symbol-field.ts index b4cd73f7..34a82b0c 100644 --- a/frontend/src/lib/components/ui/symbol-field/symbol-field.ts +++ b/frontend/src/lib/components/ui/symbol-field/symbol-field.ts @@ -7,7 +7,7 @@ import Icon from "../icon/icon"; import Picker from "../picker/picker"; import { extend } from "vee-validate"; import { arrowNavigation } from "../../../utils/ui"; -import { keyBy, mapValues, pickBy } from "lodash"; +import { keyBy, mapValues, pickBy } from "lodash-es"; import PrerenderedList from "../prerendered-list/prerendered-list"; extend("symbol", { diff --git a/frontend/src/lib/utils/box-selection.ts b/frontend/src/lib/utils/box-selection.ts index 74e79fb5..844caed0 100644 --- a/frontend/src/lib/utils/box-selection.ts +++ b/frontend/src/lib/utils/box-selection.ts @@ -1,5 +1,5 @@ import { Evented, LatLngBounds, Map } from "leaflet"; -import { throttle } from "lodash"; +import { throttle } from "lodash-es"; export default class BoxSelection extends Map.BoxZoom { diff --git a/frontend/src/lib/utils/search.ts b/frontend/src/lib/utils/search.ts index 1171357e..afd4a9a2 100644 --- a/frontend/src/lib/utils/search.ts +++ b/frontend/src/lib/utils/search.ts @@ -1,6 +1,6 @@ import { FindOnMapResult, SearchResult } from "facilmap-types"; import { numberKeys } from "facilmap-utils"; -import { isEqual } from "lodash"; +import { isEqual } from "lodash-es"; import { Client } from "./decorators"; import { FileResult, FileResultObject } from "./files"; diff --git a/frontend/src/lib/utils/storage.ts b/frontend/src/lib/utils/storage.ts index c3fb848a..2c27d44a 100644 --- a/frontend/src/lib/utils/storage.ts +++ b/frontend/src/lib/utils/storage.ts @@ -1,5 +1,5 @@ import { PadId } from "facilmap-types"; -import { isEqual } from "lodash"; +import { isEqual } from "lodash-es"; import Vue from "vue"; export interface Bookmark { diff --git a/frontend/src/lib/utils/utils.ts b/frontend/src/lib/utils/utils.ts index 144ca1d7..650bcf9d 100644 --- a/frontend/src/lib/utils/utils.ts +++ b/frontend/src/lib/utils/utils.ts @@ -1,5 +1,5 @@ import Vue from "vue"; -import { isEqual } from "lodash"; +import { isEqual } from "lodash-es"; import { clone } from "facilmap-utils"; import { Field, Line, Marker, Type } from "facilmap-types"; diff --git a/leaflet/download-icons.ts b/leaflet/download-icons.ts index c58e58c5..ff7fe368 100644 --- a/leaflet/download-icons.ts +++ b/leaflet/download-icons.ts @@ -1,12 +1,13 @@ import fetch from "node-fetch"; -import yauzl, { Entry } from "yauzl"; +import * as yauzl from "yauzl"; import util from "util"; -import svgo from "svgo"; +import * as svgo from "svgo"; import cheerio from "cheerio"; import highland from "highland"; import fs from "fs"; +import { fileURLToPath } from 'url'; -const outDir = `${__dirname}/assets/icons/osmi`; +const outDir = fileURLToPath(new URL('./assets/icons/osmi', import.meta.url)); function streamEachPromise(stream: Highland.Stream, handle: (item: T) => Promise | void): Promise { return new Promise((resolve, reject) => { @@ -20,8 +21,8 @@ function streamEachPromise(stream: Highland.Stream, handle: (item: T) => P async function updateIcons() { const buffer = await fetch("https://github.com/twain47/Open-SVG-Map-Icons/archive/master.zip").then((res) => res.buffer()); const zip = (await util.promisify(yauzl.fromBuffer)(buffer))!; - - const entryStream = highland((push) => { + + const entryStream = highland((push) => { zip.on("entry", (entry) => { push(null, entry); }); zip.on("error", (err) => { push(err); }); }).filter((entry) => entry.fileName.endsWith(".svg")); @@ -29,15 +30,15 @@ async function updateIcons() { await streamEachPromise(entryStream, async (entry) => { const readStream = (await util.promisify(zip.openReadStream.bind(zip))(entry))!; const content = await highland(readStream).collect().map((buffers) => Buffer.concat(buffers)).toPromise(Promise); - const cleanedContent = await cleanIcon(content.toString()); + const cleanedContent = cleanIcon(content.toString()); const outFile = `${outDir}/${entry.fileName.split('/').slice(-2).join('_')}`; await fs.promises.writeFile(outFile, Buffer.from(cleanedContent)); }); } -async function cleanIcon(icon: string): Promise { - const optimized = await new svgo().optimize(icon); - +function cleanIcon(icon: string): string { + const optimized = svgo.optimize(icon); + const $ = cheerio.load(optimized.data, { xmlMode: true }); diff --git a/leaflet/example.html b/leaflet/example.html index 62710380..d1824e45 100644 --- a/leaflet/example.html +++ b/leaflet/example.html @@ -3,7 +3,7 @@ facilmap-leaflet Example - + \ No newline at end of file diff --git a/frontend/src/lib/components/click-marker/click-marker.ts b/frontend/src/lib/components/click-marker/click-marker.ts deleted file mode 100644 index 8d4e0f8a..00000000 --- a/frontend/src/lib/components/click-marker/click-marker.ts +++ /dev/null @@ -1,172 +0,0 @@ -import WithRender from "./click-marker.vue"; -import Vue from "vue"; -import { Component, Watch } from "vue-property-decorator"; -import { InjectClient, Client, InjectMapComponents, InjectMapContext, InjectContext } from "../../utils/decorators"; -import { MapComponents, MapContext } from "../leaflet-map/leaflet-map"; -import { LineCreate, MarkerCreate, Point, SearchResult, Type } from "facilmap-types"; -import { round } from "facilmap-utils"; -import { lineStringToTrackPoints, mapSearchResultToType } from "../search-results/utils"; -import { showErrorToast } from "../../utils/toasts"; -import { SearchResultsLayer } from "facilmap-leaflet"; -import SearchResultInfo from "../search-result-info/search-result-info"; -import Icon from "../ui/icon/icon"; -import { Util } from "leaflet"; -import StringMap from "../../utils/string-map"; -import { Portal } from "portal-vue"; -import { Context } from "../facilmap/facilmap"; - -@WithRender -@Component({ - components: { Icon, Portal, SearchResultInfo } -}) -export default class ClickMarker extends Vue { - - @InjectContext() context!: Context; - @InjectMapContext() mapContext!: MapContext; - @InjectMapComponents() mapComponents!: MapComponents; - @InjectClient() client!: Client; - - lastClick = 0; - - results: SearchResult[] = []; - layers!: SearchResultsLayer[]; // Don't make layer objects reactive - isAdding = false; - - mounted(): void { - this.layers = []; - this.mapContext.$on("fm-map-long-click", this.handleMapLongClick); - this.mapContext.$on("fm-open-selection", this.handleOpenSelection); - } - - beforeDestroy(): void { - this.mapContext.$off("fm-map-long-click", this.handleMapLongClick); - this.mapContext.$off("fm-open-selection", this.handleOpenSelection); - } - - get layerIds(): number[] { - return this.results.map((result, i) => { // Iterate files instead of layers because it is reactive - return Util.stamp(this.layers[i]); - }); - } - - @Watch("mapContext.selection") - handleSelectionChange(): void { - for (let i = this.results.length - 1; i >= 0; i--) { - if (!this.mapContext.selection.some((item) => item.type == "searchResult" && item.layerId == this.layerIds[i])) - this.close(this.results[i]); - } - } - - handleOpenSelection(): void { - for (let i = 0; i < this.layerIds.length; i++) { - if (this.mapContext.selection.some((item) => item.type == "searchResult" && item.layerId == this.layerIds[i])) { - this.mapContext.$emit("fm-search-box-show-tab", `fm${this.context.id}-click-marker-tab-${i}`); - break; - } - } - } - - async handleMapLongClick(pos: Point): Promise { - const now = Date.now(); - this.lastClick = now; - - const results = await this.client.find({ - query: `geo:${round(pos.lat, 5)},${round(pos.lon, 5)}?z=${this.mapContext.zoom}`, - loadUrls: false, - elevation: true - }); - - if (now !== this.lastClick) { - // There has been another click since the one we are reacting to. - return; - } - - if (results.length > 0) { - const layer = new SearchResultsLayer([results[0]]).addTo(this.mapComponents.map); - this.mapComponents.selectionHandler.addSearchResultLayer(layer); - - this.results.push(results[0]); - this.layers.push(layer); - - this.mapComponents.selectionHandler.setSelectedItems([{ type: "searchResult", result: results[0], layerId: Util.stamp(layer) }]); - - setTimeout(() => { - this.mapContext.$emit("fm-search-box-show-tab", `fm${this.context.id}-click-marker-tab-${this.results.length - 1}`); - }, 0); - } - } - - close(result: SearchResult): void { - const idx = this.results.indexOf(result); - if (idx == -1) - return; - - this.mapComponents.selectionHandler.removeSearchResultLayer(this.layers[idx]); - this.layers[idx].remove(); - this.results.splice(idx, 1); - this.layers.splice(idx, 1); - } - - clear(): void { - for (let i = this.results.length - 1; i >= 0; i--) - this.close(this.results[i]); - } - - async addToMap(result: SearchResult, type: Type): Promise { - this.$bvToast.hide(`fm${this.context.id}-click-marker-add-error`); - this.isAdding = true; - - try { - const obj: Partial & LineCreate> = { - name: result.short_name, - data: mapSearchResultToType(result, type) - }; - - if(type.type == "marker") { - const marker = await this.client.addMarker({ - ...obj, - lat: result.lat!, - lon: result.lon!, - typeId: type.id - }); - - this.mapComponents.selectionHandler.setSelectedItems([{ type: "marker", id: marker.id }], true); - } else if(type.type == "line") { - const trackPoints = lineStringToTrackPoints(result.geojson as any); - const line = await this.client.addLine({ - ...obj, - typeId: type.id, - routePoints: [trackPoints[0], trackPoints[trackPoints.length-1]], - trackPoints: trackPoints, - mode: "track" - }); - - this.mapComponents.selectionHandler.setSelectedItems([{ type: "line", id: line.id }], true); - } - - this.close(result); - } catch (err) { - showErrorToast(this, `fm${this.context.id}-click-marker-add-error`, "Error adding to map", err); - } finally { - this.isAdding = false; - } - } - - useAs(result: SearchResult, event: "fm-route-set-from" | "fm-route-add-via" | "fm-route-set-to"): void { - this.mapContext.$emit(event, result.short_name, [result], [], result); - this.mapContext.$emit("fm-search-box-show-tab", `fm${this.context.id}-route-form-tab`); - } - - useAsFrom(result: SearchResult): void { - this.useAs(result, "fm-route-set-from"); - } - - useAsVia(result: SearchResult): void { - this.useAs(result, "fm-route-add-via"); - } - - useAsTo(result: SearchResult): void { - this.useAs(result, "fm-route-set-to"); - } - -} \ No newline at end of file diff --git a/frontend/src/lib/components/click-marker/click-marker.vue b/frontend/src/lib/components/click-marker/click-marker.vue index 408e8114..98becf43 100644 --- a/frontend/src/lib/components/click-marker/click-marker.vue +++ b/frontend/src/lib/components/click-marker/click-marker.vue @@ -1,18 +1,195 @@ - - - - - - \ No newline at end of file + + + \ No newline at end of file diff --git a/frontend/src/lib/components/client.vue b/frontend/src/lib/components/client.vue new file mode 100644 index 00000000..915cc1ae --- /dev/null +++ b/frontend/src/lib/components/client.vue @@ -0,0 +1,172 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/lib/components/client/client.scss b/frontend/src/lib/components/client/client.scss deleted file mode 100644 index f0387e22..00000000 --- a/frontend/src/lib/components/client/client.scss +++ /dev/null @@ -1,5 +0,0 @@ -.fm-client-provider { - display: flex; - flex-direction: column; - flex-grow: 1; -} \ No newline at end of file diff --git a/frontend/src/lib/components/client/client.ts b/frontend/src/lib/components/client/client.ts deleted file mode 100644 index 432f00b6..00000000 --- a/frontend/src/lib/components/client/client.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Component, ProvideReactive, Watch } from "vue-property-decorator"; -import Vue from "vue"; -import FmClient from "facilmap-client"; -import "./client.scss"; -import WithRender from "./client.vue"; -import { PadData, PadId } from "facilmap-types"; -import PadSettings from "../pad-settings/pad-settings"; -import { Client, CLIENT_INJECT_KEY, InjectContext } from "../../utils/decorators"; -import StringMap from "../../utils/string-map"; -import storage from "../../utils/storage"; -import { Context } from "../facilmap/facilmap"; -import { showErrorToast, showToast } from "../../utils/toasts"; - -@WithRender -@Component({ - components: { PadSettings } -}) -export class ClientProvider extends Vue { - - @InjectContext() readonly context!: Context; - - @ProvideReactive(CLIENT_INJECT_KEY) client: Client | null = null; - - counter = 1; - newClient: Client | null = null; - createId: string | null = null; - - created(): void { - this.connect(); - } - - beforeDestroy(): void { - this.client?.disconnect(); - } - - async connect(): Promise { - const existingClient = this.newClient || this.client; - if (existingClient && existingClient.server == this.context.serverUrl && existingClient.padId == this.context.activePadId) - return; - - this.$bvToast.hide(`fm${this.context.id}-client-connecting`); - this.$bvToast.hide(`fm${this.context.id}-client-error`); - this.$bvToast.hide(`fm${this.context.id}-client-deleted`); - this.createId = null; - if (this.context.activePadId) - showToast(this, `fm${this.context.id}-client-connecting`, "Loading", "Loading map…", { spinner: true }); - else - showToast(this, `fm${this.context.id}-client-connecting`, "Connecting", "Connecting to server…", { spinner: true }); - - const client = new FmClient(this.context.serverUrl, this.context.activePadId); - client._set = Vue.set; - client._delete = Vue.delete; - client._encodeData = (data) => data.toObject(); - client._decodeData = (data) => new StringMap(data); - - this.newClient = client; - - let lastPadId: PadId | undefined = undefined; - let lastPadData: PadData | undefined = undefined; - - client.on("padData", () => { - for (const bookmark of storage.bookmarks) { - if (lastPadId && bookmark.id == lastPadId) - bookmark.id = client.padId!; - - if (lastPadData && lastPadData.id == bookmark.padId) - bookmark.padId = client.padData!.id; - - if (bookmark.padId == client.padData!.id) - bookmark.name = client.padData!.name; - } - - lastPadId = client.padId; - lastPadData = client.padData; - this.context.activePadId = client.padId; - this.context.activePadName = client.padData?.name; - }); - - client.on("deletePad", () => { - showToast(this, `fm${this.context.id}-client-deleted`, "Map deleted", "This map has been deleted.", { - variant: "danger", - actions: this.context.interactive ? [ - { - label: "Close map", - href: this.context.baseUrl, - onClick: (e) => { - if (!e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey) { - e.preventDefault(); - this.context.activePadId = undefined; - } - } - } - ] : [] - }); - }) - - await new Promise((resolve) => { - client.once(this.context.activePadId ? "padData" : "connect", () => { resolve(); }); - client.on("serverError", () => { resolve(); }); - }); - - if (this.newClient !== client) { - // Another client has been initiated in the meantime - client.disconnect(); - return; - } - - // Bootstrap-Vue uses animation frames to show the connecting toast. If the map is loading in a background tab, the toast might not be shown - // yet when we are trying to hide it, so the hide operation is skipped and once the loading toast is shown, it stays forever. - // We need to wait for two animation frames to make sure that the toast is shown. - requestAnimationFrame(() => { - requestAnimationFrame(() => { - this.$bvToast.hide(`fm${this.context.id}-client-connecting`); - }); - }); - - if (client.serverError?.message?.includes("does not exist") && this.context.interactive) { - this.createId = client.padId!; - client.padId = undefined; - client.serverError = undefined; - setTimeout(() => { - this.$bvModal.show(`fm${this.context.id}-client-create-pad`); - }, 0); - } else if (client.serverError) { - showErrorToast(this, `fm${this.context.id}-client-error`, "Error opening map", client.serverError, { - noCloseButton: true, - actions: this.context.interactive ? [ - { - label: "Close map", - href: this.context.baseUrl, - onClick: (e) => { - if (!e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey) { - e.preventDefault(); - this.context.activePadId = undefined; - } - } - } - ] : [] - }); - } - - this.counter++; - this.newClient = null; - this.client?.disconnect(); - this.client = client; - } - - @Watch("context.activePadId") - handlePadIdChange(): void { - this.connect(); - } - - @Watch("context.serverUrl") - handleServerUrlChange(): void { - this.connect(); - } - -} \ No newline at end of file diff --git a/frontend/src/lib/components/client/client.vue b/frontend/src/lib/components/client/client.vue deleted file mode 100644 index d8d12400..00000000 --- a/frontend/src/lib/components/client/client.vue +++ /dev/null @@ -1,10 +0,0 @@ -
- - - - - The connection to the server was lost. Trying to reconnect… - - - -
\ No newline at end of file diff --git a/frontend/src/lib/components/edit-filter/edit-filter.scss b/frontend/src/lib/components/edit-filter/edit-filter.scss deleted file mode 100644 index 1a91a47b..00000000 --- a/frontend/src/lib/components/edit-filter/edit-filter.scss +++ /dev/null @@ -1,24 +0,0 @@ -.fm-edit-filter { - .modal-body.modal-body, form { - display: flex; - flex-direction: column; - min-height: 0; - } - - hr { - width: 100%; - } - - .fm-edit-filter-syntax { - overflow: auto; - margin-right: -16px; - padding-right: 16px; - min-height: 150px; - max-width: 100%; - } - - pre { - color: inherit; - font-size: inherit; - } -} \ No newline at end of file diff --git a/frontend/src/lib/components/edit-filter/edit-filter.ts b/frontend/src/lib/components/edit-filter/edit-filter.ts deleted file mode 100644 index 14110182..00000000 --- a/frontend/src/lib/components/edit-filter/edit-filter.ts +++ /dev/null @@ -1,47 +0,0 @@ -import WithRender from "./edit-filter.vue"; -import "./edit-filter.scss"; -import Vue from "vue"; -import { extend, ValidationProvider } from 'vee-validate'; -import { filterHasError } from 'facilmap-utils'; -import { Component, Prop } from "vue-property-decorator"; -import { Client, InjectClient, InjectMapComponents, InjectMapContext } from "../../utils/decorators"; -import { Type } from "facilmap-types"; -import FormModal from "../ui/form-modal/form-modal"; -import { MapComponents, MapContext } from "../leaflet-map/leaflet-map"; - -extend("filter", (filter: string): string | true => { - return filterHasError(filter)?.message ?? true; -}); - -@WithRender -@Component({ - components: { FormModal, ValidationProvider } -}) -export default class EditFilter extends Vue { - - @InjectMapContext() mapContext!: MapContext; - @InjectMapComponents() mapComponents!: MapComponents; - @InjectClient() client!: Client; - - @Prop({ type: String, required: true }) id!: string; - - filter: string = null as any; - - get types(): Type[] { - return Object.values(this.client.types); - } - - initialize(): void { - this.filter = this.mapContext.filter ?? ""; - } - - get isModified(): boolean { - return this.filter != (this.mapContext.filter ?? ""); - } - - save(): void { - this.mapComponents.map.setFmFilter(this.filter || undefined); - this.$bvModal.hide(this.id); - } - -} \ No newline at end of file diff --git a/frontend/src/lib/components/edit-filter/edit-filter.vue b/frontend/src/lib/components/edit-filter/edit-filter.vue index fd373ae2..847f022b 100644 --- a/frontend/src/lib/components/edit-filter/edit-filter.vue +++ b/frontend/src/lib/components/edit-filter/edit-filter.vue @@ -1,210 +1,289 @@ - - - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/frontend/src/lib/components/edit-line/edit-line.ts b/frontend/src/lib/components/edit-line/edit-line.ts deleted file mode 100644 index a9f6b694..00000000 --- a/frontend/src/lib/components/edit-line/edit-line.ts +++ /dev/null @@ -1,87 +0,0 @@ -import WithRender from "./edit-line.vue"; -import Vue from "vue"; -import { ID, Line, Type } from "facilmap-types"; -import { Client, InjectClient, InjectContext } from "../../utils/decorators"; -import { Component, Prop, Watch } from "vue-property-decorator"; -import { canControl, IdType, mergeObject } from "../../utils/utils"; -import { clone } from "facilmap-utils"; -import { isEqual, omit } from "lodash-es"; -import { showErrorToast } from "../../utils/toasts"; -import FormModal from "../ui/form-modal/form-modal"; -import { ValidationProvider } from "vee-validate"; -import ColourField from "../ui/colour-field/colour-field"; -import SymbolField from "../ui/symbol-field/symbol-field"; -import ShapeField from "../ui/shape-field/shape-field"; -import FieldInput from "../ui/field-input/field-input"; -import RouteMode from "../ui/route-mode/route-mode"; -import WidthField from "../ui/width-field/width-field"; -import StringMap from "../../utils/string-map"; -import { Context } from "../facilmap/facilmap"; - -@WithRender -@Component({ - components: { ColourField, FieldInput, FormModal, RouteMode, ShapeField, SymbolField, ValidationProvider, WidthField } -}) -export default class EditLine extends Vue { - - @InjectContext() context!: Context; - @InjectClient() client!: Client; - - @Prop({ type: String, required: true }) id!: string; - @Prop({ type: IdType, required: true }) lineId!: ID; - - line: Line = null as any; - isSaving = false; - - initialize(): void { - this.line = clone(this.client.lines[this.lineId]); - } - - clear(): void { - this.line = null as any; - } - - get isModified(): boolean { - return !isEqual(this.line, this.client.lines[this.lineId]); - } - - get originalLine(): Line | undefined { - return this.client.lines[this.lineId]; - } - - get types(): Type[] { - return Object.values(this.client.types).filter((type) => type.type === "line"); - } - - get canControl(): Array { - return canControl(this.client.types[this.line.typeId]); - } - - @Watch("originalLine") - handleChangeLine(newLine: Line | undefined, oldLine: Line): void { - if (this.line) { - if (!newLine) { - this.$bvModal.hide(this.id); - // TODO: Show message - } else { - mergeObject(oldLine, newLine, this.line); - } - } - } - - async save(): Promise { - this.isSaving = true; - this.$bvToast.hide(`fm${this.context.id}-edit-line-error`); - - try { - await this.client.editLine(omit(this.line, "trackPoints")); - this.$bvModal.hide(this.id); - } catch (err) { - showErrorToast(this, `fm${this.context.id}-edit-line-error`, "Error saving line", err); - } finally { - this.isSaving = false; - } - } - - -} diff --git a/frontend/src/lib/components/edit-line/edit-line.vue b/frontend/src/lib/components/edit-line/edit-line.vue index 8e646f5d..a8ae389c 100644 --- a/frontend/src/lib/components/edit-line/edit-line.vue +++ b/frontend/src/lib/components/edit-line/edit-line.vue @@ -1,43 +1,135 @@ - - \ No newline at end of file diff --git a/frontend/src/lib/components/edit-marker/edit-marker.ts b/frontend/src/lib/components/edit-marker/edit-marker.ts deleted file mode 100644 index 308159e0..00000000 --- a/frontend/src/lib/components/edit-marker/edit-marker.ts +++ /dev/null @@ -1,86 +0,0 @@ -import WithRender from "./edit-marker.vue"; -import Vue from "vue"; -import { ID, Marker, Type } from "facilmap-types"; -import { Client, InjectClient, InjectContext } from "../../utils/decorators"; -import { Component, Prop, Watch } from "vue-property-decorator"; -import { canControl, IdType, mergeObject } from "../../utils/utils"; -import { clone } from "facilmap-utils"; -import { isEqual } from "lodash-es"; -import { showErrorToast } from "../../utils/toasts"; -import FormModal from "../ui/form-modal/form-modal"; -import { ValidationProvider } from "vee-validate"; -import ColourField from "../ui/colour-field/colour-field"; -import SymbolField from "../ui/symbol-field/symbol-field"; -import ShapeField from "../ui/shape-field/shape-field"; -import FieldInput from "../ui/field-input/field-input"; -import SizeField from "../ui/size-field/size-field"; -import StringMap from "../../utils/string-map"; -import { Context } from "../facilmap/facilmap"; - -@WithRender -@Component({ - components: { ColourField, FieldInput, FormModal, ShapeField, SizeField, SymbolField, ValidationProvider } -}) -export default class EditMarker extends Vue { - - @InjectContext() context!: Context; - @InjectClient() client!: Client; - - @Prop({ type: String, required: true }) id!: string; - @Prop({ type: IdType, required: true }) markerId!: ID; - - marker: Marker = null as any; - isSaving = false; - - initialize(): void { - this.marker = clone(this.client.markers[this.markerId]); - } - - clear(): void { - this.marker = null as any; - } - - get isModified(): boolean { - return !isEqual(this.marker, this.client.markers[this.markerId]); - } - - get originalMarker(): Marker | undefined { - return this.client.markers[this.markerId]; - } - - get types(): Type[] { - return Object.values(this.client.types).filter((type) => type.type === "marker"); - } - - get canControl(): Array { - return canControl(this.client.types[this.marker.typeId]); - } - - @Watch("originalMarker") - handleChangeMarker(newMarker: Marker | undefined, oldMarker: Marker): void { - if (this.marker) { - if (!newMarker) { - this.$bvModal.hide(this.id); - // TODO: Show message - } else { - mergeObject(oldMarker, newMarker, this.marker); - } - } - } - - async save(): Promise { - this.isSaving = true; - this.$bvToast.hide(`fm${this.context.id}-edit-marker-error`); - - try { - await this.client.editMarker(this.marker); - this.$bvModal.hide(this.id); - } catch (err) { - showErrorToast(this, `fm${this.context.id}-edit-marker-error`, "Error saving marker", err); - } finally { - this.isSaving = false; - } - } - - -} diff --git a/frontend/src/lib/components/edit-marker/edit-marker.vue b/frontend/src/lib/components/edit-marker/edit-marker.vue index 68307707..999bc551 100644 --- a/frontend/src/lib/components/edit-marker/edit-marker.vue +++ b/frontend/src/lib/components/edit-marker/edit-marker.vue @@ -1,53 +1,144 @@ - - \ No newline at end of file diff --git a/frontend/src/lib/components/edit-type/edit-type-dropdown.scss b/frontend/src/lib/components/edit-type/edit-type-dropdown.scss deleted file mode 100644 index cc3d5c6d..00000000 --- a/frontend/src/lib/components/edit-type/edit-type-dropdown.scss +++ /dev/null @@ -1,5 +0,0 @@ -.fm-edit-type-dropdown { - td.field { - min-width: 10rem; - } -} \ No newline at end of file diff --git a/frontend/src/lib/components/edit-type/edit-type-dropdown.ts b/frontend/src/lib/components/edit-type/edit-type-dropdown.ts deleted file mode 100644 index 548cfff5..00000000 --- a/frontend/src/lib/components/edit-type/edit-type-dropdown.ts +++ /dev/null @@ -1,161 +0,0 @@ -import WithRender from "./edit-type-dropdown.vue"; -import Vue from "vue"; -import { Component, Prop, Ref, Watch } from "vue-property-decorator"; -import { Field, FieldOptionUpdate, FieldUpdate, Line, Marker, Type } from "facilmap-types"; -import { clone } from "facilmap-utils"; -import { canControl, mergeObject } from "../../utils/utils"; -import { isEqual } from "lodash-es"; -import { showErrorToast } from "../../utils/toasts"; -import ColourField from "../ui/colour-field/colour-field"; -import draggable from "vuedraggable"; -import Icon from "../ui/icon/icon"; -import FormModal from "../ui/form-modal/form-modal"; -import ShapeField from "../ui/shape-field/shape-field"; -import SizeField from "../ui/size-field/size-field"; -import SymbolField from "../ui/symbol-field/symbol-field"; -import WidthField from "../ui/width-field/width-field"; -import { extend, ValidationProvider } from "vee-validate"; -import "./edit-type-dropdown.scss"; -import { Context } from "../facilmap/facilmap"; -import { InjectContext } from "../../utils/decorators"; - -extend("uniqueFieldOptionValue", { - validate: (value: string, args: any) => { - const field: Field | undefined = args.field?.field; - return !field || field.options!.filter((option) => option.value == value).length <= 1; - }, - message: "Multiple options cannot have the same label.", - params: ["field"], - computesRequired: true // To check empty values as well -}); - -extend("fieldOptionNumber", { - validate: ({ field, type }: { field: FieldUpdate, type: Type }, args: any) => { - return getControlNumber(type, field) == 0 || (!!field.options && field.options.length > 0); - }, - message: "Controlling fields need to have at least one option.", - params: ["controlNumber"] -}); - -function getControlNumber(type: Type, field: FieldUpdate): number { - return [ - field.controlColour, - ...(type.type == "marker" ? [ - field.controlSize, - field.controlSymbol, - field.controlShape - ] : []), - ...(type.type == "line" ? [ - field.controlWidth - ] : []) - ].filter((v) => v).length; -} - -@WithRender -@Component({ - components: { ColourField, draggable, Icon, FormModal, ShapeField, SizeField, SymbolField, ValidationProvider, WidthField } -}) -export default class EditTypeDropdown extends Vue { - - @InjectContext() context!: Context; - - @Prop({ type: String, required: true }) id!: string; - @Prop({ type: Object, required: true }) type!: Type; - @Prop({ type: Object, required: true }) field!: Field; - - @Ref() fieldValidationProvider?: InstanceType; - - fieldValue: FieldUpdate = null as any; - - initialize(): void { - this.fieldValue = clone(this.initialField); - } - - clear(): void { - this.fieldValue = null as any; - } - - get initialField(): FieldUpdate { - const field: FieldUpdate = clone(this.field); - - if(field.type == 'checkbox') { - if(!field.options || field.options.length != 2) { - field.options = [ - { value: '' }, - { value: field.name } - ] - } - - // Convert legacy format - if(field.options[0].value == "0") - field.options[0].value = ""; - if(field.options[1].value == "1") - field.options[1].value = field.name; - } - - for(let option of (field.options || [])) - option.oldValue = option.value; - - return field; - } - - @Watch("field", { deep: true }) - handleFieldChange(newField: Field, oldField: Field): void { - if (this.fieldValue) { - if (newField == null) { - this.$bvModal.hide(this.id); - // TODO: Show message - } else { - mergeObject(oldField, newField, this.fieldValue); - } - } - } - - @Watch("fieldValue", { deep: true }) - handleChange(field: FieldUpdate): void { - this.fieldValidationProvider?.validate({ type: this.type, field }); - } - - get isModified(): boolean { - return !isEqual(this.fieldValue, this.initialField); - } - - get canControl(): Array { - return canControl(this.type, this.field); - } - - addOption(): void { - if(this.fieldValue.options == null) - Vue.set(this.fieldValue, "options", [ ]); - - this.fieldValue.options!.push({ value: "" }); - } - - async deleteOption(option: FieldOptionUpdate): Promise { - if (!await this.$bvModal.msgBoxConfirm(`Do you really want to delete the option “${option.value}”?`)) - return; - - var idx = this.fieldValue.options!.indexOf(option); - if(idx != -1) - this.fieldValue.options!.splice(idx, 1); - } - - save(): void { - this.$bvToast.hide(`fm${this.context.id}-edit-type-dropdown-error`); - - const idx = this.type.fields.indexOf(this.field); - if (idx === -1) - showErrorToast(this, `fm${this.context.id}-edit-type-dropdown-error`, "Error updating field", new Error("The field cannot be found on the type anymore.")); - else { - Vue.nextTick(() => { - Vue.set(this.type.fields, idx, this.fieldValue); - }); - this.$bvModal.hide(this.id); - } - } - - get controlNumber(): number { - return getControlNumber(this.type, this.fieldValue); - } - -} diff --git a/frontend/src/lib/components/edit-type/edit-type-dropdown.vue b/frontend/src/lib/components/edit-type/edit-type-dropdown.vue index b947f80f..d99a1ef1 100644 --- a/frontend/src/lib/components/edit-type/edit-type-dropdown.vue +++ b/frontend/src/lib/components/edit-type/edit-type-dropdown.vue @@ -1,108 +1,282 @@ - - + + \ No newline at end of file diff --git a/frontend/src/lib/components/edit-type/edit-type.ts b/frontend/src/lib/components/edit-type/edit-type.ts deleted file mode 100644 index af4c45c4..00000000 --- a/frontend/src/lib/components/edit-type/edit-type.ts +++ /dev/null @@ -1,148 +0,0 @@ -import WithRender from "./edit-type.vue"; -import Vue from "vue"; -import { Component, Prop, Ref, Watch } from "vue-property-decorator"; -import { Client, InjectClient, InjectContext } from "../../utils/decorators"; -import { Field, ID, Line, Marker, Type, TypeUpdate } from "facilmap-types"; -import { clone } from "facilmap-utils"; -import { canControl, IdType } from "../../utils/utils"; -import { mergeTypeObject } from "./edit-type-utils"; -import { isEqual } from "lodash-es"; -import { showErrorToast } from "../../utils/toasts"; -import FormModal from "../ui/form-modal/form-modal"; -import { extend, ValidationProvider } from "vee-validate"; -import ColourField from "../ui/colour-field/colour-field"; -import ShapeField from "../ui/shape-field/shape-field"; -import SymbolField from "../ui/symbol-field/symbol-field"; -import RouteMode from "../ui/route-mode/route-mode"; -import draggable from "vuedraggable"; -import FieldInput from "../ui/field-input/field-input"; -import Icon from "../ui/icon/icon"; -import WidthField from "../ui/width-field/width-field"; -import SizeField from "../ui/size-field/size-field"; -import EditTypeDropdown from "./edit-type-dropdown"; -import { Context } from "../facilmap/facilmap"; - -extend("uniqueFieldName", { - validate: (value: string, args: any) => { - const type: Type | undefined = args.type; - return !type || type.fields.filter((field) => field.name == value).length <= 1; - }, - message: "Multiple fields cannot have the same name.", - params: ["type"] -}); - -@WithRender -@Component({ - components: { ColourField, draggable, EditTypeDropdown, FieldInput, FormModal, Icon, RouteMode, ShapeField, SizeField, SymbolField, ValidationProvider, WidthField } -}) -export default class EditType extends Vue { - - @InjectContext() context!: Context; - @InjectClient() client!: Client; - - @Ref() typeValidationProvider?: InstanceType; - - @Prop({ type: String, required: true }) id!: string; - @Prop({ type: IdType }) typeId?: ID; - - type: Type & TypeUpdate = null as any; - isSaving = false; - editField: Field | null = null; - - setTimeout(func: () => void): void { - setTimeout(func, 0); - } - - initialize(): void { - this.type = clone(this.initialType); - } - - clear(): void { - this.type = null as any; - } - - get initialType(): Type & TypeUpdate { - const type = this.isCreate ? { fields: [] } as any : clone(this.originalType)!; - - for(const field of type.fields) { - field.oldName = field.name; - } - - return type; - } - - get isModified(): boolean { - return !isEqual(this.type, this.initialType); - } - - get isCreate(): boolean { - return this.typeId == null; - } - - get originalType(): Type | undefined { - return this.typeId != null ? this.client.types[this.typeId] : undefined; - } - - get canControl(): Array { - return canControl(this.type, null); - } - - @Watch("originalType") - handleChangeType(newType: Type | undefined, oldType: Type): void { - if (this.type) { - if (!newType) { - this.$bvModal.hide(this.id); - // TODO: Show message - } else { - mergeTypeObject(oldType, newType, this.type); - } - } - } - - @Watch("type", { deep: true }) - handleChange(type: TypeUpdate): void { - this.typeValidationProvider?.validate({ ...type }); - } - - createField(): void { - this.type.fields.push({ name: "", type: "input", "default": "" }); - } - - async deleteField(field: Field): Promise { - if (!await this.$bvModal.msgBoxConfirm(`Do you really want to delete the field “${field.name}”?`)) - return; - - var idx = this.type.fields.indexOf(field); - if(idx != -1) - this.type.fields.splice(idx, 1); - } - - async save(): Promise { - this.$bvToast.hide(`fm${this.context.id}-edit-type-error`); - this.isSaving = true; - - for (const prop of [ "defaultWidth", "defaultSize", "defaultColour" ] as Array<"defaultWidth" | "defaultSize" | "defaultColour">) { - if(this.type[prop] == "") - this.type[prop] = null; - } - - try { - if (this.isCreate) - await this.client.addType(this.type); - else - await this.client.editType(this.type); - - this.$bvModal.hide(this.id); - } catch (err) { - showErrorToast(this, `fm${this.context.id}-edit-type-error`, this.isCreate ? "Error creating type" : "Error saving type", err); - } finally { - this.isSaving = false; - } - } - - editDropdown(field: Field): void { - this.editField = field; - setTimeout(() => { this.$bvModal.show(`${this.id}-dropdown`); }, 0); - } - -} diff --git a/frontend/src/lib/components/edit-type/edit-type.vue b/frontend/src/lib/components/edit-type/edit-type.vue index b8fbe2ab..a53c09c2 100644 --- a/frontend/src/lib/components/edit-type/edit-type.vue +++ b/frontend/src/lib/components/edit-type/edit-type.vue @@ -1,173 +1,327 @@ - -