mirror of
https://github.com/directus/directus.git
synced 2026-02-11 08:45:34 -05:00
@@ -5,5 +5,6 @@ export function generateRouter(routes: RouteRecordRaw[]) {
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ export default defineComponent({
|
||||
|
||||
if (theme) {
|
||||
document.body.classList.add(theme);
|
||||
|
||||
document
|
||||
.querySelector('head meta[name="theme-color"]')
|
||||
?.setAttribute('content', theme === 'light' ? '#ffffff' : '#263238');
|
||||
|
||||
@@ -35,6 +35,7 @@ export default function (
|
||||
emit('beforeEnter');
|
||||
|
||||
el._parent = el.parentNode as (Node & ParentNode & HTMLElement) | null;
|
||||
|
||||
el._initialStyle = {
|
||||
transition: el.style.transition,
|
||||
visibility: el.style.visibility,
|
||||
@@ -84,6 +85,7 @@ export default function (
|
||||
emit('beforeLeave');
|
||||
|
||||
el._parent = el.parentNode as (Node & ParentNode & HTMLElement) | null;
|
||||
|
||||
el._initialStyle = {
|
||||
transition: el.style.transition,
|
||||
visibility: el.style.visibility,
|
||||
|
||||
@@ -23,6 +23,7 @@ beforeEach(async () => {
|
||||
component: h('div', 'empty'),
|
||||
},
|
||||
]);
|
||||
|
||||
router.push('/');
|
||||
await router.isReady();
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ beforeEach(async () => {
|
||||
component: h('div', 'empty'),
|
||||
},
|
||||
]);
|
||||
|
||||
router.push('/');
|
||||
await router.isReady();
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ const emojiPicker = new EmojiButton({
|
||||
position: 'bottom',
|
||||
emojisPerRow: 8,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['emoji-selected']);
|
||||
|
||||
emojiPicker.on('emoji', (event) => {
|
||||
|
||||
@@ -44,6 +44,7 @@ test('Should show fallback component when there is an error', async () => {
|
||||
},
|
||||
render: () => h('div', 'test'),
|
||||
});
|
||||
|
||||
const fallbackComponent = defineComponent({ render: () => h('div', 'fallback') });
|
||||
|
||||
const wrapper = mount(VErrorBoundary, {
|
||||
|
||||
@@ -47,9 +47,11 @@ const { isCopySupported, copyToClipboard } = useClipboard();
|
||||
|
||||
async function copyError() {
|
||||
const error = props.error?.response?.data || props.error;
|
||||
|
||||
const isCopied = await copyToClipboard(
|
||||
JSON.stringify(error, isPlainObject(error) ? null : Object.getOwnPropertyNames(error), 2)
|
||||
);
|
||||
|
||||
if (!isCopied) return;
|
||||
copied.value = true;
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ function filter(field: Field, parent?: FieldNode): boolean {
|
||||
const children = isNil(field.schema?.foreign_key_table)
|
||||
? fieldsStore.getFieldGroupChildren(field.collection, field.field)
|
||||
: fieldsStore.getFieldsForCollection(field.schema!.foreign_key_table);
|
||||
|
||||
return children?.some((field) => matchesSearch(field)) || matchesSearch(field);
|
||||
|
||||
function matchesSearch(field: Field) {
|
||||
|
||||
@@ -288,6 +288,7 @@ function setContent() {
|
||||
}</button>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
contentEl.value.innerHTML = newInnerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,14 @@ test('should render', () => {
|
||||
},
|
||||
global,
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// test if there is a value
|
||||
test('submitting', async () => {
|
||||
expect(formFieldRawEditor).toBeTruthy();
|
||||
|
||||
const wrapper = mount(formFieldRawEditor, {
|
||||
props: {
|
||||
showModal: true,
|
||||
@@ -38,6 +40,7 @@ test('submitting', async () => {
|
||||
},
|
||||
global,
|
||||
});
|
||||
|
||||
const button = wrapper.findAll('v-button').at(1);
|
||||
await button!.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
@@ -54,6 +57,7 @@ it('should cancel with keydown', async () => {
|
||||
},
|
||||
global,
|
||||
});
|
||||
|
||||
await wrapper.trigger('esc');
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.emitted().cancel.length).toBe(1);
|
||||
@@ -69,6 +73,7 @@ it('should cancel with the cancel button', async () => {
|
||||
},
|
||||
global,
|
||||
});
|
||||
|
||||
const button = wrapper.findAll('v-button').at(0);
|
||||
await button!.trigger('click');
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
@@ -179,6 +179,7 @@ function useRaw() {
|
||||
async function pasteRaw() {
|
||||
const pastedValue = await pasteFromClipboard();
|
||||
if (!pastedValue) return;
|
||||
|
||||
try {
|
||||
internalValue.value = parseJSON(pastedValue);
|
||||
} catch (e) {
|
||||
@@ -194,6 +195,7 @@ function useRaw() {
|
||||
function useComputedValues() {
|
||||
const defaultValue = computed<any>(() => props.field?.schema?.default_value);
|
||||
const internalValue = ref<any>(getInternalValue());
|
||||
|
||||
const isEdited = computed(
|
||||
() => props.modelValue !== undefined && isEqual(props.modelValue, props.initialValue) === false
|
||||
);
|
||||
|
||||
@@ -251,6 +251,7 @@ function useForm() {
|
||||
(field: Field) => field.meta?.group === props.group || (props.group === null && isNil(field.meta?.group))
|
||||
)
|
||||
);
|
||||
|
||||
const fieldNames = computed(() => {
|
||||
return fieldsInGroup.value.map((f) => f.field);
|
||||
});
|
||||
@@ -342,6 +343,7 @@ function apply(updates: { [field: string]: any }) {
|
||||
const groupFields = getFieldsForGroup(props.group)
|
||||
.filter((field) => !field.schema?.is_primary_key && !isDisabled(field))
|
||||
.map((field) => field.field);
|
||||
|
||||
emit('update:modelValue', assign({}, omit(props.modelValue, groupFields), pick(updates, updatableKeys)));
|
||||
} else {
|
||||
emit('update:modelValue', pick(assign({}, props.modelValue, updates), updatableKeys));
|
||||
@@ -366,6 +368,7 @@ function useBatch() {
|
||||
|
||||
function toggleBatchField(field: Field | undefined) {
|
||||
if (!field) return;
|
||||
|
||||
if (batchActiveFields.value.includes(field.field)) {
|
||||
batchActiveFields.value = batchActiveFields.value.filter((fieldKey) => fieldKey !== field.field);
|
||||
|
||||
@@ -390,6 +393,7 @@ function useRawEditor() {
|
||||
|
||||
function toggleRawField(field: Field | undefined) {
|
||||
if (!field) return;
|
||||
|
||||
if (rawActiveFields.value.has(field.field)) {
|
||||
rawActiveFields.value.delete(field.field);
|
||||
} else {
|
||||
|
||||
@@ -19,6 +19,7 @@ const imageElement = ref<HTMLImageElement>();
|
||||
|
||||
const emptyPixel =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
||||
|
||||
const srcData = ref<string>(emptyPixel);
|
||||
|
||||
const observer = new IntersectionObserver((entries, observer) => {
|
||||
@@ -31,6 +32,7 @@ const observer = new IntersectionObserver((entries, observer) => {
|
||||
loadImage();
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.src,
|
||||
() => {
|
||||
|
||||
@@ -151,6 +151,7 @@ const listeners = computed(() => ({
|
||||
},
|
||||
focus: (e: PointerEvent) => emit('focus', e),
|
||||
}));
|
||||
|
||||
const attributes = computed(() => omit(attrs, ['class']));
|
||||
|
||||
const classes = computed(() => [
|
||||
|
||||
@@ -32,6 +32,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const { modelValue: selection, multiple, max, mandatory } = toRefs(props);
|
||||
|
||||
useGroupableParent(
|
||||
{
|
||||
selection: selection,
|
||||
|
||||
@@ -27,6 +27,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const { active } = toRefs(props);
|
||||
|
||||
const { active: isActive, toggle } = useGroupable({
|
||||
value: props.value,
|
||||
group: props.scope,
|
||||
|
||||
@@ -242,6 +242,7 @@ function onClickOutsideMiddleware(e: Event) {
|
||||
|
||||
function onContentClick(e: Event) {
|
||||
e.stopPropagation();
|
||||
|
||||
if (e.target !== e.currentTarget) {
|
||||
deactivate();
|
||||
}
|
||||
@@ -329,7 +330,9 @@ function usePopper(
|
||||
modifiers: getModifiers(resolve),
|
||||
strategy: 'fixed',
|
||||
});
|
||||
|
||||
popperInstance.value.forceUpdate();
|
||||
|
||||
observer.observe(popper.value!, {
|
||||
attributes: false,
|
||||
childList: true,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { createI18n } from 'vue-i18n';
|
||||
import { Focus } from '@/__utils__/focus';
|
||||
|
||||
const i18n = createI18n({ legacy: false });
|
||||
|
||||
const global: GlobalMountOptions = {
|
||||
stubs: [
|
||||
'v-list',
|
||||
|
||||
@@ -218,9 +218,11 @@ const { t } = useI18n();
|
||||
const { internalItems, internalItemsCount, internalSearch } = useItems();
|
||||
const { displayValue } = useDisplayValue();
|
||||
const { modelValue } = toRefs(props);
|
||||
|
||||
const { otherValue, usesOtherValue } = useCustomSelection(modelValue as Ref<string>, internalItems, (value) =>
|
||||
emit('update:modelValue', value)
|
||||
);
|
||||
|
||||
const { otherValues, addOtherValue, setOtherValue } = useCustomSelectionMultiple(
|
||||
modelValue as Ref<string[]>,
|
||||
internalItems,
|
||||
@@ -228,6 +230,7 @@ const { otherValues, addOtherValue, setOtherValue } = useCustomSelectionMultiple
|
||||
);
|
||||
|
||||
const search = ref<string | null>(null);
|
||||
|
||||
watch(
|
||||
search,
|
||||
debounce((val: string | null) => {
|
||||
|
||||
@@ -233,6 +233,7 @@ function onMouseMove(event: PointerEvent) {
|
||||
if (resizing.value === true) {
|
||||
const newWidth = resizeStartWidth.value + (event.pageX - resizeStartX.value);
|
||||
const currentHeaders = clone(props.headers);
|
||||
|
||||
const newHeaders = currentHeaders.map((existing: Header) => {
|
||||
if (existing.value === resizeHeader.value?.value) {
|
||||
return {
|
||||
@@ -243,6 +244,7 @@ function onMouseMove(event: PointerEvent) {
|
||||
|
||||
return existing;
|
||||
});
|
||||
|
||||
emit('update:headers', newHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,6 +298,7 @@ function getSelectedState(item: Item) {
|
||||
const selectedKeys = props.selectionUseKeys
|
||||
? props.modelValue
|
||||
: props.modelValue.map((item: any) => item[props.itemKey]);
|
||||
|
||||
return selectedKeys.includes(item[props.itemKey]);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ function checkKeyDown(event: any) {
|
||||
input.value!.innerText.substring(caretPos),
|
||||
true
|
||||
);
|
||||
|
||||
position(input.value!, caretPos + 1);
|
||||
}
|
||||
} else if (event.code === 'ArrowUp' && !event.shiftKey) {
|
||||
@@ -123,6 +124,7 @@ function checkKeyDown(event: any) {
|
||||
event.preventDefault();
|
||||
|
||||
const newCaretPos = matchedPositions[checkCaretPos - 1];
|
||||
|
||||
parseHTML(
|
||||
(input.value!.innerText.substring(0, newCaretPos) + input.value!.innerText.substring(caretPos)).replaceAll(
|
||||
String.fromCharCode(160),
|
||||
@@ -130,6 +132,7 @@ function checkKeyDown(event: any) {
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
position(input.value!, newCaretPos);
|
||||
emit('update:modelValue', input.value!.innerText);
|
||||
}
|
||||
@@ -146,6 +149,7 @@ function checkKeyDown(event: any) {
|
||||
).replaceAll(String.fromCharCode(160), ' '),
|
||||
true
|
||||
);
|
||||
|
||||
position(input.value!, caretPos);
|
||||
emit('update:modelValue', input.value!.innerText);
|
||||
}
|
||||
@@ -257,6 +261,7 @@ function parseHTML(innerText?: string, isDirectInput = false) {
|
||||
}
|
||||
|
||||
let searchString = replaceSpaceBefore + match + replaceSpaceAfter;
|
||||
|
||||
let replacementString = `${addSpaceBefore}<mark class="preview" data-preview="${
|
||||
props.items[match.substring(props.triggerCharacter.length)]
|
||||
}" contenteditable="false">${match}</mark>${addSpaceAfter}`;
|
||||
@@ -280,6 +285,7 @@ function parseHTML(innerText?: string, isDirectInput = false) {
|
||||
}
|
||||
|
||||
lastMatchIndex = 0;
|
||||
|
||||
for (const match of matches ?? []) {
|
||||
let matchIndex = input.value.innerText.indexOf(match, lastMatchIndex);
|
||||
matchedPositions.push(matchIndex, matchIndex + match.length);
|
||||
|
||||
@@ -131,6 +131,7 @@ const filterByFolder = computed(() => {
|
||||
|
||||
function validFiles(files: FileList) {
|
||||
if (files.length === 0) return false;
|
||||
|
||||
for (const file of files) {
|
||||
if (file.size === 0) return false;
|
||||
}
|
||||
@@ -162,6 +163,7 @@ function useUpload() {
|
||||
}
|
||||
|
||||
numberOfFiles.value = files.length;
|
||||
|
||||
if (props.multiple === true) {
|
||||
const uploadedFiles = await uploadFiles(Array.from(files), {
|
||||
onProgressChange: (percentage) => {
|
||||
|
||||
@@ -306,6 +306,7 @@ function useDragDrop() {
|
||||
|
||||
function onPointerUp() {
|
||||
dragging.value = false;
|
||||
|
||||
if (
|
||||
props.editMode === false ||
|
||||
props.draggable === false ||
|
||||
|
||||
@@ -24,6 +24,7 @@ export function useAliasFields(fields: Ref<string[]>, collection: Ref<string | n
|
||||
|
||||
for (const field of fields.value) {
|
||||
const alias = getSimpleHash(field);
|
||||
|
||||
const fullFields = adjustFieldsForDisplays([field], collection.value).map((field) => {
|
||||
if (field.includes('.')) {
|
||||
return `${alias}.${field.split('.').slice(1).join('.')}`;
|
||||
|
||||
@@ -85,6 +85,7 @@ describe('useClipboard', () => {
|
||||
value: { writeText: vi.fn().mockImplementation(() => Promise.reject()) },
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
const copyValue = 'test';
|
||||
|
||||
const wrapper = mount(testComponent, { global });
|
||||
|
||||
@@ -24,15 +24,18 @@ export function useClipboard() {
|
||||
try {
|
||||
const valueString = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
await navigator?.clipboard?.writeText(valueString);
|
||||
|
||||
notify({
|
||||
title: message?.success ?? t('copy_raw_value_success'),
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: message?.fail ?? t('copy_raw_value_fail'),
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -40,15 +43,18 @@ export function useClipboard() {
|
||||
async function pasteFromClipboard(message?: Message): Promise<string | null> {
|
||||
try {
|
||||
const pasteValue = await navigator?.clipboard?.readText();
|
||||
|
||||
notify({
|
||||
title: message?.success ?? t('paste_raw_value_success'),
|
||||
});
|
||||
|
||||
return pasteValue;
|
||||
} catch (err: any) {
|
||||
notify({
|
||||
type: 'error',
|
||||
title: message?.fail ?? t('paste_raw_value_fail'),
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export function useDialogRoute(): Ref<boolean> {
|
||||
const isOpen = ref(false);
|
||||
|
||||
let resolveGuard: () => void;
|
||||
|
||||
const leaveGuard: Promise<void> = new Promise((resolve) => {
|
||||
resolveGuard = resolve;
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ import { Field, Relation } from '@directus/types';
|
||||
|
||||
test('Returns tree list of same length', () => {
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
fieldsStore.fields = [
|
||||
{
|
||||
collection: 'a',
|
||||
@@ -82,6 +83,7 @@ test('Returns tree list of same length', () => {
|
||||
|
||||
test('Returns tree list with injected field', () => {
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
fieldsStore.fields = [
|
||||
{
|
||||
collection: 'a',
|
||||
@@ -173,6 +175,7 @@ test('Returns tree list with injected field', () => {
|
||||
|
||||
test('Returns tree list with filter', () => {
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
fieldsStore.fields = [
|
||||
{
|
||||
collection: 'a',
|
||||
@@ -221,6 +224,7 @@ test('Returns tree list with filter', () => {
|
||||
const { treeList } = useFieldTree(ref('a'), undefined, filterIntegerFields);
|
||||
|
||||
expect(unref(treeList)).toHaveLength(1);
|
||||
|
||||
expect(unref(treeList)).toEqual([
|
||||
{ name: 'ID', field: 'id', collection: 'a', relatedCollection: undefined, key: 'id', path: 'id', type: 'integer' },
|
||||
]);
|
||||
@@ -228,6 +232,7 @@ test('Returns tree list with filter', () => {
|
||||
|
||||
test('Returns tree list with group', () => {
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
fieldsStore.fields = [
|
||||
{
|
||||
collection: 'a',
|
||||
@@ -313,6 +318,7 @@ test('Returns tree list with group', () => {
|
||||
|
||||
test('Returns tree list for O2M', () => {
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
fieldsStore.fields = [
|
||||
{
|
||||
collection: 'a',
|
||||
@@ -402,6 +408,7 @@ test('Returns tree list for O2M', () => {
|
||||
] as Field[];
|
||||
|
||||
const relationsStore = useRelationsStore();
|
||||
|
||||
relationsStore.relations = [
|
||||
{
|
||||
collection: 'b',
|
||||
@@ -468,6 +475,7 @@ test('Returns tree list for O2M', () => {
|
||||
|
||||
test('Returns tree list for M2O', () => {
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
fieldsStore.fields = [
|
||||
{
|
||||
collection: 'a',
|
||||
@@ -523,6 +531,7 @@ test('Returns tree list for M2O', () => {
|
||||
] as Field[];
|
||||
|
||||
const relationsStore = useRelationsStore();
|
||||
|
||||
relationsStore.relations = [
|
||||
{
|
||||
collection: 'a',
|
||||
@@ -572,6 +581,7 @@ test('Returns tree list for M2O', () => {
|
||||
|
||||
test('Returns tree list for M2A with single related collection', () => {
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
fieldsStore.fields = [
|
||||
{
|
||||
collection: 'a',
|
||||
@@ -695,6 +705,7 @@ test('Returns tree list for M2A with single related collection', () => {
|
||||
] as Field[];
|
||||
|
||||
const relationsStore = useRelationsStore();
|
||||
|
||||
relationsStore.relations = [
|
||||
{
|
||||
collection: 'a_m2a',
|
||||
@@ -788,6 +799,7 @@ test('Returns tree list for M2A with single related collection', () => {
|
||||
|
||||
test('Returns tree list for M2A with multiple related collections', () => {
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
fieldsStore.fields = [
|
||||
{
|
||||
collection: 'a',
|
||||
@@ -939,6 +951,7 @@ test('Returns tree list for M2A with multiple related collections', () => {
|
||||
] as Field[];
|
||||
|
||||
const relationsStore = useRelationsStore();
|
||||
|
||||
relationsStore.relations = [
|
||||
{
|
||||
collection: 'a_m2a',
|
||||
|
||||
@@ -194,6 +194,7 @@ export function useItem(
|
||||
|
||||
for (const relation of relations) {
|
||||
const relatedPrimaryKeyField = fieldsStore.getPrimaryKeyFieldForCollection(relation.collection);
|
||||
|
||||
const existsJunctionRelated = relationsStore.relations.find((r) => {
|
||||
return r.collection === relation.collection && r.meta?.many_field === relation.meta?.junction_field;
|
||||
});
|
||||
@@ -296,6 +297,7 @@ export function useItem(
|
||||
[`filter[${relatedPrimaryKeyField!.field}][_in]`]: existingIds.join(','),
|
||||
},
|
||||
});
|
||||
|
||||
existingItems = response.data.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ const mockUser = {
|
||||
id: '00000000-0000-0000-0000-000000000000',
|
||||
},
|
||||
};
|
||||
|
||||
const mockReadPermissions = {
|
||||
role: '00000000-0000-0000-0000-000000000000',
|
||||
permissions: {
|
||||
@@ -49,6 +50,7 @@ const mockReadPermissions = {
|
||||
collection: 'test',
|
||||
action: 'read',
|
||||
};
|
||||
|
||||
const mockFields: Field[] = [
|
||||
{
|
||||
collection: 'test',
|
||||
@@ -116,6 +118,7 @@ describe('usePermissions', () => {
|
||||
|
||||
const { fields } = usePermissions(ref('test'), ref(null), ref(false));
|
||||
expect(fields.value.length).toBeGreaterThan(0);
|
||||
|
||||
for (const field of fields.value) {
|
||||
expect(mockReadPermissions.fields.includes(field.field)).toBe(true);
|
||||
}
|
||||
|
||||
@@ -56,11 +56,13 @@ export function usePreset(
|
||||
busy.value = true;
|
||||
|
||||
const updatedValues = await presetsStore.savePreset(preset ? preset : localPreset.value);
|
||||
|
||||
localPreset.value = {
|
||||
...localPreset.value,
|
||||
id: updatedValues.id,
|
||||
user: updatedValues.user,
|
||||
};
|
||||
|
||||
bookmarkSaved.value = true;
|
||||
busy.value = false;
|
||||
return updatedValues;
|
||||
|
||||
@@ -129,6 +129,7 @@ describe('test o2m relation', () => {
|
||||
...workerData,
|
||||
{ name: 'test5', facility: 1, $type: 'created', $index: 0 },
|
||||
]);
|
||||
|
||||
expect(wrapper.emitted()['update:value'][0]).toEqual([
|
||||
{
|
||||
create: [
|
||||
@@ -166,6 +167,7 @@ describe('test o2m relation', () => {
|
||||
...workerData,
|
||||
{ name: 'test5 edited', facility: 2, $type: 'created', $index: 0 },
|
||||
]);
|
||||
|
||||
expect(wrapper.emitted()['update:value'][1]).toEqual([
|
||||
{
|
||||
create: [
|
||||
@@ -209,6 +211,7 @@ describe('test o2m relation', () => {
|
||||
changes.splice(1, 1, { id: 2, name: 'test2-edited', facility: 1, $edits: 0, $type: 'updated', $index: 0 });
|
||||
|
||||
expect(wrapper.vm.displayItems).toEqual(changes);
|
||||
|
||||
expect(wrapper.emitted()['update:value'][0]).toEqual([
|
||||
{
|
||||
create: [],
|
||||
@@ -255,6 +258,7 @@ describe('test o2m relation', () => {
|
||||
changes.splice(0, 1, { id: 1, name: 'test', facility: 1, $type: 'deleted', $index: 0 });
|
||||
|
||||
expect(wrapper.vm.displayItems).toEqual(changes);
|
||||
|
||||
expect(wrapper.emitted()['update:value'][2]).toEqual([
|
||||
{
|
||||
create: [],
|
||||
|
||||
@@ -116,6 +116,7 @@ export function useRelationMultiple(
|
||||
const editsIndex = _value.value.update.findIndex(
|
||||
(edit) => typeof edit === 'object' && edit[targetPKField] === item[targetPKField]
|
||||
);
|
||||
|
||||
const deleteIndex = _value.value.delete.findIndex((id) => id === item[targetPKField]);
|
||||
|
||||
let updatedItem: Record<string, any> = cloneDeep(item);
|
||||
@@ -157,6 +158,7 @@ export function useRelationMultiple(
|
||||
edit[relation.value.junctionField.field][relation.value.relatedPrimaryKeyField.field] ===
|
||||
item[relation.value.junctionField.field][relation.value.relatedPrimaryKeyField.field]
|
||||
);
|
||||
|
||||
case 'm2a': {
|
||||
const itemCollection = item[relation.value.collectionField.field];
|
||||
const editCollection = edit[relation.value.collectionField.field];
|
||||
@@ -171,6 +173,7 @@ export function useRelationMultiple(
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!fetchedItem) return edit;
|
||||
return merge({}, fetchedItem, edit);
|
||||
});
|
||||
@@ -275,6 +278,7 @@ export function useRelationMultiple(
|
||||
[info.relatedPrimaryKeyField.field]: item,
|
||||
},
|
||||
};
|
||||
|
||||
case 'm2a': {
|
||||
if (!collection) throw new Error('You need to provide a collection on an m2a');
|
||||
|
||||
@@ -315,6 +319,7 @@ export function useRelationMultiple(
|
||||
targetCollection = relation.value.junctionCollection.collection;
|
||||
fields.add(relation.value.junctionPrimaryKeyField.field);
|
||||
fields.add(relation.value.collectionField.field);
|
||||
|
||||
for (const collection of relation.value.allowedCollections) {
|
||||
const pkField = relation.value.relationPrimaryKeyFields[collection.collection];
|
||||
fields.add(`${relation.value.junctionField.field}:${collection.collection}.${pkField.field}`);
|
||||
@@ -466,6 +471,7 @@ export function useRelationMultiple(
|
||||
return item[relation.value.relatedPrimaryKeyField.field];
|
||||
case 'm2m':
|
||||
return item[relation.value.junctionField.field][relation.value.relatedPrimaryKeyField.field];
|
||||
|
||||
case 'm2a': {
|
||||
const collection = item[relation.value.collectionField.field];
|
||||
return item[relation.value.junctionPrimaryKeyField.field][
|
||||
@@ -533,6 +539,7 @@ export function useRelationMultiple(
|
||||
return acc;
|
||||
}, [])
|
||||
);
|
||||
|
||||
fields.add(relation.relatedPrimaryKeyField.field);
|
||||
|
||||
const relatedPKField = relation.relatedPrimaryKeyField.field;
|
||||
@@ -606,6 +613,7 @@ export function useRelationMultiple(
|
||||
[relation.junctionField.field]: item,
|
||||
}))
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, [] as Record<string, any>[]);
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ export function useTranslationStrings(): UsableTranslationStrings {
|
||||
try {
|
||||
const settingsStore = useSettingsStore();
|
||||
await settingsStore.updateSettings({ translation_strings: payload }, false);
|
||||
|
||||
if (settingsStore.settings?.translation_strings) {
|
||||
translationStrings.value = settingsStore.settings.translation_strings.map((p: TranslationStringRaw) => ({
|
||||
key: p.key,
|
||||
@@ -133,12 +134,14 @@ export function useTranslationStrings(): UsableTranslationStrings {
|
||||
|
||||
function mergeTranslationStringsForLanguage(lang: Language) {
|
||||
if (!translationStrings?.value) return;
|
||||
|
||||
const localeMessages: Record<string, any> = translationStrings.value.reduce((acc, cur) => {
|
||||
if (!cur.key || !cur.translations) return acc;
|
||||
const translationForCurrentLang = cur.translations.find((t) => t.language === lang);
|
||||
if (!translationForCurrentLang || !translationForCurrentLang.translation) return acc;
|
||||
return { ...acc, [cur.key]: getLiteralInterpolatedTranslation(translationForCurrentLang.translation, true) };
|
||||
}, {});
|
||||
|
||||
i18n.global.mergeLocaleMessage(lang, localeMessages);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ export function processValue(bindingValue: BindingValue['value']): DirectiveConf
|
||||
|
||||
if (isFunction) {
|
||||
const binding = bindingValue as Handler;
|
||||
|
||||
value = {
|
||||
handler: binding,
|
||||
middleware: () => true,
|
||||
|
||||
@@ -51,6 +51,7 @@ export function createEnterHandler(element: HTMLElement, binding: DirectiveBindi
|
||||
updateTooltip(element, binding, tooltip);
|
||||
} else {
|
||||
clearTimeout(tooltipTimer);
|
||||
|
||||
tooltipTimer = window.setTimeout(() => {
|
||||
animateIn(tooltip);
|
||||
updateTooltip(element, binding, tooltip);
|
||||
|
||||
@@ -51,6 +51,7 @@ export default defineDisplay({
|
||||
},
|
||||
options: ({ field }) => {
|
||||
const options = field.meta?.display_options || {};
|
||||
|
||||
const fields = [
|
||||
{
|
||||
field: 'relative',
|
||||
|
||||
@@ -41,6 +41,7 @@ export default defineComponent({
|
||||
},
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const displayValue = computed(() => {
|
||||
if (!props.value) return null;
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ export default defineComponent({
|
||||
const collectionExists = !!collectionsStore.collections.find(
|
||||
(collection) => collection.collection === collectionName
|
||||
);
|
||||
|
||||
if (collectionExists === false) return null;
|
||||
return collectionName;
|
||||
});
|
||||
|
||||
@@ -95,6 +95,7 @@ const displayTemplate = computed(() => {
|
||||
|
||||
return displayTemplate || `{{ ${primaryKey.value || ''} }}`;
|
||||
});
|
||||
|
||||
const requiredFields = computed(() => {
|
||||
if (!displayTemplate.value || !unref(selectedCollection)) return [];
|
||||
return adjustFieldsForDisplays(getFieldsFromTemplate(displayTemplate.value), unref(selectedCollection));
|
||||
|
||||
@@ -171,6 +171,7 @@ const editImageEditor = ref(false);
|
||||
|
||||
async function imageErrorHandler() {
|
||||
if (!src.value) return;
|
||||
|
||||
try {
|
||||
await api.get(src.value);
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -121,9 +121,11 @@ export default defineComponent({
|
||||
(newVal, oldVal) => {
|
||||
if (!props.validationErrors) return;
|
||||
if (isEqual(newVal, oldVal)) return;
|
||||
|
||||
const includedFieldsWithErrors = props.validationErrors.filter((validationError) =>
|
||||
groupFields.value.find((rootField) => rootField.field === validationError.field)
|
||||
);
|
||||
|
||||
if (includedFieldsWithErrors.length > 0) selection.value = [includedFieldsWithErrors[0].field];
|
||||
}
|
||||
);
|
||||
@@ -154,6 +156,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.values,
|
||||
(newVal) => {
|
||||
|
||||
@@ -166,6 +166,7 @@ export default defineComponent({
|
||||
|
||||
parser.parseError = (str: string, hash: any) => {
|
||||
const loc = hash.loc;
|
||||
|
||||
found.push({
|
||||
from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
|
||||
to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),
|
||||
@@ -296,6 +297,7 @@ export default defineComponent({
|
||||
async (altOptions) => {
|
||||
if (!altOptions || altOptions.size === 0) return;
|
||||
await getImports(altOptions);
|
||||
|
||||
for (const key in altOptions) {
|
||||
codemirror?.setOption(key as any, altOptions[key]);
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ export default defineComponent({
|
||||
if (props.softLength) return 100 - (props.value.length / +props.softLength) * 100;
|
||||
return 100;
|
||||
});
|
||||
|
||||
return {
|
||||
charsRemaining,
|
||||
percentageRemaining,
|
||||
|
||||
@@ -492,9 +492,11 @@ export default defineComponent({
|
||||
|
||||
editor.on('init', function () {
|
||||
editor.shortcuts.remove('meta+k');
|
||||
|
||||
editor.addShortcut('meta+k', 'Insert Link', () => {
|
||||
editor.ui.registry.getAll().buttons.customlink.onAction();
|
||||
});
|
||||
|
||||
setCount();
|
||||
});
|
||||
|
||||
|
||||
@@ -26,12 +26,14 @@ type UsableLink = {
|
||||
|
||||
export default function useLink(editor: Ref<any>): UsableLink {
|
||||
const linkDrawerOpen = ref(false);
|
||||
|
||||
const defaultLinkSelection = {
|
||||
url: null,
|
||||
displayText: null,
|
||||
title: null,
|
||||
newTab: true,
|
||||
};
|
||||
|
||||
const linkSelection = ref<LinkSelection>(defaultLinkSelection);
|
||||
const linkNode = ref<HTMLLinkElement | null>(null);
|
||||
const currentSelectionNode = ref<HTMLElement | null>(null);
|
||||
|
||||
@@ -80,6 +80,7 @@ export default function useMedia(editor: Ref<any>, imageToken: Ref<string | unde
|
||||
...mediaSelection.value,
|
||||
sourceUrl: newSource,
|
||||
};
|
||||
|
||||
mediaSelection.value.previewUrl = replaceUrlAccessToken(newSource, imageToken.value || getToken());
|
||||
},
|
||||
});
|
||||
|
||||
@@ -288,6 +288,7 @@ function sortItems(items: DisplayItem[]) {
|
||||
|
||||
return changes;
|
||||
});
|
||||
|
||||
update(...sortedItems);
|
||||
}
|
||||
|
||||
@@ -303,10 +304,12 @@ function createItem(collection: string) {
|
||||
|
||||
currentlyEditing.value = null;
|
||||
relatedPrimaryKey.value = null;
|
||||
|
||||
editsAtStart.value = {
|
||||
[relationInfo.value.collectionField.field]: collection,
|
||||
[relationInfo.value.junctionField.field]: {},
|
||||
};
|
||||
|
||||
newItem = true;
|
||||
editModalActive.value = true;
|
||||
}
|
||||
@@ -316,10 +319,12 @@ function editItem(item: DisplayItem) {
|
||||
|
||||
const relationPkField =
|
||||
relationInfo.value.relationPrimaryKeyFields[item[relationInfo.value.collectionField.field]].field;
|
||||
|
||||
const junctionField = relationInfo.value.junctionField.field;
|
||||
const junctionPkField = relationInfo.value.junctionPrimaryKeyField.field;
|
||||
|
||||
newItem = false;
|
||||
|
||||
editsAtStart.value = {
|
||||
...getItemEdits(item),
|
||||
[relationInfo.value.collectionField.field]: item[relationInfo.value.collectionField.field],
|
||||
|
||||
@@ -387,6 +387,7 @@ watch(
|
||||
const junctionCollection = relationInfo.value.junctionCollection.collection;
|
||||
|
||||
const contentWidth: Record<string, number> = {};
|
||||
|
||||
(displayItems.value ?? []).forEach((item: Record<string, any>) => {
|
||||
props.fields.forEach((key) => {
|
||||
if (!contentWidth[key]) {
|
||||
@@ -543,6 +544,7 @@ function deleteItem(item: DisplayItem) {
|
||||
}
|
||||
|
||||
const values = inject('values', ref<Record<string, any>>({}));
|
||||
|
||||
const customFilter = computed(() => {
|
||||
const filter: Filter = {
|
||||
_and: [],
|
||||
@@ -593,6 +595,7 @@ function getLinkForItem(item: DisplayItem) {
|
||||
relationInfo.value.junctionField.field,
|
||||
relationInfo.value.relatedPrimaryKeyField.field,
|
||||
]);
|
||||
|
||||
return `/content/${relationInfo.value.relatedCollection.collection}/${encodeURIComponent(primaryKey)}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -228,14 +228,17 @@ function change(event: ChangeEvent) {
|
||||
case 'created':
|
||||
create(cleanItem(event.added.element));
|
||||
break;
|
||||
|
||||
case 'updated': {
|
||||
const pkField = relationInfo.value.relatedPrimaryKeyField.field;
|
||||
const exists = displayItems.value.find((item) => item[pkField] === event.added.element[pkField]);
|
||||
|
||||
// We have to make sure we remove the reverseJunctionField when we move it back to its initial position as otherwise it will be selected.
|
||||
update({
|
||||
...cleanItem(event.added.element),
|
||||
[relationInfo.value.reverseJunctionField.field]: exists ? undefined : primaryKey.value,
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -362,6 +362,7 @@ watch(
|
||||
const relatedCollection = relationInfo.value.relatedCollection.collection;
|
||||
|
||||
const contentWidth: Record<string, number> = {};
|
||||
|
||||
(displayItems.value ?? []).forEach((item: Record<string, any>) => {
|
||||
props.fields.forEach((key) => {
|
||||
if (!contentWidth[key]) {
|
||||
@@ -505,6 +506,7 @@ function deleteItem(item: DisplayItem) {
|
||||
}
|
||||
|
||||
const values = inject('values', ref<Record<string, any>>({}));
|
||||
|
||||
const customFilter = computed(() => {
|
||||
const filter: Filter = {
|
||||
_and: [],
|
||||
|
||||
@@ -153,6 +153,7 @@ export default defineComponent({
|
||||
const basemaps = getBasemapSources();
|
||||
const appStore = useAppStore();
|
||||
const { basemap } = toRefs(appStore);
|
||||
|
||||
const style = computed(() => {
|
||||
const source = basemaps.find((source) => source.name == basemap.value) ?? basemaps[0];
|
||||
return basemap.value, getStyleFromBasemapSource(source);
|
||||
@@ -258,11 +259,14 @@ export default defineComponent({
|
||||
...props.defaultView,
|
||||
...(mapboxKey ? { accessToken: mapboxKey } : {}),
|
||||
});
|
||||
|
||||
if (controls.geocoder) {
|
||||
map.addControl(controls.geocoder as any, 'top-right');
|
||||
|
||||
controls.geocoder.on('result', (event: any) => {
|
||||
location.value = event.result.center;
|
||||
});
|
||||
|
||||
controls.geocoder.on('clear', () => {
|
||||
location.value = null;
|
||||
});
|
||||
@@ -272,6 +276,7 @@ export default defineComponent({
|
||||
const { longitude, latitude } = event.coords;
|
||||
location.value = [longitude, latitude];
|
||||
});
|
||||
|
||||
map.addControl(controls.attribution, 'bottom-left');
|
||||
map.addControl(controls.navigation, 'top-left');
|
||||
map.addControl(controls.geolocate, 'top-left');
|
||||
@@ -287,6 +292,7 @@ export default defineComponent({
|
||||
map.on('draw.modechange', handleDrawModeChange);
|
||||
map.on('draw.selectionchange', handleSelectionChange);
|
||||
map.on('move', updateProjection);
|
||||
|
||||
for (const layer of activeLayers) {
|
||||
map.on('mousedown', layer, hideTooltip);
|
||||
map.on('mousemove', layer, updateTooltip);
|
||||
@@ -310,6 +316,7 @@ export default defineComponent({
|
||||
if (!value) {
|
||||
controls.draw.deleteAll();
|
||||
currentGeometry = null;
|
||||
|
||||
if (geometryType) {
|
||||
const snaked = snakeCase(geometryType.replace('Multi', ''));
|
||||
const mode = `draw_${snaked}` as any;
|
||||
@@ -343,6 +350,7 @@ export default defineComponent({
|
||||
function fitDataBounds(options: CameraOptions & AnimationOptions) {
|
||||
if (map && currentGeometry) {
|
||||
const bbox = getBBox(currentGeometry);
|
||||
|
||||
map.fitBounds(bbox as LngLatBoundsLike, {
|
||||
padding: 80,
|
||||
maxZoom: 8,
|
||||
@@ -445,6 +453,7 @@ export default defineComponent({
|
||||
const coordinates = geometries
|
||||
.filter(({ type }) => `Multi${type}` == geometryType)
|
||||
.map(({ coordinates }) => coordinates);
|
||||
|
||||
result = { type: geometryType, coordinates } as Geometry;
|
||||
} else {
|
||||
result = geometries[geometries.length - 1];
|
||||
@@ -467,6 +476,7 @@ export default defineComponent({
|
||||
|
||||
function handleDrawUpdate() {
|
||||
currentGeometry = getCurrentGeometry();
|
||||
|
||||
if (!currentGeometry) {
|
||||
controls.draw.deleteAll();
|
||||
emit('input', null);
|
||||
|
||||
@@ -78,6 +78,7 @@ export default defineComponent({
|
||||
const basemaps = getBasemapSources();
|
||||
const appStore = useAppStore();
|
||||
const { basemap } = toRefs(appStore);
|
||||
|
||||
const style = computed(() => {
|
||||
const source = basemaps.find((source) => source.name == basemap.value) ?? basemaps[0];
|
||||
return getStyleFromBasemapSource(source);
|
||||
@@ -90,6 +91,7 @@ export default defineComponent({
|
||||
...(defaultView.value || {}),
|
||||
...(mapboxKey ? { accessToken: mapboxKey } : {}),
|
||||
});
|
||||
|
||||
map.on('moveend', () => {
|
||||
defaultView.value = {
|
||||
center: map.getCenter(),
|
||||
@@ -99,6 +101,7 @@ export default defineComponent({
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
map.remove();
|
||||
});
|
||||
|
||||
@@ -286,6 +286,7 @@ function useColor() {
|
||||
let alpha = Math.round(255 * color.value.alpha())
|
||||
.toString(16)
|
||||
.toUpperCase();
|
||||
|
||||
alpha = alpha.padStart(2, '0');
|
||||
return color.value.rgb().array().length === 4 ? `${color.value.hex()}${alpha}` : color.value.hex();
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@ const customFilter = computed(() => {
|
||||
const { t } = useI18n();
|
||||
const { collection, field } = toRefs(props);
|
||||
const { relationInfo } = useRelationM2O(collection, field);
|
||||
|
||||
const value = computed({
|
||||
get: () => props.value ?? null,
|
||||
set: (value) => {
|
||||
|
||||
@@ -161,12 +161,14 @@ const firstItem = computed(() => {
|
||||
|
||||
return getItemEdits(item);
|
||||
});
|
||||
|
||||
const secondItem = computed(() => {
|
||||
const item = getItemWithLang(displayItems.value, secondLang.value);
|
||||
if (item === undefined) return undefined;
|
||||
|
||||
return getItemEdits(item);
|
||||
});
|
||||
|
||||
const firstItemInitial = computed(() => getItemWithLang(fetchedItems.value, firstLang.value));
|
||||
const secondItemInitial = computed(() => getItemWithLang(fetchedItems.value, secondLang.value));
|
||||
|
||||
|
||||
@@ -85,14 +85,17 @@ export default defineLayout<LayoutOptions>({
|
||||
const viewInfo = syncRefProperty(layoutOptions, 'viewInfo', undefined);
|
||||
|
||||
const startDateField = syncRefProperty(layoutOptions, 'startDateField', undefined);
|
||||
|
||||
const startDateFieldInfo = computed(() => {
|
||||
return fieldsInCollection.value.find((field: Field) => field.field === startDateField.value);
|
||||
});
|
||||
|
||||
const endDateField = syncRefProperty(layoutOptions, 'endDateField', undefined);
|
||||
|
||||
const endDateFieldInfo = computed(() => {
|
||||
return fieldsInCollection.value.find((field: Field) => field.field === endDateField.value);
|
||||
});
|
||||
|
||||
const firstDay = syncRefProperty(layoutOptions, 'firstDay', undefined);
|
||||
|
||||
const queryFields = computed(() => {
|
||||
|
||||
@@ -73,6 +73,7 @@ export default defineComponent({
|
||||
const firstDayWritable = useSync(props, 'firstDay', emit);
|
||||
|
||||
const firstDayOfWeekForDate = startOfWeek(new Date());
|
||||
|
||||
const firstDayOptions: { text: string; value: number }[] = [...Array(7).keys()].map((_, i) => ({
|
||||
text: localizedFormat(add(firstDayOfWeekForDate, { days: i }), 'EEEE'),
|
||||
value: i,
|
||||
|
||||
@@ -116,9 +116,11 @@ export default defineComponent({
|
||||
|
||||
return { source, fileType };
|
||||
});
|
||||
|
||||
const showThumbnail = computed(() => {
|
||||
return imageInfo.value && imageInfo.value.fileType;
|
||||
});
|
||||
|
||||
const selectionIcon = computed(() => {
|
||||
if (!props.item) return 'radio_button_unchecked';
|
||||
|
||||
|
||||
@@ -82,29 +82,36 @@ export default defineComponent({
|
||||
const { sidebarOpen, basemap } = toRefs(appStore);
|
||||
const mapboxKey = settingsStore.settings?.mapbox_key;
|
||||
const basemaps = getBasemapSources();
|
||||
|
||||
const style = computed(() => {
|
||||
const source = basemaps.find((source) => source.name === basemap.value) ?? basemaps[0];
|
||||
return getStyleFromBasemapSource(source);
|
||||
});
|
||||
|
||||
const attributionControl = new AttributionControl();
|
||||
|
||||
const navigationControl = new NavigationControl({
|
||||
showCompass: false,
|
||||
});
|
||||
|
||||
const geolocateControl = new GeolocateControl();
|
||||
|
||||
const fitDataControl = new ButtonControl('mapboxgl-ctrl-fitdata', () => {
|
||||
emit('fitdata');
|
||||
});
|
||||
|
||||
const boxSelectControl = new BoxSelectControl({
|
||||
boxElementClass: 'map-selection-box',
|
||||
selectButtonClass: 'mapboxgl-ctrl-select',
|
||||
layers: ['__directus_polygons', '__directus_points', '__directus_lines'],
|
||||
});
|
||||
|
||||
let geocoderControl: MapboxGeocoder | undefined;
|
||||
|
||||
if (mapboxKey) {
|
||||
const marker = document.createElement('div');
|
||||
marker.className = 'mapboxgl-user-location-dot mapboxgl-search-location-dot';
|
||||
|
||||
geocoderControl = new MapboxGeocoder({
|
||||
accessToken: mapboxKey,
|
||||
collapsed: true,
|
||||
@@ -118,6 +125,7 @@ export default defineComponent({
|
||||
onMounted(() => {
|
||||
setupMap();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
map.remove();
|
||||
});
|
||||
@@ -161,10 +169,12 @@ export default defineComponent({
|
||||
map.on('mouseleave', '__directus_clusters', hoverCluster);
|
||||
map.on('select.enable', () => (selectMode.value = true));
|
||||
map.on('select.disable', () => (selectMode.value = false));
|
||||
|
||||
map.on('select.end', (event: MapLayerMouseEvent) => {
|
||||
const ids = event.features?.map((f) => f.id);
|
||||
emit('featureselect', { ids, replace: !event.alt });
|
||||
});
|
||||
|
||||
map.on('moveend', () => {
|
||||
emit('moveend', {
|
||||
center: map.getCenter(),
|
||||
@@ -174,6 +184,7 @@ export default defineComponent({
|
||||
bbox: map.getBounds().toArray().flat(),
|
||||
});
|
||||
});
|
||||
|
||||
startWatchers();
|
||||
});
|
||||
|
||||
@@ -183,6 +194,7 @@ export default defineComponent({
|
||||
if (!opened) setTimeout(() => map.resize(), 300);
|
||||
}
|
||||
);
|
||||
|
||||
setTimeout(() => map.resize(), 300);
|
||||
}
|
||||
|
||||
@@ -240,6 +252,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
map.addSource('__directus', { ...newSource, data: props.data });
|
||||
|
||||
map.once('sourcedata', () => {
|
||||
setTimeout(() => props.layers.forEach((layer) => map.addLayer(layer)));
|
||||
});
|
||||
@@ -247,9 +260,11 @@ export default defineComponent({
|
||||
|
||||
function updateLayers(newLayers?: AnyLayer[], previousLayers?: AnyLayer[]) {
|
||||
const currentMapLayersId = new Set(map.getStyle().layers?.map(({ id }) => id));
|
||||
|
||||
previousLayers?.forEach((layer) => {
|
||||
if (currentMapLayersId.has(layer.id)) map.removeLayer(layer.id);
|
||||
});
|
||||
|
||||
newLayers?.forEach((layer) => {
|
||||
map.addLayer(layer);
|
||||
});
|
||||
@@ -260,6 +275,7 @@ export default defineComponent({
|
||||
map.setFeatureState({ id, source: '__directus' }, { selected: false });
|
||||
map.removeFeatureState({ id, source: '__directus' });
|
||||
});
|
||||
|
||||
newSelection?.forEach((id) => {
|
||||
map.setFeatureState({ id, source: '__directus' }, { selected: true });
|
||||
});
|
||||
@@ -324,10 +340,13 @@ export default defineComponent({
|
||||
const features = map.queryRenderedFeatures(event.point, {
|
||||
layers: ['__directus_clusters'],
|
||||
});
|
||||
|
||||
const clusterId = features[0]?.properties?.cluster_id;
|
||||
const source = map.getSource('__directus') as GeoJSONSource;
|
||||
|
||||
source.getClusterExpansionZoom(clusterId, (err: any, zoom: number) => {
|
||||
if (err) return;
|
||||
|
||||
map.flyTo({
|
||||
center: (features[0].geometry as GeoJSON.Point).coordinates as LngLatLike,
|
||||
zoom: zoom,
|
||||
|
||||
@@ -134,6 +134,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
|
||||
function fitDataBounds() {
|
||||
shouldUpdateCamera.value = true;
|
||||
|
||||
if (isGeometryFieldNative.value) {
|
||||
return;
|
||||
}
|
||||
@@ -180,6 +181,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
geojsonError.value = null;
|
||||
geojson.value = toGeoJSON(items.value, geometryOptions.value);
|
||||
geojsonLoading.value = false;
|
||||
|
||||
if (!cameraOptions.value || shouldUpdateCamera.value) {
|
||||
geojsonBounds.value = geojson.value.bbox;
|
||||
}
|
||||
|
||||
@@ -157,6 +157,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
const limit = syncRefProperty(layoutQuery, 'limit', 25);
|
||||
const defaultSort = computed(() => (primaryKeyField.value ? [primaryKeyField.value?.field] : []));
|
||||
const sort = syncRefProperty(layoutQuery, 'sort', defaultSort);
|
||||
|
||||
const fieldsDefaultValue = computed(() => {
|
||||
return fieldsInCollection.value
|
||||
.filter((field: Field) => !field.meta?.hidden)
|
||||
|
||||
@@ -259,6 +259,7 @@ useShortcut(
|
||||
},
|
||||
table
|
||||
);
|
||||
|
||||
const permissionsStore = usePermissionsStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ init();
|
||||
|
||||
async function init() {
|
||||
console.log(DIRECTUS_LOGO);
|
||||
|
||||
console.info(
|
||||
`Hey! Interested in helping build this open-source data management platform?\nIf so, join our growing team of contributors at: https://directus.chat`
|
||||
);
|
||||
|
||||
@@ -121,6 +121,7 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
item.value = response.data.data;
|
||||
|
||||
if (item.value) {
|
||||
if (te(`field_options.directus_activity.${item.value.action}`))
|
||||
item.value.action_translated = t(`field_options.directus_activity.${item.value.action}`);
|
||||
|
||||
@@ -126,11 +126,13 @@ const name = computed(() => translate(props.bookmark.bookmark));
|
||||
|
||||
function useEditBookmark() {
|
||||
const editActive = ref(false);
|
||||
|
||||
const editValue = reactive({
|
||||
name: props.bookmark.bookmark,
|
||||
icon: props.bookmark?.icon ?? 'bookmark',
|
||||
color: props.bookmark?.color ?? null,
|
||||
});
|
||||
|
||||
const editSaving = ref(false);
|
||||
|
||||
return { editActive, editValue, editSave, editSaving, editCancel };
|
||||
|
||||
@@ -73,6 +73,7 @@ export default defineComponent({
|
||||
|
||||
const dense = computed(() => collectionsStore.visibleCollections.length > 5);
|
||||
const showSearch = computed(() => collectionsStore.visibleCollections.length > 20);
|
||||
|
||||
const hasHiddenCollections = computed(
|
||||
() => collectionsStore.allCollections.length > collectionsStore.visibleCollections.length
|
||||
);
|
||||
|
||||
@@ -604,6 +604,7 @@ export default defineComponent({
|
||||
icon: bookmark.icon,
|
||||
color: bookmark.color,
|
||||
});
|
||||
|
||||
router.push(`/content/${newBookmark.collection}?bookmark=${newBookmark.id}`);
|
||||
|
||||
bookmarkDialogActive.value = false;
|
||||
@@ -628,6 +629,7 @@ export default defineComponent({
|
||||
const updatePermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'update' && permission.collection === collection.value
|
||||
);
|
||||
|
||||
return !!updatePermissions;
|
||||
});
|
||||
|
||||
@@ -639,6 +641,7 @@ export default defineComponent({
|
||||
const updatePermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'update' && permission.collection === collection.value
|
||||
);
|
||||
|
||||
if (!updatePermissions) return false;
|
||||
if (!updatePermissions.fields) return false;
|
||||
if (updatePermissions.fields.includes('*')) return true;
|
||||
@@ -652,6 +655,7 @@ export default defineComponent({
|
||||
const deletePermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'delete' && permission.collection === collection.value
|
||||
);
|
||||
|
||||
return !!deletePermissions;
|
||||
});
|
||||
|
||||
@@ -662,6 +666,7 @@ export default defineComponent({
|
||||
const createPermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'create' && permission.collection === collection.value
|
||||
);
|
||||
|
||||
return !!createPermissions;
|
||||
});
|
||||
|
||||
|
||||
@@ -286,6 +286,7 @@ export default defineComponent({
|
||||
if (!props.file) return null;
|
||||
if (!props.file.folder) return;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/folders/${props.file.folder}`, {
|
||||
params: {
|
||||
|
||||
@@ -76,6 +76,7 @@ export default defineModule({
|
||||
const permission = permissions.find(
|
||||
(permission) => permission.collection === 'directus_files' && permission.action === 'read'
|
||||
);
|
||||
|
||||
return !!permission;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -317,6 +317,7 @@ export default defineComponent({
|
||||
onBeforeRouteLeave(() => {
|
||||
selection.value = [];
|
||||
});
|
||||
|
||||
onBeforeRouteUpdate(() => {
|
||||
selection.value = [];
|
||||
});
|
||||
@@ -494,6 +495,7 @@ export default defineComponent({
|
||||
const updatePermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'update' && permission.collection === 'directus_files'
|
||||
);
|
||||
|
||||
return !!updatePermissions;
|
||||
});
|
||||
|
||||
@@ -504,6 +506,7 @@ export default defineComponent({
|
||||
const deletePermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'delete' && permission.collection === 'directus_files'
|
||||
);
|
||||
|
||||
return !!deletePermissions;
|
||||
});
|
||||
|
||||
@@ -514,6 +517,7 @@ export default defineComponent({
|
||||
const createPermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'create' && permission.collection === 'directus_files'
|
||||
);
|
||||
|
||||
return !!createPermissions;
|
||||
});
|
||||
|
||||
@@ -524,6 +528,7 @@ export default defineComponent({
|
||||
const createPermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'create' && permission.collection === 'directus_folders'
|
||||
);
|
||||
|
||||
return !!createPermissions;
|
||||
});
|
||||
|
||||
|
||||
@@ -244,6 +244,7 @@ const { confirmLeave, leaveTo } = useEditsGuard(hasEdits);
|
||||
|
||||
const confirmDelete = ref(false);
|
||||
const editActive = ref(false);
|
||||
|
||||
const fileSrc = computed(() => {
|
||||
if (item.value && item.value.modified_on) {
|
||||
return `assets/${props.primaryKey}?cache-buster=${item.value.modified_on}&key=system-large-contain`;
|
||||
|
||||
@@ -165,8 +165,10 @@ function generateFields(updates: StateUpdates, state: State, { getCurrent }: Hel
|
||||
const junctionCurrent = getCurrent('relations.o2m.field');
|
||||
const junctionRelated = getCurrent('relations.m2o.field');
|
||||
const relatedCollection = getCurrent('relations.m2o.related_collection');
|
||||
|
||||
const relatedPrimaryKeyField =
|
||||
fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection) ?? getCurrent('collections.related.fields[0]');
|
||||
|
||||
const sort = getCurrent('relations.o2m.meta.sort_field');
|
||||
|
||||
if (junctionCollection && junctionCurrent && fieldExists(junctionCollection, junctionCurrent) === false) {
|
||||
@@ -218,6 +220,7 @@ export function setDefaults(updates: StateUpdates, state: State, { getCurrent }:
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const currentCollection = state.collection!;
|
||||
|
||||
const currentCollectionPrimaryKeyField =
|
||||
fieldsStore.getPrimaryKeyFieldForCollection(currentCollection)?.field ?? 'id';
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ export function setDefaults(updates: StateUpdates, state: State, { getCurrent }:
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const currentCollection = state.collection!;
|
||||
|
||||
const currentCollectionPrimaryKeyField =
|
||||
fieldsStore.getPrimaryKeyFieldForCollection(currentCollection)?.field ?? 'id';
|
||||
|
||||
@@ -105,11 +106,13 @@ export function setDefaults(updates: StateUpdates, state: State, { getCurrent }:
|
||||
set(updates, 'relations.o2m.field', `${currentCollection}_${currentCollectionPrimaryKeyField}`);
|
||||
set(updates, 'relations.m2o.collection', junctionName);
|
||||
set(updates, 'relations.m2o.field', 'item');
|
||||
|
||||
set(
|
||||
updates,
|
||||
'relations.m2o.meta.one_allowed_collections',
|
||||
getCurrent('relations.m2o.meta.one_allowed_collections') ?? []
|
||||
);
|
||||
|
||||
set(updates, 'relations.m2o.meta.one_collection_field', 'collection');
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ export function autoGenerateJunctionFields(updates: StateUpdates, state: State,
|
||||
|
||||
const currentCollection = state.collection!;
|
||||
const currentPrimaryKeyField = fieldsStore.getPrimaryKeyFieldForCollection(currentCollection)?.field ?? 'id';
|
||||
|
||||
const relatedCollection =
|
||||
updates.relations?.m2o?.related_collection ?? getCurrent('relations.m2o.related_collection');
|
||||
|
||||
@@ -251,8 +252,10 @@ function generateFields(updates: StateUpdates, state: State, { getCurrent }: Hel
|
||||
const junctionRelated = getCurrent('relations.m2o.field');
|
||||
const sort = getCurrent('relations.o2m.meta.sort_field');
|
||||
const relatedCollection = getCurrent('relations.m2o.related_collection');
|
||||
|
||||
const relatedPrimaryKeyField =
|
||||
fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection) ?? getCurrent('collections.related.fields[0]');
|
||||
|
||||
const existsJunctionRelated = relationsStore.relations.find(
|
||||
(relation) => relation.collection === junctionCollection && relation.field === junctionRelated
|
||||
);
|
||||
|
||||
@@ -118,6 +118,7 @@ export function updateJunctionRelated(updates: StateUpdates, _state: State, { ge
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const relatedCollection = getCurrent('relations.m2o.related_collection');
|
||||
|
||||
const relatedCollectionPrimaryKeyField =
|
||||
fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection)?.field ?? 'id';
|
||||
|
||||
@@ -289,8 +290,10 @@ function generateFields(updates: StateUpdates, state: State, { getCurrent }: Hel
|
||||
const junctionCurrent = getCurrent('relations.o2m.field');
|
||||
const junctionRelated = getCurrent('relations.m2o.field');
|
||||
const relatedCollection = getCurrent('relations.m2o.related_collection');
|
||||
|
||||
const relatedPrimaryKeyField =
|
||||
fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection) ?? getCurrent('collections.related.fields[0]');
|
||||
|
||||
const sort = getCurrent('relations.o2m.sort_field');
|
||||
|
||||
if (junctionCollection && junctionCurrent && fieldExists(junctionCollection, junctionCurrent) === false) {
|
||||
@@ -342,6 +345,7 @@ export function setDefaults(updates: StateUpdates, state: State, { getCurrent }:
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const currentCollection = state.collection!;
|
||||
|
||||
const currentCollectionPrimaryKeyField =
|
||||
fieldsStore.getPrimaryKeyFieldForCollection(currentCollection)?.field ?? 'id';
|
||||
|
||||
@@ -353,6 +357,7 @@ export function setDefaults(updates: StateUpdates, state: State, { getCurrent }:
|
||||
set(updates, 'relations.m2o.related_collection', getCurrent('relations.m2o.related_collection') ?? 'languages');
|
||||
|
||||
const languagesCollection = getCurrent('relations.m2o.related_collection');
|
||||
|
||||
const languagesCollectionPrimaryKeyField =
|
||||
fieldsStore.getPrimaryKeyFieldForCollection(languagesCollection)?.field ?? 'id';
|
||||
|
||||
|
||||
@@ -36,17 +36,20 @@ describe('Actions', () => {
|
||||
describe('startEditing', () => {
|
||||
it('New Field', () => {
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
|
||||
const testValue: { collection: string; field: string; localType: 'presentation' } = {
|
||||
collection: 'collection_a',
|
||||
field: '+',
|
||||
localType: 'presentation',
|
||||
};
|
||||
|
||||
fieldDetailStore.startEditing(testValue.collection, testValue.field, testValue.localType);
|
||||
expect(fieldDetailStore.collection).toEqual(testValue.collection);
|
||||
expect(fieldDetailStore.field.collection).toEqual(testValue.collection);
|
||||
expect(fieldDetailStore.editing).toEqual(testValue.field);
|
||||
expect(fieldDetailStore.localType).toEqual(testValue.localType);
|
||||
});
|
||||
|
||||
it('Existing Field — M2O', () => {
|
||||
const mockedField = {
|
||||
collection: 'collection_a',
|
||||
@@ -58,6 +61,7 @@ describe('Actions', () => {
|
||||
},
|
||||
name: 'Collection A Field',
|
||||
};
|
||||
|
||||
const fieldsStore = useFieldsStore();
|
||||
(fieldsStore.getField as Mock).mockReturnValue(mockedField);
|
||||
|
||||
@@ -79,6 +83,7 @@ describe('Actions', () => {
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const relationsStore = useRelationsStore();
|
||||
(relationsStore.getRelationsForField as Mock).mockReturnValue(mockedRelations);
|
||||
|
||||
@@ -90,6 +95,7 @@ describe('Actions', () => {
|
||||
expect(fieldDetailStore.field.name).toEqual(mockedField.name);
|
||||
expect(fieldDetailStore.localType).toEqual('m2o');
|
||||
expect(fieldDetailStore.relations.o2m).toEqual(undefined);
|
||||
|
||||
expect(fieldDetailStore.relations.m2o).toEqual(
|
||||
mockedRelations.find(
|
||||
(relation) => relation.collection === mockedField.collection && relation.field === mockedField.field
|
||||
@@ -97,6 +103,7 @@ describe('Actions', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it.todo('Existing Field — M2M');
|
||||
});
|
||||
|
||||
@@ -104,18 +111,22 @@ describe('Alterations', () => {
|
||||
describe('files', () => {
|
||||
it('autoGenerateJunctionRelation has changed', () => {
|
||||
const fieldDetailStore = useFieldDetailStore();
|
||||
|
||||
const testValue: { collection: string; field: string; localType: 'files' } = {
|
||||
collection: 'collection_a',
|
||||
field: '+',
|
||||
localType: 'files',
|
||||
};
|
||||
|
||||
fieldDetailStore.startEditing(testValue.collection, testValue.field, testValue.localType);
|
||||
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
(fieldsStore.getPrimaryKeyFieldForCollection as Mock).mockReturnValue({
|
||||
collection: 'collection_a',
|
||||
field: 'id',
|
||||
});
|
||||
|
||||
expect(fieldDetailStore.collection).toEqual(testValue.collection);
|
||||
expect(fieldDetailStore.field.collection).toEqual(testValue.collection);
|
||||
expect(fieldDetailStore.editing).toEqual(testValue.field);
|
||||
@@ -125,6 +136,7 @@ describe('Alterations', () => {
|
||||
expect(fieldDetailStore.relations.m2o?.field).toEqual('directus_files_id');
|
||||
|
||||
fieldDetailStore.update({ autoGenerateJunctionRelation: false });
|
||||
|
||||
fieldDetailStore.update({
|
||||
relations: {
|
||||
o2m: {
|
||||
@@ -132,6 +144,7 @@ describe('Alterations', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
fieldDetailStore.update({ autoGenerateJunctionRelation: true });
|
||||
|
||||
expect(fieldDetailStore.relations.o2m?.collection).toEqual('collection_a_files');
|
||||
|
||||
@@ -115,6 +115,7 @@ export const useFieldDetailStore = defineStore({
|
||||
this.loading = true;
|
||||
const response = await api.get(`/fields/${collection}/${field}`);
|
||||
const fetchedFieldMeta = response.data?.data?.meta;
|
||||
|
||||
this.$patch({
|
||||
field: {
|
||||
meta: {
|
||||
@@ -169,6 +170,7 @@ export const useFieldDetailStore = defineStore({
|
||||
|
||||
for (const relation of Object.values(this.relations)) {
|
||||
if (!relation || !relation.collection || !relation.field) continue;
|
||||
|
||||
if (
|
||||
// Duplicate checks for O2M & M2O
|
||||
(relation.collection === relation.related_collection && relation.field === relation.meta?.one_field) ||
|
||||
|
||||
@@ -196,6 +196,7 @@ export default defineComponent({
|
||||
'interface',
|
||||
computed(() => props.field.meta?.interface ?? null)
|
||||
);
|
||||
|
||||
const interfaceName = computed(() => inter.value?.name ?? null);
|
||||
|
||||
const hidden = computed(() => props.field.meta?.hidden === true);
|
||||
@@ -259,11 +260,13 @@ export default defineComponent({
|
||||
const duplicateActive = ref(false);
|
||||
const duplicateName = ref(props.field.field + '_copy');
|
||||
const duplicating = ref(false);
|
||||
|
||||
const collections = computed(() =>
|
||||
collectionsStore.collections
|
||||
.map(({ collection }) => collection)
|
||||
.filter((collection) => collection.startsWith('directus_') === false)
|
||||
);
|
||||
|
||||
const duplicateTo = ref(props.field.collection);
|
||||
|
||||
return {
|
||||
|
||||
@@ -69,6 +69,7 @@ const arrows = computed(() => {
|
||||
|
||||
if (props.arrowInfo?.id === panel.id && props.arrowInfo?.type === 'resolve') {
|
||||
const { x, y } = getPoints(panel, RESOLVE_OFFSET);
|
||||
|
||||
arrows.push({
|
||||
id: panel.id + '_resolve',
|
||||
d: createLine(x, y, props.arrowInfo.pos.x, props.arrowInfo.pos.y),
|
||||
@@ -77,6 +78,7 @@ const arrows = computed(() => {
|
||||
});
|
||||
} else if (resolveChild) {
|
||||
const { x, y, toX, toY } = getPoints(panel, RESOLVE_OFFSET, resolveChild);
|
||||
|
||||
arrows.push({
|
||||
id: panel.id + '_resolve',
|
||||
d: createLine(x, y, toX as number, toY as number),
|
||||
@@ -85,6 +87,7 @@ const arrows = computed(() => {
|
||||
});
|
||||
} else if (props.editMode && !props.arrowInfo && (panel.id === '$trigger' || props.hoveredPanel === panel.id)) {
|
||||
const { x: resolveX, y: resolveY } = getPoints(panel, RESOLVE_OFFSET);
|
||||
|
||||
arrows.push({
|
||||
id: panel.id + '_resolve',
|
||||
d: createLine(resolveX, resolveY, resolveX + 3 * 20, resolveY),
|
||||
@@ -96,6 +99,7 @@ const arrows = computed(() => {
|
||||
|
||||
if (props.arrowInfo?.id === panel.id && props.arrowInfo?.type === 'reject') {
|
||||
const { x, y } = getPoints(panel, REJECT_OFFSET);
|
||||
|
||||
arrows.push({
|
||||
id: panel.id + '_reject',
|
||||
d: createLine(x, y, props.arrowInfo.pos.x, props.arrowInfo.pos.y),
|
||||
@@ -104,6 +108,7 @@ const arrows = computed(() => {
|
||||
});
|
||||
} else if (rejectChild) {
|
||||
const { x, y, toX, toY } = getPoints(panel, REJECT_OFFSET, rejectChild);
|
||||
|
||||
arrows.push({
|
||||
id: panel.id + '_reject',
|
||||
d: createLine(x, y, toX as number, toY as number),
|
||||
@@ -112,6 +117,7 @@ const arrows = computed(() => {
|
||||
});
|
||||
} else if (props.editMode && !props.arrowInfo && panel.id !== '$trigger' && props.hoveredPanel === panel.id) {
|
||||
const { x: rejectX, y: rejectY } = getPoints(panel, REJECT_OFFSET);
|
||||
|
||||
arrows.push({
|
||||
id: panel.id + '_reject',
|
||||
d: createLine(rejectX, rejectY, rejectX + 3 * 20, rejectY),
|
||||
@@ -182,6 +188,7 @@ const arrows = computed(() => {
|
||||
}
|
||||
|
||||
const arrowSize = 8;
|
||||
|
||||
const arrow = `M ${points.at(-1)} L ${points
|
||||
.at(-1)
|
||||
?.clone()
|
||||
|
||||
@@ -183,6 +183,7 @@ const operationOptions = computed(() => {
|
||||
|
||||
function saveOperation() {
|
||||
saving.value = true;
|
||||
|
||||
emit('save', {
|
||||
flow: props.primaryKey,
|
||||
name: operationName.value || generatedName.value,
|
||||
|
||||
@@ -236,6 +236,7 @@ const pointermove = (event: PointerEvent) => {
|
||||
rafId = window.requestAnimationFrame(() => {
|
||||
moving.value = true;
|
||||
if (!down) return;
|
||||
|
||||
const arrowInfo: ArrowInfo =
|
||||
down === 'parent'
|
||||
? {
|
||||
|
||||
@@ -61,6 +61,7 @@ function saveTrigger() {
|
||||
...(props.flow ?? {}),
|
||||
...flowEdits.value,
|
||||
});
|
||||
|
||||
emit('update:open', false);
|
||||
}
|
||||
|
||||
|
||||
@@ -230,6 +230,7 @@ const flowsStore = useFlowsStore();
|
||||
const stagedFlow = ref<Partial<FlowRaw>>({});
|
||||
|
||||
const fetchedFlow = ref<FlowRaw>();
|
||||
|
||||
const flow = computed<FlowRaw | undefined>({
|
||||
get() {
|
||||
if (!fetchedFlow.value) return undefined;
|
||||
@@ -261,6 +262,7 @@ async function loadCurrentFlow() {
|
||||
fields: ['*', 'operations.*'],
|
||||
},
|
||||
});
|
||||
|
||||
fetchedFlow.value = response.data.data;
|
||||
} catch (err: any) {
|
||||
unexpectedError(err);
|
||||
@@ -688,6 +690,7 @@ function getNearAttachment(pos: Vector2) {
|
||||
(panel.x - 1) * 20 + ATTACHMENT_OFFSET.x,
|
||||
(panel.y - 1) * 20 + ATTACHMENT_OFFSET.y
|
||||
);
|
||||
|
||||
if (attachmentPos.distanceTo(pos) <= 40) return panel.id as string;
|
||||
}
|
||||
|
||||
|
||||
@@ -245,6 +245,7 @@ async function toggleFlowStatusById(id: string, value: string) {
|
||||
await api.patch(`/flows/${id}`, {
|
||||
status: value === 'active' ? 'inactive' : 'active',
|
||||
});
|
||||
|
||||
await flowsStore.hydrate();
|
||||
} catch (error) {
|
||||
unexpectedError(error as Error);
|
||||
|
||||
@@ -280,6 +280,7 @@ export default defineComponent({
|
||||
const updatePermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'update' && permission.collection === collection.value
|
||||
);
|
||||
|
||||
return !!updatePermissions;
|
||||
});
|
||||
|
||||
@@ -290,6 +291,7 @@ export default defineComponent({
|
||||
const deletePermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'delete' && permission.collection === collection.value
|
||||
);
|
||||
|
||||
return !!deletePermissions;
|
||||
});
|
||||
|
||||
@@ -300,6 +302,7 @@ export default defineComponent({
|
||||
const createPermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'create' && permission.collection === collection.value
|
||||
);
|
||||
|
||||
return !!createPermissions;
|
||||
});
|
||||
|
||||
|
||||
@@ -305,6 +305,7 @@ function useValues() {
|
||||
layout_options: null,
|
||||
filter: null,
|
||||
};
|
||||
|
||||
if (isNew.value === true) return defaultValues;
|
||||
if (preset.value === null) return defaultValues;
|
||||
|
||||
|
||||
@@ -193,6 +193,7 @@ export default defineComponent({
|
||||
const usersCreatePermission = permissionsStore.permissions.find(
|
||||
(permission) => permission.collection === 'directus_users' && permission.action === 'create'
|
||||
);
|
||||
|
||||
const rolesReadPermission = permissionsStore.permissions.find(
|
||||
(permission) => permission.collection === 'directus_roles' && permission.action === 'read'
|
||||
);
|
||||
|
||||
@@ -195,6 +195,7 @@ export default defineComponent({
|
||||
deep: { users: { _limit: 0 } },
|
||||
},
|
||||
});
|
||||
|
||||
role.value = response.data.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ export default function useNavigation(): { roles: Ref<BasicRole[] | null>; loadi
|
||||
fields: ['id', 'name', 'icon', 'admin_access'],
|
||||
},
|
||||
});
|
||||
|
||||
roles.value = rolesResponse.data.data;
|
||||
} catch (error: any) {
|
||||
unexpectedError(error);
|
||||
|
||||
@@ -244,6 +244,7 @@ const canInviteUsers = computed(() => {
|
||||
const usersCreatePermission = permissionsStore.permissions.find(
|
||||
(permission) => permission.collection === 'directus_users' && permission.action === 'create'
|
||||
);
|
||||
|
||||
const rolesReadPermission = permissionsStore.permissions.find(
|
||||
(permission) => permission.collection === 'directus_roles' && permission.action === 'read'
|
||||
);
|
||||
@@ -258,6 +259,7 @@ const { batchEditAllowed, batchDeleteAllowed, createAllowed } = usePermissions()
|
||||
onBeforeRouteLeave(() => {
|
||||
selection.value = [];
|
||||
});
|
||||
|
||||
onBeforeRouteUpdate(() => {
|
||||
selection.value = [];
|
||||
});
|
||||
@@ -340,6 +342,7 @@ function usePermissions() {
|
||||
const updatePermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'update' && permission.collection === 'directus_users'
|
||||
);
|
||||
|
||||
return !!updatePermissions;
|
||||
});
|
||||
|
||||
@@ -350,6 +353,7 @@ function usePermissions() {
|
||||
const deletePermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'delete' && permission.collection === 'directus_users'
|
||||
);
|
||||
|
||||
return !!deletePermissions;
|
||||
});
|
||||
|
||||
@@ -360,6 +364,7 @@ function usePermissions() {
|
||||
const createPermissions = permissionsStore.permissions.find(
|
||||
(permission) => permission.action === 'create' && permission.collection === 'directus_users'
|
||||
);
|
||||
|
||||
return !!createPermissions;
|
||||
});
|
||||
|
||||
|
||||
@@ -14,11 +14,13 @@ export default defineOperationApp({
|
||||
],
|
||||
options: (panel) => {
|
||||
const flowStore = useFlowsStore();
|
||||
|
||||
const flowChoices = flowStore.flows
|
||||
.filter((flow) => flow.trigger === 'operation')
|
||||
.map((flow) => {
|
||||
return { text: flow.name, value: flow.id };
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
field: 'flow',
|
||||
|
||||
@@ -60,6 +60,7 @@ const props = withDefaults(defineProps<Props>(), {});
|
||||
/*const emit = */ defineEmits(['input']);
|
||||
|
||||
const insightsStore = useInsightsStore();
|
||||
|
||||
const value = computed({
|
||||
get() {
|
||||
const val = insightsStore.getVariable(props.field);
|
||||
@@ -76,6 +77,7 @@ const selectModalOpen = ref(false);
|
||||
|
||||
function onSelection(data: (number | string)[]) {
|
||||
selectModalOpen.value = false;
|
||||
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
value.value = [];
|
||||
return;
|
||||
|
||||
@@ -15,6 +15,7 @@ export default function useDisplayItems(collection: Ref<string>, template: Ref<s
|
||||
const displayItems = ref([]);
|
||||
|
||||
const primaryKey = computed(() => fieldStore.getPrimaryKeyFieldForCollection(collection.value)?.field ?? '');
|
||||
|
||||
const displayTemplate = computed(() => {
|
||||
if (template.value) return template.value;
|
||||
|
||||
@@ -22,6 +23,7 @@ export default function useDisplayItems(collection: Ref<string>, template: Ref<s
|
||||
|
||||
return displayTemplate || `{{ ${primaryKey.value || 'id'} }}`;
|
||||
});
|
||||
|
||||
const requiredFields = computed(() => {
|
||||
if (!displayTemplate.value || !collection.value) return [];
|
||||
return adjustFieldsForDisplays(getFieldsFromTemplate(displayTemplate.value), collection.value);
|
||||
|
||||
@@ -53,6 +53,7 @@ export default definePanel({
|
||||
|
||||
function getParsedOptionsFilter(filter: string | undefined) {
|
||||
if (!filter) return {};
|
||||
|
||||
try {
|
||||
return JSON.parse(filter);
|
||||
} catch {
|
||||
|
||||
@@ -92,6 +92,7 @@ export const onBeforeEach: NavigationGuard = async (to) => {
|
||||
// First load
|
||||
if (firstLoad) {
|
||||
firstLoad = false;
|
||||
|
||||
// Try retrieving a fresh access token on first load
|
||||
try {
|
||||
await refresh({ navigate: false });
|
||||
@@ -111,8 +112,10 @@ export const onBeforeEach: NavigationGuard = async (to) => {
|
||||
if (to.meta?.public !== true) {
|
||||
if (appStore.hydrated === false) {
|
||||
appStore.hydrating = false;
|
||||
|
||||
if (appStore.authenticated === true) {
|
||||
await hydrate();
|
||||
|
||||
if (
|
||||
userStore.currentUser &&
|
||||
to.fullPath === '/tfa-setup' &&
|
||||
|
||||
@@ -94,6 +94,7 @@ export default defineComponent({
|
||||
|
||||
async function enable() {
|
||||
await enableTFA();
|
||||
|
||||
if (error.value === null) {
|
||||
const redirectQuery = router.currentRoute.value.query.redirect as string;
|
||||
router.push(redirectQuery || (userStore.currentUser as User)?.last_page || '/login');
|
||||
|
||||
@@ -35,6 +35,7 @@ test('parseField action should translate field name when translations are added
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
collectionsStore.collections = [mockCollectionWithTranslations].map(collectionsStore.prepareCollectionForApp);
|
||||
expect(collectionsStore.collections[0].name).toEqual('Collection A en-US');
|
||||
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(true);
|
||||
@@ -49,6 +50,7 @@ test('parseField action should translate field name when translations are added
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
collectionsStore.collections = [mockCollectionWithMissingTranslations].map(collectionsStore.prepareCollectionForApp);
|
||||
expect(collectionsStore.collections[0].name).toEqual('A');
|
||||
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(false);
|
||||
@@ -67,6 +69,7 @@ test('parseField action should translate field name when all translations are re
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
collectionsStore.collections = [mockCollectionWithTranslations].map(collectionsStore.prepareCollectionForApp);
|
||||
expect(collectionsStore.collections[0].name).toEqual('Collection A en-US');
|
||||
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(true);
|
||||
@@ -76,6 +79,7 @@ test('parseField action should translate field name when all translations are re
|
||||
translations: null,
|
||||
},
|
||||
});
|
||||
|
||||
collectionsStore.collections = [mockCollectionWithoutTranslations].map(collectionsStore.prepareCollectionForApp);
|
||||
expect(collectionsStore.collections[0].name).toEqual('A');
|
||||
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(false);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user