Refactor bookmark query navigation (#6170)

* Revert back to using query params for bookmarks

Aka "this hurts so much"

* Fix collection navigation active state

* Add active and query props to v-button

Also unify active and activated state.

* Remove not needed exact prop from collections navigation
This commit is contained in:
Nicola Krumschmidt
2021-06-10 21:11:01 +02:00
committed by GitHub
parent 976baa7206
commit f55a2072e1
19 changed files with 142 additions and 103 deletions

View File

@@ -4,19 +4,17 @@
<component
v-focus="autofocus"
:is="component"
:active-class="!exact && to ? 'activated' : null"
:exact-active-class="exact && to ? 'activated' : null"
:download="download"
class="button"
:class="[
sizeClass,
`align-${align}`,
{
active: isActiveRoute,
rounded,
icon,
outlined,
loading,
active,
dashed,
tile,
'full-width': fullWidth,
@@ -45,10 +43,11 @@
<script lang="ts">
import { defineComponent, computed, PropType } from 'vue';
import { RouteLocation } from 'vue-router';
import { RouteLocation, useRoute, useLink } from 'vue-router';
import useSizeClass, { sizeProps } from '@/composables/size-class';
import { useGroupable } from '@/composables/groupable';
import { notEmpty } from '@/utils/is-empty';
import { isEqual } from 'lodash';
export default defineComponent({
emits: ['click'],
@@ -93,10 +92,18 @@ export default defineComponent({
type: String,
default: null,
},
active: {
type: Boolean,
default: undefined,
},
exact: {
type: Boolean,
default: false,
},
query: {
type: Boolean,
default: false,
},
secondary: {
type: Boolean,
default: false,
@@ -125,6 +132,9 @@ export default defineComponent({
...sizeProps,
},
setup(props, { emit }) {
const route = useRoute();
const { route: linkRoute, isActive, isExactActive } = useLink(props);
const sizeClass = useSizeClass(props);
const component = computed<'a' | 'router-link' | 'button'>(() => {
@@ -139,7 +149,23 @@ export default defineComponent({
group: 'item-group',
});
return { sizeClass, onClick, component, active, toggle };
const isActiveRoute = computed(() => {
if (props.active !== undefined) return props.active;
if (props.to) {
const isQueryActive = !props.query || isEqual(route.query, linkRoute.value.query);
if (!props.exact) {
return (isActive.value && isQueryActive) || active.value;
} else {
return (isExactActive.value && isQueryActive) || active.value;
}
}
return false;
});
return { sizeClass, onClick, component, isActiveRoute, toggle };
function onClick(event: MouseEvent) {
if (props.loading === true) return;
@@ -157,11 +183,11 @@ export default defineComponent({
--v-button-height: 44px;
--v-button-color: var(--foreground-inverted);
--v-button-color-hover: var(--foreground-inverted);
--v-button-color-activated: var(--foreground-inverted);
--v-button-color-active: var(--foreground-inverted);
--v-button-color-disabled: var(--foreground-subdued);
--v-button-background-color: var(--primary);
--v-button-background-color-hover: var(--primary-125);
--v-button-background-color-activated: var(--primary);
--v-button-background-color-active: var(--primary);
--v-button-background-color-disabled: var(--background-normal);
--v-button-font-size: 16px;
--v-button-font-weight: 600;
@@ -177,10 +203,10 @@ export default defineComponent({
.secondary {
--v-button-color: var(--foreground-normal);
--v-button-color-hover: var(--foreground-normal);
--v-button-color-activated: var(--foreground-normal);
--v-button-color-active: var(--foreground-normal);
--v-button-background-color: var(--border-subdued);
--v-button-background-color-hover: var(--background-normal-alt);
--v-button-background-color-activated: var(--background-normal-alt);
--v-button-background-color-active: var(--background-normal-alt);
}
.v-button.full-width {
@@ -248,7 +274,7 @@ export default defineComponent({
background-color: transparent;
}
.outlined:not(.activated):hover {
.outlined:not(.active):hover {
color: var(--v-button-background-color-hover);
background-color: transparent;
border-color: var(--v-button-background-color-hover);
@@ -338,12 +364,11 @@ export default defineComponent({
--v-progress-circular-background-color: transparent;
}
.activated,
.active {
--v-button-color: var(--v-button-color-activated) !important;
--v-button-color-hover: var(--v-button-color-activated) !important;
--v-button-background-color: var(--v-button-background-color-activated) !important;
--v-button-background-color-hover: var(--v-button-background-color-activated) !important;
--v-button-color: var(--v-button-color-active) !important;
--v-button-color-hover: var(--v-button-color-active) !important;
--v-button-background-color: var(--v-button-background-color-active) !important;
--v-button-background-color-hover: var(--v-button-background-color-active) !important;
}
.tile {

View File

@@ -149,7 +149,7 @@ body {
.header-icon {
--v-button-background-color: var(--background-normal);
--v-button-background-color-activated: var(--background-normal);
--v-button-background-color-active: var(--background-normal);
--v-button-background-color-hover: var(--background-normal-alt);
--v-button-color-disabled: var(--foreground-normal);
}

View File

@@ -2,12 +2,10 @@
<component
:is="component"
v-bind="disabled === false && $attrs"
:active-class="!exact && to ? 'active' : null"
:exact-active-class="exact && to ? 'active' : null"
class="v-list-item"
:to="to"
:class="{
active,
active: isActiveRoute,
dense,
link: isLink,
disabled,
@@ -24,9 +22,10 @@
</template>
<script lang="ts">
import { RouteLocation } from 'vue-router';
import { RouteLocation, useLink, useRoute } from 'vue-router';
import { defineComponent, PropType, computed } from 'vue';
import { useGroupable } from '@/composables/groupable';
import { isEqual } from 'lodash';
export default defineComponent({
props: {
@@ -56,7 +55,7 @@ export default defineComponent({
},
active: {
type: Boolean,
default: false,
default: undefined,
},
dashed: {
type: Boolean,
@@ -66,6 +65,10 @@ export default defineComponent({
type: Boolean,
default: false,
},
query: {
type: Boolean,
default: false,
},
download: {
type: String,
default: null,
@@ -80,6 +83,10 @@ export default defineComponent({
},
},
setup(props) {
const route = useRoute();
const { route: linkRoute, isActive, isExactActive } = useLink(props);
const component = computed<string>(() => {
if (props.to) return 'router-link';
if (props.href) return 'a';
@@ -92,7 +99,23 @@ export default defineComponent({
const isLink = computed(() => Boolean(props.to || props.href || props.clickable));
return { component, isLink };
const isActiveRoute = computed(() => {
if (props.active !== undefined) return props.active;
if (props.to) {
const isQueryActive = !props.query || isEqual(route.query, linkRoute.value.query);
if (!props.exact) {
return isActive.value && isQueryActive;
} else {
return isExactActive.value && isQueryActive;
}
}
return false;
});
return { component, isLink, isActiveRoute };
},
});
</script>

View File

@@ -104,7 +104,7 @@ export default defineComponent({
if (!relatedCollection.value || !primaryKeyField.value) return null;
const primaryKey = item[primaryKeyField.value.field];
return `/collections/${relatedCollection.value}/-/${encodeURIComponent(primaryKey)}`;
return `/collections/${relatedCollection.value}/${encodeURIComponent(primaryKey)}`;
}
},
});

View File

@@ -519,8 +519,8 @@ textarea {
--v-button-color: var(--foreground-subdued);
--v-button-background-color-hover: var(--border-normal);
--v-button-color-hover: var(--foreground-normal);
--v-button-background-color-activated: var(--border-normal);
--v-button-color-activated: var(--foreground-normal);
--v-button-background-color-active: var(--border-normal);
--v-button-color-active: var(--foreground-normal);
}
}

View File

@@ -188,7 +188,7 @@ export default defineLayout<LayoutOptions>({
const endpoint = collection.value.startsWith('directus')
? collection.value.substring(9)
: `/collections/${collection.value}`;
router.push(`${endpoint}/-/${primaryKey}`);
router.push(`${endpoint}/${primaryKey}`);
},
async eventChange(info) {
if (!collection.value || !startDateField.value || !startDateFieldInfo.value) return;

View File

@@ -81,7 +81,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
});
const newLink = computed(() => {
return `/collections/${collection.value}/-/+`;
return `/collections/${collection.value}/+`;
});
const showingCount = computed(() => {
@@ -273,7 +273,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
function getLinkForItem(item: Record<string, any>) {
if (!primaryKeyField.value) return;
return `/collections/${props.collection}/-/${encodeURIComponent(item[primaryKeyField.value.field])}`;
return `/collections/${props.collection}/${encodeURIComponent(item[primaryKeyField.value.field])}`;
}
function selectAll() {

View File

@@ -362,7 +362,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
} else {
const primaryKey = item[primaryKeyField.value.field];
router.push(`/collections/${collection.value}/-/${encodeURIComponent(primaryKey)}`);
router.push(`/collections/${collection.value}/${encodeURIComponent(primaryKey)}`);
}
}

View File

@@ -89,7 +89,7 @@ export default defineComponent({
const openItemLink = computed(() => {
if (!item.value) return;
return `/collections/${item.value.collection}/-/${encodeURIComponent(item.value.item)}`;
return `/collections/${item.value.collection}/${encodeURIComponent(item.value.item)}`;
});
watch(() => props.primaryKey, loadActivity, { immediate: true });

View File

@@ -1,5 +1,5 @@
<template>
<v-list-item :to="bookmark.to" class="bookmark" @contextmenu.prevent.stop="activateContextMenu">
<v-list-item :to="bookmark.to" query class="bookmark" @contextmenu.prevent.stop="activateContextMenu">
<v-list-item-icon><v-icon name="bookmark" /></v-list-item-icon>
<v-list-item-content>
<v-text-overflow :text="bookmark.bookmark" />
@@ -142,7 +142,7 @@ export default defineComponent({
let navigateTo: string | null = null;
if (+route.query?.bookmark === props.bookmark.id) {
navigateTo = `/collections/${props.bookmark.collection}/-`;
navigateTo = `/collections/${props.bookmark.collection}`;
}
await presetsStore.delete(props.bookmark.id);

View File

@@ -11,7 +11,7 @@
<template
v-if="(group.name === undefined || group.name === null) && group.accordion === 'always_open' && index === 0"
>
<v-list-item :exact="exact" v-for="navItem in group.items" :key="navItem.to" :to="navItem.to">
<v-list-item v-for="navItem in group.items" :key="navItem.to" :to="navItem.to" query>
<v-list-item-icon><v-icon :name="navItem.icon" :color="navItem.color" /></v-list-item-icon>
<v-list-item-content>
<v-text-overflow :text="navItem.name" />
@@ -26,7 +26,7 @@
:label="group.name || null"
@update:model-value="toggleActive(group.name)"
>
<v-list-item :exact="exact" v-for="navItem in group.items" :key="navItem.to" :to="navItem.to">
<v-list-item v-for="navItem in group.items" :key="navItem.to" :to="navItem.to" query>
<v-list-item-icon><v-icon :name="navItem.icon" :color="navItem.color" /></v-list-item-icon>
<v-list-item-content>
<v-text-overflow :text="navItem.name" />
@@ -37,7 +37,7 @@
</template>
</template>
<v-list-item v-else :exact="exact" v-for="navItem in navItems" :key="navItem.to" :to="navItem.to">
<v-list-item v-else v-for="navItem in navItems" :key="navItem.to" :to="navItem.to" query>
<v-list-item-icon><v-icon :name="navItem.icon" :color="navItem.color" /></v-list-item-icon>
<v-list-item-content>
<v-text-overflow :text="navItem.name" />
@@ -65,13 +65,7 @@
<template v-if="hiddenShown">
<v-divider />
<v-list-item
class="hidden-collection"
:exact="exact"
v-for="navItem in hiddenNavItems"
:key="navItem.to"
:to="navItem.to"
>
<v-list-item class="hidden-collection" v-for="navItem in hiddenNavItems" :key="navItem.to" :to="navItem.to" query>
<v-list-item-icon><v-icon :name="navItem.icon" :color="navItem.color" /></v-list-item-icon>
<v-list-item-content>
<v-text-overflow :text="navItem.name" />
@@ -105,12 +99,6 @@ import { useSearch } from '../composables/use-search';
export default defineComponent({
components: { NavigationBookmark },
props: {
exact: {
type: Boolean,
default: false,
},
},
setup() {
const { t } = useI18n();
@@ -142,7 +130,7 @@ export default defineComponent({
return {
...preset,
to: `/collections/${preset.collection}/${preset.id}`,
to: `/collections/${preset.collection}?bookmark=${preset.id}`,
scope,
};
}),

View File

@@ -27,7 +27,7 @@ function collectionToNavItem(collection: Collection): NavItem {
icon: collection.meta?.icon || 'label',
color: collection.meta?.color,
note: collection.meta?.note || null,
to: `/collections/${collection.collection}/-`,
to: `/collections/${collection.collection}`,
};
}

View File

@@ -1,4 +1,5 @@
import { defineModule } from '@/modules/define';
import { addQueryToPath } from '@/utils/add-query-to-path';
import RouterPass from '@/utils/router-passthrough';
import { NavigationGuard } from 'vue-router';
import CollectionOrItem from './routes/collection-or-item.vue';
@@ -6,7 +7,7 @@ import Item from './routes/item.vue';
import ItemNotFound from './routes/not-found.vue';
import Overview from './routes/overview.vue';
const checkForSystem: NavigationGuard = (to) => {
const checkForSystem: NavigationGuard = (to, from) => {
if (!to.params?.collection) return;
if (to.params.collection === 'directus_users') {
@@ -40,6 +41,15 @@ const checkForSystem: NavigationGuard = (to) => {
return '/settings/webhooks';
}
}
if (
'bookmark' in from.query &&
typeof from.query.bookmark === 'string' &&
'bookmark' in to.query === false &&
to.params.collection === from.params.collection
) {
return addQueryToPath(to.fullPath, { bookmark: from.query.bookmark });
}
};
export default defineModule({
@@ -54,23 +64,16 @@ export default defineModule({
},
{
path: ':collection',
redirect: (to) => ({
name: 'collections-collection',
params: {
collection: to.params.collection,
bookmark: '-',
},
}),
},
{
path: ':collection/:bookmark',
component: RouterPass,
children: [
{
name: 'collections-collection',
path: '',
component: CollectionOrItem,
props: true,
props: (route) => ({
collection: route.params.collection,
bookmark: route.query.bookmark,
}),
beforeEnter: checkForSystem,
},
{

View File

@@ -1,6 +1,6 @@
<template>
<collections-not-found v-if="!currentCollection || collection.startsWith('directus_')" />
<private-view v-else :title="bookmark !== '-' ? bookmarkTitle : currentCollection.name">
<private-view v-else :title="bookmark ? bookmarkTitle : currentCollection.name">
<template #title-outer:prepend>
<v-button class="header-icon" rounded icon secondary disabled>
<v-icon :name="currentCollection.icon" :color="currentCollection.color" />
@@ -8,14 +8,14 @@
</template>
<template #headline>
<v-breadcrumb v-if="bookmark !== '-'" :items="breadcrumb" />
<v-breadcrumb v-if="bookmark" :items="breadcrumb" />
<v-breadcrumb v-else :items="[{ name: t('collections'), to: '/collections' }]" />
</template>
<template #title-outer:append>
<div class="bookmark-controls">
<bookmark-add
v-if="bookmark === '-'"
v-if="!bookmark"
class="add"
v-model="bookmarkDialogActive"
@save="createBookmark"
@@ -57,7 +57,7 @@
</bookmark-add>
<v-icon
v-if="bookmark !== '-' && !bookmarkSaving && bookmarkSaved === false"
v-if="bookmark && !bookmarkSaving && bookmarkSaved === false"
name="settings_backup_restore"
clickable
@click="clearLocalSave"
@@ -164,7 +164,7 @@
<v-info
type="warning"
v-if="bookmark !== '-' && bookmarkExists === false"
v-if="bookmark && bookmarkExists === false"
:title="t('bookmark_doesnt_exist')"
icon="bookmark"
center
@@ -194,7 +194,7 @@
{{ t('no_items_copy') }}
<template #append v-if="createAllowed">
<v-button :to="`/collections/${collection}/-/+`">{{ t('create_item') }}</v-button>
<v-button :to="`/collections/${collection}/+`">{{ t('create_item') }}</v-button>
</template>
</v-info>
</template>
@@ -296,7 +296,7 @@ export default defineComponent({
const permissionsStore = usePermissionsStore();
const { collection } = toRefs(props);
const bookmarkID = computed(() => (props.bookmark !== '-' ? +props.bookmark : null));
const bookmarkID = computed(() => (props.bookmark ? +props.bookmark : null));
const { selection } = useSelection();
const { info: currentCollection } = useCollection(collection);
@@ -413,7 +413,7 @@ export default defineComponent({
const breadcrumb = computed(() => [
{
name: currentCollection.value?.name,
to: `/collections/${props.collection}/-`,
to: `/collections/${props.collection}`,
},
]);
@@ -493,11 +493,11 @@ export default defineComponent({
function useLinks() {
const addNewLink = computed<string>(() => {
return `/collections/${props.collection}/-/+`;
return `/collections/${props.collection}/+`;
});
const currentCollectionLink = computed<string>(() => {
return `/collections/${props.collection}/-`;
return `/collections/${props.collection}`;
});
return { addNewLink, currentCollectionLink };
@@ -521,7 +521,7 @@ export default defineComponent({
try {
const newBookmark = await saveCurrentAsBookmark({ bookmark: name });
router.push(`/collections/${newBookmark.collection}/${newBookmark.id}`);
router.push(`/collections/${newBookmark.collection}?bookmark=${newBookmark.id}`);
bookmarkDialogActive.value = false;
} catch (err) {
@@ -617,7 +617,7 @@ export default defineComponent({
.header-icon.secondary {
--v-button-background-color: var(--background-normal);
--v-button-background-color-activated: var(--background-normal);
--v-button-background-color-active: var(--background-normal);
--v-button-background-color-hover: var(--background-normal-alt);
}

View File

@@ -419,7 +419,7 @@ export default defineComponent({
const breadcrumb = computed(() => [
{
name: collectionInfo.value?.name,
to: `/collections/${props.collection}/-`,
to: `/collections/${props.collection}`,
},
]);
@@ -431,7 +431,7 @@ export default defineComponent({
try {
await save();
if (props.singleton === false) router.push(`/collections/${props.collection}/-`);
if (props.singleton === false) router.push(`/collections/${props.collection}`);
} catch {
// Save shows unexpected error dialog
}
@@ -448,7 +448,7 @@ export default defineComponent({
if (props.primaryKey === '+') {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const newPrimaryKey = savedItem[primaryKeyField.value!.field];
router.replace(`/collections/${props.collection}/-/${encodeURIComponent(newPrimaryKey)}`);
router.replace(`/collections/${props.collection}/${encodeURIComponent(newPrimaryKey)}`);
}
} catch {
// Save shows unexpected error dialog
@@ -464,7 +464,7 @@ export default defineComponent({
if (isNew.value === true) {
refresh();
} else {
router.push(`/collections/${props.collection}/-/+`);
router.push(`/collections/${props.collection}/+`);
}
} catch {
// Save shows unexpected error dialog
@@ -474,7 +474,7 @@ export default defineComponent({
async function saveAsCopyAndNavigate() {
try {
const newPrimaryKey = await saveAsCopy();
if (newPrimaryKey) router.push(`/collections/${props.collection}/-/${encodeURIComponent(newPrimaryKey)}`);
if (newPrimaryKey) router.push(`/collections/${props.collection}/${encodeURIComponent(newPrimaryKey)}`);
} catch {
// Save shows unexpected error dialog
}
@@ -483,7 +483,7 @@ export default defineComponent({
async function deleteAndQuit() {
try {
await remove();
router.push(`/collections/${props.collection}/-`);
router.push(`/collections/${props.collection}`);
} catch {
// `remove` will show the unexpected error dialog
}
@@ -494,7 +494,7 @@ export default defineComponent({
await archive();
if (isArchived.value === true) {
router.push(`/collections/${props.collection}/-`);
router.push(`/collections/${props.collection}`);
} else {
confirmArchive.value = false;
}
@@ -538,7 +538,7 @@ export default defineComponent({
.header-icon.secondary {
--v-button-background-color: var(--background-normal);
--v-button-color-disabled: var(--foreground-normal);
--v-button-color-activated: var(--foreground-normal);
--v-button-color-active: var(--foreground-normal);
}
.v-form {

View File

@@ -9,7 +9,7 @@ providing enough consistency between views.
```html
<header-bar title="Global Settings">
<template #actions>
<v-button to="/collections/settings/-/+">
<v-button to="/collections/settings/+">
<v-icon name="add" />
</v-button>
</template>

View File

@@ -14,7 +14,7 @@
:style="
module.color
? {
'--v-button-color-activated': module.color,
'--v-button-color-active': module.color,
}
: null
"
@@ -116,10 +116,10 @@ body {
.v-button {
--v-button-color: var(--module-icon);
--v-button-color-hover: var(--white);
--v-button-color-activated: var(--module-icon-alt);
--v-button-color-active: var(--module-icon-alt);
--v-button-background-color: var(--module-background);
--v-button-background-color-hover: var(--module-background);
--v-button-background-color-activated: var(--module-background-alt);
--v-button-background-color-active: var(--module-background-alt);
}
}
</style>

View File

@@ -248,7 +248,7 @@ export default defineComponent({
&.branded :deep(.v-button) {
--v-button-background-color: var(--foreground-normal-alt);
--v-button-background-color-hover: var(--foreground-normal-alt);
--v-button-background-color-activated: var(--foreground-normal-alt);
--v-button-background-color-active: var(--foreground-normal-alt);
}
&.branded :deep(.v-input) {

View File

@@ -90,19 +90,19 @@ the loading state.
#### CSS Variables
| Variable | Default |
| --------------------------------------- | ---------------------------- |
| `--v-button-width` | `auto` |
| `--v-button-height` | `44px` |
| `--v-button-color` | `var(--foreground-inverted)` |
| `--v-button-color-hover` | `var(--foreground-inverted)` |
| `--v-button-color-activated` | `var(--foreground-inverted)` |
| `--v-button-color-disabled` | `var(--foreground-subdued)` |
| `--v-button-background-color` | `var(--primary)` |
| `--v-button-background-color-hover` | `var(--primary-125)` |
| `--v-button-background-color-activated` | `var(--primary)` |
| `--v-button-background-color-disabled` | `var(--background-normal)` |
| `--v-button-font-size` | `16px` |
| `--v-button-font-weight` | `600` |
| `--v-button-line-height` | `22px` |
| `--v-button-min-width` | `140px` |
| Variable | Default |
| -------------------------------------- | ---------------------------- |
| `--v-button-width` | `auto` |
| `--v-button-height` | `44px` |
| `--v-button-color` | `var(--foreground-inverted)` |
| `--v-button-color-hover` | `var(--foreground-inverted)` |
| `--v-button-color-active` | `var(--foreground-inverted)` |
| `--v-button-color-disabled` | `var(--foreground-subdued)` |
| `--v-button-background-color` | `var(--primary)` |
| `--v-button-background-color-hover` | `var(--primary-125)` |
| `--v-button-background-color-active` | `var(--primary)` |
| `--v-button-background-color-disabled` | `var(--background-normal)` |
| `--v-button-font-size` | `16px` |
| `--v-button-font-weight` | `600` |
| `--v-button-line-height` | `22px` |
| `--v-button-min-width` | `140px` |