Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Merge pull request #41469 from elpaso/server-landingpage-sources
Server Landing page source code Cherry-picked from master commit ac3da2e
- Loading branch information
Showing
with
14,681 additions
and 1 deletion.
- +26 −0 resources/server/src/landingpage/README.md
- +5 −0 resources/server/src/landingpage/babel.config.js
- +53 −0 resources/server/src/landingpage/package.json
- BIN resources/server/src/landingpage/public/favicon.ico
- +32 −0 resources/server/src/landingpage/public/index.html
- +31 −0 resources/server/src/landingpage/src/App.vue
- BIN resources/server/src/landingpage/src/assets/logo.png
- +244 −0 resources/server/src/landingpage/src/components/AttributeTable.vue
- +31 −0 resources/server/src/landingpage/src/components/Error.vue
- +83 −0 resources/server/src/landingpage/src/components/IdentifyResults.vue
- +178 −0 resources/server/src/landingpage/src/components/LayerTree.vue
- +307 −0 resources/server/src/landingpage/src/components/LayerTreeNode.vue
- +80 −0 resources/server/src/landingpage/src/components/LeftSidebar.vue
- +38 −0 resources/server/src/landingpage/src/components/MapFooter.vue
- +74 −0 resources/server/src/landingpage/src/components/MapToolbar.vue
- +161 −0 resources/server/src/landingpage/src/components/Metadata.vue
- +30 −0 resources/server/src/landingpage/src/js/Utils.js
- +110 −0 resources/server/src/landingpage/src/js/WmsSource.js
- +111 −0 resources/server/src/landingpage/src/main.js
- +7 −0 resources/server/src/landingpage/src/plugins/vuetify.js
- +158 −0 resources/server/src/landingpage/src/store.js
- +209 −0 resources/server/src/landingpage/src/views/Catalog.vue
- +502 −0 resources/server/src/landingpage/src/views/WebGis.vue
- +15 −0 resources/server/src/landingpage/vue.config.js
- +12,195 −0 resources/server/src/landingpage/yarn.lock
- +1 −1 scripts/spell_check/check_spelling.sh
@@ -0,0 +1,26 @@ | ||
# Landing page/catalog | ||
|
||
Landing page/catalog app source code. | ||
|
||
## Project setup | ||
``` | ||
yarn install | ||
``` | ||
|
||
### Compiles and hot-reloads for development | ||
``` | ||
yarn serve | ||
``` | ||
|
||
### Compiles and minifies for production | ||
``` | ||
yarn build | ||
``` | ||
|
||
### Lints and fixes files | ||
``` | ||
yarn lint | ||
``` | ||
|
||
### Customize configuration | ||
See [Configuration Reference](https://cli.vuejs.org/config/). |
@@ -0,0 +1,5 @@ | ||
module.exports = { | ||
presets: [ | ||
'@vue/cli-plugin-babel/preset' | ||
] | ||
} |
@@ -0,0 +1,53 @@ | ||
{ | ||
"name": "app", | ||
"version": "0.1.0", | ||
"private": true, | ||
"scripts": { | ||
"serve": "vue-cli-service serve", | ||
"build": "vue-cli-service build", | ||
"lint": "vue-cli-service lint" | ||
}, | ||
"dependencies": { | ||
"leaflet": "^1.6.0", | ||
"leaflet-wms": "^0.1.0", | ||
"reproject": "^1.2.5", | ||
"vue": "^2.6.11", | ||
"vue-router": "^3.4.1", | ||
"vue2-leaflet": "^2.5.2", | ||
"vuetify": "^2.3.7", | ||
"vuex": "^3.5.1" | ||
}, | ||
"devDependencies": { | ||
"@vue/cli": "^4.4.6", | ||
"@vue/cli-plugin-babel": "^4.4.6", | ||
"@vue/cli-plugin-eslint": "^4.4.6", | ||
"@vue/cli-service": "^4.4.6", | ||
"babel-eslint": "^10.1.0", | ||
"eslint": "^7.6.0", | ||
"eslint-plugin-vue": "^6.2.2", | ||
"sass": "^1.26.10", | ||
"sass-loader": "^9.0.3", | ||
"vue-cli-plugin-vuetify": "^2.0.7", | ||
"vue-template-compiler": "^2.6.11", | ||
"vuetify-loader": "^1.6.0" | ||
}, | ||
"eslintConfig": { | ||
"root": true, | ||
"env": { | ||
"node": true | ||
}, | ||
"extends": [ | ||
"plugin:vue/essential", | ||
"eslint:recommended" | ||
], | ||
"parserOptions": { | ||
"parser": "babel-eslint" | ||
}, | ||
"rules": {} | ||
}, | ||
"browserslist": [ | ||
"> 1%", | ||
"last 2 versions", | ||
"not dead" | ||
] | ||
} |
Binary file not shown.
@@ -0,0 +1,32 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
<meta | ||
name="viewport" | ||
content="width=device-width, initial-scale=1, shrink-to-fit=no" | ||
/> | ||
<link rel="icon" href="<%= BASE_URL %>favicon.ico" /> | ||
<title><%= htmlWebpackPlugin.options.title %></title> | ||
<link | ||
rel="stylesheet" | ||
href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" | ||
/> | ||
<link | ||
rel="stylesheet" | ||
href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css" | ||
/> | ||
</head> | ||
<body> | ||
<noscript> | ||
<strong | ||
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work | ||
properly without JavaScript enabled. Please enable it to | ||
continue.</strong | ||
> | ||
</noscript> | ||
<div id="app"></div> | ||
<!-- built files will be auto injected --> | ||
</body> | ||
</html> |
@@ -0,0 +1,31 @@ | ||
/** | ||
* Main app wrapper | ||
* | ||
* Author: elpaso@itopen.it | ||
* Date: 2020-06-30 | ||
* Copyright: Copyright 2020, ItOpen | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation; either version 2 of the License, or | ||
* (at your option) any later version. | ||
* | ||
*/ | ||
<template> | ||
<v-app id="app"> | ||
<router-view></router-view> | ||
</v-app> | ||
</template> | ||
|
||
<script> | ||
export default { | ||
name: "App" | ||
}; | ||
</script> | ||
|
||
<style> | ||
.v-app-bar.v-app-bar--fixed, | ||
.v-footer { | ||
z-index: 10000 !important; | ||
} | ||
</style> |
Binary file not shown.
@@ -0,0 +1,244 @@ | ||
/** | ||
* Attribute table | ||
* | ||
* Author: elpaso@itopen.it | ||
* Date: 2020-06-30 | ||
* Copyright: Copyright 2020, ItOpen | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation; either version 2 of the License, or | ||
* (at your option) any later version. | ||
* | ||
*/ | ||
<template> | ||
<v-card level="2"> | ||
<v-card-text> | ||
<v-btn class="btn-close" icon @click="onCloseButtonClicked"> | ||
<v-icon>mdi-close</v-icon> | ||
</v-btn> | ||
<v-card-title> | ||
{{ title }} | ||
<v-spacer></v-spacer> | ||
<v-text-field | ||
v-model="filterText" | ||
append-icon="mdi-magnify" | ||
label="Filter" | ||
hint="Case sensitive, use * to match any character" | ||
dense | ||
:error="this.hasSearchError" | ||
single-line | ||
hide-details | ||
></v-text-field> | ||
<v-spacer></v-spacer> | ||
<v-combobox | ||
v-model="filterField" | ||
:items="searchableFields" | ||
label="Search field ..." | ||
no-filter | ||
hide-details | ||
dense | ||
></v-combobox> | ||
</v-card-title> | ||
|
||
<v-data-table | ||
dense | ||
item-key="itemKeyInternalIdentifier" | ||
:page.sync="currentPage" | ||
:sort-by.sync="sortBy" | ||
:sort-desc.sync="sortDesc" | ||
:server-items-length="numberMatched" | ||
no-data-text="Attribute table has no data, search is case-sensitive, use * to match any character." | ||
:loading="tableHeaders.length == 0 || loading" | ||
:headers="tableHeaders" | ||
:items="tableData" | ||
:items-per-page="5" | ||
:footer-props="{ | ||
itemsPerPageOptions: [5], | ||
itemsPerPageText: '' | ||
}" | ||
> | ||
<template v-slot:item.zoomToFeature="{ item }"> | ||
<v-icon @click="zoomToFeature(item.feature)">mdi-magnify</v-icon> | ||
</template> | ||
</v-data-table> | ||
</v-card-text> | ||
</v-card> | ||
</template> | ||
|
||
<script> | ||
const uuidv4 = require("uuid/v4"); | ||
export default { | ||
name: "AttributeTable", | ||
props: { | ||
project: null, | ||
map: null | ||
}, | ||
computed: { | ||
/** | ||
* Layer identifier for WFS3: layer id or short name or name | ||
*/ | ||
typename() { | ||
return this.$store.state.attributeTableTypename; | ||
}, | ||
/** | ||
* Get layer name from typename | ||
*/ | ||
title() { | ||
return Object.keys(this.project.wms_layers_map).find( | ||
key => this.project.wms_layers_map[key] === this.typename | ||
); | ||
}, | ||
searchableFields() { | ||
let layerId = this.project.wms_layers_typename_id_map[this.typename]; | ||
let values = []; | ||
let fieldNames = Object.keys(this.project.wms_layers[layerId]["fields"]); | ||
for (let i = 0; i < fieldNames.length; i++) { | ||
let field = this.project.wms_layers[layerId]["fields"][fieldNames[i]]; | ||
values.push({ | ||
text: field["label"], | ||
value: fieldNames[i] | ||
}); | ||
} | ||
return values; | ||
}, | ||
fieldAliases() { | ||
let layerId = this.project.wms_layers_typename_id_map[this.typename]; | ||
let aliases = {}; | ||
let fieldNames = Object.keys(this.project.wms_layers[layerId]["fields"]); | ||
for (let i = 0; i < fieldNames.length; i++) { | ||
let field = this.project.wms_layers[layerId]["fields"][fieldNames[i]]; | ||
aliases[fieldNames[i]] = field["label"]; | ||
} | ||
return aliases; | ||
}, | ||
hasSearchError() { | ||
return this.error > 0 && this.filterText.length; | ||
} | ||
}, | ||
data() { | ||
return { | ||
error: null, | ||
currentPage: 1, | ||
sortBy: null, | ||
sortDesc: null, | ||
tableData: [], | ||
tableHeaders: [], | ||
numberMatched: 0, | ||
filterField: null, | ||
filterText: "", | ||
loading: false | ||
}; | ||
}, | ||
mounted() { | ||
this.loadData(); | ||
}, | ||
watch: { | ||
currentPage() { | ||
this.loadData(); | ||
}, | ||
sortBy() { | ||
this.loadData(); | ||
}, | ||
sortDesc() { | ||
this.loadData(); | ||
}, | ||
typename() { | ||
this.loadData(); | ||
}, | ||
filterField() { | ||
if (this.filterText) this.loadData(); | ||
}, | ||
filterText() { | ||
if (!this.filterField) { | ||
this.filterField = this.searchableFields[0]; | ||
} else { | ||
this.loadData(); | ||
} | ||
} | ||
}, | ||
methods: { | ||
onCloseButtonClicked() { | ||
this.$store.commit("clearAttributeTableTypename"); | ||
}, | ||
/** | ||
* Load table data from WFS3 | ||
*/ | ||
async loadData() { | ||
try { | ||
this.error = null; | ||
this.loading = true; | ||
let offset = (this.currentPage - 1) * 5; | ||
let sorting = ""; | ||
if (this.sortBy) { | ||
sorting = "&sortby=" + encodeURIComponent(this.sortBy); | ||
if (this.sortDesc) { | ||
sorting += "&sortdesc=1"; | ||
} | ||
} | ||
let filter = ""; | ||
if (this.filterField && this.filterText) { | ||
filter = `&${this.filterField.value}=${this.filterText}`; | ||
} | ||
fetch( | ||
`/project/${this.project.id}/wfs3/collections/${this.typename}/items.json?limit=5&offset=${offset}${sorting}${filter}` | ||
) | ||
.then(response => { | ||
if (!response) { | ||
throw Error( | ||
`Error fetching attribute table data from QGIS Server` | ||
); | ||
} | ||
if (!response.ok) { | ||
throw Error(response.statusText); | ||
} | ||
return response; | ||
}) | ||
.then(response => response.json()) | ||
.then(json => { | ||
if (json.features.length) { | ||
let headers = [{ text: "", value: "zoomToFeature" }]; | ||
for (let k in json.features[0].properties) { | ||
headers.push({ | ||
text: k, | ||
value: this.fieldAliases[k] ? this.fieldAliases[k] : k | ||
}); | ||
} | ||
this.tableHeaders = headers; | ||
let data = []; | ||
for (let i = 0; i < json.features.length; i++) { | ||
let dataRow = json.features[i].properties; | ||
dataRow["feature"] = json.features[i]; | ||
dataRow["itemKeyInternalIdentifier"] = uuidv4(); | ||
data.push(dataRow); | ||
} | ||
this.tableData = data; | ||
this.numberMatched = json.numberMatched; | ||
} else { | ||
this.tableData = []; | ||
} | ||
}) | ||
.catch(error => { | ||
this.error = error.message; | ||
this.tableData = []; | ||
}); | ||
} catch (error) { | ||
this.error = error.message; | ||
this.tableData = []; | ||
} | ||
this.loading = false; | ||
}, | ||
zoomToFeature(feature) { | ||
this.map.highlightLayer.clearLayers(); | ||
this.map.highlightLayer.addData(feature); | ||
this.map.setView(this.map.highlightLayer.getBounds().getCenter()); | ||
} | ||
} | ||
}; | ||
</script> | ||
|
||
<style scoped> | ||
.btn-close { | ||
float: right; | ||
} | ||
</style> |
Oops, something went wrong.