Add geometry rendering improvements
This commit is contained in:
BIN
client/web/compose/src/assets/PageBlocks/Geometry.png
Normal file
BIN
client/web/compose/src/assets/PageBlocks/Geometry.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
@@ -14,3 +14,4 @@ export const Comment = require('./Comment.png')
|
||||
export const Report = require('./Report.png')
|
||||
export const Progress = require('./Progress.png')
|
||||
export const Nylas = require('./Nylas.jpg')
|
||||
export const Geometry = require('./Geometry.png')
|
||||
|
||||
@@ -144,6 +144,11 @@ export default {
|
||||
block: new compose.PageBlockNylas(),
|
||||
image: images.Nylas,
|
||||
},
|
||||
{
|
||||
label: this.$t('geometry.label'),
|
||||
block: new compose.PageBlockGeometry(),
|
||||
image: images.Geometry,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
233
client/web/compose/src/components/PageBlocks/GeometryBase.vue
Normal file
233
client/web/compose/src/components/PageBlocks/GeometryBase.vue
Normal file
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<wrap
|
||||
v-bind="$props"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<div
|
||||
v-if="processing"
|
||||
class="d-flex align-items-center justify-content-center h-100"
|
||||
>
|
||||
<b-spinner />
|
||||
</div>
|
||||
<template v-else>
|
||||
<div
|
||||
class="w-100 h-100"
|
||||
@mouseover="disableMap"
|
||||
@mouseleave="enableMap"
|
||||
>
|
||||
<l-map
|
||||
v-if="map"
|
||||
ref="map"
|
||||
:zoom="map.zoom"
|
||||
:center="map.center"
|
||||
:min-zoom="map.zoomMin"
|
||||
:max-zoom="map.zoomMax"
|
||||
:bounds="map.bounds"
|
||||
:max-bounds="map.bounds"
|
||||
class="w-100 h-100"
|
||||
>
|
||||
<l-tile-layer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
:attribution="map.attribution"
|
||||
/>
|
||||
<l-polygon
|
||||
v-for="(geometry, i) in geometries"
|
||||
:key="`polygon-${i}`"
|
||||
:lat-lngs="geometry.map(value => value.geometry)"
|
||||
:color="colors[i]"
|
||||
/>
|
||||
|
||||
<l-marker
|
||||
v-for="(marker, i) in localValue"
|
||||
:key="`marker-${i}`"
|
||||
:lat-lng="marker.value"
|
||||
:icon="getIcon(marker)"
|
||||
/>
|
||||
</l-map>
|
||||
</div>
|
||||
</template>
|
||||
</wrap>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { divIcon, latLng, latLngBounds } from 'leaflet'
|
||||
import {
|
||||
LPolygon,
|
||||
} from 'vue2-leaflet'
|
||||
import { compose, NoID } from '@cortezaproject/corteza-js'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { evaluatePrefilter } from 'corteza-webapp-compose/src/lib/record-filter'
|
||||
import base from './base'
|
||||
|
||||
export default {
|
||||
components: { LPolygon },
|
||||
|
||||
extends: base,
|
||||
|
||||
data () {
|
||||
return {
|
||||
map: undefined,
|
||||
|
||||
processing: false,
|
||||
show: false,
|
||||
|
||||
geometries: [],
|
||||
colors: [],
|
||||
markers: [],
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getModuleByID: 'module/getByID',
|
||||
}),
|
||||
|
||||
localValue () {
|
||||
const values = []
|
||||
|
||||
this.geometries.forEach((geo) => {
|
||||
geo.forEach((value) => {
|
||||
if (value.displayMarker) {
|
||||
value.markers.map(subValue => {
|
||||
values.push({ value: this.getLatLng(subValue), color: value.color })
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return values
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
'record.recordID': {
|
||||
immediate: true,
|
||||
handler () {
|
||||
this.loadEvents()
|
||||
},
|
||||
},
|
||||
options: {
|
||||
deep: true,
|
||||
handler () {
|
||||
this.loadEvents()
|
||||
},
|
||||
},
|
||||
boundingRect () {
|
||||
this.loadEvents()
|
||||
},
|
||||
},
|
||||
|
||||
created () {
|
||||
this.bounds = this.options.bounds
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions({
|
||||
findModuleByID: 'module/findByID',
|
||||
}),
|
||||
|
||||
loadEvents () {
|
||||
this.geometries = []
|
||||
|
||||
this.processing = true
|
||||
|
||||
this.colors = this.options.feeds.map(feed => feed.options.color)
|
||||
|
||||
const {
|
||||
bounds,
|
||||
center,
|
||||
zoomStarting,
|
||||
zoomMin,
|
||||
zoomMax,
|
||||
} = this.options
|
||||
|
||||
this.map = {
|
||||
bounds: this.options.bounds ? latLngBounds(bounds) : null,
|
||||
center,
|
||||
zoom: zoomStarting,
|
||||
zoomMin,
|
||||
zoomMax,
|
||||
}
|
||||
|
||||
Promise.all(this.options.feeds.map((feed, idx) => {
|
||||
return this.findModuleByID({ namespace: this.namespace, moduleID: feed.options.moduleID })
|
||||
.then(module => {
|
||||
// Interpolate prefilter variables
|
||||
if (feed.options.prefilter) {
|
||||
feed.options.prefilter = evaluatePrefilter(feed.options.prefilter, {
|
||||
record: this.record,
|
||||
recordID: (this.record || {}).recordID || NoID,
|
||||
ownerID: (this.record || {}).ownedBy || NoID,
|
||||
userID: (this.$auth.user || {}).userID || NoID,
|
||||
})
|
||||
}
|
||||
|
||||
return compose.PageBlockGeometry.RecordFeed(this.$ComposeAPI, module, this.namespace, feed)
|
||||
.then(records => {
|
||||
const mapModuleField = module.fields.find(f => f.name === feed.geometryField)
|
||||
|
||||
if (mapModuleField) {
|
||||
this.geometries[idx] = records.map(e => {
|
||||
let geometry = e.values[feed.geometryField]
|
||||
let markers = []
|
||||
|
||||
if (mapModuleField.isMulti) {
|
||||
geometry = geometry.map(value => this.parseGeometryField(value))
|
||||
markers = geometry
|
||||
} else {
|
||||
geometry = this.parseGeometryField(geometry)
|
||||
markers = [geometry]
|
||||
}
|
||||
|
||||
return ({
|
||||
title: e.values[feed.titleField],
|
||||
geometry: feed.displayPolygon ? geometry : [],
|
||||
markers,
|
||||
color: feed.options.color,
|
||||
displayMarker: feed.displayMarker,
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})).finally(() => {
|
||||
this.processing = false
|
||||
})
|
||||
},
|
||||
|
||||
getIcon (item) {
|
||||
item.circleColor = '#ffffff'
|
||||
|
||||
return divIcon({
|
||||
className: 'marker-pin',
|
||||
html: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 34.892337" height="60" width="40" style="margin-top: -40px;margin-left: -15px;height: 35px;">
|
||||
<g transform="translate(-814.59595,-274.38623)">
|
||||
<g transform="matrix(1.1855854,0,0,1.1855854,-151.17715,-57.3976)">
|
||||
<path d="m 817.11249,282.97118 c -1.25816,1.34277 -2.04623,3.29881 -2.01563,5.13867 0.0639,3.84476 1.79693,5.3002 4.56836,10.59179 0.99832,2.32851 2.04027,4.79237 3.03125,8.87305 0.13772,0.60193 0.27203,1.16104 0.33416,1.20948 0.0621,0.0485 0.19644,-0.51262 0.33416,-1.11455 0.99098,-4.08068 2.03293,-6.54258 3.03125,-8.87109 2.77143,-5.29159 4.50444,-6.74704 4.56836,-10.5918 0.0306,-1.83986 -0.75942,-3.79785 -2.01758,-5.14062 -1.43724,-1.53389 -3.60504,-2.66908 -5.91619,-2.71655 -2.31115,-0.0475 -4.4809,1.08773 -5.91814,2.62162 z" style="fill:${item.color};stroke:${item.color};"/>
|
||||
<circle r="3.0355" cy="288.25278" cx="823.03064" id="path3049" style="display:inline;fill:${item.circleColor};"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>`,
|
||||
})
|
||||
},
|
||||
|
||||
parseGeometryField (value) {
|
||||
return JSON.parse(value || '{"coordinates":[]}').coordinates || []
|
||||
},
|
||||
|
||||
getLatLng (coordinates = [undefined, undefined]) {
|
||||
const [lat, lng] = coordinates
|
||||
|
||||
if (lat && lng) {
|
||||
return latLng(lat, lng)
|
||||
}
|
||||
},
|
||||
disableMap () {
|
||||
if (this.editable) this.$refs.map.mapObject._handlers.forEach(handler => handler.disable())
|
||||
},
|
||||
enableMap () {
|
||||
if (this.editable) this.$refs.map.mapObject._handlers.forEach(handler => handler.enable())
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="my-2">
|
||||
<l-map
|
||||
ref="map"
|
||||
:zoom="options.zoomStarting"
|
||||
:min-zoom="options.zoomMin"
|
||||
:max-zoom="options.zoomMax"
|
||||
:center="options.center"
|
||||
:bounds="bounds"
|
||||
:max-bounds="options.bounds"
|
||||
class="w-100 cursor-pointer"
|
||||
style="height: 45vh;"
|
||||
@update:zoom="zoomUpdated"
|
||||
@update:center="updateCenter"
|
||||
@update:bounds="boundsUpdated"
|
||||
>
|
||||
<l-tile-layer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
:attribution="map.attribution"
|
||||
/>
|
||||
</l-map>
|
||||
<b-form-text id="password-help-block">
|
||||
{{ $t('geometry.mapHelpText') }}
|
||||
</b-form-text>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
<b-row
|
||||
class="mb-2 mt-4"
|
||||
>
|
||||
<b-col
|
||||
sm="12"
|
||||
md="4"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('geometry.zoom.zoomStartingLabel')"
|
||||
class="rounded-left"
|
||||
>
|
||||
<b-form-input
|
||||
v-model="options.zoomStarting"
|
||||
number
|
||||
readonly
|
||||
type="number"
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
sm="12"
|
||||
md="4"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('geometry.zoom.zoomMinLabel')"
|
||||
:description="`${options.zoomMin}`"
|
||||
class="rounded-0"
|
||||
>
|
||||
<b-form-input
|
||||
v-model="options.zoomMin"
|
||||
number
|
||||
:min="1"
|
||||
:max="18"
|
||||
type="range"
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
sm="12"
|
||||
md="4"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('geometry.zoom.zoomMaxLabel')"
|
||||
:description="`${options.zoomMax}`"
|
||||
>
|
||||
<b-form-input
|
||||
v-model="options.zoomMax"
|
||||
number
|
||||
:min="1"
|
||||
:max="18"
|
||||
type="range"
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
sm="12"
|
||||
md="4"
|
||||
>
|
||||
<b-form-group
|
||||
label-class="text-primary"
|
||||
:label="$t('geometry.centerLabel')"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
v-model="options.center[0]"
|
||||
type="number"
|
||||
number
|
||||
:placeholder="$t('latitude')"
|
||||
/>
|
||||
<b-form-input
|
||||
v-model="options.center[1]"
|
||||
type="number"
|
||||
number
|
||||
:placeholder="$t('longitude')"
|
||||
/>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
sm="12"
|
||||
md="4"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('geometry.bounds.lockBounds')"
|
||||
class="rounded-left"
|
||||
>
|
||||
<b-form-checkbox
|
||||
v-model="options.lockBounds"
|
||||
name="lock-bounds"
|
||||
switch
|
||||
size="lg"
|
||||
@change="updateBounds"
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { latLng } from 'leaflet'
|
||||
import base from '../base'
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
|
||||
i18nOptions: {
|
||||
namespaces: 'block',
|
||||
},
|
||||
|
||||
extends: base,
|
||||
|
||||
data () {
|
||||
return {
|
||||
map: {
|
||||
show: false,
|
||||
zoom: 3,
|
||||
center: [30, 30],
|
||||
rotation: 0,
|
||||
attribution: '© <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a>',
|
||||
},
|
||||
|
||||
localValue: { coordinates: [] },
|
||||
center: [],
|
||||
bounds: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getLatLng (coordinates = [undefined, undefined]) {
|
||||
const [lat, lng] = coordinates
|
||||
|
||||
if (lat && lng) {
|
||||
return latLng(lat, lng)
|
||||
}
|
||||
},
|
||||
updateCenter (coordinates) {
|
||||
let { lat = 0, lng = 0 } = coordinates || {}
|
||||
|
||||
lat = Math.round(lat * 1e7) / 1e7
|
||||
lng = Math.round(lng * 1e7) / 1e7
|
||||
|
||||
this.options.center = [lat, lng]
|
||||
},
|
||||
boundsUpdated (coordinates) {
|
||||
this.bounds = coordinates
|
||||
|
||||
this.updateBounds(this.options.lockBounds)
|
||||
},
|
||||
zoomUpdated (zoom) {
|
||||
this.options.zoomStarting = zoom
|
||||
},
|
||||
updateBounds (value) {
|
||||
if (value) {
|
||||
const bounds = this.bounds || this.$refs.map.mapObject.getBounds()
|
||||
const { _northEast, _southWest } = bounds
|
||||
|
||||
this.options.bounds = [Object.values(_northEast), Object.values(_southWest)]
|
||||
} else {
|
||||
this.options.bounds = null
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="feed.options">
|
||||
<b-form-group
|
||||
horizontal
|
||||
:label-cols="3"
|
||||
breakpoint="md"
|
||||
:label="$t('geometry.recordFeed.moduleLabel')"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-select
|
||||
v-model="feed.options.moduleID"
|
||||
:options="modules"
|
||||
value-field="moduleID"
|
||||
text-field="name"
|
||||
>
|
||||
<template slot="first">
|
||||
<option value="0">
|
||||
{{ $t('geometry.recordFeed.modulePlaceholder') }}
|
||||
</option>
|
||||
</template>
|
||||
</b-form-select>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
|
||||
<template v-if="module">
|
||||
<b-form-group
|
||||
horizontal
|
||||
:label-cols="3"
|
||||
breakpoint="md"
|
||||
:label="$t('geometry.recordFeed.colorLabel')"
|
||||
>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
v-model="feed.options.color"
|
||||
style="max-width: 50px;"
|
||||
type="color"
|
||||
debounce="300"
|
||||
/>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
horizontal
|
||||
:label-cols="3"
|
||||
breakpoint="md"
|
||||
:label="$t('geometry.recordFeed.titleLabel')"
|
||||
>
|
||||
<b-form-select
|
||||
v-model="feed.titleField"
|
||||
:options="titleFields | optionizeFields"
|
||||
>
|
||||
<template slot="first">
|
||||
<option
|
||||
disabled
|
||||
value=""
|
||||
>
|
||||
{{ $t('geometry.recordFeed.titlePlaceholder') }}
|
||||
</option>
|
||||
</template>
|
||||
</b-form-select>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
horizontal
|
||||
:label-cols="3"
|
||||
breakpoint="md"
|
||||
:label="$t('geometry.recordFeed.displayMarker')"
|
||||
>
|
||||
<b-form-checkbox
|
||||
v-model="feed.displayMarker"
|
||||
name="display-marker"
|
||||
switch
|
||||
size="lg"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
horizontal
|
||||
:label-cols="3"
|
||||
breakpoint="md"
|
||||
:label="$t('geometry.recordFeed.displayPolygon')"
|
||||
>
|
||||
<b-form-checkbox
|
||||
v-model="feed.displayPolygon"
|
||||
name="display-marker"
|
||||
switch
|
||||
size="lg"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
horizontal
|
||||
:label-cols="3"
|
||||
breakpoint="md"
|
||||
:label="$t('geometry.recordFeed.geometryFieldLabel')"
|
||||
>
|
||||
<b-form-select
|
||||
v-model="feed.geometryField"
|
||||
:options="geometryFields | optionizeFields"
|
||||
>
|
||||
<template slot="first">
|
||||
<option
|
||||
disabled
|
||||
value=""
|
||||
>
|
||||
{{ $t('geometry.recordFeed.geometryFieldPlaceholder') }}
|
||||
</option>
|
||||
</template>
|
||||
</b-form-select>
|
||||
</b-form-group>
|
||||
|
||||
<br>
|
||||
|
||||
<b-form-group
|
||||
horizontal
|
||||
:label-cols="3"
|
||||
breakpoint="md"
|
||||
:label="$t('calendar.recordFeed.prefilterLabel')"
|
||||
>
|
||||
<b-form-textarea
|
||||
v-model="feed.options.prefilter"
|
||||
:value="true"
|
||||
:placeholder="$t('calendar.recordFeed.prefilterPlaceholder')"
|
||||
/>
|
||||
</b-form-group>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import base from './base'
|
||||
|
||||
export default {
|
||||
i18nOptions: {
|
||||
namespaces: 'block',
|
||||
},
|
||||
|
||||
extends: base,
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Finds the module, this feed configurator should use
|
||||
* @returns {Module|undefined}
|
||||
*/
|
||||
module () {
|
||||
if (!(this.feed.options || {}).moduleID) {
|
||||
return
|
||||
}
|
||||
|
||||
return this.modules.find(({ moduleID }) => moduleID === this.feed.options.moduleID)
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if given module has any multi-fields
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
hasMultiFields () {
|
||||
if (!this.module) {
|
||||
return false
|
||||
}
|
||||
return this.module.fields.reduce((acc, { isMulti }) => acc || isMulti, false)
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines available title fields based on the given module.
|
||||
* @returns {Array}
|
||||
*/
|
||||
titleFields () {
|
||||
if (!this.module) {
|
||||
return []
|
||||
}
|
||||
return [...this.module.fields]
|
||||
.filter(f => [
|
||||
'DateTime',
|
||||
'Select',
|
||||
'Number',
|
||||
'Bool',
|
||||
'String',
|
||||
'Record',
|
||||
'User',
|
||||
].includes(f.kind))
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines available geometry fields based on the given module.
|
||||
* Currently ignores multi-fields
|
||||
* @returns {Array}
|
||||
*/
|
||||
geometryFields () {
|
||||
if (!this.module) {
|
||||
return []
|
||||
}
|
||||
|
||||
const moduleFields = this.module.fields.slice().sort((a, b) => a.label.localeCompare(b.label))
|
||||
|
||||
return [
|
||||
...moduleFields,
|
||||
...this.module.systemFields().map(sf => {
|
||||
sf.label = this.$t(`field:system.${sf.name}`)
|
||||
return sf
|
||||
}),
|
||||
].filter(f => f.kind === 'Geometry')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script>
|
||||
/**
|
||||
* Provides base for each feed configurator.
|
||||
* Define common props, methods, ... in here.
|
||||
*/
|
||||
export default {
|
||||
filters: {
|
||||
/**
|
||||
* Prepares a set of select options
|
||||
* @param {Array} ff Raw options
|
||||
* @returns {Array}
|
||||
*/
|
||||
optionizeFields (ff) {
|
||||
return ff.map(f => {
|
||||
return {
|
||||
text: f.label || f.name,
|
||||
value: f.name,
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
props: {
|
||||
feed: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({}),
|
||||
},
|
||||
modules: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as record } from './Record'
|
||||
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<fieldset class="form-group">
|
||||
<div
|
||||
v-for="(feed, i) in options.feeds"
|
||||
:key="i"
|
||||
>
|
||||
<div
|
||||
class="d-flex justify-content-between mb-3"
|
||||
>
|
||||
<h5>
|
||||
{{ $t('geometry.feedLabel') }}
|
||||
</h5>
|
||||
|
||||
<template
|
||||
v-if="feed.resource"
|
||||
>
|
||||
<b-button
|
||||
variant="outline-danger"
|
||||
class="border-0"
|
||||
@click="onRemoveFeed(i)"
|
||||
>
|
||||
<font-awesome-icon :icon="['far', 'trash-alt']" />
|
||||
</b-button>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<b-form-group horizontal>
|
||||
<component
|
||||
:is="configurator(feed)"
|
||||
v-if="feed.resource && configurator(feed)"
|
||||
:feed="feed"
|
||||
:modules="modules"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<b-button
|
||||
class="btn btn-url test-feed-add"
|
||||
@click.prevent="handleAddButton"
|
||||
>
|
||||
{{ $t('geometry.addSource') }}
|
||||
</b-button>
|
||||
</fieldset>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import base from '../../base'
|
||||
import * as configs from './configs'
|
||||
import { compose } from '@cortezaproject/corteza-js'
|
||||
|
||||
export default {
|
||||
i18nOptions: {
|
||||
namespaces: 'block',
|
||||
},
|
||||
|
||||
components: {
|
||||
},
|
||||
|
||||
extends: base,
|
||||
|
||||
computed: {
|
||||
...mapGetters({
|
||||
modules: 'module/set',
|
||||
}),
|
||||
|
||||
/**
|
||||
* Provides a set of available feed sources.
|
||||
* @returns {Array}
|
||||
*/
|
||||
feedSources () {
|
||||
return Object.entries(compose.PageBlockGeometry.feedResources).map(([key, value]) => ({
|
||||
value,
|
||||
text: this.$t(`geometry.${key}Feed.optionLabel`),
|
||||
}))
|
||||
},
|
||||
},
|
||||
|
||||
created () {
|
||||
if (this.options.feeds.length === 0) {
|
||||
this.block.options.feeds = []
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Handles feed removal
|
||||
* @param {Number} i Feed's index
|
||||
*/
|
||||
onRemoveFeed (i) {
|
||||
this.block.options.feeds.splice(i, 1)
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles feed's addition
|
||||
*/
|
||||
handleAddButton () {
|
||||
this.block.options.feeds.push(compose.PageBlockGeometry.makeFeed())
|
||||
},
|
||||
|
||||
/**
|
||||
* configurator uses feed's resource to determine what configurator to use.
|
||||
* If it can't find an apropriate component, undefined is returned
|
||||
* @param {Feed} feed Feed in qestion
|
||||
* @returns {Component|undefined}
|
||||
*/
|
||||
configurator (feed) {
|
||||
if (!feed.resource) {
|
||||
return
|
||||
}
|
||||
const r = feed.resource.split(':').pop()
|
||||
return configs[r]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-tab :title="$t('geometry.viewLabel')">
|
||||
<configurator
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</b-tab>
|
||||
|
||||
<b-tab :title="$t('geometry.feedLabel')">
|
||||
<feed-source
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
/>
|
||||
</b-tab>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FeedSource from './FeedSource'
|
||||
import Configurator from './Configurator'
|
||||
|
||||
export default {
|
||||
i18nOptions: {
|
||||
namespaces: 'block',
|
||||
},
|
||||
|
||||
components: {
|
||||
FeedSource,
|
||||
Configurator,
|
||||
},
|
||||
|
||||
inheritAttrs: false,
|
||||
}
|
||||
</script>
|
||||
@@ -55,6 +55,12 @@ export default {
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
|
||||
editable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data () {
|
||||
|
||||
@@ -34,6 +34,8 @@ import ProgressBase from './ProgressBase'
|
||||
import ProgressConfigurator from './ProgressConfigurator'
|
||||
import NylasBase from './Nylas/NylasBase'
|
||||
import NylasConfigurator from './Nylas/NylasConfigurator'
|
||||
import GeometryBase from './GeometryBase'
|
||||
import GeometryConfigurator from './GeometryConfigurator/index'
|
||||
|
||||
/**
|
||||
* List of all known page block components
|
||||
@@ -73,6 +75,8 @@ const Registry = {
|
||||
ProgressConfigurator,
|
||||
NylasBase,
|
||||
NylasConfigurator,
|
||||
GeometryBase,
|
||||
GeometryConfigurator,
|
||||
}
|
||||
|
||||
const defaultMode = 'Base'
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
</div>
|
||||
|
||||
<page-block
|
||||
v-bind="{ ...$attrs, ...$props, page, block, boundingRect, blockIndex: index }"
|
||||
v-bind="{ ...$attrs, ...$props, page, block, boundingRect, blockIndex: index, editable: true }"
|
||||
:record="record"
|
||||
:module="module"
|
||||
class="p-2"
|
||||
|
||||
35
lib/js/src/compose/types/page-block/geometry/feed-record.ts
Normal file
35
lib/js/src/compose/types/page-block/geometry/feed-record.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Record } from '../../record'
|
||||
import { Namespace } from '../../namespace'
|
||||
import { Module } from '../../module'
|
||||
import { Compose as ComposeAPI } from '../../../../api-clients'
|
||||
|
||||
interface FeedOptions {
|
||||
color: string;
|
||||
prefilter: string;
|
||||
}
|
||||
|
||||
interface Feed {
|
||||
titleField: string;
|
||||
options: FeedOptions;
|
||||
}
|
||||
|
||||
interface Range {
|
||||
end: Date;
|
||||
start: Date;
|
||||
}
|
||||
|
||||
export async function RecordFeed ($ComposeAPI: ComposeAPI, module: Module, namespace: Namespace, feed: Feed): Promise<any[]> {
|
||||
// Params for record fetching
|
||||
const params = {
|
||||
namespaceID: namespace.namespaceID,
|
||||
moduleID: module.moduleID,
|
||||
query: feed.options.prefilter,
|
||||
}
|
||||
|
||||
const events: Array<any> = []
|
||||
return $ComposeAPI.recordList(params).then(({ set }) => {
|
||||
return (set as Array<{ recordID: string }>)
|
||||
// cast & freeze
|
||||
.map(r => Object.freeze(new Record(module, r)))
|
||||
})
|
||||
}
|
||||
56
lib/js/src/compose/types/page-block/geometry/feed.ts
Normal file
56
lib/js/src/compose/types/page-block/geometry/feed.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Apply, NoID } from '../../../../cast'
|
||||
import { IsOf } from '../../../../guards'
|
||||
|
||||
interface FeedOptions {
|
||||
color: string;
|
||||
prefilter: string;
|
||||
moduleID: string;
|
||||
resource: string;
|
||||
titleField: string;
|
||||
geometryField: string;
|
||||
displayMarker: boolean;
|
||||
displayPolygon: boolean;
|
||||
}
|
||||
|
||||
export type FeedInput = Partial<Feed> | Feed
|
||||
|
||||
const defOptions = {
|
||||
moduleID: NoID,
|
||||
color: '#2f85cb',
|
||||
prefilter: '',
|
||||
resource: 'compose:record',
|
||||
titleField: '',
|
||||
geometryField: '',
|
||||
displayMarker: false,
|
||||
displayPolygon: true,
|
||||
}
|
||||
|
||||
/**
|
||||
* Feed class represents an event feed for the given calendar
|
||||
*/
|
||||
export default class Feed {
|
||||
public resource = 'compose:record'
|
||||
public titleField = ''
|
||||
public color = '#2f85cb'
|
||||
public geometryField = ''
|
||||
public displayMarker = false
|
||||
public displayPolygon = true
|
||||
public options: FeedOptions = { ...defOptions }
|
||||
|
||||
constructor (i?: FeedInput) {
|
||||
this.apply(i)
|
||||
}
|
||||
|
||||
apply (i?: FeedInput): void {
|
||||
if (!i) return
|
||||
|
||||
if (IsOf<Feed>(i, 'resource')) {
|
||||
Apply(this, i, String, 'resource', 'color', 'titleField', 'geometryField')
|
||||
Apply(this, i, Boolean, 'displayMarker', 'displayPolygon')
|
||||
|
||||
if (i.options) {
|
||||
this.options = { ...this.options, ...i.options }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
lib/js/src/compose/types/page-block/geometry/index.ts
Normal file
1
lib/js/src/compose/types/page-block/geometry/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { PageBlockGeometry } from './page-block'
|
||||
63
lib/js/src/compose/types/page-block/geometry/page-block.ts
Normal file
63
lib/js/src/compose/types/page-block/geometry/page-block.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { PageBlock, Registry } from '../base'
|
||||
import { Apply } from '../../../../cast'
|
||||
import Feed, { FeedInput } from './feed'
|
||||
import { RecordFeed } from './feed-record'
|
||||
|
||||
const kind = 'Geometry'
|
||||
|
||||
type Bounds = number[][]
|
||||
|
||||
interface Options {
|
||||
defaultView: string;
|
||||
center: Array<number>;
|
||||
feeds: Array<Feed>;
|
||||
zoomStarting: number;
|
||||
zoomMin: number;
|
||||
zoomMax: number;
|
||||
bounds: Bounds | null;
|
||||
lockBounds: boolean;
|
||||
}
|
||||
|
||||
const defaults: Readonly<Options> = Object.freeze({
|
||||
defaultView: '',
|
||||
center: [35, -30],
|
||||
feeds: [],
|
||||
zoomStarting: 2,
|
||||
zoomMin: 1,
|
||||
zoomMax: 18,
|
||||
bounds: null,
|
||||
lockBounds: false,
|
||||
})
|
||||
|
||||
export class PageBlockGeometry extends PageBlock {
|
||||
readonly kind = kind
|
||||
options: Options = { ...defaults }
|
||||
|
||||
static feedResources = Object.freeze({
|
||||
record: 'compose:record',
|
||||
})
|
||||
|
||||
constructor (i?: PageBlock | Partial<PageBlock>) {
|
||||
super(i)
|
||||
this.applyOptions(i?.options as Partial<Options>)
|
||||
}
|
||||
|
||||
applyOptions (o?: Partial<Options>): void {
|
||||
if (!o) return
|
||||
|
||||
this.options.feeds = (o.feeds || []).map(f => new Feed(f))
|
||||
this.options.center = (o.center || [])
|
||||
this.options.bounds = (o.bounds || null)
|
||||
|
||||
Apply(this.options, o, Number, 'zoomStarting', 'zoomMin', 'zoomMax')
|
||||
Apply(this.options, o, Boolean, 'lockBounds')
|
||||
}
|
||||
|
||||
static makeFeed (f?: FeedInput): Feed {
|
||||
return new Feed(f)
|
||||
}
|
||||
|
||||
static RecordFeed = RecordFeed
|
||||
}
|
||||
|
||||
Registry.set(kind, PageBlockGeometry)
|
||||
@@ -0,0 +1,3 @@
|
||||
export const feedResources = {
|
||||
record: 'compose:record',
|
||||
}
|
||||
@@ -15,6 +15,8 @@ export { PageBlockComment } from './comment'
|
||||
export { PageBlockReport } from './report'
|
||||
export { PageBlockProgress } from './progress'
|
||||
export { PageBlockNylas } from './nylas'
|
||||
export { PageBlockGeometry } from './geometry'
|
||||
|
||||
|
||||
export function PageBlockMaker<T extends PageBlock> (i: { kind: string }): T {
|
||||
const PageBlockTemp = Registry.get(i.kind)
|
||||
|
||||
@@ -565,4 +565,34 @@ nylas:
|
||||
email: Email
|
||||
mailbox: Mailbox
|
||||
|
||||
|
||||
geometry:
|
||||
label: Geometry
|
||||
viewLabel: Geometry
|
||||
feedLabel: Configure Sources
|
||||
addSource: Add source
|
||||
lock: Lock
|
||||
mapHelpText: Move and scroll the map to the position that will be used as a default. You can also lock the position or zoom to prevent users from moving too far.
|
||||
recordFeed:
|
||||
moduleLabel: Select module
|
||||
modulePlaceholder: (No module)
|
||||
noMultiFields: Multi-value fields are currently not supported
|
||||
optionLabel: Records
|
||||
prefilterLabel: Prefilter sources
|
||||
colorLabel: Marker color
|
||||
geometryFieldLabel: Geometry field
|
||||
titleLabel: Title
|
||||
titlePlaceholder: Title
|
||||
geometryFieldPlaceholder: Geometry field
|
||||
displayMarker: Display marker
|
||||
displayPolygon: Display polygon
|
||||
centerLabel: Center
|
||||
zoom:
|
||||
zoomLabel: Zoom
|
||||
zoomMinLabel: Zoom min
|
||||
zoomMaxLabel: Zoom max
|
||||
zoomStartingLabel: Zoom Starting
|
||||
bounds:
|
||||
boundsLabel: Bounds
|
||||
lockBounds: Lock bounds
|
||||
topLeft: Bounds top left
|
||||
lowerRight: Bounds lower right
|
||||
|
||||
@@ -96,6 +96,7 @@ kind:
|
||||
modulePlaceholder: Pick module
|
||||
queryFieldsLabel: Query fields on search
|
||||
moduleField: Module field
|
||||
variantField: Variant field
|
||||
fieldFromModuleField: Label field from related module field
|
||||
pickField: Pick field
|
||||
suggestionPlaceholder: Start typing to search for records
|
||||
|
||||
Reference in New Issue
Block a user