Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions resources/js/components/forms/FieldNumber.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script setup lang="ts">
import { computed, inject, type ComputedRef } from 'vue';

const props = defineProps<{
fieldKey?: string | null;
}>();

const fieldNumbers = inject<ComputedRef<Map<string, number>> | null>('fieldNumbers', null);

const number = computed(() => (props.fieldKey ? fieldNumbers?.value?.get(props.fieldKey) ?? null : null));
</script>

<template>
<span
v-if="number"
data-field-number
class="font-mono text-2xs text-gray-500 tabular-nums dark:text-gray-400"
>{{ number }}.</span>
</template>
22 changes: 22 additions & 0 deletions resources/js/components/forms/FieldNumberingToggle.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup lang="ts">
import { computed } from 'vue';
import { ToggleGroup, ToggleItem } from '@ui';
import { useFieldNumberingPreference } from '@/composables/forms/field-numbering';

const { showFieldNumbers } = useFieldNumberingPreference();

const toggle = () => showFieldNumbers.value = !showFieldNumbers.value;
const label = computed(() => showFieldNumbers.value ? __('Hide field numbers') : __('Show field numbers'));
</script>

<template>
<ToggleGroup :model-value="showFieldNumbers ? 'on' : null" size="xs">
<ToggleItem
value="on"
icon="mail-sign-hashtag"
:aria-label="label"
v-tooltip="label"
@click.prevent="toggle"
/>
</ToggleGroup>
</template>
3 changes: 3 additions & 0 deletions resources/js/components/forms/builder/FieldInspector.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import axios from 'axios';
import { injectBuilderContext } from '@/pages/forms/Builder.vue';
import FieldValidationBuilder from '@/components/field-validation/Builder.vue';
import FieldConditionsBuilder from '@/components/field-conditions/Builder.vue';
import FieldNumber from '@/components/forms/FieldNumber.vue';
import { categories, categoryColorClasses } from './categories';
import debounce from '@/util/debounce';

Expand Down Expand Up @@ -267,6 +268,7 @@ onMounted(() => load());
>
<template #field-option="{ value, label }">
<span class="inline-flex items-center gap-2">
<FieldNumber :field-key="value" />
<Icon
v-if="findSuggestableField(value)?.icon"
:name="findSuggestableField(value).icon"
Expand All @@ -277,6 +279,7 @@ onMounted(() => load());
</template>
<template #field-selected="{ option, field: selectedField }">
<span class="inline-flex items-center gap-2 truncate">
<FieldNumber :field-key="option.value" />
<Icon
v-if="selectedField?.icon"
:name="selectedField.icon"
Expand Down
2 changes: 2 additions & 0 deletions resources/js/components/forms/builder/ImportField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Button, Description, Field, Icon, Label } from '@ui';
import { computed } from 'vue';
import { usePage } from '@inertiajs/vue3';
import FieldNumber from '@/components/forms/FieldNumber.vue';
import { injectBuilderContext, InspectorType } from '@/pages/forms/Builder.vue';
import { categoryColorClasses } from './categories';
import { __ } from '@/bootstrap/globals';
Expand Down Expand Up @@ -77,6 +78,7 @@ const errorMessage = computed(() => {
<template #label>
<Label>
<span class="inline-flex flex-wrap items-center gap-x-2 gap-y-1">
<FieldNumber :field-key="`${field._id}:${fieldsetField.handle}`" class="me-1" />
<Icon name="link" data-collapsed-field-icon class="size-3.5 me-1 " :class="categoryColorClasses['fieldsets']?.icon" aria-hidden="true" />
{{ __(fieldsetField.config.display) }}
<span v-if="fieldsetField.config.validate?.includes('required')" class="relative -top-px ms-0.5 text-red-600">*</span>
Expand Down
3 changes: 3 additions & 0 deletions resources/js/components/forms/builder/RegularFormField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Button, Field, Icon, Label } from '@ui';
import { computed } from 'vue';
import { FieldView, injectBuilderContext, InspectorType } from '@/pages/forms/Builder.vue';
import { categories, categoryColorClasses } from './categories';
import FieldNumber from '@/components/forms/FieldNumber.vue';
import WidthSelector from '@/components/fields/WidthSelector.vue';
import { __ } from '@/bootstrap/globals';

Expand Down Expand Up @@ -105,6 +106,7 @@ const hasErrors = computed(() => {
>
<template #label>
<Label :class="['mb-0', { 'cursor-pointer': !isInspecting }]">
<FieldNumber :field-key="field._id" class="me-1" />
<Icon :name="field.icon" data-collapsed-field-icon :class="['size-3.5 mb-0.25! me-2.5', iconColorClass]" aria-hidden="true" />
<Icon
v-if="field.config.if || field.config.unless"
Expand Down Expand Up @@ -140,6 +142,7 @@ const hasErrors = computed(() => {
>
<template #label>
<Label :class="['', { 'cursor-pointer': !isInspecting }]">
<FieldNumber :field-key="field._id" class="me-1" />
<Icon :name="field.icon" data-collapsed-field-icon :class="['size-3.5 mb-0.25! me-2.5', iconColorClass]" aria-hidden="true" />
<Icon
v-if="field.config.if || field.config.unless"
Expand Down
3 changes: 3 additions & 0 deletions resources/js/components/forms/builder/pages/PageRule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Button, Combobox, Icon } from '@ui';
import { computed } from 'vue';
import { nanoid as uniqid } from 'nanoid';
import Condition from '@/components/field-conditions/Condition.vue';
import FieldNumber from '@/components/forms/FieldNumber.vue';
import { categories, categoryColorClasses } from '../categories';

const emit = defineEmits(['update:rule', 'remove']);
Expand Down Expand Up @@ -155,6 +156,7 @@ const shouldBeIndented = (index) => {
>
<template #field-option="{ value, label }">
<span class="inline-flex items-center gap-2">
<FieldNumber :field-key="value" />
<Icon
v-if="findSuggestableField(value)?.icon"
:name="findSuggestableField(value).icon"
Expand All @@ -165,6 +167,7 @@ const shouldBeIndented = (index) => {
</template>
<template #field-selected="{ option, field: selectedField }">
<span class="inline-flex items-center gap-2 truncate">
<FieldNumber :field-key="option.value" />
<Icon
v-if="selectedField?.icon"
:name="selectedField.icon"
Expand Down
4 changes: 4 additions & 0 deletions resources/js/components/forms/logic/FieldLogicRule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Icon,
Subheading,
} from '@/components/ui';
import FieldNumber from '@/components/forms/FieldNumber.vue';
import FieldConditionsBuilder from '@/components/field-conditions/Builder.vue';
import Converter from '@/components/field-conditions/Converter.js';
import { categories, categoryColorClasses } from '@/components/forms/builder/categories.js';
Expand Down Expand Up @@ -121,6 +122,7 @@ const onAlwaysSaveUpdated = (alwaysSave) => emit('update:conditions', { ...props
>
<button type="button" class="show-focus-within_target flex flex-1 items-center gap-1.75 p-2 py-1.75 ps-0 min-w-0 focus:outline-none cursor-pointer" @click="toggleCollapsedState">
<Badge size="lg" pill color="white" class="px-3 text-gray-950 gap-1">
<FieldNumber :field-key="config?.handle" class="me-0.5" />
<Icon
v-if="config?.icon"
:name="config.icon"
Expand Down Expand Up @@ -193,6 +195,7 @@ const onAlwaysSaveUpdated = (alwaysSave) => emit('update:conditions', { ...props
>
<template #field-option="{ value, label }">
<span class="inline-flex items-center gap-2">
<FieldNumber :field-key="value" />
<Icon
v-if="findSuggestableField(value)?.icon"
:name="findSuggestableField(value).icon"
Expand All @@ -203,6 +206,7 @@ const onAlwaysSaveUpdated = (alwaysSave) => emit('update:conditions', { ...props
</template>
<template #field-selected="{ option, field: selectedField }">
<span class="inline-flex items-center gap-2 truncate">
<FieldNumber :field-key="option.value" />
<Icon
v-if="selectedField?.icon"
:name="selectedField.icon"
Expand Down
3 changes: 2 additions & 1 deletion resources/js/components/forms/logic/LogicRulePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Primitive } from 'reka-ui';
import fuzzysort from 'fuzzysort';
import { computed, ref, watch, onBeforeUnmount } from 'vue';
import { keys } from '@api';
import FieldNumber from '@/components/forms/FieldNumber.vue';

const emit = defineEmits(['added']);

Expand Down Expand Up @@ -140,7 +141,7 @@ onBeforeUnmount(() => unbindKeys());
<ui-icon :name="item.icon || 'plus'" class="size-4" :class="item.iconClass || 'text-gray-600 dark:text-gray-300'" />
<div class="flex-1">
<div class="line-clamp-1 text-sm text-gray-900 dark:text-gray-200">
{{ __(item.display || item.handle) }}
<FieldNumber :field-key="item.handle" class="me-1" />{{ __(item.display || item.handle) }}
</div>
<ui-description v-if="item.instructions" class="w-56 truncate text-2xs">
{{ __(item.instructions) }}
Expand Down
4 changes: 4 additions & 0 deletions resources/js/components/forms/logic/PageLogicRule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Subheading,
} from '@/components/ui';
import PageRule from '@/components/forms/builder/pages/PageRule.vue';
import FieldNumber from '@/components/forms/FieldNumber.vue';
import { categories, categoryColorClasses } from '@/components/forms/builder/categories';

const emit = defineEmits(['collapsed', 'expanded', 'removed', 'update:rule']);
Expand Down Expand Up @@ -59,6 +60,7 @@ const firstFieldConfig = computed(() => {
if (!firstCondition?.field) return null;
const field = getFieldConfig(firstCondition.field);
return {
handle: firstCondition.field,
display: field?.config?.display || firstCondition.field,
icon: field?.icon || 'generic-field',
iconClass: getIconClass(field?.category),
Expand Down Expand Up @@ -136,6 +138,7 @@ const toggleCollapsedState = () => props.collapsed ? emit('expanded') : emit('co
{{ __('If') }}
</Badge>
<Badge v-if="collapsed && firstFieldConfig" size="lg" pill color="white" class="px-3 text-gray-950 gap-1">
<FieldNumber :field-key="firstFieldConfig.handle" class="me-0.5" />
<Icon
:name="firstFieldConfig.icon"
class="size-3.5 me-1 rounded-sm opacity-100!"
Expand Down Expand Up @@ -177,6 +180,7 @@ const toggleCollapsedState = () => props.collapsed ? emit('expanded') : emit('co
</template>
</Subheading>
<Badge v-if="!collapsed && firstFieldConfig" size="lg" pill color="white" class="px-3 text-gray-950 gap-1">
<FieldNumber :field-key="firstFieldConfig.handle" class="me-0.5" />
<Icon
:name="firstFieldConfig.icon"
class="size-3.5 me-0.5 rounded-sm opacity-100!"
Expand Down
4 changes: 3 additions & 1 deletion resources/js/components/ui/Publish/Form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ onUnmounted(() => saveKeyBinding.destroy());
<template v-if="!!$slots.title" #title>
<slot name="title" />
</template>
<Button v-if="!readOnly" variant="primary" :text="__('Save')" @click="save" :disabled="saving" />
<slot name="actions">
<Button v-if="!readOnly" variant="primary" :text="__('Save')" @click="save" :disabled="saving" />
</slot>
</Header>
<Container
ref="container"
Expand Down
14 changes: 14 additions & 0 deletions resources/js/composables/forms/field-numbering.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ref, watch } from 'vue';
import { preferences } from '@api';

let state = null;

export function useFieldNumberingPreference() {
if (! state) {
const showFieldNumbers = ref(preferences.get('forms.field_numbering', false));
watch(showFieldNumbers, (value) => preferences.set('forms.field_numbering', value));
state = { showFieldNumbers };
}

return state;
}
71 changes: 56 additions & 15 deletions resources/js/pages/forms/Builder.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const [injectBuilderContext, provideBuilderContext] = createContext('Form

<script setup lang="ts">
import { Button, Header, Icon, StatusIndicator, ToggleGroup, ToggleItem } from '@ui';
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import { computed, nextTick, onMounted, onUnmounted, provide, ref, watch } from 'vue';
import axios from 'axios';
import FormsLayout from './Layout.vue';
import Head from '@/pages/layout/Head.vue';
Expand All @@ -38,8 +38,11 @@ import Page from '@/components/forms/builder/Page.vue';
import PageInspector from '@/components/forms/builder/PageInspector.vue';
import SectionInspector from '@/components/forms/builder/SectionInspector.vue';
import { useFieldtypeDraggable } from '@/components/forms/builder/use-drag-and-drop';
import FieldNumberingToggle from '@/components/forms/FieldNumberingToggle.vue';
import { useFieldNumberingPreference } from '@/composables/forms/field-numbering';
import { __, uniqid } from '@/bootstrap/globals';
import { progress, keys } from '@api';
import { usePage } from '@inertiajs/vue3';
import type Binding from '@/components/keys/Binding';

defineOptions({ layout: [Layout, PanelLayout, FormsLayout] });
Expand Down Expand Up @@ -301,6 +304,41 @@ const save = () => {
.finally(() => saving.value = false);
};

const { showFieldNumbers } = useFieldNumberingPreference();

const fieldNumbers = computed(() => {
if (!showFieldNumbers.value) return new Map();

const fieldsets = Object.values(usePage().props.fieldsets ?? {});

const fieldKeys = pages.value
.flatMap((page) => page.sections || [])
.flatMap((section) => section.fields || [])
.flatMap((field) => {
if (field.type === 'link_fields') return [];

if (field.type === 'import') {
const fieldset = fieldsets.find((fieldset) => fieldset.handle === field.fieldset);

return (fieldset?.fields || [])
.filter((f) => f.type !== 'import')
.map((f) => [`${field._id}:${f.handle}`]);
}

let isInformational = props.fieldtypes.find((fieldtype) => fieldtype.handle === field.fieldtype)?.categories?.[0] === 'information';

if (field.config?.hidden || isInformational) return [];

return [[field._id, field.handle]];
});

const map = new Map();
fieldKeys.forEach((keys, index) => keys.forEach((key) => map.set(key, index + 1)));
return map;
});

provide('fieldNumbers', fieldNumbers);

provideBuilderContext({
addSection,
deletePage,
Expand Down Expand Up @@ -385,20 +423,23 @@ onUnmounted(() => {
{{ __(form.title) }}
</template>
<template #actions>
<ToggleGroup v-if="shouldShowViewSelector" v-model="fieldView" size="xs">
<ToggleItem
:value="FieldView.Expanded"
icon="expand"
:aria-label="__('Expanded view')"
v-tooltip="__('Expanded view')"
/>
<ToggleItem
:value="FieldView.Collapsed"
icon="collapse"
:aria-label="__('Collapsed view')"
v-tooltip="__('Collapsed view')"
/>
</ToggleGroup>
<div class="flex items-center gap-2.5">
<FieldNumberingToggle />
<ToggleGroup v-if="shouldShowViewSelector" v-model="fieldView" size="xs">
<ToggleItem
:value="FieldView.Expanded"
icon="expand"
:aria-label="__('Expanded view')"
v-tooltip="__('Expanded view')"
/>
<ToggleItem
:value="FieldView.Collapsed"
icon="collapse"
:aria-label="__('Collapsed view')"
v-tooltip="__('Collapsed view')"
/>
</ToggleGroup>
</div>
</template>
</Header>

Expand Down
24 changes: 23 additions & 1 deletion resources/js/pages/forms/Logic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import Layout from '@/pages/layout/Layout.vue';
import PanelLayout from '@/pages/layout/PanelLayout.vue';
import FormsLayout from './Layout.vue';
import { Button, Header, Icon, StatusIndicator } from '@ui';
import FieldNumberingToggle from '@/components/forms/FieldNumberingToggle.vue';
import FieldLogic from '@/components/forms/logic/FieldLogic.vue';
import PageLogic from '@/components/forms/logic/PageLogic.vue';
import Head from '@/pages/layout/Head.vue';
import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
import { useFieldNumberingPreference } from '@/composables/forms/field-numbering';
import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue';
import { keys } from '@api';
import axios from 'axios';
import { usePage } from '@inertiajs/vue3';

defineOptions({ layout: [Layout, PanelLayout, FormsLayout] });

Expand All @@ -26,6 +29,22 @@ const saving = ref(false);
const saveBinding = ref(null);
const errors = ref({});

const { showFieldNumbers } = useFieldNumberingPreference();
const fieldNumbers = computed(() => {
if (!showFieldNumbers.value) return new Map();

let number = 0;
const map = new Map();

fields.value.forEach((field) => {
if (field.hidden || field.category === 'information') return;
map.set(field._id, ++number);
});

return map;
});
provide('fieldNumbers', fieldNumbers);

const suggestableFields = computed(() => {
return fields.value
.filter(field => !['information', 'structure'].includes(field.category))
Expand Down Expand Up @@ -113,6 +132,9 @@ onUnmounted(() => {
<StatusIndicator status="published" />
{{ __(form.title) }}
</template>
<template #actions>
<FieldNumberingToggle />
</template>
</Header>

<PageLogic
Expand Down
Loading
Loading