From 96db381fbf5980a765fb86604df3d08ef8d99060 Mon Sep 17 00:00:00 2001 From: Rijk van Zanten Date: Wed, 22 Apr 2020 15:41:00 -0400 Subject: [PATCH] Status display (#453) * Convert render function into component * Allow types definition in display registration * Add status-dot interface * Fix render display rendering in tabular view * Add types to other displays * Export tooltip from index * Start on readme * Add tests for status dot --- src/directives/tooltip/index.ts | 4 ++ src/displays/format-title/index.ts | 1 + src/displays/icon/index.ts | 1 + src/displays/index.ts | 3 +- src/displays/status-dot/index.ts | 11 +++ src/displays/status-dot/readme.md | 4 ++ src/displays/status-dot/status-dot.story.ts | 0 src/displays/status-dot/status-dot.test.ts | 72 +++++++++++++++++++ src/displays/status-dot/status-dot.vue | 47 ++++++++++++ src/displays/types.ts | 1 + src/lang/en-US/index.json | 2 + src/layouts/tabular/tabular.vue | 20 ++++-- .../components/render-display/index.ts | 40 +---------- .../render-display/render-display.vue | 48 +++++++++++++ 14 files changed, 210 insertions(+), 44 deletions(-) create mode 100644 src/directives/tooltip/index.ts create mode 100644 src/displays/status-dot/index.ts create mode 100644 src/displays/status-dot/readme.md create mode 100644 src/displays/status-dot/status-dot.story.ts create mode 100644 src/displays/status-dot/status-dot.test.ts create mode 100644 src/displays/status-dot/status-dot.vue create mode 100644 src/views/private/components/render-display/render-display.vue diff --git a/src/directives/tooltip/index.ts b/src/directives/tooltip/index.ts new file mode 100644 index 0000000000..bba5e6fcfa --- /dev/null +++ b/src/directives/tooltip/index.ts @@ -0,0 +1,4 @@ +import Tooltip from './tooltip'; + +export { Tooltip }; +export default Tooltip; diff --git a/src/displays/format-title/index.ts b/src/displays/format-title/index.ts index 368f628b98..32729f00f2 100644 --- a/src/displays/format-title/index.ts +++ b/src/displays/format-title/index.ts @@ -7,4 +7,5 @@ export default defineDisplay({ icon: 'text_format', handler: handler, options: null, + types: ['string'], }); diff --git a/src/displays/icon/index.ts b/src/displays/icon/index.ts index 0a2969bf62..a513d21808 100644 --- a/src/displays/icon/index.ts +++ b/src/displays/icon/index.ts @@ -14,4 +14,5 @@ export default defineDisplay(({ i18n }) => ({ width: 'half', }, ], + types: ['string'], })); diff --git a/src/displays/index.ts b/src/displays/index.ts index 3dbdf1f672..074ec1614f 100644 --- a/src/displays/index.ts +++ b/src/displays/index.ts @@ -1,5 +1,6 @@ import DisplayIcon from './icon/'; import DisplayFormatTitle from './format-title/'; +import DisplayStatusDot from './status-dot/'; -export const displays = [DisplayIcon, DisplayFormatTitle]; +export const displays = [DisplayIcon, DisplayFormatTitle, DisplayStatusDot]; export default displays; diff --git a/src/displays/status-dot/index.ts b/src/displays/status-dot/index.ts new file mode 100644 index 0000000000..dbdb0ceb07 --- /dev/null +++ b/src/displays/status-dot/index.ts @@ -0,0 +1,11 @@ +import { defineDisplay } from '@/displays/define'; +import DisplayStatusDot from './status-dot.vue'; + +export default defineDisplay(({ i18n }) => ({ + id: 'status-dot', + name: i18n.t('status_dot'), + types: ['status'], + icon: 'box', + handler: DisplayStatusDot, + options: null, +})); diff --git a/src/displays/status-dot/readme.md b/src/displays/status-dot/readme.md new file mode 100644 index 0000000000..df081d6a8a --- /dev/null +++ b/src/displays/status-dot/readme.md @@ -0,0 +1,4 @@ +# Status Dot + +Renders the background color as set in the status mapping of the status type interface as a small dot. + diff --git a/src/displays/status-dot/status-dot.story.ts b/src/displays/status-dot/status-dot.story.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/displays/status-dot/status-dot.test.ts b/src/displays/status-dot/status-dot.test.ts new file mode 100644 index 0000000000..7fca8daa5f --- /dev/null +++ b/src/displays/status-dot/status-dot.test.ts @@ -0,0 +1,72 @@ +import DisplayStatusDot from './status-dot.vue'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import VIcon from '@/components/v-icon'; +import VueCompositionAPI from '@vue/composition-api'; +import Tooltip from '@/directives/tooltip'; + +const localVue = createLocalVue(); +localVue.component('v-icon', VIcon); +localVue.use(VueCompositionAPI); +localVue.directive('tooltip', Tooltip); + +describe('Displays / Status Dot', () => { + it('Renders an empty span if no value is passed', () => { + const component = shallowMount(DisplayStatusDot, { + localVue, + propsData: { + value: null, + }, + }); + + expect(component.find('span').exists()).toBe(true); + expect(component.find('span').text()).toBe(''); + }); + + it('Renders a question mark icon is status is unknown in interface options', () => { + const component = shallowMount(DisplayStatusDot, { + localVue, + propsData: { + value: 'draft', + interfaceOptions: { + status_mapping: { + published: {}, + }, + }, + }, + }); + + expect(component.find(VIcon).exists()).toBe(true); + expect(component.attributes('name')).toBe('help_outline'); + }); + + it('Renders the dot with the correct color', () => { + const component = shallowMount(DisplayStatusDot, { + localVue, + propsData: { + value: 'draft', + interfaceOptions: { + status_mapping: { + draft: { + background_color: 'rgb(171, 202, 188)', + }, + }, + }, + }, + }); + + expect(component.exists()).toBe(true); + expect(component.attributes('style')).toBe('background-color: rgb(171, 202, 188);'); + }); + + it('Sets status to null if interface options are missing', () => { + const component = shallowMount(DisplayStatusDot, { + localVue, + propsData: { + value: 'draft', + interfaceOptions: null, + }, + }); + + expect((component.vm as any).status).toBe(null); + }); +}); diff --git a/src/displays/status-dot/status-dot.vue b/src/displays/status-dot/status-dot.vue new file mode 100644 index 0000000000..03b7191922 --- /dev/null +++ b/src/displays/status-dot/status-dot.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/src/displays/types.ts b/src/displays/types.ts index a551fb96d7..08f70ba752 100644 --- a/src/displays/types.ts +++ b/src/displays/types.ts @@ -12,6 +12,7 @@ export type DisplayConfig = { // eslint-disable-next-line @typescript-eslint/no-explicit-any handler: DisplayHandlerFunction | Component; options: null | Partial[] | Component; + types: string[]; }; export type DisplayContext = { i18n: VueI18n }; diff --git a/src/lang/en-US/index.json b/src/lang/en-US/index.json index 79934e44b4..b10ea6547a 100644 --- a/src/lang/en-US/index.json +++ b/src/lang/en-US/index.json @@ -272,6 +272,8 @@ "select_statuses": "Select Statuses", + "status_dot": "Status (Dot)", + "about_directus": "About Directus", "activity_log": "Activity Log", "add_field_filter": "Add a field filter", diff --git a/src/layouts/tabular/tabular.vue b/src/layouts/tabular/tabular.vue index 378a5c660c..825699a509 100644 --- a/src/layouts/tabular/tabular.vue +++ b/src/layouts/tabular/tabular.vue @@ -69,13 +69,17 @@ @update:sort="onSortChange" > @@ -331,8 +335,12 @@ export default defineComponent({ localWidths.value[field.field] || _viewOptions.value?.widths?.[field.field] || null, - display: field.display, - display_options: field.display_options, + field: { + display: field.display, + displayOptions: field.display_options, + interface: field.interface, + interfaceOptions: field.options, + }, })); }, set(val) { diff --git a/src/views/private/components/render-display/index.ts b/src/views/private/components/render-display/index.ts index 590ac95877..7efeebffef 100644 --- a/src/views/private/components/render-display/index.ts +++ b/src/views/private/components/render-display/index.ts @@ -1,38 +1,4 @@ -import { defineComponent } from '@vue/composition-api'; -import displays from '@/displays'; -import { DisplayHandlerFunction } from '@/displays/types'; +import RenderDisplay from './render-display.vue'; -export default defineComponent({ - props: { - display: { - type: String, - default: null, - }, - options: { - type: Object, - default: () => ({}), - }, - value: { - type: [String, Number, Object, Array], - default: null, - }, - }, - render(createElement, { props }) { - const display = displays.find((display) => display.id === props.display); - - if (!display) { - return props.value; - } - - if (typeof display.handler === 'function') { - return (display.handler as DisplayHandlerFunction)(props.value, props.options); - } - - return createElement(`display-${props.display}`, { - props: { - ...props.options, - value: props.value, - }, - }); - }, -}); +export { RenderDisplay }; +export default RenderDisplay; diff --git a/src/views/private/components/render-display/render-display.vue b/src/views/private/components/render-display/render-display.vue new file mode 100644 index 0000000000..8938c75cdb --- /dev/null +++ b/src/views/private/components/render-display/render-display.vue @@ -0,0 +1,48 @@ + + +