Skip to content

Commit

Permalink
Merge pull request #668 from geoadmin/develop
Browse files Browse the repository at this point in the history
New Release v1.7.0 - #minor
  • Loading branch information
ltshb authored Feb 28, 2024
2 parents a5e37c8 + f3ff066 commit 6a3f252
Show file tree
Hide file tree
Showing 115 changed files with 1,887 additions and 1,230 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The next generation map viewer application of geo.admin.ch: Digital data can be
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [Project structure](#project-structure)
- [Best practices](#best-practices)
- [Architectural decisions](#architectural-decisions)
- [Vue Composition API](#vue-composition-api)
- [Store module](#store-module)
Expand Down Expand Up @@ -79,6 +80,14 @@ Here's a sample of what project folder structure looks like :
│   │ │ ├── <Module name>.js
```

### Best practices

- Prefer primitive data or javascript plain object in reactive data (Vue Component data or refs, Vuex store data)
- Don't use complex object as reactive data
- Avoid using javascript getter and setter in class that are used in reactive data

See also [Store Best Practices](./src/store/README.md#best-practices)

### Architectural decisions

All project related architectural decision will be described in the folder [`/adr`](adr/) (ADR stands for "Architectural Decision Report"). For all more macro decisions (like the CI we use or other broad subjects), please refer to [the `/adr` folder on the project doc-guidelines](https://github.com/geoadmin/doc-guidelines/tree/master/adr).
Expand Down
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"prod": "vite --port 8080 --host --cors --mode production",
"preview": "npm run build:prod && vite preview --port 8080 --host --outDir dist/production",
"preview:dev": "npm run build:dev && vite preview --port 8080 --host --outDir dist/development",
"preview:int": "npm run build:int && vite preview --port 8080 --host --outDir dist/integration",
"preview:int": "npm run build:int && vite preview --mode integration --port 8080 --host --outDir dist/integration",
"preview:prod": "npm run build:prod && vite preview --mode production --port 8080 --host --outDir dist/production",
"lint": "eslint . --fix --ignore-path .gitignore --ext .js,.vue",
"lint:no-fix": "eslint . --ignore-path .gitignore --ext .js,.vue",
"test:unit": "npm run delete:reports:unit && vitest --run --mode development --environment jsdom",
Expand Down Expand Up @@ -69,6 +70,7 @@
"print-js": "^1.6.0",
"proj4": "^2.10.0",
"reproject": "^1.2.7",
"sortablejs": "^1.15.2",
"tippy.js": "^6.3.7",
"vue": "^3.4.15",
"vue-chartjs": "^5.3.0",
Expand All @@ -78,6 +80,7 @@
"vuex": "^4.1.0"
},
"devDependencies": {
"@4tw/cypress-drag-drop": "^2.2.5",
"@cypress/vite-dev-server": "^5.0.7",
"@cypress/vue": "^6.0.0",
"@nuintun/qrcode": "^3.4.0",
Expand Down
3 changes: 3 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const withOutline = ref(false)
const store = useStore()
const i18n = useI18n()
const dispatcher = { dispatcher: 'App.vue' }
let debouncedOnResize
onMounted(() => {
Expand All @@ -33,6 +35,7 @@ function setScreenSizeFromWindowSize() {
store.dispatch('setSize', {
width: window.innerWidth,
height: window.innerHeight,
...dispatcher,
})
}
function refreshPageTitle() {
Expand Down
185 changes: 87 additions & 98 deletions src/api/topics.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,25 +71,22 @@ const readTopicTreeRecursive = (node, availableLayers) => {
* Loads the topic tree for a topic. This will be used to create the UI of the topic in the menu.
*
* @param {String} lang The lang in which to load the topic tree
* @param {Topic} topic The topic we want to load the topic tree
* @param {String} topicId The topic we want to load the topic tree
* @param {GeoAdminLayer[]} layersConfig All available layers for this app (the "layers config")
* @returns {Promise<{ layers: GeoAdminLayer[]; itemIdToOpen: String[] }>} A list of topic's layers
*/
export const loadTopicTreeForTopic = (lang, topic, layersConfig) => {
export const loadTopicTreeForTopic = (lang, topicId, layersConfig) => {
return new Promise((resolve, reject) => {
axios
.get(`${API_BASE_URL}rest/services/${topic.id}/CatalogServer?lang=${lang}`)
.get(`${API_BASE_URL}rest/services/${topicId}/CatalogServer?lang=${lang}`)
.then((response) => {
const treeItems = []
const topicRoot = response.data.results.root
topicRoot.children.forEach((child) => {
try {
treeItems.push(readTopicTreeRecursive(child, layersConfig))
} catch (err) {
log.error(
`Error while loading Layer ${child.id} for Topic ${topic.id}`,
err
)
log.error(`Error while loading Layer ${child.id} for Topic ${topicId}`, err)
}
})
const itemIdToOpen = gatherItemIdThatShouldBeOpened(topicRoot)
Expand All @@ -102,100 +99,92 @@ export const loadTopicTreeForTopic = (lang, topic, layersConfig) => {
}

/**
* Loads all topics (without their tree) from the backend. Those topics will already by filled with
* the correct layer object, coming from the `layersConfig` param)
* Loads all topics (without their tree) from the backend.
*
* @returns {Promise<{ topics: [] }>} Raw topics from backend
*/
export async function loadTopics() {
try {
const response = await axios.get(`${API_BASE_URL}rest/services`)
return response.data
} catch (error) {
log.error(`Failed to load topics from backend`, error)
return {}
}
}

/**
* Parse topics from backend response.
*
* The topics will already by filled with the correct layer object, coming from the `layersConfig`
* param
*
* @param {GeoAdminLayer[]} layersConfig All available layers for this app (the "layers config")
* @returns {Promise<Topic[]>} All topics available for this app
* @returns {Topic[]} All topics available for this app
*/
const loadTopicsFromBackend = (layersConfig) => {
return new Promise((resolve, reject) => {
if (!API_BASE_URL) {
// this could happen if we are testing the app in unit tests, we simply reject and do nothing
reject(new Error('API base URL is undefined'))
} else {
const topics = []
axios
.get(`${API_BASE_URL}rest/services`)
.then(({ data: rawTopics }) => {
if ('topics' in rawTopics) {
rawTopics.topics.forEach((rawTopic) => {
const {
id: topicId,
backgroundLayers: backgroundLayersId,
defaultBackground: defaultBackgroundLayerId,
plConfig: legacyUrlParams,
} = rawTopic
const backgroundLayers = layersConfig.filter(
(layer) => backgroundLayersId.indexOf(layer.getID()) !== -1
)
const backgroundLayerFromUrlParam =
getBackgroundLayerFromLegacyUrlParams(layersConfig, legacyUrlParams)
// first we get the background from the "plConfig" of the API response
let defaultBackground = backgroundLayerFromUrlParam
// checking if there was something in the "plConfig"
// null is a valid background as it is the void layer in our app
// so we have to exclude only the "undefined" value and fill this variable
// with what is in "defaultBackground" in this case
if (backgroundLayerFromUrlParam === undefined) {
defaultBackground = backgroundLayers.find(
(layer) => layer.getID() === defaultBackgroundLayerId
)
}
const params = new URLSearchParams(legacyUrlParams)
const layersToActivate = [
...getLayersFromLegacyUrlParams(
layersConfig,
params.get('layers'),
params.get('layers_visibility'),
params.get('layers_opacity'),
params.get('layers_timestamp')
),
]
if (
Array.isArray(rawTopic.activatedLayers) &&
rawTopic.activatedLayers.length > 0
) {
rawTopic.activatedLayers.forEach((layerId) => {
let layer = layersConfig.find(
(layer) => layer.getID() === layerId
)
if (layer) {
// deep copy so that we can reassign values later on
// (layers come from the Vuex store so it can't be modified directly)
layer = Object.assign(
Object.create(Object.getPrototypeOf(layer)),
layer
)
// checking if the layer should be also visible
layer.visible =
Array.isArray(rawTopic.selectedLayers) &&
rawTopic.selectedLayers.indexOf(layerId) !== -1
layersToActivate.push(layer)
}
})
}
topics.push(
new Topic(
topicId,
backgroundLayers,
defaultBackground,
layersToActivate
)
)
})
} else {
reject(new Error('Wrong API output structure'))
}
resolve(topics)
})
.catch((error) => {
const message = 'Error while loading topics config from backend'
log.error(message, error)
reject(message)
})
export function parseTopics(layersConfig, rawTopics) {
if (!rawTopics.topics) {
log.error(`Invalid topics input`, rawTopics)
throw new Error('Invalid topics input')
}
const topics = []
rawTopics.topics.forEach((rawTopic) => {
const {
id: topicId,
backgroundLayers: backgroundLayersId,
defaultBackground: defaultBackgroundLayerId,
plConfig: legacyUrlParams,
} = rawTopic
const backgroundLayers = layersConfig.filter(
(layer) => backgroundLayersId.indexOf(layer.getID()) !== -1
)
const backgroundLayerFromUrlParam = getBackgroundLayerFromLegacyUrlParams(
layersConfig,
legacyUrlParams
)
// first we get the background from the "plConfig" of the API response
let defaultBackground = backgroundLayerFromUrlParam
// checking if there was something in the "plConfig"
// null is a valid background as it is the void layer in our app
// so we have to exclude only the "undefined" value and fill this variable
// with what is in "defaultBackground" in this case
if (backgroundLayerFromUrlParam === undefined) {
defaultBackground = backgroundLayers.find(
(layer) => layer.getID() === defaultBackgroundLayerId
)
}
const params = new URLSearchParams(legacyUrlParams)
const layersToActivate = [
...getLayersFromLegacyUrlParams(
layersConfig,
params.get('layers'),
params.get('layers_visibility'),
params.get('layers_opacity'),
params.get('layers_timestamp')
),
]
const activatedLayers = [
...new Set([...(rawTopic.activatedLayers ?? []), ...(rawTopic.selectedLayers ?? [])]),
]
// Filter out layers that have been already added by the infamous
// plConfig topic config that has priority, this avoid duplicate
// layers
.filter((layerId) => !layersToActivate.some((layer) => layer.getID() === layerId))
activatedLayers.forEach((layerId) => {
let layer = layersConfig.find((layer) => layer.getID() === layerId)
if (layer) {
// deep copy so that we can reassign values later on
// (layers come from the Vuex store so it can't be modified directly)
layer = layer.clone()
// checking if the layer should be also visible
layer.visible = rawTopic.selectedLayers?.indexOf(layerId) !== -1 ?? false
// In the backend the layers are in the wrong order
// so we need to reverse the order here by simply adding
// the layer at the beginning of the array
layersToActivate.unshift(layer)
}
})
topics.push(new Topic(topicId, backgroundLayers, defaultBackground, layersToActivate))
})
return topics
}

export default loadTopicsFromBackend
4 changes: 4 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ setupChartJS()

const app = createApp(App)

if (ENVIRONMENT !== 'production') {
app.config.performance = true
}

app.use(router)
app.use(i18n)
app.use(store)
Expand Down
18 changes: 10 additions & 8 deletions src/modules/drawing/DrawingModule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import useKmlDataManagement from '@/modules/drawing/useKmlDataManagement.composa
import { getIcon, parseIconUrl } from '@/utils/kmlUtils'
import log from '@/utils/logging'
const dispatcher = { dispatcher: 'DrawingModule.vue' }
const olMap = inject('olMap')
const drawingInteractions = ref(null)
Expand Down Expand Up @@ -81,12 +83,12 @@ watch(availableIconSets, () => {
onMounted(() => {
// if icons have not yet been loaded, we do so
if (availableIconSets.value.length === 0) {
store.dispatch('loadAvailableIconSets')
store.dispatch('loadAvailableIconSets', dispatcher)
}
// We need to make sure that no drawing features are selected when entering the drawing
// mode otherwise we cannot edit the selected features.
store.dispatch('clearAllSelectedFeatures')
store.dispatch('clearAllSelectedFeatures', dispatcher)
isNewDrawing.value = true
// if a KML was previously created with the drawing module
Expand All @@ -106,9 +108,9 @@ onMounted(() => {
window.drawingLayer = drawingLayer
}
})
onBeforeUnmount(async () => {
await store.dispatch('clearAllSelectedFeatures')
await store.dispatch('setDrawingMode', null)
onBeforeUnmount(() => {
store.dispatch('clearAllSelectedFeatures', dispatcher)
store.dispatch('setDrawingMode', { mode: null, ...dispatcher })
drawingLayer.getSource().clear()
olMap.removeLayer(drawingLayer)
Expand Down Expand Up @@ -140,7 +142,7 @@ function removeLastPointOnDeleteKeyUp(event) {
}
async function closeDrawing() {
await store.dispatch('setShowLoadingBar', true)
store.dispatch('setShowLoadingBar', { loading: true, ...dispatcher })
log.debug(
`Closing drawing menu: isModified=${isDrawingModified.value}, isNew=${isNewDrawing.value}, isEmpty=${isDrawingEmpty.value}`
Expand All @@ -156,8 +158,8 @@ async function closeDrawing() {
await saveDrawing(false)
}
await store.dispatch('toggleDrawingOverlay')
await store.dispatch('setShowLoadingBar', false)
await store.dispatch('toggleDrawingOverlay', dispatcher)
store.dispatch('setShowLoadingBar', { loading: false, ...dispatcher })
}
</script>
Expand Down
Loading

0 comments on commit 6a3f252

Please sign in to comment.