Skip to content

Commit 8a8627a

Browse files
committed
Merge branch 'style-manager'
2 parents 55595a4 + 1fb4418 commit 8a8627a

23 files changed

+895
-50
lines changed

core/client/components/collection/KItem.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<!--
1212
Avatar section
1313
-->
14-
<q-item-section top avatar @click="onItemSelected('avatar')">
14+
<q-item-section v-if="avatar" top avatar @click="onItemSelected('avatar')">
1515
<slot name="item-avatar">
1616
<KAvatar
1717
:subject="item"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<template>
2+
<div>
3+
<q-btn id="style-point-color" round style="max-width: 16px" :style="{ 'background-color': color }"/>
4+
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
5+
<q-color
6+
v-model="color"
7+
:no-header-tabs="noHeaderTabs"
8+
:no-footer="noFooter"
9+
:format-model="formatModel"
10+
:default-view="defaultView"
11+
/>
12+
</q-popup-proxy>
13+
</div>
14+
</template>
15+
16+
<script setup>
17+
import { ref, watch } from 'vue'
18+
19+
const props = defineProps({
20+
modelValue: {
21+
type: String,
22+
default: '#FF0000'
23+
},
24+
formatModel: {
25+
type: String,
26+
default: 'hex'
27+
},
28+
noHeaderTabs: {
29+
type: Boolean,
30+
default: true
31+
},
32+
noFooter: {
33+
type: Boolean,
34+
default: false
35+
},
36+
defaultView: {
37+
type: String,
38+
default: 'spectrum',
39+
validator: (value) => {
40+
return ['spectrum', 'tune', 'palette', 'swatches'].includes(value)
41+
}
42+
}
43+
})
44+
45+
const emit = defineEmits(['update:modelValue'])
46+
const color = ref(props.modelValue)
47+
48+
watch(color, newColor => {
49+
emit('update:modelValue', newColor)
50+
})
51+
52+
</script>

core/client/mixins/mixin.base-item.js

+3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ export const baseItem = {
6060
},
6161
description () {
6262
return _.get(this.item, this.options.descriptionField || 'description', '')
63+
},
64+
avatar () {
65+
return _.get(this.options, 'avatar', true)
6366
}
6467
},
6568
methods: {

core/client/utils/utils.shapes.js

+1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ export function createShape (options) {
210210
if (options.stroke.join) svgShapeContent = addSvgAttribute(svgShapeContent, 'stroke-linejoin', options.stroke.join)
211211
if (options.stroke.dashArray) svgShapeContent = addSvgAttribute(svgShapeContent, 'stroke-dasharray', options.stroke.dashArray)
212212
if (options.stroke.dashOffset) svgShapeContent = addSvgAttribute(svgShapeContent, 'stroke-dashoffset', options.stroke.dashOffset)
213+
if (options.stroke.opacity) svgShapeContent = addSvgAttribute(svgShapeContent, 'stroke-opacity', options.stroke.opacity)
213214
const clipId = uid()
214215
// clip the shape to avoid stroke overflow
215216
svgShapeContent = addSvgAttribute(svgShapeContent, 'clip-path', `url(#${clipId})`)
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default function (app, options) {
2+
const db = options.db || app.db
3+
options.Model = db.collection('styles')
4+
// Collation provided in query ensure sorting to be case insensitive w.r.t. user's language
5+
// We built indices with collation to cover the most used languages, it requires different naming...
6+
options.Model.createIndex({ name: 1 }, { name: 'name-en', collation: { locale: 'en', strength: 1 } })
7+
options.Model.createIndex({ name: 1 }, { name: 'name-fr', collation: { locale: 'fr', strength: 1 } })
8+
}

map/api/services/index.js

+18
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,19 @@ export async function createCatalogFeaturesServices (options = {}) {
245245
}
246246
}
247247

248+
export function createStylesService (options = {}) {
249+
const app = this
250+
return app.createService('styles', Object.assign({
251+
servicesPath,
252+
modelsPath
253+
}, options))
254+
}
255+
256+
export function removeStylesService (options = {}) {
257+
const app = this
258+
return app.removeService(app.getService('styles', options.context))
259+
}
260+
248261
export default async function () {
249262
const app = this
250263

@@ -268,6 +281,11 @@ export default async function () {
268281
if (alertsConfig) {
269282
await createAlertsService.call(app)
270283
}
284+
const stylesConfig = app.get('styles')
285+
if (stylesConfig) {
286+
await createStylesService.call(app)
287+
debug('\'styles\' service created')
288+
}
271289

272290
/*
273291
app.createService('daptiles', Object.assign({
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import fuzzySearch from 'feathers-mongodb-fuzzy-search'
2+
import { hooks as kdkCoreHooks } from '../../../../core/api/index.js'
3+
4+
export default {
5+
before: {
6+
all: [],
7+
find: [
8+
fuzzySearch({ fields: ['name'] }),
9+
kdkCoreHooks.diacriticSearch()
10+
],
11+
get: [],
12+
create: [],
13+
update: [],
14+
patch: [],
15+
remove: []
16+
},
17+
18+
after: {
19+
all: [],
20+
find: [],
21+
get: [],
22+
create: [],
23+
update: [],
24+
patch: [],
25+
remove: []
26+
},
27+
28+
error: {
29+
all: [],
30+
find: [],
31+
get: [],
32+
create: [],
33+
update: [],
34+
patch: [],
35+
remove: []
36+
}
37+
}

map/client/components/selection/KSelectedLayerFeatures.vue

+101-42
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,58 @@
11
<template>
2-
<q-tree
3-
:nodes="[root]"
4-
node-key="_id"
5-
label-key="label"
6-
children-key="children"
7-
v-model:expanded="expandedNodes"
8-
dense
9-
>
10-
<template v-slot:default-header="prop">
11-
<!-- Layer rendering -->
12-
<q-icon v-if="prop.node.icon" :name="prop.node.icon"/>
13-
<KLayerItem v-if="isLayerNode(prop.node)"
14-
v-bind="$props"
15-
:togglable="false"
16-
:layer="root"
17-
/>
18-
<!-- Features rendering -->
19-
<div v-else-if="prop.node.label" class="row fit items-center q-pl-md q-pr-sm no-wrap">
20-
<div :class="{
21-
'text-primary': root.isVisible,
22-
'text-grey-6': root.isDisabled || !root.isVisible
23-
}"
24-
>
25-
<span v-html="prop.node.label" />
2+
<div>
3+
<q-tree
4+
:nodes="[root]"
5+
node-key="_id"
6+
label-key="label"
7+
children-key="children"
8+
v-model:expanded="expandedNodes"
9+
dense
10+
>
11+
<template v-slot:default-header="prop">
12+
<!-- Layer rendering -->
13+
<q-icon v-if="prop.node.icon" :name="prop.node.icon"/>
14+
<KLayerItem v-if="isLayerNode(prop.node)"
15+
v-bind="$props"
16+
:togglable="false"
17+
:layer="root"
18+
/>
19+
<!-- Features rendering -->
20+
<div v-else-if="prop.node.label" class="row fit items-center q-pl-md q-pr-sm no-wrap">
21+
<div :class="{
22+
'text-primary': root.isVisible,
23+
'text-grey-6': root.isDisabled || !root.isVisible
24+
}"
25+
>
26+
<span v-html="prop.node.label" />
27+
</div>
28+
<q-space v-if="isFeatureNode(prop.node)"/>
29+
<!-- Features actions -->
30+
<KPanel v-if="isFeatureNode(prop.node)"
31+
:id="`${prop.node.label}-feature-actions`"
32+
:content="featureActions"
33+
:context="prop.node"
34+
/>
2635
</div>
27-
<q-space v-if="isFeatureNode(prop.node)"/>
28-
<!-- Features actions -->
29-
<KPanel v-if="isFeatureNode(prop.node)"
30-
:id="`${prop.node.label}-feature-actions`"
31-
:content="featureActions"
32-
:context="prop.node"
36+
</template>
37+
<!-- Feature properties rendering -->
38+
<template v-slot:default-body="prop">
39+
<KView v-if="isFeaturePropertiesNode(prop.node)"
40+
class="q-pa-md full-width"
41+
:values="prop.node"
42+
:schema="schema"
43+
:separators="true"
3344
/>
34-
</div>
35-
</template>
36-
<!-- Feature properties rendering -->
37-
<template v-slot:default-body="prop">
38-
<KView v-if="isFeaturePropertiesNode(prop.node)"
39-
class="q-pa-md full-width"
40-
:values="prop.node"
41-
:schema="schema"
42-
:separators="true"
43-
/>
44-
</template>
45-
</q-tree>
45+
</template>
46+
</q-tree>
47+
<KModal
48+
id="style-editor-modal"
49+
:title="styleEditorTitle"
50+
:buttons="[]"
51+
v-model="isFeatureStyleEdited"
52+
>
53+
<KStyleEditor :edit-name="false" :allowed-styles="[editedFeatureType]" :style="editedFeatureStyle" @cancel="onCancelFeatureStyle" @apply="onApplyFeatureStyle" />
54+
</KModal>
55+
</div>
4656
</template>
4757

4858
<script setup>
@@ -54,9 +64,11 @@ import bbox from '@turf/bbox'
5464
import { Store, i18n } from '../../../../core/client'
5565
import { KView } from '../../../../core/client/components'
5666
import KLayerItem from '../catalog/KLayerItem.vue'
67+
import KStyleEditor from '../styles/KStyleEditor.vue'
5768
import { useCurrentActivity } from '../../composables/activity.js'
5869
import { getFeatureId, getFeatureLabel } from '../../utils/utils.js'
5970
import { isLayerDataEditable } from '../../utils/utils.layers.js'
71+
import { getFeatureStyleType } from '../../utils/utils.features.js'
6072
import { generatePropertiesSchema } from '../../utils/utils.schema.js'
6173
6274
// Props
@@ -73,8 +85,17 @@ const router = useRouter()
7385
const { CurrentActivity } = useCurrentActivity()
7486
const expandedNodes = ref([props.item.layer._id])
7587
const editedFeatures = ref([])
88+
const editedFeature = ref(null)
89+
const editedFeatureType = ref(null)
90+
const editedFeatureStyle = ref(null)
7691
7792
// Computed
93+
const isFeatureStyleEdited = computed(() => {
94+
return !_.isNil(editedFeatureStyle.value)
95+
})
96+
const styleEditorTitle = computed(() => {
97+
return (editedFeature.value ? getFeatureLabel(editedFeature.value, props.item.layer) : '')
98+
})
7899
const layerActions = computed(() => {
79100
return [{
80101
id: 'layer-actions',
@@ -94,6 +115,12 @@ const layerActions = computed(() => {
94115
icon: 'las la-edit',
95116
handler: editSelectedFeatures,
96117
visible: isLayerDataEditable(props.item.layer)
118+
}, {
119+
id: 'reset-style-selected-features',
120+
label: 'KSelectedLayerFeatures.RESET_FEATURES_STYLE_LABEL',
121+
icon: 'las la-ban',
122+
handler: resetSelectedFeaturesStyle,
123+
visible: isLayerDataEditable(props.item.layer)
97124
}, {
98125
id: 'remove-selected-features',
99126
label: 'KSelectedLayerFeatures.REMOVE_FEATURES_LABEL',
@@ -128,6 +155,18 @@ const featureActions = computed(() => {
128155
icon: 'las la-address-card',
129156
handler: editSelectedFeatureProperties,
130157
visible: isLayerDataEditable(props.item.layer) && _.get(props.item.layer, 'schema.content')
158+
}, {
159+
id: 'edit-style-selected-feature',
160+
label: 'KSelectedLayerFeatures.EDIT_FEATURE_STYLE_LABEL',
161+
icon: 'las la-paint-brush',
162+
handler: editSelectedFeatureStyle,
163+
visible: isLayerDataEditable(props.item.layer)
164+
}, {
165+
id: 'reset-style-selected-feature',
166+
label: 'KSelectedLayerFeatures.RESET_FEATURE_STYLE_LABEL',
167+
icon: 'las la-ban',
168+
handler: resetSelectedFeatureStyle,
169+
visible: isLayerDataEditable(props.item.layer)
131170
}, {
132171
id: 'remove-selected-feature',
133172
label: 'KSelectedLayerFeatures.REMOVE_FEATURE_LABEL',
@@ -247,6 +286,26 @@ function editSelectedFeatureProperties (feature) {
247286
})
248287
})
249288
}
289+
function editSelectedFeatureStyle (feature) {
290+
editedFeature.value = feature
291+
editedFeatureType.value = getFeatureStyleType(feature)
292+
editedFeatureStyle.value = { [editedFeatureType.value]: _.get(feature, 'style', {}) }
293+
}
294+
function onCancelFeatureStyle () {
295+
editedFeature.value = null
296+
editedFeatureType.value = null
297+
editedFeatureStyle.value = null
298+
}
299+
async function onApplyFeatureStyle (style) {
300+
await CurrentActivity.value.editFeaturesStyle(Object.assign(editedFeature.value, { style: style[editedFeatureType.value] }), props.item.layer)
301+
onCancelFeatureStyle()
302+
}
303+
async function resetSelectedFeaturesStyle () {
304+
await CurrentActivity.value.editFeaturesStyle({ type: 'FeatureCollection', features: props.item.features.map(feature => Object.assign(feature, { style: {} })) }, props.item.layer)
305+
}
306+
async function resetSelectedFeatureStyle (feature) {
307+
await CurrentActivity.value.editFeaturesStyle(Object.assign(feature, { style: {} }), props.item.layer)
308+
}
250309
function removeSelectedFeatures () {
251310
Dialog.create({
252311
title: i18n.t('KSelectedLayerFeatures.REMOVE_FEATURES_DIALOG_TITLE'),

0 commit comments

Comments
 (0)