@globalbrain/sefirot 4.34.1 → 4.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/nuxt.js +44 -1
- package/config/vite.js +2 -3
- package/lib/blocks/lens/FieldContext.ts +5 -0
- package/lib/blocks/lens/FieldData.ts +140 -0
- package/lib/blocks/lens/FieldRegistry.ts +23 -0
- package/lib/blocks/lens/FileDownloader.ts +1 -0
- package/lib/blocks/lens/FilterOperator.ts +33 -0
- package/lib/blocks/lens/LensQuery.ts +10 -0
- package/lib/blocks/lens/LensResult.ts +20 -0
- package/lib/blocks/lens/ResourceFetcher.ts +3 -0
- package/lib/blocks/lens/Rule.ts +12 -0
- package/lib/blocks/lens/components/LensCatalog.vue +490 -0
- package/lib/blocks/lens/components/LensCatalogControl.vue +220 -0
- package/lib/blocks/lens/components/LensCatalogFooter.vue +46 -0
- package/lib/blocks/lens/components/LensCatalogStateFilter.vue +171 -0
- package/lib/blocks/lens/components/LensCatalogStateFilterCondition.vue +86 -0
- package/lib/blocks/lens/components/LensCatalogStateFilterGroup.vue +102 -0
- package/lib/blocks/lens/components/LensCatalogStateSort.vue +159 -0
- package/lib/blocks/lens/components/LensFormFilter.vue +169 -0
- package/lib/blocks/lens/components/LensFormFilterCondition.vue +205 -0
- package/lib/blocks/lens/components/LensFormFilterGroup.vue +175 -0
- package/lib/blocks/lens/components/LensFormOverride.vue +45 -0
- package/lib/blocks/lens/components/LensFormOverrideBase.vue +204 -0
- package/lib/blocks/lens/components/LensFormView.vue +347 -0
- package/lib/blocks/lens/components/LensTable.vue +154 -0
- package/lib/blocks/lens/composables/FieldFactory.ts +27 -0
- package/lib/blocks/lens/composables/FieldRegistry.ts +16 -0
- package/lib/blocks/lens/composables/FileDownloader.ts +10 -0
- package/lib/blocks/lens/composables/ResourceFetcher.ts +30 -0
- package/lib/blocks/lens/composables/SetupLens.ts +55 -0
- package/lib/blocks/lens/fields/ContentField.ts +34 -0
- package/lib/blocks/lens/fields/DateField.ts +66 -0
- package/lib/blocks/lens/fields/DatetimeField.ts +35 -0
- package/lib/blocks/lens/fields/Field.ts +244 -0
- package/lib/blocks/lens/fields/FileUploadField.ts +63 -0
- package/lib/blocks/lens/fields/IdField.ts +34 -0
- package/lib/blocks/lens/fields/LinkField.ts +53 -0
- package/lib/blocks/lens/fields/NumberField.ts +32 -0
- package/lib/blocks/lens/fields/RelatedManyField.ts +62 -0
- package/lib/blocks/lens/fields/SelectField.ts +198 -0
- package/lib/blocks/lens/fields/SlackMessageField.ts +34 -0
- package/lib/blocks/lens/fields/TextField.ts +46 -0
- package/lib/blocks/lens/fields/TextareaField.ts +49 -0
- package/lib/blocks/lens/filter-inputs/FilterInput.ts +72 -0
- package/lib/blocks/lens/filter-inputs/NumberFilterInput.ts +26 -0
- package/lib/blocks/lens/filter-inputs/SelectFilterInput.ts +76 -0
- package/lib/blocks/lens/filter-inputs/TextFilterInput.ts +26 -0
- package/lib/blocks/lens/validation/RuleMapper.ts +22 -0
- package/lib/components/SInputTextarea.vue +28 -10
- package/lib/components/STable.vue +230 -61
- package/lib/components/STableCell.vue +2 -2
- package/lib/composables/TableAnimation.ts +180 -0
- package/lib/support/Scroll.ts +263 -0
- package/lib/support/Utils.ts +1 -1
- package/package.json +7 -15
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type LensResult } from '../LensResult'
|
|
3
|
+
|
|
4
|
+
defineProps<{
|
|
5
|
+
result: LensResult
|
|
6
|
+
loading: boolean
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
defineEmits<{
|
|
10
|
+
prev: []
|
|
11
|
+
next: []
|
|
12
|
+
}>()
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<div class="LensCatalogFooter">
|
|
17
|
+
<SControl>
|
|
18
|
+
<SControlRight>
|
|
19
|
+
<SControlPagination
|
|
20
|
+
:total="result.pagination.total"
|
|
21
|
+
:page="result.pagination.page"
|
|
22
|
+
:per-page="result.pagination.perPage"
|
|
23
|
+
:disabled="loading"
|
|
24
|
+
@prev="$emit('prev')"
|
|
25
|
+
@next="$emit('next')"
|
|
26
|
+
/>
|
|
27
|
+
</SControlRight>
|
|
28
|
+
</SControl>
|
|
29
|
+
</div>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<style scoped lang="postcss">
|
|
33
|
+
.LensCatalogFooter {
|
|
34
|
+
position: relative;
|
|
35
|
+
z-index: 10;
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-shrink: 0;
|
|
38
|
+
align-items: center;
|
|
39
|
+
margin-top: -1px;
|
|
40
|
+
border-top: 1px solid var(--c-gutter);
|
|
41
|
+
border-radius: 0 0 5px 5px;
|
|
42
|
+
padding: var(--lens-catalog-footer-padding, 0 12px);
|
|
43
|
+
height: 57px;
|
|
44
|
+
background-color: var(--c-bg-1);
|
|
45
|
+
}
|
|
46
|
+
</style>
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import IconArrowsInLineVertical from '~icons/ph/arrows-in-line-vertical'
|
|
3
|
+
import IconArrowsOutLineVertical from '~icons/ph/arrows-out-line-vertical'
|
|
4
|
+
import IconX from '~icons/ph/x'
|
|
5
|
+
import { computed, ref } from 'vue'
|
|
6
|
+
import { useTrans } from '../../../composables/Lang'
|
|
7
|
+
import { type FieldData } from '../FieldData'
|
|
8
|
+
import LensCatalogStateFilterGroup, { type FilterGroup } from './LensCatalogStateFilterGroup.vue'
|
|
9
|
+
|
|
10
|
+
export interface Props {
|
|
11
|
+
fields: Record<string, FieldData>
|
|
12
|
+
filters: any[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Count {
|
|
16
|
+
value: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const props = defineProps<Props>()
|
|
20
|
+
|
|
21
|
+
defineEmits<{
|
|
22
|
+
reset: []
|
|
23
|
+
}>()
|
|
24
|
+
|
|
25
|
+
const { t } = useTrans({
|
|
26
|
+
en: {
|
|
27
|
+
filter_count: (count: number) => `${count} filter${count > 1 ? 's' : ''} applied`,
|
|
28
|
+
reset_filters: 'Reset filters'
|
|
29
|
+
},
|
|
30
|
+
ja: {
|
|
31
|
+
filter_count: (count: number) => `${count}件のフィルターが適用中`,
|
|
32
|
+
reset_filters: 'フィルターをリセット'
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const isOpen = ref(true)
|
|
37
|
+
|
|
38
|
+
const group = computed(() => {
|
|
39
|
+
const count: Count = { value: 0 }
|
|
40
|
+
const group = lensFiltersToGroup(props.filters, '$and', count)
|
|
41
|
+
return {
|
|
42
|
+
count: count.value,
|
|
43
|
+
group
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
function lensFiltersToGroup(filters: any[], connector: '$and' | '$or', count: Count) {
|
|
48
|
+
const group = newFilterGroup()
|
|
49
|
+
|
|
50
|
+
group.connector = connector
|
|
51
|
+
|
|
52
|
+
for (const filter of filters) {
|
|
53
|
+
const [fieldOrConnector, operatorOrFilters, value] = filter
|
|
54
|
+
|
|
55
|
+
if (!isConnector(fieldOrConnector)) {
|
|
56
|
+
group.conditions.push({
|
|
57
|
+
field: fieldOrConnector,
|
|
58
|
+
operator: operatorOrFilters,
|
|
59
|
+
value
|
|
60
|
+
})
|
|
61
|
+
count.value++
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!Array.isArray(operatorOrFilters)) {
|
|
66
|
+
throw new TypeError(`Invalid filter format: ${JSON.stringify(filter)}`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
group.groups.push(
|
|
70
|
+
lensFiltersToGroup(operatorOrFilters, fieldOrConnector, count)
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return group
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function newFilterGroup(): FilterGroup {
|
|
78
|
+
return {
|
|
79
|
+
connector: '$and',
|
|
80
|
+
conditions: [],
|
|
81
|
+
groups: []
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isConnector(value: any): value is '$and' | '$or' {
|
|
86
|
+
return value === '$and' || value === '$or'
|
|
87
|
+
}
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<template>
|
|
91
|
+
<SCardBlock class="LensCatalogStateFilter">
|
|
92
|
+
<div v-if="!isOpen" class="closed">
|
|
93
|
+
<span class="filter-count">{{ t.filter_count(group.count) }}</span>
|
|
94
|
+
</div>
|
|
95
|
+
<div v-else class="filters">
|
|
96
|
+
<LensCatalogStateFilterGroup
|
|
97
|
+
:fields
|
|
98
|
+
:group="group.group"
|
|
99
|
+
is-root
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
<div class="actions">
|
|
103
|
+
<button class="action" @click="$emit('reset')">
|
|
104
|
+
<span class="action-icon"><IconX class="action-svg" /></span>
|
|
105
|
+
<span class="action-text">{{ t.reset_filters }}</span>
|
|
106
|
+
</button>
|
|
107
|
+
<button class="action icon" @click="isOpen = !isOpen">
|
|
108
|
+
<span class="action-icon">
|
|
109
|
+
<IconArrowsInLineVertical v-if="isOpen" class="action-svg" />
|
|
110
|
+
<IconArrowsOutLineVertical v-else class="action-svg" />
|
|
111
|
+
</span>
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
</SCardBlock>
|
|
115
|
+
</template>
|
|
116
|
+
|
|
117
|
+
<style scoped lang="postcss">
|
|
118
|
+
.LensCatalogStateFilter {
|
|
119
|
+
display: flex;
|
|
120
|
+
gap: 16px;
|
|
121
|
+
padding: var(--lens-catalog-filters-block-padding, 12px);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.closed {
|
|
125
|
+
flex-grow: 1;
|
|
126
|
+
padding-left: 4px;
|
|
127
|
+
line-height: 24px;
|
|
128
|
+
font-size: 12px;
|
|
129
|
+
color: var(--c-text-2);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.filters {
|
|
133
|
+
flex-grow: 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.actions {
|
|
137
|
+
display: flex;
|
|
138
|
+
gap: 8px;
|
|
139
|
+
flex-shrink: 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.action {
|
|
143
|
+
display: flex;
|
|
144
|
+
justify-content: center;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: 4px;
|
|
147
|
+
border: 1px dashed var(--c-divider);
|
|
148
|
+
border-radius: 6px;
|
|
149
|
+
padding: 0 6px;
|
|
150
|
+
height: 24px;
|
|
151
|
+
font-size: 12px;
|
|
152
|
+
color: var(--c-text-2);
|
|
153
|
+
background-color: var(--c-bg-1);
|
|
154
|
+
cursor: pointer;
|
|
155
|
+
transition: background-color 0.25s;
|
|
156
|
+
|
|
157
|
+
&.icon {
|
|
158
|
+
padding: 0;
|
|
159
|
+
width: 24px;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
&:hover {
|
|
163
|
+
background-color: var(--c-bg-2);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.action-svg {
|
|
168
|
+
width: 14px;
|
|
169
|
+
height: 14px;
|
|
170
|
+
}
|
|
171
|
+
</style>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computedAsync } from '@vueuse/core'
|
|
3
|
+
import { computed } from 'vue'
|
|
4
|
+
import { type FieldData } from '../FieldData'
|
|
5
|
+
import { type FilterOperator, FilterOperatorTextDict } from '../FilterOperator'
|
|
6
|
+
import { useFieldFactory } from '../composables/FieldFactory'
|
|
7
|
+
|
|
8
|
+
export interface Props {
|
|
9
|
+
fields: Record<string, FieldData>
|
|
10
|
+
condition: FilterCondition
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface FilterCondition {
|
|
14
|
+
field: string
|
|
15
|
+
operator: FilterOperator
|
|
16
|
+
value: any
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const props = defineProps<Props>()
|
|
20
|
+
|
|
21
|
+
const fieldFactory = useFieldFactory()
|
|
22
|
+
|
|
23
|
+
const field = computed(() => {
|
|
24
|
+
return fieldFactory.make(props.fields[props.condition.field])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const input = computed(() => {
|
|
28
|
+
return field.value.filterInputByOperator(props.condition.operator)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const fieldText = computed(() => {
|
|
32
|
+
return field.value.label()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const operatorText = computed(() => {
|
|
36
|
+
return FilterOperatorTextDict[props.condition.operator]
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const valueText = computedAsync(async () => {
|
|
40
|
+
return input.value.valueToText(props.condition.value)
|
|
41
|
+
}, '...')
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<div class="LensCatalogStateFilterCondition">
|
|
46
|
+
<div class="field">{{ fieldText }}</div>
|
|
47
|
+
<div class="operator">{{ operatorText }}</div>
|
|
48
|
+
<div v-if="input === null" class="value">
|
|
49
|
+
...
|
|
50
|
+
</div>
|
|
51
|
+
<div v-else class="value">
|
|
52
|
+
{{ valueText }}
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<style scoped lang="postcss">
|
|
58
|
+
.LensCatalogStateFilterCondition {
|
|
59
|
+
display: flex;
|
|
60
|
+
gap: 1px;
|
|
61
|
+
border: 1px dashed var(--c-divider);
|
|
62
|
+
border-radius: 6px;
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
background-color: var(--c-gutter);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.field,
|
|
68
|
+
.operator,
|
|
69
|
+
.value {
|
|
70
|
+
padding: 0 8px;
|
|
71
|
+
line-height: 22px;
|
|
72
|
+
font-size: 12px;
|
|
73
|
+
background-color: var(--c-bg-1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.operator {
|
|
77
|
+
color: var(--c-text-2);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.value {
|
|
81
|
+
max-width: 192px;
|
|
82
|
+
overflow: hidden;
|
|
83
|
+
text-overflow: ellipsis;
|
|
84
|
+
white-space: nowrap;
|
|
85
|
+
}
|
|
86
|
+
</style>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { type FieldData } from '../FieldData'
|
|
3
|
+
import LensCatalogStateFilterCondition, { type FilterCondition } from './LensCatalogStateFilterCondition.vue'
|
|
4
|
+
|
|
5
|
+
export interface Props {
|
|
6
|
+
fields: Record<string, FieldData>
|
|
7
|
+
isRoot: boolean
|
|
8
|
+
group: FilterGroup
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface FilterGroup {
|
|
12
|
+
connector: '$and' | '$or'
|
|
13
|
+
conditions: FilterCondition[]
|
|
14
|
+
groups: FilterGroup[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
defineProps<Props>()
|
|
18
|
+
|
|
19
|
+
const connectorTextDict = {
|
|
20
|
+
$and: 'AND',
|
|
21
|
+
$or: 'OR'
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<div class="LensCatalogStateFilterGroup" :class="{ 'is-root': isRoot }">
|
|
27
|
+
<div v-if="!isRoot" class="connector">
|
|
28
|
+
<div class="connector-text">{{ connectorTextDict[group.connector] }}</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="body">
|
|
31
|
+
<div v-if="group.conditions.length" class="conditions">
|
|
32
|
+
<LensCatalogStateFilterCondition
|
|
33
|
+
v-for="condition, index in group.conditions"
|
|
34
|
+
:key="index"
|
|
35
|
+
:fields
|
|
36
|
+
:condition
|
|
37
|
+
/>
|
|
38
|
+
</div>
|
|
39
|
+
<div v-if="group.groups.length" class="groups">
|
|
40
|
+
<LensCatalogStateFilterGroup
|
|
41
|
+
v-for="g, i in group.groups"
|
|
42
|
+
:key="i"
|
|
43
|
+
:fields
|
|
44
|
+
:is-root="false"
|
|
45
|
+
:group="g"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<style scoped lang="postcss">
|
|
53
|
+
.LensCatalogStateFilterGroup {
|
|
54
|
+
display: flex;
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
gap: 8px;
|
|
57
|
+
border: 1px dashed var(--c-divider);
|
|
58
|
+
border-radius: 6px;
|
|
59
|
+
padding: 8px;
|
|
60
|
+
|
|
61
|
+
&.is-root {
|
|
62
|
+
border: 0;
|
|
63
|
+
padding: 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.connector {
|
|
68
|
+
display: flex;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.connector-text {
|
|
72
|
+
display: flex;
|
|
73
|
+
justify-content: center;
|
|
74
|
+
align-items: center;
|
|
75
|
+
flex-shrink: 0;
|
|
76
|
+
border: 1px dashed var(--c-divider);
|
|
77
|
+
border-radius: 6px;
|
|
78
|
+
padding: 0 8px;
|
|
79
|
+
height: 24px;
|
|
80
|
+
font-size: 12px;
|
|
81
|
+
color: var(--c-text-2);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.body {
|
|
85
|
+
display: flex;
|
|
86
|
+
flex-direction: column;
|
|
87
|
+
gap: 8px;
|
|
88
|
+
flex-grow: 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.conditions {
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-wrap: wrap;
|
|
94
|
+
gap: 8px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.groups {
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
gap: 8px;
|
|
101
|
+
}
|
|
102
|
+
</style>
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import IconArrowsInLineVertical from '~icons/ph/arrows-in-line-vertical'
|
|
3
|
+
import IconArrowsOutLineVertical from '~icons/ph/arrows-out-line-vertical'
|
|
4
|
+
import IconX from '~icons/ph/x'
|
|
5
|
+
import { ref } from 'vue'
|
|
6
|
+
import { useLang, useTrans } from '../../../composables/Lang'
|
|
7
|
+
import { type FieldData } from '../FieldData'
|
|
8
|
+
import { type LensQuerySort } from '../LensQuery'
|
|
9
|
+
|
|
10
|
+
export interface Props {
|
|
11
|
+
fields: Record<string, FieldData>
|
|
12
|
+
sorts: LensQuerySort[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const props = defineProps<Props>()
|
|
16
|
+
|
|
17
|
+
defineEmits<{
|
|
18
|
+
reset: []
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
const lang = useLang()
|
|
22
|
+
|
|
23
|
+
const { t } = useTrans({
|
|
24
|
+
en: {
|
|
25
|
+
sort_count: (count: number) => `${count} sort${count > 1 ? 's' : ''} applied`,
|
|
26
|
+
reset_sorts: 'Reset sorts'
|
|
27
|
+
},
|
|
28
|
+
ja: {
|
|
29
|
+
sort_count: (count: number) => `${count}件のソートが適用中`,
|
|
30
|
+
reset_sorts: 'ソートをリセット'
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const isOpen = ref(true)
|
|
35
|
+
|
|
36
|
+
const orderTextDict = {
|
|
37
|
+
asc: 'ASC',
|
|
38
|
+
desc: 'DESC'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getFieldName(sort: LensQuerySort): string {
|
|
42
|
+
return lang === 'ja'
|
|
43
|
+
? props.fields[sort[0]].labelJa
|
|
44
|
+
: props.fields[sort[0]].labelEn
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getOrderText(sort: LensQuerySort): string {
|
|
48
|
+
return orderTextDict[sort[1]]
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<SCardBlock class="LensCatalogStateSort">
|
|
54
|
+
<div v-if="!isOpen" class="closed">
|
|
55
|
+
<span class="sort-count">{{ t.sort_count(sorts.length) }}</span>
|
|
56
|
+
</div>
|
|
57
|
+
<div v-else class="sorts">
|
|
58
|
+
<div v-for="sort in sorts" :key="sort[0]" class="sort">
|
|
59
|
+
<div class="field">{{ getFieldName(sort) }}</div>
|
|
60
|
+
<div class="order">{{ getOrderText(sort) }}</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="actions">
|
|
64
|
+
<button class="action" @click="$emit('reset')">
|
|
65
|
+
<span class="action-icon"><IconX class="action-svg" /></span>
|
|
66
|
+
<span class="action-text">{{ t.reset_sorts }}</span>
|
|
67
|
+
</button>
|
|
68
|
+
<button class="action icon" @click="isOpen = !isOpen">
|
|
69
|
+
<span class="action-icon">
|
|
70
|
+
<IconArrowsInLineVertical v-if="isOpen" class="action-svg" />
|
|
71
|
+
<IconArrowsOutLineVertical v-else class="action-svg" />
|
|
72
|
+
</span>
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
</SCardBlock>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<style scoped lang="postcss">
|
|
79
|
+
.LensCatalogStateSort {
|
|
80
|
+
display: flex;
|
|
81
|
+
gap: 16px;
|
|
82
|
+
padding: var(--lens-catalog-sorts-block-padding, 12px);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.closed {
|
|
86
|
+
flex-grow: 1;
|
|
87
|
+
padding-left: 4px;
|
|
88
|
+
line-height: 24px;
|
|
89
|
+
font-size: 12px;
|
|
90
|
+
color: var(--c-text-2);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.sorts {
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-wrap: wrap;
|
|
96
|
+
gap: 8px;
|
|
97
|
+
flex-grow: 1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.sort {
|
|
101
|
+
display: flex;
|
|
102
|
+
gap: 1px;
|
|
103
|
+
border: 1px dashed var(--c-divider);
|
|
104
|
+
border-radius: 6px;
|
|
105
|
+
background-color: var(--c-gutter);
|
|
106
|
+
overflow: hidden;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.field,
|
|
110
|
+
.order {
|
|
111
|
+
display: flex;
|
|
112
|
+
justify-content: center;
|
|
113
|
+
align-items: center;
|
|
114
|
+
padding: 0 8px;
|
|
115
|
+
font-size: 12px;
|
|
116
|
+
height: 22px;
|
|
117
|
+
background-color: var(--c-bg-1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.order {
|
|
121
|
+
color: var(--c-text-2);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.actions {
|
|
125
|
+
display: flex;
|
|
126
|
+
gap: 8px;
|
|
127
|
+
flex-shrink: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.action {
|
|
131
|
+
display: flex;
|
|
132
|
+
justify-content: center;
|
|
133
|
+
align-items: center;
|
|
134
|
+
gap: 4px;
|
|
135
|
+
border: 1px dashed var(--c-divider);
|
|
136
|
+
border-radius: 6px;
|
|
137
|
+
padding: 0 6px;
|
|
138
|
+
height: 24px;
|
|
139
|
+
font-size: 12px;
|
|
140
|
+
color: var(--c-text-2);
|
|
141
|
+
background-color: var(--c-bg-1);
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
transition: background-color 0.25s;
|
|
144
|
+
|
|
145
|
+
&.icon {
|
|
146
|
+
padding: 0;
|
|
147
|
+
width: 24px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
&:hover {
|
|
151
|
+
background-color: var(--c-bg-2);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.action-svg {
|
|
156
|
+
width: 14px;
|
|
157
|
+
height: 14px;
|
|
158
|
+
}
|
|
159
|
+
</style>
|