snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s

This commit is contained in:
upstream-mirror
2026-09-18 01:22:23 +05:00
commit 7e385fc126
8391 changed files with 1766236 additions and 0 deletions

View File

@@ -0,0 +1 @@
export { default as CItemPicker } from './CItemPicker.c3'

View File

@@ -0,0 +1,49 @@
// eslint-disable-next-line
import { default as component } from './CItemPicker.vue'
import { components } from '@cortezaproject/corteza-vue'
const { checkbox } = components.C3.controls
const props = {
options: [
{ value: 'pl', text: 'pl' },
{ value: 'pl2', text: 'pl' },
{ value: 'pl3', text: 'pl' },
{ value: 'oo', text: 'oo' },
],
value: [
'pl',
'pl2',
'pl3',
],
labels: {
searchPlaceholder: 'Type here to search among module fields',
availableItems: 'Available fields',
selectAllItems: 'Select all',
selectedItems: 'Selected fields',
systemItem: '(system field)',
unselectAllItems: 'Unselect all',
noItemsFound: 'No Items Found',
},
hideFilter: false,
hideIcons: false,
disabled: false,
disabledFilter: false,
disabledSorting: false,
disabledDragging: false,
}
export default {
name: 'CItemPicker',
group: ['Picker'],
component,
props,
controls: [
checkbox('Hide filter', 'hideFilter'),
checkbox('Hide icons', 'hideIcons'),
checkbox('Disabled', 'disabled'),
checkbox('Disabled filter', 'disabledFilter'),
checkbox('Disabled sorting', 'disabledSorting'),
checkbox('Disabled dragging', 'disabledDragging'),
],
}

View File

@@ -0,0 +1,491 @@
<template>
<div
class="overflow-hidden d-flex flex-column w-100 vh-100"
>
<b-card
no-body
class="h-100"
>
<b-card-header
class="p-0"
>
<c-input-search
v-if="!hideFilter"
v-model.trim="query"
:disabled="_disabledFilter"
:placeholder="labels.searchPlaceholder"
/>
</b-card-header>
<b-card-body
class="d-flex p-0"
>
<b-card
no-body
class="col-sm-6 col-12 h-100 p-0"
>
<b-card-header
class="py-2 pl-0 pr-2"
>
<div
class="d-flex align-items-center"
>
<label
class="text-primary mb-0 py-1"
>
{{ labels.availableItems }}
</label>
<b-button
v-show="filteredAvailable.length && !disabled"
data-test-id="link-select-all"
variant="outline-light"
class="ml-auto border-0 text-muted"
@click="selectAll()"
>
{{ labels.selectAllItems }}
</b-button>
</div>
</b-card-header>
<b-card-body
class="overflow-auto py-0 pl-0 pr-2"
>
<b-list-group
vertical
class="h-100"
>
<draggable
v-model="filteredAvailable"
:sort="!_disabledSorting"
:move="_disableDragging"
:disabled="query"
draggable=".item"
group="items"
class="overflow-auto h-100"
>
<b-list-group-item
v-for="item in filteredAvailable"
:key="item.value"
class="item mb-3 border rounded"
:class="{
'handle': !isDraggable
}"
@dblclick="select(item)"
>
<c-item-picker-item
:item="item"
:disabled="disabled"
:disabled-dragging="isDraggable"
:disabled-sorting="disabledSorting"
:hide-icons="hideIcons"
@select="select(item)"
>
<template
v-for="(_, slot) of $scopedSlots"
#[slot]="scope"
>
<slot
:name="slot"
:text-field="textField"
:disabled="disabled"
:disabled-dragging="isDraggable"
:disabled-sorting="disabledSorting"
:hide-icons="hideIcons"
v-bind="scope"
/>
</template>
</c-item-picker-item>
</b-list-group-item>
<template #footer>
<h6
v-if="!filteredAvailable.length && query"
class="text-center my-4"
>
{{ labels.noItemsFound }}
</h6>
</template>
</draggable>
</b-list-group>
</b-card-body>
</b-card>
<b-card
no-body
class="h-100 pl-sm-0 col-sm-6 col-12 p-0"
>
<b-card-header
class="py-2 pl-2 pr-0"
>
<div
class="d-flex align-items-center"
>
<label
class="text-primary mb-0 py-1"
>
{{ labels.selectedItems }}
</label>
<b-button
v-show="filteredSelected.length && !disabled"
data-test-id="link-unselect-all"
variant="outline-light"
class="ml-auto border-0 text-muted"
@click="unselectAll()"
>
{{ labels.unselectAllItems }}
</b-button>
</div>
</b-card-header>
<b-card-body
class="overflow-auto py-0 pl-2 pr-0"
>
<b-list-group
vertical
class="h-100"
>
<draggable
v-model="filteredSelected"
:sort="!_disabledSorting"
:move="_disableDragging"
:disabled="query"
draggable=".item"
group="items"
class="overflow-auto h-100"
>
<b-list-group-item
v-for="item in filteredSelected"
:key="item.value"
class="item mb-3 border rounded"
:class="{
'handle': !isDraggable
}"
@dblclick="unselect(item)"
>
<c-item-picker-item
:item="item"
:disabled="disabled"
:disabled-dragging="isDraggable"
:disabled-sorting="disabledSorting"
:hide-icons="hideIcons"
selected
@unselect="unselect(item)"
>
<template
v-for="(_, slot) of $scopedSlots"
#[slot]="scope"
>
<slot
:name="slot"
:text-field="textField"
:disabled="disabled"
:disabled-dragging="isDraggable"
:disabled-sorting="disabledSorting"
:hide-icons="hideIcons"
v-bind="scope"
/>
</template>
</c-item-picker-item>
</b-list-group-item>
<template #footer>
<h6
v-if="!filteredSelected.length && query"
class="text-center my-4"
>
{{ labels.noItemsFound }}
</h6>
</template>
</draggable>
</b-list-group>
</b-card-body>
</b-card>
</b-card-body>
</b-card>
</div>
</template>
<script>
import draggable from 'vuedraggable'
import CItemPickerItem from './CItemPickerItem.vue'
import CInputSearch from '../input/CInputSearch.vue'
import { throttle } from 'lodash'
export default {
name: 'CItemPicker',
components: {
draggable,
CItemPickerItem,
CInputSearch,
},
props: {
/**
* List of all items, available and selected/picked
*
* Internally, we'll build 2 arrays for each group (available + selected)
* and work with them.
* On the outside, we'll always deal input array of (full) items and
* array of selected/picked values of those items.
*
* This component mimics the behaviour of <b-form-select> component from
* Vue Bootstrap and could serve as a drop-in replacement!
*/
options: {
type: Array,
required: true,
},
/**
* List of values that can be found in the items
*/
value: {
type: Array,
required: true,
},
valueField: {
type: String,
default: 'value',
},
textField: {
type: String,
default: 'text',
},
labels: {
type: Object,
default: () => ({
searchPlaceholder: 'Filter items',
availableItems: 'Available',
selectAllItems: 'Select all',
selectedItems: 'Selected',
unselectAllItems: 'Unselect all',
noItemsFound: 'No items found',
}),
},
disabled: {
type: Boolean,
},
disabledFilter: {
type: Boolean,
},
disabledSorting: {
type: Boolean,
},
disabledDragging: {
type: Boolean,
},
hideIcons: {
type: Boolean,
},
hideFilter: {
type: Boolean,
},
},
data () {
return {
query: '',
available: [],
selected: [],
}
},
computed: {
_disabledFilter () {
return this.disabled || this.disabledFilter
},
_disabledSorting () {
return this.disabled || this.disabledSorting
},
/**
* Provides list of all available items, filtered by query
* and a setter for draggable component to update available items on drag
*/
filteredAvailable: {
get () {
const q = this.query.toLowerCase()
return this.available.filter(i => i.text.toLowerCase().indexOf(q) > -1)
},
set (items) {
this.available = items
},
},
/**
* Provides list of all selected items, filtered by query
* and a setter for draggable component to update selected items on drag
*/
filteredSelected: {
get () {
const q = this.query.toLowerCase()
return this.selected.filter(i => i.text.toLowerCase().indexOf(q) > -1)
},
set (items) {
this.selected = items
},
},
isDraggable () {
return this.disabledDragging || this.query.length > 0
},
},
watch: {
/**
* Update parent component (if needed)
* @param v
*/
selected: {
immediate: false,
handler (items) {
const value = items.map(i => i[this.valueField])
// satisfy value.sync
this.$emit('update:value', value)
// satisfy v-model
this.$emit('input', value)
},
},
options: {
deep: true,
immediate: true,
handler () {
this.sync()
},
},
value: {
immediate: false,
handler (value = [], oldValue = []) {
/**
* Make sure we do not fall into an infinite loop
*
* If we update the value then sync will trigger recomputation of selected
* Which then emits the update event and the loop will begin
*/
if (value.length === oldValue.length) {
if (value.filter(v => !oldValue.includes(v)).length === 0) {
return
}
}
this.sync()
},
},
},
created () {
this.options.forEach(o => {
if (typeof o !== 'object') {
throw new Error('expecting array of objects for options prop')
}
})
},
beforeUnmount () {
this.setDefaultValues()
},
methods: {
_disableDragging (e) {
if (this.disabledDragging && e.to !== e.from) {
return false
}
},
selectAll () {
this.selected.push(...this.filteredAvailable)
this.available = this.frozen().filter(i => !this.selected.includes(i))
},
select: throttle(function (item) {
// remove available
this.available = this.available.filter(i => i !== item)
// put the selected item in the place where it was (if sorting is disabled)
// or at the end
if (this.disabledSorting) {
this.selected = this.frozen().filter(i => !this.available.includes(i))
} else {
if (!this.selected.some(({ value = '' }) => value === item.value)) {
this.selected.push(item)
}
}
}, 300),
unselectAll () {
this.filteredSelected.forEach(this.unselect)
},
unselect (item) {
// filtering out the unselected item
this.selected = this.selected.filter(i => i !== item)
// sync available from the list of frozen items without selected
this.available = this.frozen().filter(i => !this.selected.includes(i))
},
/**
* Returns true given item is selected or not
*
* Item is considered selected if value of item's value-field is inside the selected array.
*
* @param item object
* @returns boolean
*/
isPicked (item) {
return item[this.valueField] && this.value.includes(item[this.valueField])
},
sync () {
/**
* filter all unpicked options, freeze each item in the array and
* build list of available items
*/
this.available = this.frozen().filter(opt => !this.isPicked(opt))
/**
* filter all unpicked options, freeze each item in the array and
* build list of selected items, traverse value to keep order
*/
this.selected = this.value.map(v => {
return this.frozen().find(item => item[this.valueField] === v)
}).filter(f => f)
},
frozen () {
return this.options.map(Object.freeze)
},
setDefaultValues () {
this.query = ''
this.available = []
this.selected = []
},
},
}
</script>
<style lang="scss" scoped>
.handle {
cursor: grab;
}
.handle:active {
cursor: grabbing;
}
</style>

View File

@@ -0,0 +1,80 @@
<template>
<div
class="d-flex align-items-center"
>
<font-awesome-icon
v-if="!disabled && !disabledSorting && !hideIcons"
:icon="['fas', 'grip-vertical']"
:class="{
'text-muted': disabledDragging,
}"
class="align-baseline mr-3 text-primary"
/>
<b
class="text-truncate"
>
<slot
v-bind="item"
>
{{ item[textField] }}
</slot>
</b>
<b-button
v-if="_hideIcons"
v-b-tooltip.noninteractive.hover="{ title: selected ? 'Unselect' : 'Select', boundary: 'body' , delay: 1000 }"
:data-test-id="`button-${selected ? 'unselect' : 'select'}`"
variant="outline-light"
class="d-flex align-items-center ml-auto p-2 border-0"
@click.prevent.stop="$emit(selected ? 'unselect' : 'select')"
>
<font-awesome-icon
:icon="[selected ? 'far' : 'fas', selected ? 'eye' : 'eye-slash']"
class="text-muted"
/>
</b-button>
</div>
</template>
<script>
export default {
name: 'CItemPickerItem',
props: {
item: {
type: Object,
required: true,
},
textField: {
type: String,
default: 'text',
},
selected: {
type: Boolean,
},
disabled: {
type: Boolean,
},
disabledDragging: {
type: Boolean,
},
disabledSorting: {
type: Boolean,
},
hideIcons: {
type: Boolean,
},
},
computed: {
_hideIcons () {
return !this.disabled && !this.hideIcons
},
},
}
</script>

View File

@@ -0,0 +1,2 @@
export { default as CItemPicker } from './CItemPicker.vue'
export { default as CItemPickerItem } from './CItemPickerItem.vue'