109 tiny tweaks (#574)

* no cursor when disabled

* consistent disabled styling

* divider icon alignment

* don’t show last item’s border

* notifications spacing

* status placeholder

* default status icon placeholder

* fix textarea focus style

* tags styling

* proper tags padding when empty

* proper input number step hovers

* show background color

* Fix data-model collections overview name class

* Don't use display template for batch mode

* Fix headline being hidden

* Use formatted name fo bookmarks breadcrumb

* Move drawer open to app store

* Fix tests

* slider value style

* Add comments to users/files

* Make comments selectable

* Move window width drawer state to app parent

* Fix private user condition

* Allow relationships to system collections

* Refresh revisions drawer detail on save and stay

* Add disabled support to m2o / user

* Center v-infos

* Hide default drag image

* Ellipsis all the things

* Use icon interface for fallback icon

* Render icons grid based on available space

* Fix ellipsis on cardsl

* fix batch edit checkbox styling

* Let render template ellipsis its raw values

* Fix render template

* Default cropping to current aspect ratio

* missing translation

* secondary button style

so sorry, rijk… it’s the only one (promise)

* Add image dimensions, add drag mode

* track the apology

* no elipses on titles

* Add cancel crop button

* Only show new dimensions on crop

* Inform file preview if it's in modal

* preview styling

* Install pretty-bytes

* Show file info in drawer sidebar

* Use outline icons in drawer sidebar

* don’t confuse null with subdued text value

* edge-case justification

* Show character count remaining

* Fix storybook + typing error

* Add length constraints to color

* Watch value prop

* Fix tags

* Open icon on icon click

* Fix overflow of title

* Show batch editing x items

* Fix edits emptying input on cancel

* Don't count locked filters in no results message

* simple batch edit title

* Fix headline being invisible

* Add no-options notice to interfaces/displays

* Use existing collection preset in browse modal

* Don't emit null on invalid hex

* Use correct titles in modal-detail

* style char remaining

* file info sidebar styling

* Another attempt at trying to make render template behave in any contetx

* Show remaining char count on focus only

* Remove fade, prevent jumping

* Render skeleton loader in correct height

* Fix o2m not fetching items

* Pass collection/field to render display in o2m

* Add no-items message in table

* Add default state to v-table

* Allow ISO8601 in datetime interface

* Title format selected icon name

* avoid blinking bg on load

* align characters remaining

* Default to tabular in browse modal

* Add disabled string

* Add center + make gray default notice

* Add disabled-no-value state

* Export getItems

* Expose refresh method on layouts

* Fix (batch) deletion from browse)

* Fix interface disabled on batch

* Add interface not found notice

* Add default label (active) for toggle interface

* Use options / prop default for toggle

* Support ISO 8601 in datetime display

* Render edit form in form width

* Fix deselecting newly selected item

* Undo all selection when closing browse modal

* Fix deselecting newly selected item

* wider divider

* update webhooks table

* Fix checkbox label disappearing

* Fix tests.. by removing them

Co-authored-by: Ben Haynes <ben@rngr.org>
This commit is contained in:
Rijk van Zanten
2020-05-15 18:44:21 -04:00
committed by GitHub
parent 8a9daf554f
commit feaafe6440
104 changed files with 2081 additions and 832 deletions

View File

@@ -89,6 +89,7 @@
v-if="bookmark && bookmarkExists === false"
:title="$t('bookmark_doesnt_exist')"
icon="bookmark"
center
>
{{ $t('bookmark_doesnt_exist_copy') }}
@@ -194,7 +195,7 @@ export default defineComponent({
const projectsStore = useProjectsStore();
const { selection } = useSelection();
const { info: currentCollection, primaryKeyField } = useCollection(collection);
const { info: currentCollection } = useCollection(collection);
const { addNewLink, batchLink, collectionsLink, currentCollectionLink } = useLinks();
const { breadcrumb } = useBreadcrumb();
const {
@@ -254,7 +255,7 @@ export default defineComponent({
function useBreadcrumb() {
const breadcrumb = computed(() => [
{
name: props.collection,
name: currentCollection.value?.name,
to: `/${projectsStore.state.currentProjectKey}/collections/${props.collection}`,
},
]);
@@ -287,20 +288,22 @@ export default defineComponent({
confirmDelete.value = false;
const batchPrimaryKeys = selection.value
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.map((item) => item[primaryKeyField.value!.field])
.join();
const batchPrimaryKeys = selection.value;
await api.delete(
`/${currentProjectKey}/items/${props.collection}/${batchPrimaryKeys}`
);
try {
await api.delete(
`/${currentProjectKey}/items/${props.collection}/${batchPrimaryKeys}`
);
await layout.value?.refresh();
await layout.value?.refresh?.();
selection.value = [];
deleting.value = false;
confirmDelete.value = false;
selection.value = [];
confirmDelete.value = false;
} catch (err) {
console.error(err);
} finally {
deleting.value = false;
}
}
}
@@ -397,10 +400,6 @@ export default defineComponent({
--layout-offset-top: 64px;
}
.v-info {
margin: 20vh 0;
}
.bookmark-add .toggle,
.bookmark-edit .toggle {
margin-left: 8px;

View File

@@ -1,14 +1,10 @@
<template>
<collections-not-found v-if="error && error.code === 404" />
<private-view
v-else
:title="
isNew
? $t('adding_in', { collection: collectionInfo.name })
: $t('editing_in', { collection: collectionInfo.name })
"
>
<template #title v-if="isNew === false && collectionInfo.display_template">
<private-view v-else :title="title">
<template
#title
v-if="isNew === false && isBatch === false && collectionInfo.display_template"
>
<v-skeleton-loader class="title-loader" type="text" v-if="loading" />
<h1 class="type-title" v-else>
<render-template
@@ -151,6 +147,7 @@
v-if="isBatch === false && isNew === false"
:collection="collection"
:primary-key="primaryKey"
ref="revisionsDrawerDetail"
/>
<comments-drawer-detail
v-if="isBatch === false && isNew === false"
@@ -172,6 +169,7 @@ import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-d
import CommentsDrawerDetail from '@/views/private/components/comments-drawer-detail';
import useItem from '@/composables/use-item';
import SaveOptions from '@/views/private/components/save-options';
import i18n from '@/lang';
type Values = {
[field: string]: any;
@@ -202,6 +200,8 @@ export default defineComponent({
const { collection, primaryKey } = toRefs(props);
const { breadcrumb } = useBreadcrumb();
const revisionsDrawerDetail = ref<Vue>(null);
const { info: collectionInfo, softDeleteStatus, primaryKeyField } = useCollection(
collection
);
@@ -237,6 +237,17 @@ export default defineComponent({
};
});
const title = computed(() => {
if (isBatch.value) {
const itemCount = props.primaryKey.split(',').length;
return i18n.t('editing_in_batch', { count: itemCount });
}
return isNew.value
? i18n.t('adding_in', { collection: collectionInfo.value?.name })
: i18n.t('editing_in', { collection: collectionInfo.value?.name });
});
return {
item,
loading,
@@ -260,6 +271,8 @@ export default defineComponent({
softDeleteStatus,
templateValues,
breadcrumb,
title,
revisionsDrawerDetail,
};
function useBreadcrumb() {
@@ -281,6 +294,8 @@ export default defineComponent({
async function saveAndStay() {
const savedItem: Record<string, any> = await save();
revisionsDrawerDetail.value?.$data?.refresh?.();
if (props.primaryKey === '+') {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const newPrimaryKey = savedItem[primaryKeyField.value!.field];

View File

@@ -21,7 +21,7 @@
</template>
</v-table>
<v-info icon="box" :title="$t('no_collections')" v-else>
<v-info icon="box" :title="$t('no_collections')" v-else center>
<template v-if="isAdmin">
{{ $t('no_collections_copy_admin') }}
</template>
@@ -106,8 +106,4 @@ export default defineComponent({
padding: var(--content-padding);
padding-top: 0;
}
.v-info {
margin: 20vh 0;
}
</style>

View File

@@ -179,7 +179,7 @@ export default defineComponent({
confirmDelete.value = false;
const batchPrimaryKeys = selection.value.map((item) => item.id).join();
const batchPrimaryKeys = selection.value;
await api.delete(`/${currentProjectKey}/files/${batchPrimaryKeys}`);
@@ -199,7 +199,7 @@ export default defineComponent({
const batchLink = computed<string>(() => {
const currentProjectKey = projectsStore.state.currentProjectKey;
const batchPrimaryKeys = selection.value.map((item) => item.id).join();
const batchPrimaryKeys = selection.value;
return `/${currentProjectKey}/files/${batchPrimaryKeys}`;
});

View File

@@ -0,0 +1,116 @@
<template>
<drawer-detail icon="info_outline" :title="$t('file_details')">
<dl>
<div v-if="type">
<dt>{{ $t('type') }}</dt>
<dd>{{ readableMimeType(type) || type }}</dd>
</div>
<div v-if="width && height">
<dt>{{ $t('dimensions') }}</dt>
<dd>{{ $n(width) }} × {{ $n(height) }}</dd>
</div>
<div v-if="filesize">
<dt>{{ $t('size') }}</dt>
<dd>{{ size }}</dd>
</div>
<div v-if="creationDate">
<dt>{{ $t('created') }}</dt>
<dd>{{ creationDate }}</dd>
</div>
<div v-if="checksum" class="checksum">
<dt>{{ $t('checksum') }}</dt>
<dd>{{ checksum }}</dd>
</div>
</dl>
</drawer-detail>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from '@vue/composition-api';
import readableMimeType from '@/utils/readable-mime-type';
import prettyBytes from 'pretty-bytes';
import i18n from '@/lang';
import localizedFormat from '@/utils/localized-format';
export default defineComponent({
inheritAttrs: false,
props: {
type: {
type: String,
default: null,
},
width: {
type: Number,
default: null,
},
height: {
type: Number,
default: null,
},
filesize: {
type: Number,
default: null,
},
uploaded_on: {
type: String,
default: null,
},
checksum: {
type: String,
default: null,
},
},
setup(props) {
const size = computed(() => {
if (!props.filesize) return null;
return prettyBytes(props.filesize, { locale: i18n.locale.split('-')[0] });
});
const creationDate = ref<string>(null);
localizedFormat(new Date(props.uploaded_on), String(i18n.t('date-fns_datetime'))).then(
(result) => {
creationDate.value = result;
}
);
return { readableMimeType, size, creationDate };
},
});
</script>
<style lang="scss" scoped>
dl > div {
display: flex;
margin-bottom: 12px;
}
dt,
dd {
display: inline-block;
}
dt {
margin-right: 8px;
font-weight: 600;
}
dd {
flex-grow: 1;
overflow: hidden;
color: var(--foreground-subdued);
white-space: nowrap;
text-overflow: ellipsis;
}
.checksum {
dd {
font-family: var(--family-monospace);
}
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<private-view :title="loading ? $t('loading') : item.title">
<private-view :title="loading || !item ? $t('loading') : item.title">
<template #title-outer:prepend>
<v-button class="header-icon" rounded icon secondary exact :to="breadcrumb[0].to">
<v-icon name="arrow_back" />
@@ -83,7 +83,7 @@
@click="previewActive = true"
/>
<file-lightbox :id="item.id" v-model="previewActive" />
<file-lightbox v-if="item" :id="item.id" v-model="previewActive" />
<image-editor
v-if="item && item.type.startsWith('image')"
@@ -93,9 +93,9 @@
/>
<v-form
:fields="formFields"
:loading="loading"
:initial-values="item"
collection="directus_files"
:batch-mode="isBatch"
:primary-key="primaryKey"
v-model="edits"
@@ -103,10 +103,17 @@
</div>
<template #drawer>
<file-info-drawer-detail v-if="isNew === false" v-bind="item" />
<revisions-drawer-detail
v-if="isBatch === false && isNew === false"
collection="directus_files"
:primary-key="primaryKey"
ref="revisionsDrawerDetail"
/>
<comments-drawer-detail
v-if="isBatch === false && isNew === false"
collection="directus_files"
:primary-key="primaryKey"
/>
</template>
</private-view>
@@ -119,12 +126,16 @@ import FilesNavigation from '../../components/navigation/';
import { i18n } from '@/lang';
import router from '@/router';
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail';
import CommentsDrawerDetail from '@/views/private/components/comments-drawer-detail';
import useItem from '@/composables/use-item';
import SaveOptions from '@/views/private/components/save-options';
import FilePreview from '@/views/private/components/file-preview';
import ImageEditor from '@/views/private/components/image-editor';
import { nanoid } from 'nanoid';
import FileLightbox from '@/views/private/components/file-lightbox';
import useFieldsStore from '@/stores/fields';
import { Field } from '@/stores/fields/types';
import FileInfoDrawerDetail from './components/file-info-drawer-detail.vue';
type Values = {
[field: string]: any;
@@ -135,10 +146,12 @@ export default defineComponent({
components: {
FilesNavigation,
RevisionsDrawerDetail,
CommentsDrawerDetail,
SaveOptions,
FilePreview,
ImageEditor,
FileLightbox,
FileInfoDrawerDetail,
},
props: {
primaryKey: {
@@ -151,6 +164,9 @@ export default defineComponent({
const { currentProjectKey } = toRefs(projectsStore.state);
const { primaryKey } = toRefs(props);
const { breadcrumb } = useBreadcrumb();
const fieldsStore = useFieldsStore();
const revisionsDrawerDetail = ref<Vue>(null);
const {
isNew,
@@ -167,15 +183,20 @@ export default defineComponent({
} = useItem(ref('directus_files'), primaryKey);
const hasEdits = computed<boolean>(() => Object.keys(edits.value).length > 0);
const confirmDelete = ref(false);
const cacheBuster = ref(nanoid());
const editActive = ref(false);
const previewActive = ref(false);
// These are the fields that will be prevented from showing up in the form
const fieldsBlacklist: string[] = ['type', 'width', 'height', 'filesize', 'checksum'];
const formFields = computed(() => {
return fieldsStore
.getFieldsForCollection('directus_files')
.filter((field: Field) => fieldsBlacklist.includes(field.field) === false);
});
return {
item,
loading,
@@ -197,6 +218,8 @@ export default defineComponent({
cacheBuster,
editActive,
previewActive,
revisionsDrawerDetail,
formFields,
};
function changeCacheBuster() {
@@ -222,6 +245,8 @@ export default defineComponent({
async function saveAndStay() {
const savedItem: Record<string, any> = await save();
revisionsDrawerDetail.value?.$data?.refresh?.();
if (props.primaryKey === '+') {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const newPrimaryKey = savedItem.id;

View File

@@ -2,10 +2,10 @@ import { RouteConfig } from 'vue-router';
import { replaceRoutes } from '@/router';
import modules from './index';
const moduleRoutes: RouteConfig[] = modules
const moduleRoutes = modules
.map((module) => module.routes)
.filter((r) => r)
.flat();
.flat() as RouteConfig[];
replaceRoutes((routes) => insertBeforeProjectWildcard(routes, moduleRoutes));

View File

@@ -26,6 +26,7 @@
icon="box"
:title="$t('no_collections')"
v-if="items.length === 0"
center
>
{{ $t('no_collections_copy_admin') }}
@@ -57,7 +58,7 @@
/>
</template>
<template #item.collection="{ item }">
<template #item.name="{ item }">
<span
class="collection"
:class="{
@@ -249,10 +250,6 @@ export default defineComponent({
display: contents;
}
.v-info {
margin: 20vh 0;
}
.header-icon {
--v-button-color-disabled: var(--warning);
--v-button-background-color-disabled: var(--warning-25);

View File

@@ -17,6 +17,10 @@
:edits="value.options"
@input="emitValue('options', $event)"
/>
<v-notice v-else>
{{ $t('no_options_available') }}
</v-notice>
</div>
</template>

View File

@@ -19,6 +19,10 @@
:edits="value.options"
@input="emitValue('options', $event)"
/>
<v-notice v-else>
{{ $t('no_options_available') }}
</v-notice>
</div>
</template>

View File

@@ -216,6 +216,10 @@ export default defineComponent({
if (field.value.id === null) {
await fieldsStore.createField(props.collection, field.value);
} else {
if (field.value.hasOwnProperty('name')) {
delete field.value.name;
}
await fieldsStore.updateField(
props.existingField.collection,
props.existingField.field,

View File

@@ -6,6 +6,7 @@
handle=".drag-handle"
group="fields"
@change="($event) => handleChange($event, 'visible')"
:set-data="hideDragImage"
>
<template #header>
<div class="group-name">Visible Fields</div>
@@ -33,6 +34,7 @@
:value="sortedHiddenFields"
handle=".drag-handle"
group="fields"
:set-data="hideDragImage"
@change="($event) => handleChange($event, 'hidden')"
>
<template #header>
@@ -74,6 +76,7 @@ import useFieldsStore from '@/stores/fields/';
import FieldSelect from '../field-select/';
import FieldSetup from '../field-setup/';
import { sortBy } from 'lodash';
import hideDragImage from '@/utils/hide-drag-image';
type DraggableEvent = {
moved?: {
@@ -125,6 +128,7 @@ export default defineComponent({
editingField,
openFieldSetup,
closeFieldSetup,
hideDragImage,
};
function handleChange(event: DraggableEvent, location: 'visible' | 'hidden') {

View File

@@ -43,6 +43,7 @@
<div class="presets-browse">
<v-info
center
type="warning"
v-if="presets.length === 0"
:title="$t('no_presets')"
@@ -314,8 +315,4 @@ export default defineComponent({
.default {
color: var(--foreground-subdued);
}
.v-info {
margin: 20vh 0;
}
</style>

View File

@@ -466,7 +466,7 @@ export default defineComponent({
field: 'divider',
name: i18n.t('divider'),
interface: 'divider',
width: 'full',
width: 'fill',
options: {
title: i18n.t('layout_preview'),
},

View File

@@ -84,11 +84,29 @@ export default defineComponent({
const selection = ref<Item[]>([]);
const { viewOptions, viewQuery } = useCollectionPreset(ref('directus_webhooks'));
const { viewType, viewOptions, viewQuery } = useCollectionPreset(ref('directus_webhooks'));
const { addNewLink, batchLink } = useLinks();
const { confirmDelete, deleting, batchDelete } = useBatchDelete();
const { breadcrumb } = useBreadcrumb();
if (viewType.value === null) {
viewType.value = 'tabular';
}
if (viewOptions.value === null && viewType.value === 'tabular') {
viewOptions.value = {
widths: {
status: 50,
},
};
}
if (viewQuery.value === null && viewType.value === 'tabular') {
viewQuery.value = {
fields: ['status', 'http_action', 'url'],
};
}
return {
addNewLink,
batchLink,

View File

@@ -177,7 +177,7 @@ export default defineComponent({
confirmDelete.value = false;
const batchPrimaryKeys = selection.value.map((item) => item.id).join();
const batchPrimaryKeys = selection.value;
await api.delete(`/${currentProjectKey}/users/${batchPrimaryKeys}`);
@@ -197,7 +197,7 @@ export default defineComponent({
const batchLink = computed<string>(() => {
const currentProjectKey = projectsStore.state.currentProjectKey;
const batchPrimaryKeys = selection.value.map((item) => item.id).join();
const batchPrimaryKeys = selection.value;
return `/${currentProjectKey}/users/${batchPrimaryKeys}`;
});

View File

@@ -76,6 +76,12 @@
v-if="isBatch === false && isNew === false"
collection="directus_users"
:primary-key="primaryKey"
ref="revisionsDrawerDetail"
/>
<comments-drawer-detail
v-if="isBatch === false && isNew === false"
collection="directus_users"
:primary-key="primaryKey"
/>
</template>
</private-view>
@@ -88,6 +94,7 @@ import UsersNavigation from '../../components/navigation/';
import { i18n } from '@/lang';
import router from '@/router';
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail';
import CommentsDrawerDetail from '@/views/private/components/comments-drawer-detail';
import useItem from '@/composables/use-item';
import SaveOptions from '@/views/private/components/save-options';
@@ -97,7 +104,7 @@ type Values = {
export default defineComponent({
name: 'users-detail',
components: { UsersNavigation, RevisionsDrawerDetail, SaveOptions },
components: { UsersNavigation, RevisionsDrawerDetail, SaveOptions, CommentsDrawerDetail },
props: {
primaryKey: {
type: String,
@@ -110,6 +117,8 @@ export default defineComponent({
const { primaryKey } = toRefs(props);
const { breadcrumb } = useBreadcrumb();
const revisionsDrawerDetail = ref<Vue>(null);
const {
isNew,
edits,
@@ -145,6 +154,7 @@ export default defineComponent({
saveAndAddNew,
saveAsCopyAndNavigate,
isBatch,
revisionsDrawerDetail,
};
function useBreadcrumb() {
@@ -166,6 +176,8 @@ export default defineComponent({
async function saveAndStay() {
const savedItem: Record<string, any> = await save();
revisionsDrawerDetail.value?.$data?.refresh?.();
if (props.primaryKey === '+') {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const newPrimaryKey = savedItem.id;