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
29 changes: 27 additions & 2 deletions adminforth/spa/src/afcl/Select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
</div>
<teleport to="body" v-if="teleportToBody && showDropdown">
<div ref="dropdownEl" :style="getDropdownPosition" :class="{'shadow-none': isTop}"
class="fixed z-50 w-full bg-white shadow-lg dark:shadow-black dark:bg-gray-700
class="fixed z-[5] w-full bg-white shadow-lg dark:shadow-black dark:bg-gray-700
dark:border-gray-600 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm max-h-48">
<div
v-for="item in filteredItems"
Expand Down Expand Up @@ -202,6 +202,23 @@ watch(
}
);

const handleScroll = () => {
if (showDropdown.value && inputEl.value) {
const rect = inputEl.value.getBoundingClientRect();
const style = {
left: `${rect.left}px`,
top: isTop.value && dropdownHeight.value
? `${rect.top - dropdownHeight.value - 8}px`
: `${rect.bottom + 8}px`,
width: `${rect.width}px`
};

if (dropdownEl.value) {
Object.assign(dropdownEl.value.style, style);
}
}
};

onMounted(() => {
updateFromProps();

Expand All @@ -214,7 +231,11 @@ onMounted(() => {
});

addClickListener();


// Add scroll listeners if teleportToBody is true
if (props.teleportToBody) {
window.addEventListener('scroll', handleScroll, true);
}
});

const filteredItems = computed(() => {
Expand Down Expand Up @@ -268,6 +289,10 @@ const toogleItem = (item) => {

onUnmounted(() => {
removeClickListener();
// Remove scroll listeners if teleportToBody is true
if (props.teleportToBody) {
window.removeEventListener('scroll', handleScroll, true);
}
});

const getDropdownPosition = computed(() => {
Expand Down
3 changes: 3 additions & 0 deletions adminforth/spa/src/components/ColumnValueInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
ref="input"
class="w-full"
:options="columnOptions[column.name] || []"
teleportToBody
:placeholder = "columnOptions[column.name]?.length ?$t('Select...'): $t('There are no options available')"
:modelValue="value"
:readonly="column.editReadonly && source === 'edit'"
Expand All @@ -28,6 +29,7 @@
ref="input"
class="w-full"
:options="column.enum"
teleportToBody
:modelValue="value"
:readonly="column.editReadonly && source === 'edit'"
@update:modelValue="$emit('update:modelValue', $event)"
Expand All @@ -37,6 +39,7 @@
ref="input"
class="w-full"
:options="getBooleanOptions(column)"
teleportToBody
:modelValue="value"
:readonly="column.editReadonly && source === 'edit'"
@update:modelValue="$emit('update:modelValue', $event)"
Expand Down
75 changes: 75 additions & 0 deletions adminforth/spa/src/components/ColumnValueInputWrapper.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<template>
<template v-if="column.isArray?.enabled">
<div class="flex flex-col">
<ColumnValueInput
v-for="(arrayItemValue, arrayItemIndex) in currentValues[column.name]"
:key="`${column.name}-${arrayItemIndex}`"
ref="arrayItemRefs"
:class="{'mt-2': arrayItemIndex}"
:source="source"
:column="column"
:type="column.isArray.itemType"
:value="arrayItemValue"
:currentValues="currentValues"
:mode="mode"
:columnOptions="columnOptions"
:deletable="!column.editReadonly"
@update:modelValue="setCurrentValue(column.name, $event, arrayItemIndex)"
@update:unmasked="$emit('update:unmasked', column.name)"
@update:inValidity="$emit('update:inValidity', { name: column.name, value: $event })"
@update:emptiness="$emit('update:emptiness', { name: column.name, value: $event })"
@delete="setCurrentValue(column.name, currentValues[column.name].filter((_, index) => index !== arrayItemIndex))"
/>
</div>
<button
v-if="!column.editReadonly"
type="button"
@click="addArrayItem"
class="flex items-center py-1 px-3 me-2 text-sm font-medium rounded-default text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
:class="{'mt-2': currentValues[column.name].length}"
>
<IconPlusOutline class="w-4 h-4 me-2"/>
{{ $t('Add') }}
</button>
</template>
<ColumnValueInput
v-else
:source="source"
:column="column"
:value="currentValues[column.name]"
:currentValues="currentValues"
:mode="mode"
:columnOptions="columnOptions"
:unmasked="unmasked"
@update:modelValue="setCurrentValue(column.name, $event)"
@update:unmasked="$emit('update:unmasked', column.name)"
@update:inValidity="$emit('update:inValidity', { name: column.name, value: $event })"
@update:emptiness="$emit('update:emptiness', { name: column.name, value: $event })"
/>
</template>

<script setup lang="ts">
import { IconPlusOutline } from '@iconify-prerendered/vue-flowbite';
import ColumnValueInput from "./ColumnValueInput.vue";
import { ref, nextTick } from 'vue';

const props = defineProps<{
source: 'create' | 'edit',
column: any,
currentValues: any,
mode: string,
columnOptions: any,
unmasked: any,
setCurrentValue: Function
}>();

const emit = defineEmits(['update:unmasked', 'update:inValidity', 'update:emptiness', 'focus-last-input']);

const arrayItemRefs = ref([]);

async function addArrayItem() {
props.setCurrentValue(props.column.name, props.currentValues[props.column.name], props.currentValues[props.column.name].length);
await nextTick();
arrayItemRefs.value[arrayItemRefs.value.length - 1].focus();
}
</script>
55 changes: 7 additions & 48 deletions adminforth/spa/src/components/GroupsTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,50 +42,17 @@
class="px-6 py-4 whitespace-pre-wrap relative block md:table-cell"
:class="{'rounded-br-lg': i === group.columns.length - 1}"
>
<template v-if="column.isArray?.enabled">
<ColumnValueInput
v-for="(arrayItemValue, arrayItemIndex) in currentValues[column.name]"
:key="`${column.name}-${arrayItemIndex}`"
ref="arrayItemRefs"
:class="{'mt-2': arrayItemIndex}"
:source="source"
:column="column"
:type="column.isArray.itemType"
:value="arrayItemValue"
:currentValues="currentValues"
:mode="mode"
:columnOptions="columnOptions"
:deletable="!column.editReadonly"
@update:modelValue="setCurrentValue(column.name, $event, arrayItemIndex)"
@update:unmasked="unmasked[column.name] = !unmasked[column.name]"
@update:inValidity="customComponentsInValidity[column.name] = $event"
@update:emptiness="customComponentsEmptiness[column.name] = $event"
@delete="setCurrentValue(column.name, currentValues[column.name].filter((_, index) => index !== arrayItemIndex))"
/>
<button
v-if="!column.editReadonly"
type="button"
@click="setCurrentValue(column.name, currentValues[column.name], currentValues[column.name].length); focusOnLastInput(column.name)"
class="flex items-center py-1 px-3 me-2 text-sm font-medium rounded-default text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
:class="{'mt-2': currentValues[column.name].length}"
>
<IconPlusOutline class="w-4 h-4 me-2"/>
{{ $t('Add') }}
</button>
</template>
<ColumnValueInput
v-else
<ColumnValueInputWrapper
:source="source"
:column="column"
:value="currentValues[column.name]"
:currentValues="currentValues"
:mode="mode"
:columnOptions="columnOptions"
:unmasked="unmasked"
@update:modelValue="setCurrentValue(column.name, $event)"
@update:unmasked="unmasked[column.name] = !unmasked[column.name]"
@update:inValidity="customComponentsInValidity[column.name] = $event"
@update:emptiness="customComponentsEmptiness[column.name] = $event"
:setCurrentValue="setCurrentValue"
@update:unmasked="unmasked[$event] = !unmasked[$event]"
@update:inValidity="customComponentsInValidity[$event.name] = $event.value"
@update:emptiness="customComponentsEmptiness[$event.name] = $event.value"
/>
<div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
<div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
Expand All @@ -98,10 +65,10 @@

<script setup lang="ts">
import { IconExclamationCircleSolid, IconPlusOutline } from '@iconify-prerendered/vue-flowbite';
import ColumnValueInput from "@/components/ColumnValueInput.vue";
import { Tooltip } from '@/afcl';
import { ref, computed, watch, nextTick, type Ref } from 'vue';
import { useI18n } from 'vue-i18n';
import ColumnValueInputWrapper from "@/components/ColumnValueInputWrapper.vue";

const { t } = useI18n();

Expand All @@ -117,8 +84,6 @@
columnOptions: any,
}>();

const arrayItemRefs = ref([]);

const customComponentsInValidity: Ref<Record<string, boolean>> = ref({});
const customComponentsEmptiness: Ref<Record<string, boolean>> = ref({});
const allColumnsHaveCustomComponent = computed(() => {
Expand All @@ -130,12 +95,6 @@

const emit = defineEmits(['update:customComponentsInValidity', 'update:customComponentsEmptiness']);

async function focusOnLastInput(column) {
// wait for element to register
await nextTick();
arrayItemRefs.value[arrayItemRefs.value.length - 1].focus();
}

watch(customComponentsInValidity.value, (newVal) => {
emit('update:customComponentsInValidity', newVal);
});
Expand All @@ -144,4 +103,4 @@
emit('update:customComponentsEmptiness', newVal);
});

</script>
</script>
3 changes: 2 additions & 1 deletion plugins/install-plugins.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
PLUGINS="adminforth-audit-log adminforth-email-password-reset adminforth-foreign-inline-list \
adminforth-i18n adminforth-import-export adminforth-text-complete adminforth-open-signup \
adminforth-rich-editor adminforth-two-factors-auth adminforth-upload adminforth-oauth"
adminforth-rich-editor adminforth-two-factors-auth adminforth-upload adminforth-oauth \
adminforth-list-in-place-edit"

# for each plugin
for plugin in $PLUGINS; do
Expand Down