|
| 1 | +<script setup lang="ts"> |
| 2 | +import { computed, ref, watch, onMounted } from 'vue' |
| 3 | +
|
| 4 | +import Column, { ColumnType } from '@core/entities/column' |
| 5 | +
|
| 6 | +import { useStore as useCollectionStore } from '@/modules/collection/store' |
| 7 | +import { useStore } from '@/modules/collection-column/store' |
| 8 | +
|
| 9 | +import MEditor from '@/modules/monaco/components/MEditor.vue' |
| 10 | +import { useNonReactive } from '@/composables/utils' |
| 11 | +import { useI18n } from 'vue-i18n' |
| 12 | +
|
| 13 | +import { lib as libScriptColumn } from '@/modules/monaco/libs/column-script' |
| 14 | +
|
| 15 | +// Props & emits |
| 16 | +const props = defineProps({ |
| 17 | + collectionId: { |
| 18 | + type: String, |
| 19 | + required: true, |
| 20 | + }, |
| 21 | + columnId: { |
| 22 | + type: String, |
| 23 | + required: true, |
| 24 | + }, |
| 25 | +}) |
| 26 | +
|
| 27 | +// column |
| 28 | +const store = useStore() |
| 29 | +
|
| 30 | +const column = computed(() => store.get(props.collectionId, props.columnId)) |
| 31 | +const loading = computed(() => store.isLoading(props.collectionId)) |
| 32 | +
|
| 33 | +onMounted(async () => { |
| 34 | + if (!column.value) { |
| 35 | + await store.set(props.collectionId) |
| 36 | + } |
| 37 | +}) |
| 38 | +
|
| 39 | +// form |
| 40 | +const tm = useI18n() |
| 41 | +
|
| 42 | +const payload = ref<Column>({ |
| 43 | + id: '', |
| 44 | + label: '', |
| 45 | + field: '', |
| 46 | + type: ColumnType.text, |
| 47 | + content: '', |
| 48 | + options: undefined, |
| 49 | + collectionId: undefined, |
| 50 | + displayField: undefined, |
| 51 | +}) |
| 52 | +
|
| 53 | +const types = Object.values(ColumnType).map((v) => ({ |
| 54 | + label: tm.t(v), |
| 55 | + value: v, |
| 56 | +})) |
| 57 | +
|
| 58 | +async function submit() { |
| 59 | + if (!column.value) return |
| 60 | +
|
| 61 | + Object.keys(payload.value).forEach((key) => { |
| 62 | + column.value![key] = payload.value[key] |
| 63 | + }) |
| 64 | +
|
| 65 | + dialog.value = false |
| 66 | +} |
| 67 | +
|
| 68 | +// icons |
| 69 | +const icons: Record<Column['type'], any> = { |
| 70 | + text: 'font', |
| 71 | + number: 'hashtag', |
| 72 | + select: 'fa-regular fa-square-caret-down', |
| 73 | + relation: 'arrow-up', |
| 74 | + script: 'code', |
| 75 | + entry: 'file', |
| 76 | +} |
| 77 | +
|
| 78 | +// dialog |
| 79 | +const dialog = ref(false) |
| 80 | +
|
| 81 | +watch(dialog, (value) => { |
| 82 | + if (!value) return |
| 83 | +
|
| 84 | + if (column.value) { |
| 85 | + payload.value = useNonReactive(column.value) |
| 86 | + } |
| 87 | +}) |
| 88 | +
|
| 89 | +async function deleteColumn() { |
| 90 | + await store.destroy(props.collectionId, props.columnId) |
| 91 | +
|
| 92 | + dialog.value = false |
| 93 | +} |
| 94 | +
|
| 95 | +// collections |
| 96 | +const collectionStore = useCollectionStore() |
| 97 | +
|
| 98 | +// relation selected collection options |
| 99 | +
|
| 100 | +const relation = computed(() => { |
| 101 | + const { collectionId } = payload.value |
| 102 | +
|
| 103 | + if (!collectionId) return null |
| 104 | +
|
| 105 | + const collection = collectionStore.collections.find((c) => c.id === collectionId) |
| 106 | +
|
| 107 | + if (!collection) return null |
| 108 | +
|
| 109 | + return collection |
| 110 | +}) |
| 111 | +</script> |
| 112 | +<template> |
| 113 | + <div |
| 114 | + v-if="loading" |
| 115 | + class="text-t-secondary text-sm overflow-hidden animate-pulse" |
| 116 | + v-bind="$attrs" |
| 117 | + > |
| 118 | + {{ $t('loading') }}... |
| 119 | + </div> |
| 120 | + |
| 121 | + <div v-else-if="!column" class="text-t-secondary text-sm overflow-hidden" v-bind="$attrs"> |
| 122 | + <is-icon name="triangle-exclamation" class="mr-2 text-xs" /> |
| 123 | + {{ $t('errors.unknown') }} |
| 124 | + </div> |
| 125 | + |
| 126 | + <v-dialog v-else v-model="dialog"> |
| 127 | + <template #activator="{ attrs }"> |
| 128 | + <div |
| 129 | + class="cursor-pointer text-t-secondary text-sm flex items-center overflow-hidden" |
| 130 | + v-bind="{ ...attrs, ...$attrs }" |
| 131 | + > |
| 132 | + <fa-icon :icon="icons[column.type] || 'font'" class="mr-2 text-xs" /> |
| 133 | + |
| 134 | + {{ column.label }} |
| 135 | + </div> |
| 136 | + </template> |
| 137 | + |
| 138 | + <v-card width="500" color="b-secondary"> |
| 139 | + <v-card-head class="px-4"> |
| 140 | + <v-card-title> |
| 141 | + {{ $t('editEntity', [$t('column')]) }} |
| 142 | + </v-card-title> |
| 143 | + <v-btn text color="danger" class="ml-auto" @click="deleteColumn"> |
| 144 | + <is-icon name="trash" /> |
| 145 | + </v-btn> |
| 146 | + </v-card-head> |
| 147 | + |
| 148 | + <v-card-content> |
| 149 | + <w-form class="w-full" @submit="submit"> |
| 150 | + <div class="mb-4"> |
| 151 | + <is-input v-model="payload.label" label="Label" /> |
| 152 | + </div> |
| 153 | + |
| 154 | + <div class="mb-4"> |
| 155 | + <is-select |
| 156 | + v-model="payload.type" |
| 157 | + label="Type" |
| 158 | + :options="types" |
| 159 | + label-key="label" |
| 160 | + value-key="value" |
| 161 | + card:color="b-primary" |
| 162 | + /> |
| 163 | + </div> |
| 164 | + |
| 165 | + <div class="mb-4"> |
| 166 | + <is-input v-model="payload.field" label="Field" /> |
| 167 | + </div> |
| 168 | + |
| 169 | + <div v-if="payload.type === 'select'" class="mb-4"> |
| 170 | + <is-input |
| 171 | + v-model="payload.options" |
| 172 | + label="Options (separate by comma)" |
| 173 | + placeholder="item-01,item-02" |
| 174 | + /> |
| 175 | + </div> |
| 176 | + |
| 177 | + <template v-if="payload.type === 'relation'"> |
| 178 | + <div class="mb-4"> |
| 179 | + <is-select |
| 180 | + v-model="payload.collectionId" |
| 181 | + label="Collection" |
| 182 | + :options="collectionStore.collections" |
| 183 | + label-key="name" |
| 184 | + value-key="id" |
| 185 | + card:color="b-primary" |
| 186 | + /> |
| 187 | + </div> |
| 188 | + |
| 189 | + <div v-if="payload.collectionId" class="mb-4"> |
| 190 | + <is-select |
| 191 | + v-model="payload.displayField" |
| 192 | + label="Collection display field" |
| 193 | + :options="relation ? relation.columns : []" |
| 194 | + label-key="label" |
| 195 | + value-key="field" |
| 196 | + card:color="b-primary" |
| 197 | + /> |
| 198 | + </div> |
| 199 | + </template> |
| 200 | + |
| 201 | + <is-drawer v-if="payload.type === 'script'" width="800"> |
| 202 | + <template #activator="{ attrs }"> |
| 203 | + <is-input |
| 204 | + v-bind="attrs" |
| 205 | + :model-value="$t('editEntity', [$t('script')])" |
| 206 | + :label="$t('content')" |
| 207 | + readonly |
| 208 | + class="cursor-pointer mb-4" |
| 209 | + input:class="cursor-pointer max-w-[calc(100%_-_32px)]" |
| 210 | + > |
| 211 | + <template #append> |
| 212 | + <is-icon name="code" class="ml-auto" /> |
| 213 | + </template> |
| 214 | + </is-input> |
| 215 | + </template> |
| 216 | + |
| 217 | + <v-card color="b-secondary" class="h-full"> |
| 218 | + <m-editor |
| 219 | + v-model="payload.content" |
| 220 | + :libs="libScriptColumn.mount(store.all(collectionId))" |
| 221 | + /> |
| 222 | + </v-card> |
| 223 | + </is-drawer> |
| 224 | + |
| 225 | + <div v-if="payload.type === 'entry'" class="mb-4"> |
| 226 | + <is-input |
| 227 | + v-model="payload.filename" |
| 228 | + :label="$t('filename')" |
| 229 | + placeholder="thumbnail.jpg" |
| 230 | + /> |
| 231 | + </div> |
| 232 | + |
| 233 | + <div> |
| 234 | + <v-btn type="submit" class="w-full">{{ $t('save') }}</v-btn> |
| 235 | + </div> |
| 236 | + </w-form> |
| 237 | + </v-card-content> |
| 238 | + </v-card> |
| 239 | + </v-dialog> |
| 240 | +</template> |
0 commit comments