refac: duplicates, crap, dry etc. on last features commits
This commit is contained in:
1 parent
fba20f83c5
commit
89051b3f1f
12 files changed
+260
-153
No files matched your search
@@ -6,8 +6,26 @@ import type { FieldErrors } from '@/lib/schemaBuilderValidation'
|
||||
|
||||
type Builder = ReturnType<typeof useSchemaBuilder>
|
||||
|
||||
type InspectorActions = Pick<
|
||||
Builder,
|
||||
| 'setLabel'
|
||||
| 'setFieldKey'
|
||||
| 'unlockFieldKey'
|
||||
| 'resetFieldKey'
|
||||
| 'setFieldType'
|
||||
| 'patchField'
|
||||
| 'setLimit'
|
||||
| 'setDefault'
|
||||
| 'addChoice'
|
||||
| 'removeChoice'
|
||||
| 'renameChoice'
|
||||
| 'reorderChoices'
|
||||
>
|
||||
|
||||
const props = defineProps<{
|
||||
builder: Builder
|
||||
actions: InspectorActions
|
||||
saving: boolean
|
||||
focusToken: number
|
||||
field: BuilderField
|
||||
errors: FieldErrors
|
||||
compact: boolean
|
||||
@@ -20,22 +38,22 @@ const uid = () => props.field.uid
|
||||
<FieldInspector
|
||||
:field="field"
|
||||
:errors="errors"
|
||||
:disabled="builder.saving.value"
|
||||
:disabled="saving"
|
||||
:compact="compact"
|
||||
:focus-token="builder.labelFocusToken.value"
|
||||
@label="builder.setLabel(uid(), $event)"
|
||||
@key="builder.setFieldKey(uid(), $event)"
|
||||
@unlock="builder.unlockFieldKey(uid())"
|
||||
@reset="builder.resetFieldKey(uid())"
|
||||
@type="builder.setFieldType(uid(), $event)"
|
||||
@required="builder.patchField(uid(), { required: $event })"
|
||||
@unique="builder.patchField(uid(), { unique: $event })"
|
||||
@limit="(which, value) => builder.setLimit(uid(), which, value)"
|
||||
@default="builder.setDefault(uid(), $event)"
|
||||
@default-on="builder.patchField(uid(), { defaultOn: $event })"
|
||||
@add-choice="builder.addChoice(uid(), $event)"
|
||||
@remove-choice="builder.removeChoice(uid(), $event)"
|
||||
@rename-choice="(index, text) => builder.renameChoice(uid(), index, text)"
|
||||
@reorder-choices="builder.reorderChoices(uid(), $event)"
|
||||
:focus-token="focusToken"
|
||||
@label="actions.setLabel(uid(), $event)"
|
||||
@key="actions.setFieldKey(uid(), $event)"
|
||||
@unlock="actions.unlockFieldKey(uid())"
|
||||
@reset="actions.resetFieldKey(uid())"
|
||||
@type="actions.setFieldType(uid(), $event)"
|
||||
@required="actions.patchField(uid(), { required: $event })"
|
||||
@unique="actions.patchField(uid(), { unique: $event })"
|
||||
@limit="(which, value) => actions.setLimit(uid(), which, value)"
|
||||
@default="actions.setDefault(uid(), $event)"
|
||||
@default-on="actions.patchField(uid(), { defaultOn: $event })"
|
||||
@add-choice="actions.addChoice(uid(), $event)"
|
||||
@remove-choice="actions.removeChoice(uid(), $event)"
|
||||
@rename-choice="(index, text) => actions.renameChoice(uid(), index, text)"
|
||||
@reorder-choices="actions.reorderChoices(uid(), $event)"
|
||||
/>
|
||||
</template>
|
||||
@@ -2,11 +2,10 @@
|
||||
import { t, translate } from '@/i18n'
|
||||
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useContentTypesStore } from '@/stores/contentTypes'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
import { useUpdateStore } from '@/stores/update'
|
||||
import { useApi } from '@/composables/useApi'
|
||||
import { useContentTypesPreload, useShellLogout } from '@/composables/useShellSession'
|
||||
import { useSidebarCollapsed } from '@/composables/useSidebarCollapsed'
|
||||
import AppBoot from '@/components/AppBoot.vue'
|
||||
import AccountMenuSheet from '@/components/AccountMenuSheet.vue'
|
||||
@@ -68,27 +67,15 @@ const bodyUi = computed(() => {
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const session = useSessionStore()
|
||||
const update = useUpdateStore()
|
||||
const api = useApi()
|
||||
const contentTypes = useContentTypesStore()
|
||||
const { loggingOut, logout } = useShellLogout()
|
||||
|
||||
const wantsBuiltin = computed(
|
||||
() => session.canManageContent && route.meta.needsBuiltinTypes === true,
|
||||
)
|
||||
|
||||
watch(
|
||||
[() => session.status === 'ready' && session.session?.verified === true, wantsBuiltin],
|
||||
([verified, builtin], [wasVerified]) => {
|
||||
if (verified) {
|
||||
contentTypes.load({ builtin })
|
||||
} else if (wasVerified) {
|
||||
contentTypes.reset()
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
useContentTypesPreload(() => wantsBuiltin.value)
|
||||
|
||||
const publishingChildren = computed(() => [
|
||||
{ label: t('Publishing'), type: 'label' as const },
|
||||
@@ -237,7 +224,6 @@ const navUi = computed(() => ({
|
||||
childLink: 'h-8 max-lg:h-11 gap-2.5 py-0 pr-2.5 pl-3 text-sm',
|
||||
childLinkIcon: 'size-3.5',
|
||||
}))
|
||||
const loggingOut = ref(false)
|
||||
const sidebarOpen = ref(false)
|
||||
const accountSheetOpen = ref(false)
|
||||
const NAVBAR_TOGGLE_SELECTOR = '[data-slot="toggle"]'
|
||||
@@ -272,17 +258,6 @@ const showBoot = computed(() => session.status === 'checking' && !props.renderWh
|
||||
function onRetry() {
|
||||
session.retry()
|
||||
}
|
||||
|
||||
async function onLogout() {
|
||||
loggingOut.value = true
|
||||
try {
|
||||
await api.request('/v1/auth/logout', { method: 'POST' })
|
||||
} finally {
|
||||
loggingOut.value = false
|
||||
}
|
||||
session.clear()
|
||||
await router.push('/login')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -335,7 +310,7 @@ async function onLogout() {
|
||||
:email="session.session?.email"
|
||||
:collapsed="sidebarCollapsed"
|
||||
:sheet-open="accountSheetOpen"
|
||||
@logout="onLogout"
|
||||
@logout="logout"
|
||||
@open-sheet="openAccountSheet"
|
||||
/>
|
||||
</template>
|
||||
@@ -438,7 +413,7 @@ async function onLogout() {
|
||||
:username="session.session?.username ?? ''"
|
||||
:display-name="session.session?.displayName"
|
||||
:email="session.session?.email"
|
||||
@logout="onLogout"
|
||||
@logout="logout"
|
||||
/>
|
||||
</UDashboardGroup>
|
||||
</template>
|
||||
@@ -2,9 +2,18 @@
|
||||
import { t } from '@/i18n'
|
||||
|
||||
import BuilderStatusChip from '@/components/BuilderStatusChip.vue'
|
||||
import type { EntryEditorPage } from '@/composables/useEntryEditorPage'
|
||||
import type { EntryChipStatus } from '@/composables/useEntryEditorPage'
|
||||
|
||||
defineProps<{ page: EntryEditorPage; bar?: boolean }>()
|
||||
defineProps<{
|
||||
chip: EntryChipStatus
|
||||
savedClock: string
|
||||
creating: boolean
|
||||
saving: boolean
|
||||
canSave: boolean
|
||||
bar?: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{ remove: []; cancel: [] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -13,20 +22,16 @@ defineProps<{ page: EntryEditorPage; bar?: boolean }>()
|
||||
:class="bar ? 'w-full gap-2' : 'gap-3'"
|
||||
:data-testid="bar ? 'entry-bar' : 'entry-actions'"
|
||||
>
|
||||
<BuilderStatusChip
|
||||
v-if="!bar"
|
||||
:status="page.chip.value"
|
||||
:saved-at="page.savedClock.value"
|
||||
/>
|
||||
<BuilderStatusChip v-if="!bar" :status="chip" :saved-at="savedClock" />
|
||||
<UButton
|
||||
v-if="!bar && !page.creating.value"
|
||||
v-if="!bar && !creating"
|
||||
type="button"
|
||||
color="error"
|
||||
variant="outline"
|
||||
icon="i-lucide-trash-2"
|
||||
:disabled="page.saving.value"
|
||||
:disabled="saving"
|
||||
class="h-8 px-3"
|
||||
@click="page.removal.open"
|
||||
@click="$emit('remove')"
|
||||
>
|
||||
{{ t('Delete') }}
|
||||
</UButton>
|
||||
@@ -34,25 +39,22 @@ defineProps<{ page: EntryEditorPage; bar?: boolean }>()
|
||||
type="button"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
:disabled="page.saving.value"
|
||||
:disabled="saving"
|
||||
class="h-11 border border-accented bg-default px-3 text-highlighted ring-0 md:h-8"
|
||||
:class="bar ? 'flex-1 justify-center' : ''"
|
||||
@click="page.cancel"
|
||||
@click="$emit('cancel')"
|
||||
>
|
||||
{{ t('Cancel') }}
|
||||
</UButton>
|
||||
<UButton
|
||||
type="submit"
|
||||
form="entry-form"
|
||||
:disabled="!page.canSave.value"
|
||||
:loading="page.saving.value"
|
||||
:disabled="!canSave"
|
||||
:loading="saving"
|
||||
class="h-11 px-4 md:h-8"
|
||||
:class="[
|
||||
bar ? 'flex-1 justify-center' : '',
|
||||
page.saving.value ? 'disabled:opacity-100' : '',
|
||||
]"
|
||||
:class="[bar ? 'flex-1 justify-center' : '', saving ? 'disabled:opacity-100' : '']"
|
||||
>
|
||||
{{ page.saving.value ? t('Saving…') : t('Save') }}
|
||||
{{ saving ? t('Saving…') : t('Save') }}
|
||||
</UButton>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { t } from '@/i18n'
|
||||
|
||||
import { computed } from 'vue'
|
||||
import BuilderStatusChip from '@/components/BuilderStatusChip.vue'
|
||||
import ContentState from '@/components/ContentState.vue'
|
||||
import DirectoryLoadError from '@/components/DirectoryLoadError.vue'
|
||||
@@ -28,6 +29,14 @@ function loadErrorLine() {
|
||||
)
|
||||
}
|
||||
|
||||
const actionProps = computed(() => ({
|
||||
chip: props.page.chip.value,
|
||||
savedClock: props.page.savedClock.value,
|
||||
creating: props.page.creating.value,
|
||||
saving: props.page.saving.value,
|
||||
canSave: props.page.canSave.value,
|
||||
}))
|
||||
|
||||
function goHome() {
|
||||
router.push(HOME_PATH)
|
||||
}
|
||||
@@ -65,7 +74,12 @@ function backToList() {
|
||||
<p class="text-muted">{{ page.subtitle.value }}</p>
|
||||
</template>
|
||||
</div>
|
||||
<EntryActions v-if="isDesktop && phase === 'ready'" :page="page" />
|
||||
<EntryActions
|
||||
v-if="isDesktop && phase === 'ready'"
|
||||
v-bind="actionProps"
|
||||
@remove="page.removal.open"
|
||||
@cancel="page.cancel"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -83,8 +97,32 @@ function backToList() {
|
||||
/>
|
||||
<EntryEditorSkeleton v-else-if="phase === 'loading'" />
|
||||
<template v-else>
|
||||
<EntryNotices :page="page" />
|
||||
<EntryForm :page="page" />
|
||||
<EntryNotices
|
||||
:failure="page.failure.value"
|
||||
:schema-changed="page.schemaChanged.value"
|
||||
:schema-field="page.schemaField.value"
|
||||
:issues="page.form.issues.value"
|
||||
:fields="page.fields.value"
|
||||
:type-name="page.typeName.value"
|
||||
@retry="page.submit"
|
||||
@reload="page.reload"
|
||||
/>
|
||||
<EntryForm
|
||||
:fields="page.fields.value"
|
||||
:values="page.form.values.value"
|
||||
:issues="page.form.issues.value"
|
||||
:first-issue="page.form.firstIssue.value"
|
||||
:focus-token="page.focusToken.value"
|
||||
:locked="page.locked.value"
|
||||
:saving="page.saving.value"
|
||||
:creating="page.creating.value"
|
||||
:is-default="page.form.isDefault"
|
||||
:entry-line="page.entryLine.value"
|
||||
:created-line="page.createdLine.value"
|
||||
:updated-line="page.updatedLine.value"
|
||||
@update="page.change"
|
||||
@submit="page.submit"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@@ -92,7 +130,7 @@ function backToList() {
|
||||
v-if="!isDesktop && phase === 'ready'"
|
||||
class="sticky bottom-0 -mx-4 mt-auto flex border-t border-default bg-default px-4 py-3"
|
||||
>
|
||||
<EntryActions :page="page" bar />
|
||||
<EntryActions v-bind="actionProps" bar @remove="page.removal.open" @cancel="page.cancel" />
|
||||
</div>
|
||||
|
||||
<EntryDeleteDialog
|
||||
|
||||
@@ -3,21 +3,35 @@ import { t } from '@/i18n'
|
||||
|
||||
import { computed, nextTick, watch } from 'vue'
|
||||
import EntryField from '@/components/EntryField.vue'
|
||||
import type { EntryEditorPage } from '@/composables/useEntryEditorPage'
|
||||
import type { BuiltinField } from '@/lib/contentTypes'
|
||||
import type { EntryIssues, EntryValues } from '@/lib/entryForm'
|
||||
import { groupFields } from '@/lib/entryLayout'
|
||||
|
||||
const FOCUSABLE =
|
||||
'input:not([type="hidden"]), textarea, [role="spinbutton"], [role="switch"], [role="combobox"]'
|
||||
|
||||
const props = defineProps<{ page: EntryEditorPage }>()
|
||||
const props = defineProps<{
|
||||
fields: BuiltinField[]
|
||||
values: EntryValues
|
||||
issues: EntryIssues
|
||||
firstIssue: string | null
|
||||
focusToken: number
|
||||
locked: boolean
|
||||
saving: boolean
|
||||
creating: boolean
|
||||
isDefault: (field: BuiltinField) => boolean
|
||||
entryLine: string
|
||||
createdLine: string
|
||||
updatedLine: string
|
||||
}>()
|
||||
|
||||
const { form, fields, locked, focusToken } = props.page
|
||||
const rows = computed(() => groupFields(fields.value))
|
||||
const dimmed = computed(() => locked.value)
|
||||
const emit = defineEmits<{ update: [key: string, value: unknown]; submit: [] }>()
|
||||
|
||||
const rows = computed(() => groupFields(props.fields))
|
||||
|
||||
async function focusFirstIssue() {
|
||||
await nextTick()
|
||||
const key = form.firstIssue.value
|
||||
const key = props.firstIssue
|
||||
if (!key) {
|
||||
return
|
||||
}
|
||||
@@ -29,7 +43,7 @@ async function focusFirstIssue() {
|
||||
;(wrapper ?? control)?.scrollIntoView?.({ block: 'center' })
|
||||
}
|
||||
|
||||
watch(focusToken, focusFirstIssue)
|
||||
watch(() => props.focusToken, focusFirstIssue)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -38,9 +52,9 @@ watch(focusToken, focusFirstIssue)
|
||||
novalidate
|
||||
data-testid="entry-form"
|
||||
class="flex max-w-190 flex-col gap-5 border border-default bg-default p-4 transition-opacity md:p-6"
|
||||
:class="dimmed ? 'opacity-70' : ''"
|
||||
:aria-busy="page.saving.value ? 'true' : undefined"
|
||||
@submit.prevent="page.submit"
|
||||
:class="locked ? 'opacity-70' : ''"
|
||||
:aria-busy="saving ? 'true' : undefined"
|
||||
@submit.prevent="emit('submit')"
|
||||
>
|
||||
<div
|
||||
v-for="row in rows"
|
||||
@@ -52,11 +66,11 @@ watch(focusToken, focusFirstIssue)
|
||||
v-for="field in row"
|
||||
:key="field.key"
|
||||
:field="field"
|
||||
:value="form.values.value[field.key]"
|
||||
:issue="form.issues.value[field.key]"
|
||||
:is-default="form.isDefault(field)"
|
||||
:value="values[field.key]"
|
||||
:issue="issues[field.key]"
|
||||
:is-default="isDefault(field)"
|
||||
:disabled="locked"
|
||||
@update="page.change"
|
||||
@update="(key, value) => emit('update', key, value)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -65,11 +79,11 @@ watch(focusToken, focusFirstIssue)
|
||||
class="grid grid-cols-1 gap-x-4 gap-y-1.5 border-t border-default pt-4 text-xs/4.5 md:grid-cols-[auto_1fr]"
|
||||
>
|
||||
<dt class="text-muted">{{ t('Entry') }}</dt>
|
||||
<dd :class="page.creating.value ? '' : 'font-mono'">{{ page.entryLine.value }}</dd>
|
||||
<dd :class="creating ? '' : 'font-mono'">{{ entryLine }}</dd>
|
||||
<dt class="text-muted">{{ t('Created by') }}</dt>
|
||||
<dd>{{ page.createdLine.value }}</dd>
|
||||
<dd>{{ createdLine }}</dd>
|
||||
<dt class="text-muted">{{ t('Updated by') }}</dt>
|
||||
<dd>{{ page.updatedLine.value }}</dd>
|
||||
<dd>{{ updatedLine }}</dd>
|
||||
</dl>
|
||||
</form>
|
||||
</template>
|
||||
@@ -3,15 +3,24 @@ import { t } from '@/i18n'
|
||||
|
||||
import { computed } from 'vue'
|
||||
import RetryAlert from '@/components/RetryAlert.vue'
|
||||
import type { EntryEditorPage } from '@/composables/useEntryEditorPage'
|
||||
import type { SaveFailure } from '@/composables/useEntryDraft'
|
||||
import type { BuiltinField } from '@/lib/contentTypes'
|
||||
import type { EntryIssues } from '@/lib/entryForm'
|
||||
import { schemaChangedLine, uniqueAlert } from '@/lib/entryMessages'
|
||||
|
||||
const props = defineProps<{ page: EntryEditorPage }>()
|
||||
const props = defineProps<{
|
||||
failure: SaveFailure
|
||||
schemaChanged: boolean
|
||||
schemaField: string
|
||||
issues: EntryIssues
|
||||
fields: BuiltinField[]
|
||||
typeName: string
|
||||
}>()
|
||||
|
||||
const { failure, schemaChanged, schemaField, form, fields, typeName } = props.page
|
||||
defineEmits<{ retry: []; reload: [] }>()
|
||||
|
||||
const uniqueField = computed(() =>
|
||||
fields.value.find((field) => form.issues.value[field.key]?.rule === 'unique'),
|
||||
props.fields.find((field) => props.issues[field.key]?.rule === 'unique'),
|
||||
)
|
||||
const alertUi = {
|
||||
root: 'items-start gap-2.5 px-3.5 py-3',
|
||||
@@ -29,7 +38,7 @@ const alertUi = {
|
||||
'Something went wrong on our side. Your changes are still here. Try again in a moment.',
|
||||
)
|
||||
"
|
||||
@retry="page.submit"
|
||||
@retry="$emit('retry')"
|
||||
/>
|
||||
|
||||
<UAlert
|
||||
@@ -61,7 +70,7 @@ const alertUi = {
|
||||
icon="i-lucide-rotate-cw"
|
||||
class="h-11 gap-1.5 border border-accented bg-default px-2.5 text-xs text-highlighted ring-0 md:h-8"
|
||||
:ui="{ leadingIcon: 'size-3.5' }"
|
||||
@click="page.reload"
|
||||
@click="$emit('reload')"
|
||||
>
|
||||
{{ t('Reload form') }}
|
||||
</UButton>
|
||||
|
||||
@@ -42,7 +42,7 @@ const height = computed(() => (props.touch ? 'h-11' : 'h-8'))
|
||||
trailingIcon: 'size-3.5 text-muted',
|
||||
content: 'w-40 rounded-none',
|
||||
itemTrailingIcon: 'hidden',
|
||||
item: `${height} rounded-none data-[state=checked]:bg-(--rc-sand) data-[state=checked]:font-semibold data-[state=checked]:text-(--rc-on-sand)`,
|
||||
item: `${height} items-center rounded-none py-0 data-[state=checked]:bg-(--rc-sand) data-[state=checked]:font-semibold data-[state=checked]:text-(--rc-on-sand)`,
|
||||
}"
|
||||
@update:model-value="emit('update:size', $event as number)"
|
||||
/>
|
||||
|
||||
@@ -256,7 +256,9 @@ async function restoreRowFocus() {
|
||||
<BuilderBadge v-if="selectedMarks?.changed" kind="changed" />
|
||||
</div>
|
||||
<BuilderInspector
|
||||
:builder="builder"
|
||||
:actions="builder"
|
||||
:saving="builder.saving.value"
|
||||
:focus-token="builder.labelFocusToken.value"
|
||||
:field="builder.selected.value"
|
||||
:errors="selectedErrors"
|
||||
:compact="false"
|
||||
@@ -313,7 +315,9 @@ async function restoreRowFocus() {
|
||||
>
|
||||
<BuilderInspector
|
||||
v-if="builder.selected.value"
|
||||
:builder="builder"
|
||||
:actions="builder"
|
||||
:saving="builder.saving.value"
|
||||
:focus-token="builder.labelFocusToken.value"
|
||||
:field="builder.selected.value"
|
||||
:errors="selectedErrors"
|
||||
compact
|
||||
|
||||
Reference in new issue
Block a user