mirror of
https://github.com/directus/directus.git
synced 2026-02-11 04:45:34 -05:00
Interface single file (#605)
* Start on file interface * Add preview icon * Add readable mimetypes to extension * Add file interface strings * Fix type errors * Add file select / deselect * Add new file upload support * Add import from url * Fix loading state of url import * Add filename in download attribute * Replace option names with replace when file exists * Remove / fix outdated tests
This commit is contained in:
@@ -15,6 +15,8 @@
|
||||
disabled,
|
||||
dashed,
|
||||
}"
|
||||
:href="href"
|
||||
:download="download"
|
||||
v-on="disabled === false && $listeners"
|
||||
>
|
||||
<slot></slot>
|
||||
@@ -39,6 +41,10 @@ export default defineComponent({
|
||||
type: [String, Object] as PropType<string | Location>,
|
||||
default: null,
|
||||
},
|
||||
href: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -55,10 +61,22 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
download: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props, { listeners }) {
|
||||
const component = computed<string>(() => (props.to ? 'router-link' : 'li'));
|
||||
const isClickable = computed(() => Boolean(props.to || listeners.click !== undefined));
|
||||
const component = computed<string>(() => {
|
||||
if (props.to) return 'router-link';
|
||||
if (props.href) return 'a';
|
||||
return 'li';
|
||||
});
|
||||
|
||||
const isClickable = computed(() =>
|
||||
Boolean(props.to || props.href || listeners.click !== undefined)
|
||||
);
|
||||
|
||||
return { component, isClickable };
|
||||
},
|
||||
});
|
||||
|
||||
310
src/interfaces/file/file.vue
Normal file
310
src/interfaces/file/file.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<div class="file">
|
||||
<v-menu attached close-on-content-click :disabled="disabled || loading">
|
||||
<template #activator="{ toggle }">
|
||||
<div>
|
||||
<v-skeleton-loader type="input" v-if="loading" />
|
||||
<v-input
|
||||
v-else
|
||||
@click="toggle"
|
||||
readonly
|
||||
:placeholder="$t('no_file_selected')"
|
||||
:disabled="disabled"
|
||||
:value="file && file.title"
|
||||
>
|
||||
<template #prepend>
|
||||
<div class="preview" :class="{ 'has-file': file }">
|
||||
<img
|
||||
v-if="imageThumbnail"
|
||||
:src="imageThumbnail"
|
||||
:alt="file.title"
|
||||
/>
|
||||
<span class="extension" v-else-if="fileExtension">
|
||||
{{ fileExtension }}
|
||||
</span>
|
||||
<v-icon v-else name="folder_open" />
|
||||
</div>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-icon
|
||||
class="deselect"
|
||||
name="close"
|
||||
v-if="file"
|
||||
@click.stop="$emit('input', null)"
|
||||
/>
|
||||
<v-icon v-else name="attach_file" />
|
||||
</template>
|
||||
</v-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<v-list dense>
|
||||
<template v-if="file">
|
||||
<v-list-item :download="file.filename_download" :href="file.data.asset_url">
|
||||
<v-list-item-icon><v-icon name="file_download" /></v-list-item-icon>
|
||||
<v-list-item-content>{{ $t('download_file') }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider />
|
||||
</template>
|
||||
<v-list-item @click="activeDialog = 'upload'">
|
||||
<v-list-item-icon><v-icon name="phonelink" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ $t(file ? 'replace_from_device' : 'upload_from_device') }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item @click="activeDialog = 'choose'">
|
||||
<v-list-item-icon><v-icon name="folder_open" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ $t(file ? 'replace_from_library' : 'choose_from_library') }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item @click="activeDialog = 'url'">
|
||||
<v-list-item-icon><v-icon name="link" /></v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ $t(file ? 'replace_from_url' : 'import_from_url') }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<v-dialog :active="activeDialog === 'upload'" @toggle="activeDialog = null">
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('upload_from_device') }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-upload @upload="onUpload" />
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-button @click="activeDialog = null" secondary>{{ $t('cancel') }}</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<modal-browse
|
||||
collection="directus_files"
|
||||
:active="activeDialog === 'choose'"
|
||||
@update:active="activeDialog = null"
|
||||
@input="setSelection"
|
||||
/>
|
||||
|
||||
<v-dialog
|
||||
:active="activeDialog === 'url'"
|
||||
@toggle="activeDialog = null"
|
||||
:persistent="urlLoading"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('import_from_url') }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-input :placeholder="$t('url')" v-model="url" :disabled="urlLoading" />
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-button :disabled="urlLoading" @click="activeDialog = null" secondary>
|
||||
{{ $t('cancel') }}
|
||||
</v-button>
|
||||
<v-button
|
||||
:loading="urlLoading"
|
||||
@click="importFromURL"
|
||||
:disabled="isValidURL === false"
|
||||
>
|
||||
{{ $t('import') }}
|
||||
</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, computed } from '@vue/composition-api';
|
||||
import ModalBrowse from '@/views/private/components/modal-browse';
|
||||
import useProjectsStore from '@/stores/projects/';
|
||||
import api from '@/api';
|
||||
import readableMimeType from '@/utils/readable-mime-type';
|
||||
|
||||
type FileInfo = {
|
||||
title: string;
|
||||
type: string;
|
||||
data: {
|
||||
thumbnails?: {
|
||||
key: string;
|
||||
url: string;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
components: { ModalBrowse },
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const activeDialog = ref<'upload' | 'choose' | 'url'>(null);
|
||||
const { loading, error, file, fetchFile } = useFile();
|
||||
|
||||
watch(() => props.value, fetchFile);
|
||||
|
||||
const fileExtension = computed(() => {
|
||||
if (file.value === null) return null;
|
||||
return readableMimeType(file.value.type, true);
|
||||
});
|
||||
|
||||
const imageThumbnail = computed(() => {
|
||||
if (file.value === null) return null;
|
||||
if (file.value.type.includes('image') === false) return null;
|
||||
return file.value.data.thumbnails?.find((thumb) => thumb.key === 'directus-small-crop')
|
||||
?.url;
|
||||
});
|
||||
|
||||
const {
|
||||
url,
|
||||
isValidURL,
|
||||
loading: urlLoading,
|
||||
error: urlError,
|
||||
importFromURL,
|
||||
} = useURLImport();
|
||||
|
||||
return {
|
||||
activeDialog,
|
||||
setSelection,
|
||||
loading,
|
||||
error,
|
||||
file,
|
||||
fileExtension,
|
||||
imageThumbnail,
|
||||
onUpload,
|
||||
url,
|
||||
urlLoading,
|
||||
urlError,
|
||||
importFromURL,
|
||||
isValidURL,
|
||||
};
|
||||
|
||||
function useFile() {
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
const file = ref<FileInfo>(null);
|
||||
|
||||
return { loading, error, file, fetchFile };
|
||||
|
||||
async function fetchFile() {
|
||||
if (props.value === null) {
|
||||
file.value = null;
|
||||
loading.value = false;
|
||||
error.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/files/${props.value}`, {
|
||||
params: {
|
||||
fields: ['title', 'data', 'type', 'filename_download'],
|
||||
},
|
||||
});
|
||||
|
||||
file.value = response.data.data;
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setSelection(selection: number[]) {
|
||||
if (selection[0]) {
|
||||
emit('input', selection[0]);
|
||||
} else {
|
||||
emit('input', null);
|
||||
}
|
||||
}
|
||||
|
||||
function onUpload(fileInfo: FileInfo) {
|
||||
file.value = fileInfo;
|
||||
activeDialog.value = null;
|
||||
}
|
||||
|
||||
function useURLImport() {
|
||||
const url = ref('');
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
const isValidURL = computed(() => {
|
||||
try {
|
||||
new URL(url.value);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return { url, loading, error, isValidURL, importFromURL };
|
||||
|
||||
async function importFromURL() {
|
||||
loading.value = true;
|
||||
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
try {
|
||||
const response = await api.post(`/${currentProjectKey}/files`, {
|
||||
data: url.value,
|
||||
});
|
||||
|
||||
file.value = response.data.data;
|
||||
|
||||
activeDialog.value = null;
|
||||
url.value = '';
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.preview {
|
||||
--v-icon-color: var(--foreground-subdued);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-left: -8px;
|
||||
overflow: hidden;
|
||||
background-color: var(--background-normal);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
&.has-file {
|
||||
background-color: var(--primary-alt);
|
||||
}
|
||||
}
|
||||
|
||||
.extension {
|
||||
color: var(--foreground-subdued);
|
||||
color: var(--primary);
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.deselect:hover {
|
||||
--v-icon-color: var(--danger);
|
||||
}
|
||||
</style>
|
||||
10
src/interfaces/file/index.ts
Normal file
10
src/interfaces/file/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { defineInterface } from '../define';
|
||||
import InterfaceFile from './file.vue';
|
||||
|
||||
export default defineInterface(({ i18n }) => ({
|
||||
id: 'file',
|
||||
name: i18n.t('file'),
|
||||
icon: 'note_add',
|
||||
component: InterfaceFile,
|
||||
options: [],
|
||||
}));
|
||||
@@ -22,6 +22,7 @@ import InterfaceSlug from './slug';
|
||||
import InterfaceUser from './user';
|
||||
import InterfaceTags from './tags';
|
||||
import InterfaceRepeater from './repeater';
|
||||
import InterfaceFile from './file';
|
||||
|
||||
export const interfaces = [
|
||||
InterfaceTextInput,
|
||||
@@ -48,6 +49,7 @@ export const interfaces = [
|
||||
InterfaceUser,
|
||||
InterfaceTags,
|
||||
InterfaceRepeater,
|
||||
InterfaceFile,
|
||||
];
|
||||
|
||||
export default interfaces;
|
||||
|
||||
@@ -139,6 +139,8 @@
|
||||
"one_to_many": "One to Many (O2M)",
|
||||
"many_to_one": "Many to One (M2O)",
|
||||
"original": "Original",
|
||||
"url": "URL",
|
||||
"import": "Import",
|
||||
|
||||
"file_details": "File Details",
|
||||
"dimensions": "Dimensions",
|
||||
@@ -155,6 +157,15 @@
|
||||
"download": "Download",
|
||||
"open": "Open",
|
||||
|
||||
"upload_from_device": "Upload File from Device",
|
||||
"choose_from_library": "Choose File from Library",
|
||||
"import_from_url": "Import File from URL",
|
||||
"no_file_selected": "No File Selected",
|
||||
"download_file": "Download File",
|
||||
"replace_from_device": "Replace File from Device",
|
||||
"replace_from_library": "Replace File from Library",
|
||||
"replace_from_url": "Replace File from URL",
|
||||
|
||||
"name": "Name",
|
||||
"primary_key_field": "Primary Key Field",
|
||||
"type": "Type",
|
||||
|
||||
@@ -13,14 +13,17 @@ export interface Project {
|
||||
project_color: string;
|
||||
project_logo: {
|
||||
full_url: string;
|
||||
asset_url: string;
|
||||
url: string;
|
||||
} | null;
|
||||
project_foreground: {
|
||||
full_url: string;
|
||||
asset_url: string;
|
||||
url: string;
|
||||
} | null;
|
||||
project_background: {
|
||||
full_url: string;
|
||||
asset_url: string;
|
||||
url: string;
|
||||
} | null;
|
||||
project_public_note: string | null;
|
||||
|
||||
73
src/utils/readable-mime-type/extensions.json
Normal file
73
src/utils/readable-mime-type/extensions.json
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"audio/aac": "aac",
|
||||
"application/x-abiword": "abw",
|
||||
"application/x-freearc": "arc",
|
||||
"video/x-msvideo": "avi",
|
||||
"application/vnd.amazon.ebook": "azw",
|
||||
"application/octet-stream": "bin",
|
||||
"image/bmp": "bmp",
|
||||
"application/x-bzip": "bz",
|
||||
"application/x-bzip2": "bz2",
|
||||
"application/x-csh": "csh",
|
||||
"text/css": "css",
|
||||
"text/csv": "csv",
|
||||
"application/msword": "doc",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
|
||||
"application/vnd.ms-fontobject": "eot",
|
||||
"application/epub+zip": "epub",
|
||||
"application/gzip": "gz",
|
||||
"image/gif": "gif",
|
||||
"text/html": "html",
|
||||
"image/vnd.microsoft.icon": "ico",
|
||||
"text/calendar": "ics",
|
||||
"application/java-archive": "jar",
|
||||
"image/jpeg": "jpg",
|
||||
"text/javascript": "js",
|
||||
"application/json": "json",
|
||||
"application/ld+json": "jsonld",
|
||||
"audio/midi audio/x-midi": "midi",
|
||||
"audio/mpeg": "mp3",
|
||||
"video/mpeg": "mpeg",
|
||||
"application/vnd.apple.installer+xml": "mpkg",
|
||||
"application/vnd.oasis.opendocument.presentation": "odp",
|
||||
"application/vnd.oasis.opendocument.spreadsheet": "ods",
|
||||
"application/vnd.oasis.opendocument.text": "odt",
|
||||
"audio/ogg": "oga",
|
||||
"video/ogg": "ogv",
|
||||
"application/ogg": "ogx",
|
||||
"audio/opus": "opus",
|
||||
"font/otf": "otf",
|
||||
"image/png": "png",
|
||||
"application/pdf": "pdf",
|
||||
"application/x-httpd-php": "php",
|
||||
"application/vnd.ms-powerpoint": "ppt",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx",
|
||||
"application/vnd.rar": "rar",
|
||||
"application/rtf": "rtf",
|
||||
"application/x-sh": "sh",
|
||||
"image/svg+xml": "svg",
|
||||
"application/x-shockwave-flash": "swf",
|
||||
"application/x-tar": "tar",
|
||||
"image/tiff": "tiff",
|
||||
"video/mp2t": "ts",
|
||||
"font/ttf": "ttf",
|
||||
"text/plain": "txt",
|
||||
"application/vnd.visio": "vsd",
|
||||
"audio/wav": "wav",
|
||||
"audio/webm": "weba",
|
||||
"video/webm": "webm",
|
||||
"image/webp": "webp",
|
||||
"font/woff": "woff",
|
||||
"font/woff2": "woff2",
|
||||
"application/xhtml+xml": "xhtml",
|
||||
"application/vnd.ms-excel": "xls",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
|
||||
"application/xml": "xml",
|
||||
"application/vnd.mozilla.xul+xml": "xul",
|
||||
"application/zip": "zip",
|
||||
"video/3gpp": "3gp",
|
||||
"video/3gpp2": "3g2",
|
||||
"audio/3gpp": "3gp",
|
||||
"audio/3gpp2": "3g2",
|
||||
"application/x-7z-compressed": "7z"
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
import types from './types.json';
|
||||
import extensions from './extensions.json';
|
||||
|
||||
export default function readableMimeType(type: string, extension = false) {
|
||||
if (extension) {
|
||||
return (extensions as any)[type] || null;
|
||||
}
|
||||
|
||||
export default function readableMimeType(type: string) {
|
||||
return (types as any)[type] || null;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ export const withCustomLogo = () =>
|
||||
'https://demo.directus.io/uploads/thumper/originals/19acff06-4969-5c75-9cd5-dc3f27506de2.svg',
|
||||
url:
|
||||
'/uploads/thumper/originals/19acff06-4969-5c75-9cd5-dc3f27506de2.svg',
|
||||
asset_url:
|
||||
'/uploads/thumper/originals/19acff06-4969-5c75-9cd5-dc3f27506de2.svg',
|
||||
},
|
||||
project_color: '#4CAF50',
|
||||
project_foreground: null,
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import ModuleBarLogo from './module-bar-logo.vue';
|
||||
import { useRequestsStore } from '@/stores/requests';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
|
||||
describe('Views / Private / Module Bar / Logo', () => {
|
||||
it('Renders the default rabbit when we are not in a project', () => {
|
||||
const component = shallowMount(ModuleBarLogo, { localVue });
|
||||
expect((component.vm as any).customLogoPath).toBe(null);
|
||||
});
|
||||
|
||||
it('Renders the default rabbit when the current project errored out', () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.currentProject = {
|
||||
value: {
|
||||
key: 'my-project',
|
||||
status: 500,
|
||||
error: {
|
||||
code: 400,
|
||||
message: 'Could not connect to the database',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const component = shallowMount(ModuleBarLogo, { localVue });
|
||||
|
||||
expect((component.vm as any).customLogoPath).toBe(null);
|
||||
});
|
||||
|
||||
it('Renders the default rabbit when the current project does not have a custom logo', () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.currentProject = {
|
||||
value: {
|
||||
key: 'my-project',
|
||||
api: {
|
||||
requires2FA: false,
|
||||
project_foreground: null,
|
||||
project_background: null,
|
||||
project_color: '#abcdef',
|
||||
project_public_note: '',
|
||||
default_locale: 'en-US',
|
||||
telemetry: false,
|
||||
project_name: 'test',
|
||||
project_logo: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const component = shallowMount(ModuleBarLogo, { localVue });
|
||||
|
||||
expect((component.vm as any).customLogoPath).toBe(null);
|
||||
});
|
||||
|
||||
it('Renders the custom logo if set', () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.currentProject = {
|
||||
value: {
|
||||
key: 'my-project',
|
||||
api: {
|
||||
requires2FA: false,
|
||||
project_foreground: null,
|
||||
project_background: null,
|
||||
project_color: '#abcdef',
|
||||
project_public_note: '',
|
||||
default_locale: 'en-US',
|
||||
telemetry: false,
|
||||
project_name: 'test',
|
||||
project_logo: {
|
||||
full_url: 'abc',
|
||||
url: 'abc',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const component = shallowMount(ModuleBarLogo, { localVue });
|
||||
|
||||
expect((component.vm as any).customLogoPath).toBe('abc');
|
||||
expect(component.find('img').attributes().src).toBe('abc');
|
||||
});
|
||||
|
||||
it('Only stops running if the queue is empty', () => {
|
||||
const requestsStore = useRequestsStore({});
|
||||
requestsStore.queueHasItems = { value: false };
|
||||
|
||||
let component = shallowMount(ModuleBarLogo, { localVue });
|
||||
(component.vm as any).isRunning = true;
|
||||
(component.vm as any).stopRunningIfQueueIsEmpty();
|
||||
expect((component.vm as any).isRunning).toBe(false);
|
||||
|
||||
requestsStore.queueHasItems = { value: true };
|
||||
component = shallowMount(ModuleBarLogo, { localVue });
|
||||
expect((component.vm as any).isRunning).toBe(true);
|
||||
(component.vm as any).stopRunningIfQueueIsEmpty();
|
||||
expect((component.vm as any).isRunning).toBe(true);
|
||||
|
||||
requestsStore.queueHasItems = { value: false };
|
||||
component = shallowMount(ModuleBarLogo, { localVue });
|
||||
(component.vm as any).stopRunningIfQueueIsEmpty();
|
||||
expect((component.vm as any).isRunning).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -33,17 +33,20 @@ const mockProject: ProjectWithKey = {
|
||||
full_url:
|
||||
'http://localhost:8080/uploads/my-project/originals/19acff06-4969-5c75-9cd5-dc3f27506de2.svg',
|
||||
url: '/uploads/my-project/originals/19acff06-4969-5c75-9cd5-dc3f27506de2.svg',
|
||||
asset_url: '/uploads/my-project/assets/abc',
|
||||
},
|
||||
project_color: '#4CAF50',
|
||||
project_foreground: {
|
||||
full_url:
|
||||
'http://localhost:8080/uploads/my-project/originals/f28c49b0-2b4f-571e-bf62-593107cbf2ec.svg',
|
||||
url: '/uploads/my-project/originals/f28c49b0-2b4f-571e-bf62-593107cbf2ec.svg',
|
||||
asset_url: '/uploads/my-project/assets/abc',
|
||||
},
|
||||
project_background: {
|
||||
full_url:
|
||||
'http://localhost:8080/uploads/my-project/originals/03a06753-6794-4b9a-803b-3e1cd15e0742.jpg',
|
||||
url: '/uploads/my-project/originals/03a06753-6794-4b9a-803b-3e1cd15e0742.jpg',
|
||||
asset_url: '/uploads/my-project/assets/abc',
|
||||
},
|
||||
telemetry: true,
|
||||
default_locale: 'en-US',
|
||||
@@ -125,7 +128,7 @@ describe('Views / Public', () => {
|
||||
store.state.projects = [mockProject];
|
||||
store.state.currentProjectKey = 'my-project';
|
||||
expect((component.vm as any).artStyles).toEqual({
|
||||
background: `url(${mockProject.api?.project_background?.full_url})`,
|
||||
background: `url(${mockProject.api?.project_background?.asset_url})`,
|
||||
backgroundPosition: 'center center',
|
||||
backgroundSize: 'cover',
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user