diff --git a/src/components/v-list/v-list-item.vue b/src/components/v-list/v-list-item.vue index 6c6801ffb2..43ec1b39f4 100644 --- a/src/components/v-list/v-list-item.vue +++ b/src/components/v-list/v-list-item.vue @@ -15,6 +15,8 @@ disabled, dashed, }" + :href="href" + :download="download" v-on="disabled === false && $listeners" > @@ -39,6 +41,10 @@ export default defineComponent({ type: [String, Object] as PropType, 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(() => (props.to ? 'router-link' : 'li')); - const isClickable = computed(() => Boolean(props.to || listeners.click !== undefined)); + const component = computed(() => { + 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 }; }, }); diff --git a/src/interfaces/file/file.vue b/src/interfaces/file/file.vue new file mode 100644 index 0000000000..4713f81f57 --- /dev/null +++ b/src/interfaces/file/file.vue @@ -0,0 +1,310 @@ + + + + + diff --git a/src/interfaces/file/index.ts b/src/interfaces/file/index.ts new file mode 100644 index 0000000000..5955957775 --- /dev/null +++ b/src/interfaces/file/index.ts @@ -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: [], +})); diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index f8eb56fcca..a9ee43c69c 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -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; diff --git a/src/lang/en-US/index.json b/src/lang/en-US/index.json index a68455bcbb..7bcc1f6735 100644 --- a/src/lang/en-US/index.json +++ b/src/lang/en-US/index.json @@ -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", diff --git a/src/stores/projects/types.ts b/src/stores/projects/types.ts index f061901b43..13ff6c56d2 100644 --- a/src/stores/projects/types.ts +++ b/src/stores/projects/types.ts @@ -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; diff --git a/src/utils/readable-mime-type/extensions.json b/src/utils/readable-mime-type/extensions.json new file mode 100644 index 0000000000..65bda82f3d --- /dev/null +++ b/src/utils/readable-mime-type/extensions.json @@ -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" +} diff --git a/src/utils/readable-mime-type/readable-mime-type.ts b/src/utils/readable-mime-type/readable-mime-type.ts index 2e190ee715..1f4fb2718e 100644 --- a/src/utils/readable-mime-type/readable-mime-type.ts +++ b/src/utils/readable-mime-type/readable-mime-type.ts @@ -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; } diff --git a/src/views/private/components/module-bar-logo/module-bar-logo.story.ts b/src/views/private/components/module-bar-logo/module-bar-logo.story.ts index 793cba94b8..7020bbbe5e 100644 --- a/src/views/private/components/module-bar-logo/module-bar-logo.story.ts +++ b/src/views/private/components/module-bar-logo/module-bar-logo.story.ts @@ -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, diff --git a/src/views/private/components/module-bar-logo/module-bar-logo.test.ts b/src/views/private/components/module-bar-logo/module-bar-logo.test.ts deleted file mode 100644 index 1eb5c1d119..0000000000 --- a/src/views/private/components/module-bar-logo/module-bar-logo.test.ts +++ /dev/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); - }); -}); diff --git a/src/views/public/public-view.test.ts b/src/views/public/public-view.test.ts index 7675aeeaf9..2ef407db90 100644 --- a/src/views/public/public-view.test.ts +++ b/src/views/public/public-view.test.ts @@ -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', });