mirror of
https://github.com/directus/directus.git
synced 2026-01-30 10:37:56 -05:00
Merge branch 'main' into relational-sort
This commit is contained in:
2
api/package-lock.json
generated
2
api/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "directus",
|
||||
"version": "9.0.0-beta.13",
|
||||
"version": "9.0.0-rc.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "directus",
|
||||
"version": "9.0.0-beta.13",
|
||||
"version": "9.0.0-rc.0",
|
||||
"license": "GPL-3.0-only",
|
||||
"homepage": "https://github.com/directus/next#readme",
|
||||
"description": "Directus is a real-time API and App dashboard for managing SQL database content.",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export const drivers = {
|
||||
sqlite3: 'SQLite',
|
||||
mysql: 'MySQL / MariaDB / Aurora',
|
||||
pg: 'PostgreSQL / Redshift',
|
||||
oracledb: 'Oracle Database',
|
||||
mssql: 'Microsoft SQL Server',
|
||||
mysql: 'MySQL / MariaDB / Aurora',
|
||||
sqlite3: 'SQLite (Beta)',
|
||||
oracledb: 'Oracle Database (Alpha)',
|
||||
mssql: 'Microsoft SQL Server (Alpha)',
|
||||
};
|
||||
|
||||
export function getDriverForClient(client: string): keyof typeof drivers | null {
|
||||
|
||||
@@ -215,7 +215,7 @@ router.get(
|
||||
'/oauth',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
const providers = toArray(env.OAUTH_PROVIDERS);
|
||||
res.locals.payload = { data: providers.length > 0 ? providers : null };
|
||||
res.locals.payload = { data: env.OAUTH_PROVIDERS ? providers : null };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
|
||||
@@ -49,7 +49,7 @@ router.get(
|
||||
router.get(
|
||||
'/me',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (!req.accountability?.user || !req.accountability?.role) {
|
||||
if (!req.accountability?.user) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
|
||||
@@ -58,11 +58,21 @@ router.get(
|
||||
if (!req.accountability?.user) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
const service = new UsersService({ accountability: req.accountability });
|
||||
|
||||
const item = await service.readByKey(req.accountability.user, req.sanitizedQuery);
|
||||
try {
|
||||
const item = await service.readByKey(req.accountability.user, req.sanitizedQuery);
|
||||
res.locals.payload = { data: item || null };
|
||||
} catch (error) {
|
||||
if (error instanceof ForbiddenException) {
|
||||
res.locals.payload = { data: { id: req.accountability.user } };
|
||||
return next();
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
|
||||
@@ -101,7 +101,7 @@ fields:
|
||||
display: user
|
||||
- collection: directus_files
|
||||
field: modified_on
|
||||
interface: dateTime
|
||||
interface: datetime
|
||||
locked: true
|
||||
special: date-updated
|
||||
width: half
|
||||
@@ -111,4 +111,4 @@ fields:
|
||||
display: datetime
|
||||
- collection: directus_files
|
||||
field: created_by
|
||||
display: user
|
||||
display: user
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { Filter, Accountability } from '../types';
|
||||
import { deepMap } from './deep-map';
|
||||
import { toArray } from '../utils/to-array';
|
||||
|
||||
export function parseFilter(filter: Filter, accountability: Accountability | null) {
|
||||
return deepMap(filter, (val: any, key: string) => {
|
||||
if (val === 'true') return true;
|
||||
if (val === 'false') return false;
|
||||
|
||||
if (key === '_in' || key === '_nin') return val.split(',').filter((val: any) => val);
|
||||
if (key === '_in' || key === '_nin') {
|
||||
if (typeof val === 'string' && val.includes(',')) return val.split(',');
|
||||
else return toArray(val);
|
||||
}
|
||||
|
||||
if (val === '$NOW') return new Date();
|
||||
if (val === '$CURRENT_USER') return accountability?.user || null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Accountability, Query, Sort, Filter, Meta } from '../types';
|
||||
import logger from '../logger';
|
||||
import { parseFilter } from '../utils/parse-filter';
|
||||
import { flatten } from 'lodash';
|
||||
|
||||
export function sanitizeQuery(
|
||||
rawQuery: Record<string, any>,
|
||||
@@ -75,6 +76,9 @@ function sanitizeFields(rawFields: any) {
|
||||
if (typeof rawFields === 'string') fields = rawFields.split(',');
|
||||
else if (Array.isArray(rawFields)) fields = rawFields as string[];
|
||||
|
||||
// Case where array item includes CSV (fe fields[]=id,name):
|
||||
fields = flatten(fields.map((field) => (field.includes(',') ? field.split(',') : field)));
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const querySchema = Joi.object({
|
||||
offset: Joi.number(),
|
||||
page: Joi.number(),
|
||||
single: Joi.boolean(),
|
||||
meta: Joi.array().items(Joi.string().valid('total_count', 'result_count')),
|
||||
meta: Joi.array().items(Joi.string().valid('total_count', 'filter_count')),
|
||||
search: Joi.string(),
|
||||
export: Joi.string().valid('json', 'csv'),
|
||||
deep: Joi.link('#query'),
|
||||
|
||||
164
app/package-lock.json
generated
164
app/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/app",
|
||||
"version": "9.0.0-beta.13",
|
||||
"version": "9.0.0-rc.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -6601,51 +6601,6 @@
|
||||
"tslint": "^5.20.1",
|
||||
"webpack": "^4.0.0",
|
||||
"yorkie": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"fork-ts-checker-webpack-plugin-v5": {
|
||||
"version": "npm:fork-ts-checker-webpack-plugin@5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.0.tgz",
|
||||
"integrity": "sha512-NEKcI0+osT5bBFZ1SFGzJMQETjQWZrSvMO1g0nAR/w0t328Z41eN8BJEIZyFCl2HsuiJpa9AN474Nh2qLVwGLQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.8.3",
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"chalk": "^4.1.0",
|
||||
"cosmiconfig": "^6.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"fs-extra": "^9.0.0",
|
||||
"memfs": "^3.1.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"schema-utils": "2.7.0",
|
||||
"semver": "^7.3.2",
|
||||
"tapable": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
|
||||
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.4",
|
||||
"ajv": "^6.12.2",
|
||||
"ajv-keywords": "^3.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@vue/cli-plugin-unit-jest": {
|
||||
@@ -6785,17 +6740,6 @@
|
||||
"unique-filename": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
@@ -6879,18 +6823,6 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
@@ -7004,18 +6936,6 @@
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-oouKUQWWHbSihqSD7mhymGPX1OQ4hedzAHyvm8RdyHh6m3oIvoRF+NM45i/bhNOlo8jCnuJhaSUf/6oDjv978g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
@@ -11744,6 +11664,51 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"fork-ts-checker-webpack-plugin-v5": {
|
||||
"version": "npm:fork-ts-checker-webpack-plugin@5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.0.tgz",
|
||||
"integrity": "sha512-NEKcI0+osT5bBFZ1SFGzJMQETjQWZrSvMO1g0nAR/w0t328Z41eN8BJEIZyFCl2HsuiJpa9AN474Nh2qLVwGLQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.8.3",
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"chalk": "^4.1.0",
|
||||
"cosmiconfig": "^6.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"fs-extra": "^9.0.0",
|
||||
"memfs": "^3.1.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"schema-utils": "2.7.0",
|
||||
"semver": "^7.3.2",
|
||||
"tapable": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
|
||||
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.4",
|
||||
"ajv": "^6.12.2",
|
||||
"ajv-keywords": "^3.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"form-data": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||
@@ -20377,6 +20342,43 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-oouKUQWWHbSihqSD7mhymGPX1OQ4hedzAHyvm8RdyHh6m3oIvoRF+NM45i/bhNOlo8jCnuJhaSUf/6oDjv978g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-router": {
|
||||
"version": "3.4.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.6.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@directus/app",
|
||||
"version": "9.0.0-beta.13",
|
||||
"version": "9.0.0-rc.0",
|
||||
"private": false,
|
||||
"description": "Directus is an Open-Source Headless CMS & API for Managing Custom Databases",
|
||||
"author": "Rijk van Zanten <rijk@rngr.org>",
|
||||
|
||||
@@ -14,15 +14,7 @@
|
||||
</template>
|
||||
</v-info>
|
||||
|
||||
<router-view v-else-if="!hydrating && appAccess" />
|
||||
|
||||
<v-info v-else-if="appAccess === false" center :title="$t('no_app_access')" type="danger" icon="block">
|
||||
{{ $t('no_app_access_copy') }}
|
||||
|
||||
<template #append>
|
||||
<v-button to="/logout">Switch User</v-button>
|
||||
</template>
|
||||
</v-info>
|
||||
<router-view v-else-if="!hydrating" />
|
||||
|
||||
<portal-target name="dialog-outlet" transition="transition-dialog" multiple />
|
||||
<portal-target name="menu-outlet" transition="transition-bounce" multiple />
|
||||
@@ -48,7 +40,7 @@ export default defineComponent({
|
||||
const userStore = useUserStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const { hydrating, drawerOpen } = toRefs(appStore.state);
|
||||
const { hydrating, sidebarOpen } = toRefs(appStore.state);
|
||||
|
||||
const brandStyle = computed(() => {
|
||||
return {
|
||||
@@ -73,9 +65,9 @@ export default defineComponent({
|
||||
if (newWidth === oldWidth) return;
|
||||
|
||||
if (newWidth >= 1424) {
|
||||
if (drawerOpen.value === false) drawerOpen.value = true;
|
||||
if (sidebarOpen.value === false) sidebarOpen.value = true;
|
||||
} else {
|
||||
if (drawerOpen.value === true) drawerOpen.value = false;
|
||||
if (sidebarOpen.value === true) sidebarOpen.value = false;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
@@ -88,7 +80,7 @@ export default defineComponent({
|
||||
document.body.classList.remove('light');
|
||||
document.body.classList.remove('auto');
|
||||
|
||||
if (newUser !== undefined && newUser !== null) {
|
||||
if (newUser !== undefined && newUser !== null && newUser.theme) {
|
||||
document.body.classList.add(newUser.theme);
|
||||
} else {
|
||||
// Default to light mode
|
||||
@@ -108,11 +100,6 @@ export default defineComponent({
|
||||
return settingsStore.state?.settings?.custom_css || '';
|
||||
});
|
||||
|
||||
const appAccess = computed(() => {
|
||||
if (!userStore.state.currentUser) return true;
|
||||
return userStore.state.currentUser?.role?.app_access || false;
|
||||
});
|
||||
|
||||
const error = computed(() => appStore.state.error);
|
||||
|
||||
/**
|
||||
@@ -124,7 +111,7 @@ export default defineComponent({
|
||||
axios,
|
||||
});
|
||||
|
||||
return { hydrating, brandStyle, appAccess, error, customCSS };
|
||||
return { hydrating, brandStyle, error, customCSS };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -22,8 +22,7 @@ import VInput from './v-input/';
|
||||
import VItemGroup, { VItem } from './v-item-group';
|
||||
import VList, { VListGroup, VListItem, VListItemContent, VListItemHint, VListItemIcon, VListItemText } from './v-list/';
|
||||
import VMenu from './v-menu/';
|
||||
import VModal from './v-modal/';
|
||||
import VModalHeading from './v-modal/v-modal-heading.vue';
|
||||
import VDrawer from './v-drawer/';
|
||||
import VNotice from './v-notice/';
|
||||
import VOverlay from './v-overlay/';
|
||||
import VPagination from './v-pagination/';
|
||||
@@ -73,8 +72,7 @@ Vue.component('v-list-item-text', VListItemText);
|
||||
Vue.component('v-list-item', VListItem);
|
||||
Vue.component('v-list', VList);
|
||||
Vue.component('v-menu', VMenu);
|
||||
Vue.component('v-modal', VModal);
|
||||
Vue.component('v-modal-heading', VModalHeading);
|
||||
Vue.component('v-drawer', VDrawer);
|
||||
Vue.component('v-notice', VNotice);
|
||||
Vue.component('v-overlay', VOverlay);
|
||||
Vue.component('v-pagination', VPagination);
|
||||
@@ -104,14 +102,14 @@ Vue.component('transition-expand', TransitionExpand);
|
||||
|
||||
import RenderDisplay from '@/views/private/components/render-display';
|
||||
import RenderTemplate from '@/views/private/components/render-template';
|
||||
import DrawerDetail from '@/views/private/components/drawer-detail/';
|
||||
import FilterDrawerDetail from '@/views/private/components/filter-drawer-detail';
|
||||
import SidebarDetail from '@/views/private/components/sidebar-detail/';
|
||||
import FilterSidebarDetail from '@/views/private/components/filter-sidebar-detail';
|
||||
import UserPopover from '@/views/private/components/user-popover';
|
||||
import ValueNull from '@/views/private/components/value-null';
|
||||
|
||||
Vue.component('render-display', RenderDisplay);
|
||||
Vue.component('render-template', RenderTemplate);
|
||||
Vue.component('filter-drawer-detail', FilterDrawerDetail);
|
||||
Vue.component('drawer-detail', DrawerDetail);
|
||||
Vue.component('filter-sidebar-detail', FilterSidebarDetail);
|
||||
Vue.component('sidebar-detail', SidebarDetail);
|
||||
Vue.component('user-popover', UserPopover);
|
||||
Vue.component('value-null', ValueNull);
|
||||
|
||||
@@ -5,24 +5,34 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
/** @NOTE this is not scoped on purpose. The children are outsisde of the tree (portal) */
|
||||
/** @NOTE this is not scoped on purpose. The children are outside of the tree (portal) */
|
||||
.dialog-enter-active,
|
||||
.dialog-leave-active {
|
||||
transition: opacity var(--slow) var(--transition);
|
||||
|
||||
& > *:not(.v-overlay) {
|
||||
&.center > *:not(.v-overlay) {
|
||||
transform: translateY(0px);
|
||||
transition: transform var(--slow) var(--transition-in);
|
||||
}
|
||||
|
||||
&.right > *:not(.v-overlay) {
|
||||
transform: translateX(0px);
|
||||
transition: transform var(--slow) var(--transition-in);
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-enter,
|
||||
.dialog-leave-to {
|
||||
opacity: 0;
|
||||
|
||||
& > *:not(.v-overlay) {
|
||||
&.center > *:not(.v-overlay) {
|
||||
transform: translateY(50px);
|
||||
transition: transform var(--slow) var(--transition-out);
|
||||
}
|
||||
|
||||
&.right > *:not(.v-overlay) {
|
||||
transform: translateX(50px);
|
||||
transition: transform var(--slow) var(--transition-out);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<slot name="activator" v-bind="{ on: () => (_active = true) }" />
|
||||
|
||||
<portal to="dialog-outlet">
|
||||
<div v-if="_active" class="container" :class="[className]" :key="id">
|
||||
<div v-if="_active" class="container" :class="[className, placement]" :key="id">
|
||||
<v-overlay active absolute @click="emitToggle" />
|
||||
<slot />
|
||||
</div>
|
||||
@@ -30,6 +30,11 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'center',
|
||||
validator: (val: string) => ['center', 'right'].includes(val),
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const dialog = ref<HTMLElement | null>(null);
|
||||
@@ -92,11 +97,32 @@ export default defineComponent({
|
||||
left: 0;
|
||||
z-index: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: opacity var(--medium) var(--transition);
|
||||
|
||||
::v-deep > * {
|
||||
z-index: 2;
|
||||
box-shadow: 0px 4px 12px rgba(38, 50, 56, 0.1);
|
||||
}
|
||||
|
||||
&.center {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.nudge > ::v-deep *:not(:first-child) {
|
||||
animation: nudge 200ms;
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
&.nudge > ::v-deep *:not(:first-child) {
|
||||
transform-origin: right;
|
||||
animation: shake 200ms;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .v-card {
|
||||
--v-card-min-width: 540px;
|
||||
@@ -112,15 +138,6 @@ export default defineComponent({
|
||||
.v-overlay {
|
||||
--v-overlay-z-index: 1;
|
||||
}
|
||||
|
||||
&.nudge {
|
||||
animation: nudge 200ms;
|
||||
}
|
||||
|
||||
::v-deep > * {
|
||||
z-index: 2;
|
||||
box-shadow: 0px 4px 12px rgba(38, 50, 56, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes nudge {
|
||||
@@ -136,4 +153,18 @@ export default defineComponent({
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0% {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scaleX(0.98);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
4
app/src/components/v-drawer/index.ts
Normal file
4
app/src/components/v-drawer/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import VDrawer from './v-drawer.vue';
|
||||
|
||||
export { VDrawer };
|
||||
export default VDrawer;
|
||||
@@ -1,20 +1,23 @@
|
||||
<template>
|
||||
<v-dialog v-model="_active" @esc="$emit('esc')" :persistent="persistent">
|
||||
<v-dialog v-model="_active" @esc="$emit('cancel')" :persistent="persistent" placement="right">
|
||||
<template #activator="{ on }">
|
||||
<slot name="activator" v-bind="{ on }" />
|
||||
</template>
|
||||
|
||||
<article class="v-modal" :class="{ 'form-width': formWidth }">
|
||||
<header class="header">
|
||||
<v-icon class="menu-toggle" name="menu" @click="sidebarActive = !sidebarActive" />
|
||||
<h2 class="title">{{ title }}</h2>
|
||||
<slot name="subtitle">
|
||||
<p v-if="subtitle" class="subtitle">{{ subtitle }}</p>
|
||||
</slot>
|
||||
<div class="spacer" />
|
||||
<slot name="header:append" />
|
||||
</header>
|
||||
<div class="content" :class="{ 'no-padding': noPadding }">
|
||||
<article class="v-drawer">
|
||||
<v-button
|
||||
v-if="showCancel"
|
||||
class="cancel"
|
||||
@click="$emit('cancel')"
|
||||
icon
|
||||
rounded
|
||||
secondary
|
||||
v-tooltip.bottom="$t('cancel')"
|
||||
>
|
||||
<v-icon name="close" />
|
||||
</v-button>
|
||||
|
||||
<div class="content">
|
||||
<v-overlay v-if="$slots.sidebar" absolute :active="sidebarActive" @click="sidebarActive = false" />
|
||||
<nav
|
||||
v-if="$slots.sidebar"
|
||||
@@ -25,20 +28,40 @@
|
||||
<slot name="sidebar" />
|
||||
</nav>
|
||||
<main ref="mainEl" class="main">
|
||||
<header-bar :title="title">
|
||||
<template #headline>
|
||||
<slot name="subtitle">
|
||||
<p v-if="subtitle" class="subtitle">{{ subtitle }}</p>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<template #title-outer:prepend>
|
||||
<v-button class="header-icon" rounded icon secondary disabled>
|
||||
<v-icon :name="icon" />
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<template #actions:prepend><slot name="actions:prepend" /></template>
|
||||
<template #actions><slot name="actions" /></template>
|
||||
|
||||
<template #title:append><slot name="header:append" /></template>
|
||||
</header-bar>
|
||||
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
<footer class="footer" v-if="$slots.footer || $scopedSlots.footer">
|
||||
<slot name="footer" v-bind="{ close: () => (_active = false) }" />
|
||||
</footer>
|
||||
</article>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, provide } from '@vue/composition-api';
|
||||
import HeaderBar from '@/views/private/components/header-bar/header-bar.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
HeaderBar,
|
||||
},
|
||||
model: {
|
||||
prop: 'active',
|
||||
event: 'toggle',
|
||||
@@ -60,18 +83,12 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
noPadding: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
formWidth: {
|
||||
// If the modal is used to just render a form, it needs to be a little smaller to
|
||||
// allow the form to be rendered in it's correct full size
|
||||
type: Boolean,
|
||||
default: false,
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'box',
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
setup(props, { emit, listeners }) {
|
||||
const sidebarActive = ref(false);
|
||||
const localActive = ref(false);
|
||||
|
||||
@@ -89,63 +106,47 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
return { sidebarActive, _active, mainEl };
|
||||
const showCancel = computed(() => {
|
||||
return listeners.hasOwnProperty('cancel');
|
||||
});
|
||||
|
||||
return { sidebarActive, _active, mainEl, showCancel };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
--v-modal-max-width: 916px;
|
||||
--v-drawer-max-width: 856px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/mixins/breakpoint';
|
||||
|
||||
.v-modal {
|
||||
.v-drawer {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: calc(100% - 16px);
|
||||
max-width: var(--v-modal-max-width);
|
||||
height: calc(100% - 16px);
|
||||
max-height: 800px;
|
||||
max-width: var(--v-drawer-max-width);
|
||||
height: 100%;
|
||||
background-color: var(--background-page);
|
||||
border-radius: 4px;
|
||||
|
||||
.cancel {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
left: -76px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
padding: 0 16px;
|
||||
border-bottom: 2px solid var(--background-normal);
|
||||
|
||||
.title {
|
||||
margin-right: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--foreground-subdued);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
margin-right: 8px;
|
||||
|
||||
@include breakpoint(medium) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(medium) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
.header-icon {
|
||||
--v-button-background-color: var(--background-normal);
|
||||
--v-button-background-color-activated: var(--background-normal);
|
||||
--v-button-background-color-hover: var(--background-normal-alt);
|
||||
--v-button-color-disabled: var(--foreground-normal);
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -190,45 +191,20 @@ body {
|
||||
}
|
||||
|
||||
.main {
|
||||
--content-padding: 16px;
|
||||
--content-padding-bottom: 32px;
|
||||
|
||||
flex-grow: 1;
|
||||
padding: 16px 16px 32px;
|
||||
overflow: auto;
|
||||
|
||||
@include breakpoint(medium) {
|
||||
padding: 32px;
|
||||
--content-padding: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-padding .main {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
height: 60px;
|
||||
padding: 0 16px;
|
||||
border-top: 2px solid var(--background-normal);
|
||||
|
||||
::v-deep > *:not(:last-child) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@include breakpoint(medium) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(medium) {
|
||||
width: calc(100% - 64px);
|
||||
height: calc(100% - 64px);
|
||||
}
|
||||
}
|
||||
|
||||
.form-width {
|
||||
--v-modal-max-width: 856px;
|
||||
}
|
||||
</style>
|
||||
@@ -139,10 +139,10 @@ export default defineComponent({
|
||||
const gridClass = computed<string | null>(() => {
|
||||
if (el.value === null) return null;
|
||||
|
||||
if (width.value > 588 && width.value <= 792) {
|
||||
return 'grid';
|
||||
} else {
|
||||
if (width.value > 792) {
|
||||
return 'grid with-fill';
|
||||
} else {
|
||||
return 'grid';
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -161,7 +161,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
if (props.slug === true) {
|
||||
if (props.dbSafe === true) {
|
||||
const dbSafeCharacters = 'abcdefghijklmnopqrstuvwxyz01234567890-_~ '.split('');
|
||||
|
||||
const isAllowed = dbSafeCharacters.includes(key) || systemKeys.includes(key);
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import VModal from './v-modal.vue';
|
||||
|
||||
export { VModal };
|
||||
export default VModal;
|
||||
@@ -1,73 +0,0 @@
|
||||
# Modal
|
||||
|
||||
A modal is basically an elaborate pre-configured dialog. It supports an optional left sidebar that allows for easier tab usage.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<v-modal title="My Modal" v-modal="active">
|
||||
Hello, world!
|
||||
</v-modal>
|
||||
```
|
||||
|
||||
```html
|
||||
<v-modal title="My Modal">
|
||||
<template #activator="{ on }">
|
||||
<v-button @click="on">Open modal</v-button>
|
||||
</template>
|
||||
|
||||
Hello, world!
|
||||
</v-modal>
|
||||
```
|
||||
|
||||
```html
|
||||
<v-modal title="My Modal" v-model="active">
|
||||
<template #activator="{ on }">
|
||||
<v-button @click="on">Open modal</v-button>
|
||||
</template>
|
||||
|
||||
<template #sidebar>
|
||||
<v-tabs vertical>
|
||||
<v-tab>Hello</v-tab>
|
||||
<v-tab>Page 2</v-tab>
|
||||
<v-tab>Page 3</v-tab>
|
||||
</v-tabs>
|
||||
</template>
|
||||
|
||||
<v-tabs-items>
|
||||
<v-tab-item>Hello, world!</v-tab-item>
|
||||
<v-tab-item>I'm page 2!</v-tab-item>
|
||||
<v-tab-item>I'm page 3!</v-tab-item>
|
||||
</v-tabs-items>
|
||||
|
||||
<template #footer="{ close }">
|
||||
<v-button @click="close">Close modal</v-button>
|
||||
</template>
|
||||
</v-modal>
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Description | Default |
|
||||
|--------------|-----------------------------------------------------------------|---------|
|
||||
| `title`* | Title for the modal | |
|
||||
| `subtitle` | Optional subtitle for the modal | |
|
||||
| `active` | If the modal is active. Used in `v-model` | `false` |
|
||||
| `persistent` | Prevent the user from exiting the modal by clicking the overlay | `false` |
|
||||
|
||||
## Events
|
||||
|
||||
| Event | Description | Value |
|
||||
|----------|--------------------------|-----------|
|
||||
| `toggle` | Sync the `v-model` value | `boolean` |
|
||||
|
||||
## Slots
|
||||
| Slot | Description | Data |
|
||||
|-------------|--------------------------------------------------------|-------------------------|
|
||||
| _default_ | Modal content | |
|
||||
| `activator` | Element to enable the modal | `{ on: () => void }` |
|
||||
| `sidebar` | Sidebar content for the modal. Often used for `v-tabs` | |
|
||||
| `footer` | Footer content. Often used for action buttons | `{ close: () => void }` |
|
||||
|
||||
## CSS Variables
|
||||
n/a
|
||||
@@ -1,34 +0,0 @@
|
||||
<template>
|
||||
<div class="v-modal-heading">
|
||||
<div class="type-title">{{ heading }}</div>
|
||||
<div v-if="subheading" class="subheading">{{ subheading }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
heading: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
subheading: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-modal-heading {
|
||||
margin-bottom: 48px;
|
||||
|
||||
.subheading {
|
||||
margin-top: 4px;
|
||||
color: var(--foreground-subdued);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,84 +0,0 @@
|
||||
import withPadding from '../../../.storybook/decorators/with-padding';
|
||||
import readme from './readme.md';
|
||||
import { defineComponent, ref } from '@vue/composition-api';
|
||||
|
||||
export default {
|
||||
title: 'Components / Modal',
|
||||
parameters: {
|
||||
notes: readme,
|
||||
},
|
||||
decorators: [withPadding],
|
||||
};
|
||||
|
||||
export const basic = () =>
|
||||
defineComponent({
|
||||
setup() {
|
||||
const active = ref(false);
|
||||
return { active };
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<v-modal
|
||||
v-model="active"
|
||||
title="Creating New Collection"
|
||||
subtitle="called Customers"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-button @click="on">Enable modal</v-button>
|
||||
</template>
|
||||
|
||||
<p>Hello world!</p>
|
||||
|
||||
<template #footer="{ close }">
|
||||
<v-button @click="close">Close modal</v-button>
|
||||
</template>
|
||||
</v-modal>
|
||||
<portal-target name="outlet" />
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
export const withNav = () =>
|
||||
defineComponent({
|
||||
setup() {
|
||||
const active = ref(false);
|
||||
const current = ref(['hello']);
|
||||
return { active, current };
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<v-modal
|
||||
v-model="active"
|
||||
title="Creating New Collection"
|
||||
subtitle="called Customers"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-button @click="on">Enable modal</v-button>
|
||||
</template>
|
||||
|
||||
<template #sidebar>
|
||||
<v-tabs v-model="current" vertical>
|
||||
<v-tab value="hello">Hello</v-tab>
|
||||
<v-tab value="introduce">Modal</v-tab>
|
||||
</v-tabs>
|
||||
</template>
|
||||
|
||||
<v-tabs-items v-model="current">
|
||||
<v-tab-item value="hello">
|
||||
<p>Hello world!</p>
|
||||
</v-tab-item>
|
||||
|
||||
<v-tab-item value="introduce">
|
||||
<p>I'm a modal with tabs</p>
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
|
||||
|
||||
<template #footer="{ close }">
|
||||
<v-button @click="close">Close modal</v-button>
|
||||
</template>
|
||||
</v-modal>
|
||||
<portal-target name="outlet" />
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
import VModal from './v-modal.vue';
|
||||
import VDialog from '@/components/v-dialog/';
|
||||
import VIcon from '@/components/v-icon/';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.component('v-dialog', VDialog);
|
||||
localVue.component('v-icon', VIcon);
|
||||
|
||||
describe('Components / Modal', () => {
|
||||
it('Renders', () => {
|
||||
const component = shallowMount(VModal, {
|
||||
localVue,
|
||||
propsData: {
|
||||
title: 'My Modal',
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.isVueInstance()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -52,7 +52,7 @@
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<modal-collection
|
||||
<drawer-collection
|
||||
collection="directus_files"
|
||||
:active="activeDialog === 'choose'"
|
||||
@update:active="activeDialog = null"
|
||||
@@ -88,12 +88,12 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch } from '@vue/composition-api';
|
||||
import uploadFiles from '@/utils/upload-files';
|
||||
import ModalCollection from '@/views/private/components/modal-collection';
|
||||
import DrawerCollection from '@/views/private/components/drawer-collection';
|
||||
import api from '@/api';
|
||||
import useItem from '@/composables/use-item';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ModalCollection },
|
||||
components: { DrawerCollection },
|
||||
props: {
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -57,11 +57,11 @@ export async function hydrate(stores = useStores()) {
|
||||
*/
|
||||
await userStore.hydrate();
|
||||
|
||||
setLanguage((userStore.state.currentUser?.language as Language) || 'en-US');
|
||||
|
||||
await Promise.all(stores.filter(({ id }) => id !== 'userStore').map((store) => store.hydrate?.()));
|
||||
|
||||
await registerModules();
|
||||
if (userStore.state.currentUser?.role) {
|
||||
await setLanguage((userStore.state.currentUser?.language as Language) || 'en-US');
|
||||
await Promise.all(stores.filter(({ id }) => id !== 'userStore').map((store) => store.hydrate?.()));
|
||||
await registerModules();
|
||||
}
|
||||
} catch (error) {
|
||||
appStore.state.error = error;
|
||||
} finally {
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<modal-collection
|
||||
<drawer-collection
|
||||
collection="directus_files"
|
||||
:active="activeDialog === 'choose'"
|
||||
@update:active="activeDialog = null"
|
||||
@@ -112,7 +112,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, computed } from '@vue/composition-api';
|
||||
import ModalCollection from '@/views/private/components/modal-collection';
|
||||
import DrawerCollection from '@/views/private/components/drawer-collection';
|
||||
import api from '@/api';
|
||||
import readableMimeType from '@/utils/readable-mime-type';
|
||||
import getRootPath from '@/utils/get-root-path';
|
||||
@@ -124,7 +124,7 @@ type FileInfo = {
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
components: { ModalCollection },
|
||||
components: { DrawerCollection },
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</v-button>
|
||||
</div>
|
||||
|
||||
<modal-item
|
||||
<drawer-item
|
||||
v-if="!disabled"
|
||||
:active="editModalActive"
|
||||
:collection="relationInfo.junctionCollection"
|
||||
@@ -46,7 +46,7 @@
|
||||
@update:active="cancelEdit"
|
||||
/>
|
||||
|
||||
<modal-collection
|
||||
<drawer-collection
|
||||
v-if="!disabled"
|
||||
:active.sync="selectModalActive"
|
||||
:collection="relationInfo.relationCollection"
|
||||
@@ -71,8 +71,8 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, toRefs, PropType } from '@vue/composition-api';
|
||||
import { Header as TableHeader } from '@/components/v-table/types';
|
||||
import ModalCollection from '@/views/private/components/modal-collection';
|
||||
import ModalItem from '@/views/private/components/modal-item';
|
||||
import DrawerCollection from '@/views/private/components/drawer-collection';
|
||||
import DrawerItem from '@/views/private/components/drawer-item';
|
||||
import { get } from 'lodash';
|
||||
import i18n from '@/lang';
|
||||
|
||||
@@ -83,7 +83,7 @@ import usePreview from '@/interfaces/many-to-many/use-preview';
|
||||
import useEdit from '@/interfaces/many-to-many/use-edit';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ModalCollection, ModalItem },
|
||||
components: { DrawerCollection, DrawerItem },
|
||||
props: {
|
||||
primaryKey: {
|
||||
type: [Number, String],
|
||||
|
||||
@@ -91,6 +91,8 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
function setIcon(icon: string | null) {
|
||||
searchQuery.value = '';
|
||||
|
||||
emit('input', icon);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
</v-button>
|
||||
</div>
|
||||
|
||||
<modal-item
|
||||
<drawer-item
|
||||
v-if="!disabled"
|
||||
:active="editModalActive"
|
||||
:collection="relationInfo.junctionCollection"
|
||||
@@ -54,7 +54,7 @@
|
||||
@update:active="cancelEdit"
|
||||
/>
|
||||
|
||||
<modal-collection
|
||||
<drawer-collection
|
||||
v-if="!disabled"
|
||||
:active.sync="selectModalActive"
|
||||
:collection="relationCollection.collection"
|
||||
@@ -68,8 +68,8 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch, PropType, toRefs } from '@vue/composition-api';
|
||||
import ModalItem from '@/views/private/components/modal-item';
|
||||
import ModalCollection from '@/views/private/components/modal-collection';
|
||||
import DrawerItem from '@/views/private/components/drawer-item';
|
||||
import DrawerCollection from '@/views/private/components/drawer-collection';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import useActions from './use-actions';
|
||||
@@ -80,7 +80,7 @@ import useSelection from './use-selection';
|
||||
import useSort from './use-sort';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ModalItem, ModalCollection },
|
||||
components: { DrawerItem, DrawerCollection },
|
||||
props: {
|
||||
value: {
|
||||
type: Array as PropType<(number | string | Record<string, any>)[] | null>,
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<modal-item
|
||||
<drawer-item
|
||||
v-if="!disabled"
|
||||
:active.sync="editModalActive"
|
||||
:collection="relatedCollection.collection"
|
||||
@@ -91,7 +91,7 @@
|
||||
@input="stageEdits"
|
||||
/>
|
||||
|
||||
<modal-collection
|
||||
<drawer-collection
|
||||
v-if="!disabled"
|
||||
:active.sync="selectModalActive"
|
||||
:collection="relatedCollection.collection"
|
||||
@@ -107,8 +107,8 @@ import { useCollectionsStore, useRelationsStore } from '@/stores/';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
|
||||
import api from '@/api';
|
||||
import ModalItem from '@/views/private/components/modal-item';
|
||||
import ModalCollection from '@/views/private/components/modal-collection';
|
||||
import DrawerItem from '@/views/private/components/drawer-item';
|
||||
import DrawerCollection from '@/views/private/components/drawer-collection';
|
||||
|
||||
/**
|
||||
* @NOTE
|
||||
@@ -119,7 +119,7 @@ import ModalCollection from '@/views/private/components/modal-collection';
|
||||
*/
|
||||
|
||||
export default defineComponent({
|
||||
components: { ModalItem, ModalCollection },
|
||||
components: { DrawerItem, DrawerCollection },
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String, Object],
|
||||
|
||||
@@ -28,7 +28,3 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
//
|
||||
</style>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
</v-button>
|
||||
</div>
|
||||
|
||||
<modal-item
|
||||
<drawer-item
|
||||
v-if="!disabled"
|
||||
:active="currentlyEditing !== null"
|
||||
:collection="relatedCollection.collection"
|
||||
@@ -52,7 +52,7 @@
|
||||
@update:active="cancelEdit"
|
||||
/>
|
||||
|
||||
<modal-collection
|
||||
<drawer-collection
|
||||
v-if="!disabled"
|
||||
:active.sync="selectModalActive"
|
||||
:collection="relatedCollection.collection"
|
||||
@@ -69,15 +69,14 @@ import { defineComponent, ref, computed, watch, PropType } from '@vue/compositio
|
||||
import api from '@/api';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import { useCollectionsStore, useRelationsStore, useFieldsStore } from '@/stores/';
|
||||
import ModalItem from '@/views/private/components/modal-item';
|
||||
import ModalCollection from '@/views/private/components/modal-collection';
|
||||
import { Sort } from '@/components/v-table/types';
|
||||
import DrawerItem from '@/views/private/components/drawer-item';
|
||||
import DrawerCollection from '@/views/private/components/drawer-collection';
|
||||
import { Filter, Field } from '@/types';
|
||||
import { Header } from '@/components/v-table/types';
|
||||
import { Header, Sort } from '@/components/v-table/types';
|
||||
import { isEqual, sortBy } from 'lodash';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ModalItem, ModalCollection },
|
||||
components: { DrawerItem, DrawerCollection },
|
||||
props: {
|
||||
value: {
|
||||
type: Array as PropType<(number | string | Record<string, any>)[] | null>,
|
||||
|
||||
@@ -1,46 +1,43 @@
|
||||
<template>
|
||||
<div v-if="itemsLoading || languagesLoading" class="loader">
|
||||
<div v-if="languagesLoading">
|
||||
<v-skeleton-loader v-for="n in 5" :key="n" />
|
||||
</div>
|
||||
|
||||
<v-item-group v-else scope="translations" class="translations">
|
||||
<v-item
|
||||
scope="translations"
|
||||
class="row"
|
||||
v-for="(item, index) in languages"
|
||||
:key="item[languagesPrimaryKeyField.field]"
|
||||
#default="{ active, toggle }"
|
||||
<div class="translations" v-else>
|
||||
<button
|
||||
v-for="languageItem in languages"
|
||||
:key="languageItem[languagesPrimaryKeyField]"
|
||||
@click="startEditing(languageItem[languagesPrimaryKeyField])"
|
||||
class="language-row"
|
||||
>
|
||||
<div class="header" @click="toggle">
|
||||
<render-template :template="rowTemplate" :collection="languagesCollection" :item="item" />
|
||||
</div>
|
||||
<transition-expand>
|
||||
<div v-if="active">
|
||||
<div class="form">
|
||||
<v-divider />
|
||||
<v-form
|
||||
:initial-values="existing[index]"
|
||||
:collection="translationsCollection"
|
||||
:primary-key="existing[index][translationsPrimaryKeyField.field] || '+'"
|
||||
:edits="edits[index]"
|
||||
@input="emitValue($event, existing[index][translationsPrimaryKeyField.field])"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</transition-expand>
|
||||
</v-item>
|
||||
</v-item-group>
|
||||
<v-icon class="translate" name="translate" />
|
||||
<render-template :template="languagesTemplate" :collection="languagesCollection" :item="languageItem" />
|
||||
<div class="spacer" />
|
||||
<v-icon class="launch" name="launch" />
|
||||
</button>
|
||||
|
||||
<drawer-item
|
||||
v-if="editing"
|
||||
active
|
||||
:collection="translationsCollection"
|
||||
:primary-key="editing"
|
||||
:edits="edits"
|
||||
@input="stageEdits"
|
||||
@update:active="cancelEdit"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref, toRefs, watch, PropType } from '@vue/composition-api';
|
||||
import { useCollectionsStore, useRelationsStore, useFieldsStore } from '@/stores/';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import { defineComponent, PropType, computed, ref, watch } from '@vue/composition-api';
|
||||
import { useRelationsStore } from '@/stores/';
|
||||
import api from '@/api';
|
||||
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
|
||||
import { Relation } from '@/types';
|
||||
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
|
||||
import DrawerItem from '@/views/private/components/drawer-item/drawer-item.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { DrawerItem },
|
||||
props: {
|
||||
collection: {
|
||||
type: String,
|
||||
@@ -59,70 +56,63 @@ export default defineComponent({
|
||||
default: null,
|
||||
},
|
||||
value: {
|
||||
type: Array as PropType<Record<string, any>[]>,
|
||||
type: Array as PropType<(string | number | Record<string, any>)[]>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
|
||||
const {
|
||||
relations,
|
||||
relationsForField,
|
||||
translationsRelation,
|
||||
translationsCollection,
|
||||
languagesCollection,
|
||||
languageField,
|
||||
translationsPrimaryKeyField,
|
||||
} = useRelation();
|
||||
languagesRelation,
|
||||
languagesCollection,
|
||||
languagesPrimaryKeyField,
|
||||
translationsLanguageField,
|
||||
} = useRelations();
|
||||
|
||||
const {
|
||||
languages,
|
||||
loading: languagesLoading,
|
||||
error: languagesError,
|
||||
primaryKeyField: languagesPrimaryKeyField,
|
||||
template: languagesTemplate,
|
||||
} = useLanguages();
|
||||
|
||||
const { items, loading: itemsLoading, error: itemsError } = useCurrent();
|
||||
const { existing, edits, emitValue } = useValues();
|
||||
|
||||
const rowTemplate = computed(() => {
|
||||
const { info, primaryKeyField } = useCollection(languagesCollection);
|
||||
const defaultTemplate = info.value?.meta?.display_template;
|
||||
|
||||
return props.template || defaultTemplate || `{{ ${primaryKeyField.value.field} }}`;
|
||||
});
|
||||
const { startEditing, editing, edits, stageEdits, cancelEdit } = useEdits();
|
||||
|
||||
return {
|
||||
relations,
|
||||
relationsForField,
|
||||
translationsRelation,
|
||||
translationsCollection,
|
||||
languagesCollection,
|
||||
languagesRelation,
|
||||
languages,
|
||||
languagesLoading,
|
||||
languagesError,
|
||||
languagesTemplate,
|
||||
languagesCollection,
|
||||
languagesPrimaryKeyField,
|
||||
items,
|
||||
itemsLoading,
|
||||
itemsError,
|
||||
existing,
|
||||
languagesLoading,
|
||||
startEditing,
|
||||
translationsLanguageField,
|
||||
editing,
|
||||
stageEdits,
|
||||
cancelEdit,
|
||||
edits,
|
||||
emitValue,
|
||||
rowTemplate,
|
||||
translationsPrimaryKeyField,
|
||||
};
|
||||
|
||||
function useRelation() {
|
||||
const relations = computed(() => {
|
||||
function useRelations() {
|
||||
const relationsForField = computed(() => {
|
||||
return relationsStore.getRelationsForField(props.collection, props.field);
|
||||
});
|
||||
|
||||
const translationsRelation = computed(() => {
|
||||
if (!relations.value || relations.value.length === 0) return null;
|
||||
|
||||
if (!relationsForField.value) return null;
|
||||
return (
|
||||
relations.value.find((relation: Relation) => {
|
||||
return relation.one_collection === props.collection && relation.one_field === props.field;
|
||||
}) || null
|
||||
relationsForField.value.find(
|
||||
(relation: Relation) =>
|
||||
relation.one_collection === props.collection && relation.one_field === props.field
|
||||
) || null
|
||||
);
|
||||
});
|
||||
|
||||
@@ -132,16 +122,15 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
const translationsPrimaryKeyField = computed(() => {
|
||||
return fieldsStore.getPrimaryKeyFieldForCollection(translationsCollection.value);
|
||||
if (!translationsRelation.value) return null;
|
||||
return translationsRelation.value.many_primary;
|
||||
});
|
||||
|
||||
const languagesRelation = computed(() => {
|
||||
if (!relations.value || relations.value.length === 0) return null;
|
||||
|
||||
if (!relationsForField.value) return null;
|
||||
return (
|
||||
relations.value.find((relation: Relation) => {
|
||||
return relation.one_collection !== props.collection && relation.one_field !== props.field;
|
||||
}) || null
|
||||
relationsForField.value.find((relation: Relation) => relation !== translationsRelation.value) ||
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
@@ -150,49 +139,51 @@ export default defineComponent({
|
||||
return languagesRelation.value.one_collection;
|
||||
});
|
||||
|
||||
const languageField = computed(() => {
|
||||
const languagesPrimaryKeyField = computed(() => {
|
||||
if (!languagesRelation.value) return null;
|
||||
return languagesRelation.value.one_primary;
|
||||
});
|
||||
|
||||
const translationsLanguageField = computed(() => {
|
||||
if (!languagesRelation.value) return null;
|
||||
return languagesRelation.value.many_field;
|
||||
});
|
||||
|
||||
return {
|
||||
relations,
|
||||
relationsForField,
|
||||
translationsRelation,
|
||||
translationsCollection,
|
||||
languagesCollection,
|
||||
languageField,
|
||||
translationsPrimaryKeyField,
|
||||
languagesRelation,
|
||||
languagesCollection,
|
||||
languagesPrimaryKeyField,
|
||||
translationsLanguageField,
|
||||
};
|
||||
}
|
||||
|
||||
function useLanguages() {
|
||||
const languages = ref<Record<string, any> | null>(null);
|
||||
const languages = ref();
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
const error = ref<any>(null);
|
||||
|
||||
const { primaryKeyField } = useCollection(languagesCollection);
|
||||
const template = computed(() => {
|
||||
if (!languagesPrimaryKeyField.value) return '';
|
||||
return props.template || `{{ ${languagesPrimaryKeyField.value} }}`;
|
||||
});
|
||||
|
||||
watch(languagesCollection, fetchLanguages, { immediate: true });
|
||||
|
||||
return { languages, loading, error, primaryKeyField };
|
||||
return { languages, loading, error, template };
|
||||
|
||||
async function fetchLanguages() {
|
||||
if (!languagesCollection.value) return;
|
||||
|
||||
const fields = getFieldsFromTemplate(template.value);
|
||||
|
||||
loading.value = true;
|
||||
|
||||
// const fields = getFieldsFromTemplate(props.template);
|
||||
const fields = ['*'];
|
||||
|
||||
if (fields.includes(primaryKeyField.value.field) === false) {
|
||||
fields.push(primaryKeyField.value.field);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.get(`/items/${languagesCollection.value}`, {
|
||||
params: {
|
||||
fields: fields,
|
||||
limit: -1,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await api.get(`/items/${languagesCollection.value}`, { params: { fields } });
|
||||
languages.value = response.data.data;
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
@@ -202,100 +193,135 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
function useCurrent() {
|
||||
function useEdits() {
|
||||
const keyMap = ref<Record<string, string | number>[]>();
|
||||
|
||||
const loading = ref(false);
|
||||
const items = ref<any[]>([]);
|
||||
const error = ref(null);
|
||||
const error = ref<any>(null);
|
||||
|
||||
watch(
|
||||
() => props.primaryKey,
|
||||
(newKey) => {
|
||||
if (newKey !== null && newKey !== '+') {
|
||||
fetchCurrent();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
const editing = ref<boolean | string | number>(false);
|
||||
const edits = ref<Record<string, any>>();
|
||||
|
||||
const existingPrimaryKeys = computed(() => {
|
||||
return (props.value || [])
|
||||
.map((value) => {
|
||||
if (typeof value === 'string' || typeof value === 'number') return value;
|
||||
return value[translationsPrimaryKeyField.value];
|
||||
})
|
||||
.filter((key) => key);
|
||||
});
|
||||
|
||||
watch(() => props.value, fetchKeyMap, { immediate: true });
|
||||
|
||||
return { startEditing, editing, edits, stageEdits, cancelEdit };
|
||||
|
||||
function startEditing(language: string | number) {
|
||||
edits.value = {
|
||||
[translationsLanguageField.value]: language,
|
||||
};
|
||||
|
||||
const existingEdits = (props.value || []).find((val) => {
|
||||
if (typeof val === 'string' || typeof val === 'number') return false;
|
||||
return val[translationsLanguageField.value] === language;
|
||||
});
|
||||
|
||||
if (existingEdits) {
|
||||
edits.value = {
|
||||
...edits.value,
|
||||
...(existingEdits as Record<string, any>),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
return { loading, items, error };
|
||||
const primaryKey =
|
||||
keyMap.value?.find((record) => record[translationsLanguageField.value] === language)?.[
|
||||
translationsPrimaryKeyField.value
|
||||
] || '+';
|
||||
|
||||
if (primaryKey !== '+') {
|
||||
edits.value = {
|
||||
...edits.value,
|
||||
[translationsPrimaryKeyField.value]: primaryKey,
|
||||
};
|
||||
}
|
||||
|
||||
editing.value = primaryKey;
|
||||
}
|
||||
|
||||
async function fetchKeyMap() {
|
||||
if (!props.value) return;
|
||||
if (keyMap.value) return;
|
||||
|
||||
const collection = translationsRelation.value.many_collection;
|
||||
const fields = [translationsPrimaryKeyField.value, translationsLanguageField.value];
|
||||
|
||||
async function fetchCurrent() {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/items/${props.collection}/${props.primaryKey}`, {
|
||||
const response = await api.get(`/items/${collection}`, {
|
||||
params: {
|
||||
fields: props.field + '.*',
|
||||
fields,
|
||||
filter: {
|
||||
[translationsPrimaryKeyField.value]: {
|
||||
_in: existingPrimaryKeys.value,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
items.value = response.data.data[props.field];
|
||||
keyMap.value = response.data.data;
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useValues() {
|
||||
const existing = computed(() => {
|
||||
if (!languages.value) return [];
|
||||
function stageEdits(edits: any) {
|
||||
const editedLanguage = edits[translationsLanguageField.value];
|
||||
|
||||
return languages.value.map((language: any) => {
|
||||
const existing =
|
||||
items.value.find(
|
||||
(item) => item[languageField.value] === language[languagesPrimaryKeyField.value.field]
|
||||
) || {};
|
||||
|
||||
return existing;
|
||||
const languageAlreadyEdited = !!(props.value || []).find((val) => {
|
||||
if (typeof val === 'string' || typeof val === 'number') return false;
|
||||
return val[translationsLanguageField.value] === editedLanguage;
|
||||
});
|
||||
});
|
||||
|
||||
const edits = computed(() => {
|
||||
if (!languages.value) return [];
|
||||
|
||||
return languages.value.map((language: any) => {
|
||||
const edits =
|
||||
(props.value || []).find(
|
||||
(edit) => edit[languageField.value] === language[languagesPrimaryKeyField.value.field]
|
||||
) || {};
|
||||
|
||||
edits[languageField.value] = language[languagesPrimaryKeyField.value.field];
|
||||
|
||||
return edits;
|
||||
});
|
||||
});
|
||||
|
||||
return { existing, edits, emitValue };
|
||||
|
||||
function emitValue(newEdit: any, existingPrimaryKey: undefined | string | number) {
|
||||
const currentEdits = [...(props.value || [])];
|
||||
|
||||
if (existingPrimaryKey) {
|
||||
newEdit = {
|
||||
...newEdit,
|
||||
[translationsPrimaryKeyField.value.field]: existingPrimaryKey,
|
||||
};
|
||||
}
|
||||
|
||||
if (currentEdits.some((edit) => edit[languageField.value] === newEdit[languageField.value])) {
|
||||
if (languageAlreadyEdited === true) {
|
||||
emit(
|
||||
'input',
|
||||
currentEdits.map((edit) => {
|
||||
if (edit[languageField.value] === newEdit[languageField.value]) {
|
||||
return newEdit;
|
||||
props.value.map((val) => {
|
||||
if (typeof val === 'string' || typeof val === 'number') return val;
|
||||
|
||||
if (val[translationsLanguageField.value] === editedLanguage) {
|
||||
return edits;
|
||||
}
|
||||
|
||||
return edit;
|
||||
return val;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
currentEdits.push(newEdit);
|
||||
emit('input', currentEdits);
|
||||
if (editing.value === '+') {
|
||||
emit('input', [...(props.value || []), edits]);
|
||||
} else {
|
||||
emit(
|
||||
'input',
|
||||
props.value.map((val) => {
|
||||
if (typeof val === 'string' || typeof val === 'number') {
|
||||
if (val === editing.value) return edits;
|
||||
} else {
|
||||
if (val[translationsPrimaryKeyField.value] === editing.value) return edits;
|
||||
}
|
||||
|
||||
return val;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
editing.value = false;
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
edits.value = {};
|
||||
editing.value = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -303,41 +329,35 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/mixins/type-styles.scss';
|
||||
.language-row {
|
||||
--v-icon-color: var(--foreground-subdued);
|
||||
|
||||
.loader .v-skeleton-loader + .v-skeleton-loader {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.row {
|
||||
text-align: left;
|
||||
background-color: var(--background-subdued);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
& + .row {
|
||||
& + & {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.v-divider {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.translate {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.form {
|
||||
--v-form-vertical-gap: 24px;
|
||||
--v-form-horizontal-gap: 12px;
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
padding: 12px;
|
||||
padding-top: 0;
|
||||
.launch {
|
||||
transition: color var(--fast) var(--transition);
|
||||
}
|
||||
|
||||
::v-deep .type-label {
|
||||
@include type-text;
|
||||
&:hover .launch {
|
||||
--v-icon-color: var(--foreground-normal);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
</v-list>
|
||||
</v-menu>
|
||||
|
||||
<modal-item
|
||||
<drawer-item
|
||||
:active.sync="editModalActive"
|
||||
collection="directus_users"
|
||||
:primary-key="currentPrimaryKey"
|
||||
@@ -81,7 +81,7 @@
|
||||
v-if="!disabled"
|
||||
/>
|
||||
|
||||
<modal-collection
|
||||
<drawer-collection
|
||||
:active.sync="selectModalActive"
|
||||
collection="directus_users"
|
||||
:selection="selection"
|
||||
@@ -95,11 +95,11 @@
|
||||
import { defineComponent, computed, ref, watch, PropType } from '@vue/composition-api';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import api from '@/api';
|
||||
import ModalItem from '@/views/private/components/modal-item';
|
||||
import ModalCollection from '@/views/private/components/modal-collection';
|
||||
import DrawerItem from '@/views/private/components/drawer-item';
|
||||
import DrawerCollection from '@/views/private/components/drawer-collection';
|
||||
|
||||
export default defineComponent({
|
||||
components: { ModalItem, ModalCollection },
|
||||
components: { DrawerItem, DrawerCollection },
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
/* stylelint-disable font-family-no-missing-generic-family-keyword */
|
||||
|
||||
.tox {
|
||||
font-family: var(--family-sans-serif);
|
||||
}
|
||||
|
||||
.tox .tox-tbtn {
|
||||
margin: 2px 2px 4px 0;
|
||||
color: var(--foreground-normal);
|
||||
@@ -138,8 +142,9 @@ body.dark .tox .tox-toolbar__overflow {
|
||||
}
|
||||
|
||||
.tox .tox-dialog__header {
|
||||
padding: 16px 24px 0 24px;
|
||||
padding: 20px;
|
||||
color: var(--foreground-normal);
|
||||
font-size: 16px;
|
||||
background-color: var(--background-page);
|
||||
}
|
||||
|
||||
@@ -156,18 +161,61 @@ body.dark .tox .tox-toolbar__overflow {
|
||||
}
|
||||
|
||||
.tox .tox-textfield,
|
||||
.tox .tox-listboxfield .tox-listbox,
|
||||
.tox .tox-toolbar-textfield,
|
||||
.tox .tox-selectfield select,
|
||||
.tox .tox-textarea {
|
||||
padding: 12px;
|
||||
color: var(--foreground-normal);
|
||||
font-family: monospace;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
font-family: var(--family-sans-serif);
|
||||
background-color: var(--background-page);
|
||||
border: 2px solid var(--border-normal);
|
||||
border-radius: var(--border-radius);
|
||||
transition: var(--fast) var(--transition);
|
||||
}
|
||||
|
||||
.tox .tox-textarea {
|
||||
font-family: var(--family-monospace);
|
||||
}
|
||||
|
||||
.tox .tox-textfield:focus,
|
||||
.tox .tox-listboxfield .tox-listbox:focus,
|
||||
.tox .tox-toolbar-textfield:focus,
|
||||
.tox .tox-selectfield select:focus,
|
||||
.tox .tox-textarea:focus {
|
||||
background-color: var(--background-page);
|
||||
}
|
||||
|
||||
.tox .tox-menu {
|
||||
box-sizing: border-box;
|
||||
padding: 4px !important;
|
||||
color: var(--foreground-normal);
|
||||
font-family: var(--family-sans-serif);
|
||||
background-color: var(--background-subdued);
|
||||
border: 2px solid var(--border-normal);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.tox .tox-collection__item {
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.tox .tox-collection--list .tox-collection__item {
|
||||
color: var(--foreground-normal);
|
||||
}
|
||||
|
||||
.tox .tox-collection--list .tox-collection__item--active {
|
||||
color: var(--foreground-normal) !important;
|
||||
background-color: var(--background-page) !important;
|
||||
}
|
||||
|
||||
.tox .tox-collection--list .tox-collection__item--enabled {
|
||||
color: var(--foreground-normal);
|
||||
background-color: var(--background-page);
|
||||
}
|
||||
|
||||
.tox .tox-textfield:focus,
|
||||
.tox .tox-selectfield select:focus,
|
||||
.tox .tox-textarea:focus {
|
||||
@@ -184,6 +232,7 @@ body.dark .tox .tox-toolbar__overflow {
|
||||
color: var(--white);
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
font-family: var(--family-sans-serif);
|
||||
line-height: 19px;
|
||||
background-color: var(--primary);
|
||||
border: 2px solid var(--primary);
|
||||
@@ -228,12 +277,45 @@ body.dark .tox .tox-toolbar__overflow {
|
||||
}
|
||||
|
||||
.tox .tox-form__group {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.tox .tox-label,
|
||||
.tox .tox-toolbar-label {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 4px;
|
||||
color: var(--foreground-normal);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tox .tox-dialog__body-nav-item {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin-bottom: 4px;
|
||||
padding: 12px 16px;
|
||||
color: var(--foreground-normal);
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
border-bottom: none;
|
||||
border-radius: var(--border-radius);
|
||||
transition: var(--fast) var(--transition);
|
||||
transition-property: background-color, color;
|
||||
}
|
||||
|
||||
.tox .tox-dialog__body-nav-item:hover {
|
||||
background-color: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
.tox .tox-dialog__body-nav-item--active {
|
||||
background-color: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
.tox .tox-dialog__body-nav-item--active:focus {
|
||||
background-color: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.tox .tox-dialog__body-nav-item {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@@ -162,8 +162,8 @@
|
||||
"create_field": "Create Field",
|
||||
"update_field": "Update Field",
|
||||
|
||||
"creating_new_field": "{collection}: New Field",
|
||||
"updating_field_field": "{collection}: \"{field}\" Field",
|
||||
"creating_new_field": "New Field ({collection})",
|
||||
"updating_field_field": "{field} ({collection})",
|
||||
"within_collection": "Within {collection}",
|
||||
|
||||
"field_standard": "Standard",
|
||||
@@ -919,7 +919,7 @@
|
||||
"scope": "Scope",
|
||||
"layout": "Layout",
|
||||
"editing_file": "Editing File: {title}",
|
||||
"changes_are_immediate_and_permanent": "Changes are immediate and permanent",
|
||||
"changes_are_permanent": "Changes are permanent",
|
||||
|
||||
"preset_name_placeholder": "Name of bookmark...",
|
||||
"editing_preset": "Editing Preset",
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
</v-detail>
|
||||
</portal>
|
||||
|
||||
<portal to="drawer">
|
||||
<filter-drawer-detail v-model="_filters" :collection="collection" :loading="loading" />
|
||||
<portal to="sidebar">
|
||||
<filter-sidebar-detail v-model="_filters" :collection="collection" :loading="loading" />
|
||||
</portal>
|
||||
|
||||
<portal to="actions:prepend">
|
||||
|
||||
@@ -49,8 +49,8 @@
|
||||
</div>
|
||||
</portal>
|
||||
|
||||
<portal to="drawer">
|
||||
<filter-drawer-detail v-model="_filters" :collection="collection" :loading="loading" />
|
||||
<portal to="sidebar">
|
||||
<filter-sidebar-detail v-model="_filters" :collection="collection" :loading="loading" />
|
||||
</portal>
|
||||
|
||||
<portal to="actions:prepend">
|
||||
|
||||
@@ -31,12 +31,12 @@
|
||||
|
||||
<router-view name="detail" :primary-key="primaryKey" />
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="page-description" v-html="marked($t('page_help_activity_collection'))" />
|
||||
</drawer-detail>
|
||||
<layout-drawer-detail @input="layout = $event" :value="layout" />
|
||||
<portal-target name="drawer" />
|
||||
</sidebar-detail>
|
||||
<layout-sidebar-detail @input="layout = $event" :value="layout" />
|
||||
<portal-target name="sidebar" />
|
||||
</template>
|
||||
</private-view>
|
||||
</template>
|
||||
@@ -47,8 +47,8 @@ import ActivityNavigation from '../components/navigation.vue';
|
||||
import { i18n } from '@/lang';
|
||||
import usePreset from '@/composables/use-preset';
|
||||
import marked from 'marked';
|
||||
import FilterDrawerDetail from '@/views/private/components/filter-drawer-detail';
|
||||
import LayoutDrawerDetail from '@/views/private/components/layout-drawer-detail';
|
||||
import FilterSidebarDetail from '@/views/private/components/filter-sidebar-detail';
|
||||
import LayoutSidebarDetail from '@/views/private/components/layout-sidebar-detail';
|
||||
import SearchInput from '@/views/private/components/search-input';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
@@ -58,7 +58,7 @@ type Item = {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'activity-collection',
|
||||
components: { ActivityNavigation, FilterDrawerDetail, LayoutDrawerDetail, SearchInput },
|
||||
components: { ActivityNavigation, FilterSidebarDetail, LayoutSidebarDetail, SearchInput },
|
||||
props: {
|
||||
primaryKey: {
|
||||
type: String,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<v-modal active title="Activity Item" @toggle="close" @esc="close">
|
||||
<v-drawer active title="Activity Item" @toggle="close" @cancel="close">
|
||||
<v-progress-circular indeterminate v-if="loading" />
|
||||
|
||||
<template v-else-if="error">
|
||||
<div class="content" v-else-if="error">
|
||||
<v-notice type="danger">
|
||||
{{ error }}
|
||||
</v-notice>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div class="content" v-else>
|
||||
<!-- @TODO add final design -->
|
||||
<p class="type-label">User:</p>
|
||||
<user-popover v-if="item.user" :user="item.user.id">
|
||||
@@ -32,17 +32,18 @@
|
||||
|
||||
<p class="type-label">Item:</p>
|
||||
<p>{{ item.item }}</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<v-button v-if="openItemLink" :to="openItemLink">
|
||||
<v-icon name="launch" left />
|
||||
{{ $t('open') }}
|
||||
<template #actions>
|
||||
<v-button v-if="openItemLink" :to="openItemLink" icon rounded v-tooltip.bottom="$t('open')">
|
||||
<v-icon name="launch" />
|
||||
</v-button>
|
||||
|
||||
<v-button to="/activity">{{ $t('done') }}</v-button>
|
||||
<v-button to="/activity" icon rounded v-tooltip.bottom="$t('done')">
|
||||
<v-icon name="check" />
|
||||
</v-button>
|
||||
</template>
|
||||
</v-modal>
|
||||
</v-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -137,4 +138,10 @@ export default defineComponent({
|
||||
.type-label:not(:first-child) {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--content-padding);
|
||||
padding-top: 0;
|
||||
padding-bottom: var(--content-padding);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -203,8 +203,8 @@
|
||||
</template>
|
||||
</component>
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div
|
||||
class="page-description"
|
||||
v-html="
|
||||
@@ -215,10 +215,10 @@
|
||||
)
|
||||
"
|
||||
/>
|
||||
</drawer-detail>
|
||||
<layout-drawer-detail @input="layout = $event" :value="layout" />
|
||||
<portal-target name="drawer" />
|
||||
<export-drawer-detail
|
||||
</sidebar-detail>
|
||||
<layout-sidebar-detail @input="layout = $event" :value="layout" />
|
||||
<portal-target name="sidebar" />
|
||||
<export-sidebar-detail
|
||||
:layout-query="layoutQuery"
|
||||
:search-query="searchQuery"
|
||||
:collection="currentCollection"
|
||||
@@ -247,8 +247,8 @@ import { LayoutComponent } from '@/layouts/types';
|
||||
import CollectionsNotFound from './not-found.vue';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import usePreset from '@/composables/use-preset';
|
||||
import LayoutDrawerDetail from '@/views/private/components/layout-drawer-detail';
|
||||
import ExportDrawerDetail from '@/views/private/components/export-drawer-detail';
|
||||
import LayoutSidebarDetail from '@/views/private/components/layout-sidebar-detail';
|
||||
import ExportSidebarDetail from '@/views/private/components/export-sidebar-detail';
|
||||
import SearchInput from '@/views/private/components/search-input';
|
||||
import BookmarkAdd from '@/views/private/components/bookmark-add';
|
||||
import BookmarkEdit from '@/views/private/components/bookmark-edit';
|
||||
@@ -265,8 +265,8 @@ export default defineComponent({
|
||||
components: {
|
||||
CollectionsNavigation,
|
||||
CollectionsNotFound,
|
||||
LayoutDrawerDetail,
|
||||
ExportDrawerDetail,
|
||||
LayoutSidebarDetail,
|
||||
ExportSidebarDetail,
|
||||
SearchInput,
|
||||
BookmarkAdd,
|
||||
BookmarkEdit,
|
||||
@@ -591,14 +591,14 @@ export default defineComponent({
|
||||
--v-button-background-color-hover: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
.layout {
|
||||
--layout-offset-top: 64px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
--v-button-color-disabled: var(--foreground-normal);
|
||||
}
|
||||
|
||||
.layout {
|
||||
--layout-offset-top: 64px;
|
||||
}
|
||||
|
||||
.bookmark-controls {
|
||||
.add,
|
||||
.save,
|
||||
|
||||
@@ -176,10 +176,10 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="page-description" v-html="marked($t('page_help_collections_item'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
<revisions-drawer-detail
|
||||
v-if="
|
||||
collectionInfo.meta &&
|
||||
@@ -192,7 +192,7 @@
|
||||
ref="revisionsDrawerDetail"
|
||||
@revert="refresh"
|
||||
/>
|
||||
<comments-drawer-detail
|
||||
<comments-sidebar-detail
|
||||
v-if="
|
||||
collectionInfo.meta &&
|
||||
collectionInfo.meta.singleton === false &&
|
||||
@@ -215,7 +215,7 @@ import router from '@/router';
|
||||
import CollectionsNotFound from './not-found.vue';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail';
|
||||
import CommentsDrawerDetail from '@/views/private/components/comments-drawer-detail';
|
||||
import CommentsSidebarDetail from '@/views/private/components/comments-sidebar-detail';
|
||||
import useItem from '@/composables/use-item';
|
||||
import SaveOptions from '@/views/private/components/save-options';
|
||||
import i18n from '@/lang';
|
||||
@@ -236,7 +236,7 @@ export default defineComponent({
|
||||
CollectionsNavigation,
|
||||
CollectionsNotFound,
|
||||
RevisionsDrawerDetail,
|
||||
CommentsDrawerDetail,
|
||||
CommentsSidebarDetail,
|
||||
SaveOptions,
|
||||
},
|
||||
props: {
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
</template>
|
||||
</v-info>
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="page-description" v-html="marked($t('page_help_collections_overview'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
@@ -338,10 +338,14 @@ export default defineComponent({
|
||||
table tr {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: white;
|
||||
background-color: var(--background-normal);
|
||||
border-top: 1px solid var(--background-normal);
|
||||
}
|
||||
|
||||
table thead tr {
|
||||
background-color: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
table tr:nth-child(2n) {
|
||||
background-color: var(--background-page);
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
<markdown>{{ markdownWithoutTitle }}</markdown>
|
||||
</div>
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="page-description" v-html="marked($t('page_help_docs_global'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<drawer-detail icon="info_outline" :title="$t('file_details')" close>
|
||||
<sidebar-detail icon="info_outline" :title="$t('file_details')" close>
|
||||
<dl v-if="file">
|
||||
<div v-if="file.type">
|
||||
<dt>{{ $t('type') }}</dt>
|
||||
@@ -41,11 +41,11 @@
|
||||
<dd>{{ file.checksum }}</dd>
|
||||
</div>
|
||||
|
||||
<div v-if="user_created">
|
||||
<div v-if="userCreated">
|
||||
<dt>{{ $t('owner') }}</dt>
|
||||
<dd>
|
||||
<user-popover :user="user_created.id">
|
||||
<router-link :to="user_created.link">{{ user_created.name }}</router-link>
|
||||
<user-popover :user="userCreated.id">
|
||||
<router-link :to="userCreated.link">{{ userCreated.name }}</router-link>
|
||||
</user-popover>
|
||||
</dd>
|
||||
</div>
|
||||
@@ -55,11 +55,11 @@
|
||||
<dd>{{ modificationDate }}</dd>
|
||||
</div>
|
||||
|
||||
<div v-if="user_modified">
|
||||
<div v-if="userModified">
|
||||
<dt>{{ $t('edited_by') }}</dt>
|
||||
<dd>
|
||||
<user-popover :user="user_modified.id">
|
||||
<router-link :to="user_modified.link">{{ user_modified.name }}</router-link>
|
||||
<user-popover :user="userModified.id">
|
||||
<router-link :to="userModified.link">{{ userModified.name }}</router-link>
|
||||
</user-popover>
|
||||
</dd>
|
||||
</div>
|
||||
@@ -104,7 +104,7 @@
|
||||
<v-divider />
|
||||
|
||||
<div class="page-description" v-html="marked($t('page_help_files_item'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -126,12 +126,12 @@
|
||||
|
||||
<router-view name="addNew" :preset="queryFilters" @upload="refresh" />
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="page-description" v-html="marked($t('page_help_files_collection'))" />
|
||||
</drawer-detail>
|
||||
<layout-drawer-detail @input="layout = $event" :value="layout" />
|
||||
<portal-target name="drawer" />
|
||||
</sidebar-detail>
|
||||
<layout-sidebar-detail @input="layout = $event" :value="layout" />
|
||||
<portal-target name="sidebar" />
|
||||
</template>
|
||||
|
||||
<template v-if="showDropEffect">
|
||||
@@ -150,8 +150,8 @@ import { i18n } from '@/lang';
|
||||
import api from '@/api';
|
||||
import { LayoutComponent } from '@/layouts/types';
|
||||
import usePreset from '@/composables/use-preset';
|
||||
import FilterDrawerDetail from '@/views/private/components/filter-drawer-detail';
|
||||
import LayoutDrawerDetail from '@/views/private/components/layout-drawer-detail';
|
||||
import FilterSidebarDetail from '@/views/private/components/filter-sidebar-detail';
|
||||
import LayoutSidebarDetail from '@/views/private/components/layout-sidebar-detail';
|
||||
import AddFolder from '../components/add-folder.vue';
|
||||
import SearchInput from '@/views/private/components/search-input';
|
||||
import marked from 'marked';
|
||||
@@ -171,7 +171,7 @@ type Item = {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'files-collection',
|
||||
components: { FilesNavigation, FilterDrawerDetail, LayoutDrawerDetail, AddFolder, SearchInput, FolderPicker },
|
||||
components: { FilesNavigation, FilterSidebarDetail, LayoutSidebarDetail, AddFolder, SearchInput, FolderPicker },
|
||||
props: {
|
||||
queryFilters: {
|
||||
type: Object as PropType<Record<string, string>>,
|
||||
@@ -506,9 +506,11 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
await uploadFiles(files, {
|
||||
preset: {
|
||||
folder: props.queryFilters?.folder || null,
|
||||
},
|
||||
preset: props.queryFilters?.folder
|
||||
? {
|
||||
folder: props.queryFilters.folder,
|
||||
}
|
||||
: {},
|
||||
onProgressChange: (progress) => {
|
||||
const percentageDone = progress.reduce((val, cur) => (val += cur)) / progress.length;
|
||||
|
||||
|
||||
@@ -155,15 +155,15 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<template #drawer>
|
||||
<file-info-drawer-detail :file="item" @move-folder="moveToDialogActive = true" />
|
||||
<template #sidebar>
|
||||
<file-info-sidebar-detail :file="item" @move-folder="moveToDialogActive = true" />
|
||||
<revisions-drawer-detail
|
||||
v-if="isBatch === false && isNew === false"
|
||||
collection="directus_files"
|
||||
:primary-key="primaryKey"
|
||||
ref="revisionsDrawerDetail"
|
||||
/>
|
||||
<comments-drawer-detail
|
||||
<comments-sidebar-detail
|
||||
v-if="isBatch === false && isNew === false"
|
||||
collection="directus_files"
|
||||
:primary-key="primaryKey"
|
||||
@@ -178,7 +178,7 @@ import FilesNavigation from '../components/navigation.vue';
|
||||
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 CommentsSidebarDetail from '@/views/private/components/comments-sidebar-detail';
|
||||
import useItem from '@/composables/use-item';
|
||||
import SaveOptions from '@/views/private/components/save-options';
|
||||
import FilePreview from '@/views/private/components/file-preview';
|
||||
@@ -187,7 +187,7 @@ import { nanoid } from 'nanoid';
|
||||
import FileLightbox from '@/views/private/components/file-lightbox';
|
||||
import { useFieldsStore } from '@/stores/';
|
||||
import { Field } from '@/types';
|
||||
import FileInfoDrawerDetail from '../components/file-info-drawer-detail.vue';
|
||||
import FileInfoSidebarDetail from '../components/file-info-sidebar-detail.vue';
|
||||
import useFormFields from '@/composables/use-form-fields';
|
||||
import FolderPicker from '../components/folder-picker.vue';
|
||||
import api from '@/api';
|
||||
@@ -216,12 +216,12 @@ export default defineComponent({
|
||||
components: {
|
||||
FilesNavigation,
|
||||
RevisionsDrawerDetail,
|
||||
CommentsDrawerDetail,
|
||||
CommentsSidebarDetail,
|
||||
SaveOptions,
|
||||
FilePreview,
|
||||
ImageEditor,
|
||||
FileLightbox,
|
||||
FileInfoDrawerDetail,
|
||||
FileInfoSidebarDetail,
|
||||
FolderPicker,
|
||||
FilesNotFound,
|
||||
},
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<v-icon
|
||||
class="icon"
|
||||
:class="{
|
||||
hidden: item.meta && item.meta.hidden || false,
|
||||
hidden: (item.meta && item.meta.hidden) || false,
|
||||
system: item.collection.startsWith('directus_'),
|
||||
unmanaged: item.meta === null && item.collection.startsWith('directus_') === false,
|
||||
}"
|
||||
@@ -52,7 +52,7 @@
|
||||
<span
|
||||
class="collection"
|
||||
:class="{
|
||||
hidden: item.meta && item.meta.hidden || false,
|
||||
hidden: (item.meta && item.meta.hidden) || false,
|
||||
system: item.collection.startsWith('directus_'),
|
||||
unmanaged: item.meta === null && item.collection.startsWith('directus_') === false,
|
||||
}"
|
||||
@@ -86,10 +86,10 @@
|
||||
|
||||
<router-view name="add" />
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="page-description" v-html="marked($t('page_help_settings_datamodel_collections'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
<collections-filter v-model="activeTypes" />
|
||||
</template>
|
||||
</private-view>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<drawer-detail class="collections-filter" icon="filter_list" :title="$tc('collection', 2)">
|
||||
<sidebar-detail class="collections-filter" icon="filter_list" :title="$tc('collection', 2)">
|
||||
<div class="type-label label">{{ $t('collections_shown') }}</div>
|
||||
<v-checkbox value="visible" v-model="_value" :label="$t('visible_collections')" />
|
||||
<v-checkbox value="unmanaged" v-model="_value" :label="$t('unmanaged_collections')" />
|
||||
<v-checkbox value="hidden" v-model="_value" :label="$t('hidden_collections')" />
|
||||
<v-checkbox value="system" v-model="_value" :label="$t('system_collections')" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<template>
|
||||
<div class="actions">
|
||||
<v-button secondary @click="$emit('cancel')">
|
||||
{{ $t('cancel') }}
|
||||
<v-button
|
||||
v-if="!isExisting && currentTabIndex < tabs.length - 1"
|
||||
@click="nextTab"
|
||||
:disabled="nextDisabled"
|
||||
icon
|
||||
rounded
|
||||
v-tooltip.bottom="$t('next')"
|
||||
>
|
||||
<v-icon name="arrow_forward" />
|
||||
</v-button>
|
||||
<div class="spacer" />
|
||||
<v-button v-if="!isExisting && currentTabIndex < tabs.length - 1" @click="nextTab" :disabled="nextDisabled">
|
||||
{{ $t('next') }}
|
||||
</v-button>
|
||||
<v-button v-else @click="$emit('save')" :loading="saving">
|
||||
{{ $t('save') }}
|
||||
|
||||
<v-button v-else @click="$emit('save')" :loading="saving" icon rounded v-tooltip.bottom="$t('save')">
|
||||
<v-icon name="check" />
|
||||
</v-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('display_setup_title') }}</h2>
|
||||
<v-notice type="info">{{ $t('display_setup_title') }}</v-notice>
|
||||
|
||||
<v-fancy-select class="select" :items="selectItems" v-model="fieldData.meta.display" />
|
||||
|
||||
@@ -130,4 +130,8 @@ export default defineComponent({
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('schema_field_title') }}</h2>
|
||||
<v-notice type="info">{{ $t('schema_field_title') }}</v-notice>
|
||||
|
||||
<div class="form">
|
||||
<div class="field half-left" v-if="fieldData.meta">
|
||||
@@ -103,4 +103,8 @@ export default defineComponent({
|
||||
.required {
|
||||
--v-icon-color: var(--primary);
|
||||
}
|
||||
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('interface_setup_title') }}</h2>
|
||||
<v-notice type="info">{{ $t('interface_setup_title') }}</v-notice>
|
||||
|
||||
<v-fancy-select class="select" :items="selectItems" v-model="fieldData.meta.interface" />
|
||||
|
||||
@@ -139,4 +139,8 @@ export default defineComponent({
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('configure_languages') }}</h2>
|
||||
<div class="grid">
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('translations_collection') }}</div>
|
||||
<v-input disabled :value="relations[1].many_collection" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('languages_collection') }}</div>
|
||||
<v-input :class="{ matches: languagesCollectionExists }" db-safe key="languages-collection" v-model="relations[1].one_collection" :disabled="isExisting" :placeholder="$t('collection') + '...'">
|
||||
<template #append>
|
||||
<v-menu show-arrow placement="bottom-end">
|
||||
<template #activator="{ toggle }">
|
||||
<v-icon name="list_alt" @click="toggle" v-tooltip="$t('select_existing')" :disabled="isExisting" />
|
||||
</template>
|
||||
|
||||
<v-list class="monospace">
|
||||
<v-list-item
|
||||
v-for="item in items"
|
||||
:key="item.value"
|
||||
:active="relations[1].one_collection === item.value"
|
||||
:disabled="item.disabled"
|
||||
@click="relations[1].one_collection = item.value"
|
||||
>
|
||||
<v-list-item-content>
|
||||
{{ item.text }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
</v-input>
|
||||
</div>
|
||||
<v-input :value="relations[1].many_field" :placeholder="$t('foreign_key') + '...'"/>
|
||||
<v-input db-safe :disabled="languagesCollectionExists" v-model="relations[1].one_primary" :placeholder="$t('primary_key') + '...'" />
|
||||
<v-icon class="arrow" name="arrow_back" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, watch } from '@vue/composition-api';
|
||||
import { Relation } from '@/types';
|
||||
import { Field } from '@/types';
|
||||
import { orderBy } from 'lodash';
|
||||
import useSync from '@/composables/use-sync';
|
||||
import { useCollectionsStore, useFieldsStore } from '@/stores';
|
||||
import i18n from '@/lang';
|
||||
|
||||
import { state } from '../store';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isExisting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
const { items } = useRelation();
|
||||
|
||||
const languagesCollectionExists = computed(() => {
|
||||
return !!collectionsStore.getCollection(state.relations[1].one_collection);
|
||||
});
|
||||
|
||||
return {
|
||||
relations: state.relations,
|
||||
items,
|
||||
fieldData: state.fieldData,
|
||||
languagesCollectionExists,
|
||||
};
|
||||
|
||||
function useRelation() {
|
||||
const availableCollections = computed(() => {
|
||||
return orderBy(
|
||||
collectionsStore.state.collections.filter((collection) => {
|
||||
return collection.collection.startsWith('directus_') === false;
|
||||
}),
|
||||
['collection'],
|
||||
['asc']
|
||||
);
|
||||
});
|
||||
|
||||
const items = computed(() =>
|
||||
availableCollections.value.map((collection) => ({
|
||||
text: collection.collection,
|
||||
value: collection.collection,
|
||||
}))
|
||||
);
|
||||
|
||||
return { items };
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.grid {
|
||||
--v-select-font-family: var(--family-monospace);
|
||||
--v-input-font-family: var(--family-monospace);
|
||||
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 32px;
|
||||
margin-top: 48px;
|
||||
|
||||
.v-input.matches {
|
||||
--v-input-color: var(--primary);
|
||||
}
|
||||
|
||||
.arrow {
|
||||
--v-icon-color: var(--primary);
|
||||
|
||||
position: absolute;
|
||||
bottom: 14px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.v-list {
|
||||
--v-list-item-content-font-family: var(--family-monospace);
|
||||
}
|
||||
|
||||
.v-divider {
|
||||
margin: 48px 0;
|
||||
}
|
||||
|
||||
.type-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('configure_m2m') }}</h2>
|
||||
<v-notice type="info">{{ $t('configure_m2m') }}</v-notice>
|
||||
|
||||
<div class="grid">
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('this_collection') }}</div>
|
||||
@@ -452,4 +453,8 @@ export default defineComponent({
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('configure_m2o') }}</h2>
|
||||
<v-notice type="info">{{ $t('configure_m2o') }}</v-notice>
|
||||
|
||||
<div class="grid">
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('this_collection') }}</div>
|
||||
@@ -255,4 +256,8 @@ export default defineComponent({
|
||||
.type-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('configure_o2m') }}</h2>
|
||||
<v-notice type="info">{{ $t('configure_o2m') }}</v-notice>
|
||||
|
||||
<div class="grid">
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('this_collection') }}</div>
|
||||
@@ -380,4 +381,8 @@ export default defineComponent({
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('schema_setup_title') }}</h2>
|
||||
<v-notice type="info">
|
||||
{{ $t('schema_setup_title') }}
|
||||
</v-notice>
|
||||
|
||||
<div class="form">
|
||||
<div class="field">
|
||||
@@ -392,10 +394,6 @@ export default defineComponent({
|
||||
@import '@/styles/mixins/breakpoint';
|
||||
@import '@/styles/mixins/form-grid';
|
||||
|
||||
.type-title {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.form {
|
||||
--v-form-vertical-gap: 32px;
|
||||
--v-form-horizontal-gap: 32px;
|
||||
@@ -416,4 +414,8 @@ export default defineComponent({
|
||||
grid-gap: 12px;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="type-title">{{ $t('configure_m2m') }}</h2>
|
||||
<v-notice type="info">{{ $t('configure_m2m') }}</v-notice>
|
||||
|
||||
<div class="grid">
|
||||
<div class="field">
|
||||
<div class="type-label">{{ $t('this_collection') }}</div>
|
||||
@@ -349,4 +350,8 @@ export default defineComponent({
|
||||
.v-list {
|
||||
--v-list-item-content-font-family: var(--family-monospace);
|
||||
}
|
||||
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-modal
|
||||
<v-drawer
|
||||
v-else
|
||||
:active="true"
|
||||
@toggle="cancelField"
|
||||
@esc="cancelField"
|
||||
@cancel="cancelField"
|
||||
:title="
|
||||
field === '+'
|
||||
? $t('creating_new_field', { collection: collectionInfo.name })
|
||||
@@ -45,49 +45,51 @@
|
||||
<setup-tabs :current.sync="currentTab" :tabs="tabs" :type="localType" />
|
||||
</template>
|
||||
|
||||
<setup-schema
|
||||
v-if="currentTab[0] === 'schema'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
<div class="content">
|
||||
<setup-schema
|
||||
v-if="currentTab[0] === 'schema'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
|
||||
<setup-field
|
||||
v-if="currentTab[0] === 'field'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
<setup-field
|
||||
v-if="currentTab[0] === 'field'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
|
||||
<setup-relationship
|
||||
v-if="currentTab[0] === 'relationship'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
<setup-relationship
|
||||
v-if="currentTab[0] === 'relationship'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
|
||||
<setup-translations
|
||||
v-if="currentTab[0] === 'translations'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
<setup-translations
|
||||
v-if="currentTab[0] === 'translations'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
|
||||
<setup-interface
|
||||
v-if="currentTab[0] === 'interface'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
<setup-interface
|
||||
v-if="currentTab[0] === 'interface'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
|
||||
<setup-display
|
||||
v-if="currentTab[0] === 'display'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
<setup-display
|
||||
v-if="currentTab[0] === 'display'"
|
||||
:is-existing="field !== '+'"
|
||||
:collection="collection"
|
||||
:type="localType"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<template #actions>
|
||||
<setup-actions
|
||||
:saving="saving"
|
||||
:collection="collection"
|
||||
@@ -98,7 +100,7 @@
|
||||
@cancel="cancelField"
|
||||
/>
|
||||
</template>
|
||||
</v-modal>
|
||||
</v-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -404,4 +406,10 @@ export default defineComponent({
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--content-padding);
|
||||
padding-top: 0;
|
||||
padding-bottom: var(--content-padding);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -474,7 +474,7 @@ function initLocalStore(collection: string, field: string, type: typeof localTyp
|
||||
name: 'German',
|
||||
},
|
||||
{
|
||||
code: 'fr-Fr',
|
||||
code: 'fr-FR',
|
||||
name: 'French',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -49,7 +49,15 @@
|
||||
|
||||
<template #input>
|
||||
<div class="label">
|
||||
<span class="name" v-tooltip="field.name">{{ field.field }}</span>
|
||||
<span class="name" v-tooltip="field.name">
|
||||
{{ field.field }}
|
||||
<v-icon
|
||||
name="star"
|
||||
class="required"
|
||||
sup
|
||||
v-if="field.schema && field.schema.is_nullable === false"
|
||||
/>
|
||||
</span>
|
||||
<span v-if="field.meta" class="interface">{{ interfaceName }}</span>
|
||||
<span v-else class="interface">{{ $t('db_only_click_to_configure') }}</span>
|
||||
</div>
|
||||
@@ -90,7 +98,7 @@
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item @click="duplicateActive = true">
|
||||
<v-list-item v-if="duplicable" @click="duplicateActive = true">
|
||||
<v-list-item-icon>
|
||||
<v-icon name="content_copy" />
|
||||
</v-list-item-icon>
|
||||
@@ -217,7 +225,15 @@ export default defineComponent({
|
||||
const editActive = ref(false);
|
||||
|
||||
const { deleteActive, deleting, deleteField } = useDeleteField();
|
||||
const { duplicateActive, duplicateName, collections, duplicateTo, saveDuplicate, duplicating } = useDuplicate();
|
||||
const {
|
||||
duplicateActive,
|
||||
duplicateName,
|
||||
collections,
|
||||
duplicateTo,
|
||||
saveDuplicate,
|
||||
duplicating,
|
||||
duplicable,
|
||||
} = useDuplicate();
|
||||
|
||||
const interfaceName = computed(() => {
|
||||
return interfaces.value.find((inter) => inter.id === props.field.meta?.interface)?.name;
|
||||
@@ -248,6 +264,7 @@ export default defineComponent({
|
||||
localType,
|
||||
translationsCollection,
|
||||
translationsFieldsCount,
|
||||
duplicable,
|
||||
};
|
||||
|
||||
function setWidth(width: string) {
|
||||
@@ -288,6 +305,13 @@ export default defineComponent({
|
||||
);
|
||||
const duplicateTo = ref(props.field.collection);
|
||||
|
||||
const duplicable = computed(() => {
|
||||
return (
|
||||
['o2m', 'm2m', 'm2o', 'files', 'file', 'm2a'].includes(props.field.type) === false &&
|
||||
props.field.schema?.is_primary_key === false
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
duplicateActive,
|
||||
duplicateName,
|
||||
@@ -295,6 +319,7 @@ export default defineComponent({
|
||||
duplicateTo,
|
||||
saveDuplicate,
|
||||
duplicating,
|
||||
duplicable,
|
||||
};
|
||||
|
||||
async function saveDuplicate() {
|
||||
@@ -505,4 +530,8 @@ export default defineComponent({
|
||||
--v-button-background-color: var(--danger);
|
||||
--v-button-background-color-hover: var(--danger-125);
|
||||
}
|
||||
|
||||
.required {
|
||||
color: var(--primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -73,10 +73,10 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="page-description" v-html="marked($t('page_help_settings_datamodel_fields'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
</private-view>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<v-modal
|
||||
<v-drawer
|
||||
:title="$t('creating_new_collection')"
|
||||
:active="true"
|
||||
class="new-collection"
|
||||
persistent
|
||||
@esc="$router.push('/settings/data-model')"
|
||||
@cancel="$router.push('/settings/data-model')"
|
||||
>
|
||||
<v-dialog :active="saveError !== null" @toggle="saveError = null" @esc="saveError = null">
|
||||
<v-card class="selectable">
|
||||
@@ -29,9 +29,10 @@
|
||||
</v-tabs>
|
||||
</template>
|
||||
|
||||
<v-tabs-items v-model="currentTab">
|
||||
<v-tabs-items class="content" v-model="currentTab">
|
||||
<v-tab-item value="collection">
|
||||
<h2 class="type-title">{{ $t('creating_collection_info') }}</h2>
|
||||
<v-notice type="info">{{ $t('creating_collection_info') }}</v-notice>
|
||||
|
||||
<div class="grid">
|
||||
<div>
|
||||
<div class="type-label">
|
||||
@@ -83,7 +84,8 @@
|
||||
</div>
|
||||
</v-tab-item>
|
||||
<v-tab-item value="system">
|
||||
<h2 class="type-title">{{ $t('creating_collection_system') }}</h2>
|
||||
<v-notice type="info">{{ $t('creating_collection_system') }}</v-notice>
|
||||
|
||||
<div class="grid system">
|
||||
<div v-for="(info, field) in systemFields" :key="field">
|
||||
<div class="type-label">{{ $t(info.label) }}</div>
|
||||
@@ -106,23 +108,29 @@
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
|
||||
<template #footer>
|
||||
<v-button secondary to="/settings/data-model">
|
||||
{{ $t('cancel') }}
|
||||
</v-button>
|
||||
<div class="spacer" />
|
||||
<template #actions>
|
||||
<v-button
|
||||
:disabled="!collectionName || collectionName.length === 0"
|
||||
v-if="currentTab[0] === 'collection'"
|
||||
@click="currentTab = ['system']"
|
||||
v-tooltip.bottom="$t('next')"
|
||||
icon
|
||||
rounded
|
||||
>
|
||||
{{ $t('next') }}
|
||||
<v-icon name="arrow_forward" />
|
||||
</v-button>
|
||||
<v-button v-if="currentTab[0] === 'system'" @click="save" :loading="saving">
|
||||
{{ $t('finish_setup') }}
|
||||
<v-button
|
||||
v-if="currentTab[0] === 'system'"
|
||||
@click="save"
|
||||
:loading="saving"
|
||||
v-tooltip.bottom="$t('finish_setup')"
|
||||
icon
|
||||
rounded
|
||||
>
|
||||
<v-icon name="check" />
|
||||
</v-button>
|
||||
</template>
|
||||
</v-modal>
|
||||
</v-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -469,4 +477,14 @@ export default defineComponent({
|
||||
.required {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: var(--content-padding);
|
||||
padding-top: 0;
|
||||
padding-bottom: var(--content-padding);
|
||||
}
|
||||
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -84,8 +84,8 @@
|
||||
</v-table>
|
||||
</div>
|
||||
|
||||
<template #drawer>
|
||||
<presets-info-drawer-detail />
|
||||
<template #sidebar>
|
||||
<presets-info-sidebar-detail />
|
||||
</template>
|
||||
</private-view>
|
||||
</template>
|
||||
@@ -102,7 +102,7 @@ import { getLayouts } from '@/layouts';
|
||||
import { TranslateResult } from 'vue-i18n';
|
||||
import router from '@/router';
|
||||
import ValueNull from '@/views/private/components/value-null';
|
||||
import PresetsInfoDrawerDetail from './components/presets-info-drawer-detail.vue';
|
||||
import PresetsInfoSidebarDetail from './components/presets-info-sidebar-detail.vue';
|
||||
|
||||
type PresetRaw = {
|
||||
id: number;
|
||||
@@ -122,7 +122,7 @@ type Preset = {
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
components: { SettingsNavigation, ValueNull, PresetsInfoDrawerDetail },
|
||||
components: { SettingsNavigation, ValueNull, PresetsInfoSidebarDetail },
|
||||
setup() {
|
||||
const layouts = getLayouts();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>{{ $t('bookmarks') }}</dt>
|
||||
@@ -14,7 +14,7 @@
|
||||
<v-divider />
|
||||
|
||||
<div class="page-description" v-html="marked($t('page_help_settings_presets_collection'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -39,7 +39,7 @@ export default defineComponent({
|
||||
try {
|
||||
const response = await api.get(`/presets`, {
|
||||
params: {
|
||||
[`filter[bookmark][_nnull]`]: 1,
|
||||
[`filter[bookmark][_nnull]`]: true,
|
||||
fields: ['id'],
|
||||
meta: 'filter_count,total_count',
|
||||
},
|
||||
@@ -75,18 +75,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="page-description" v-html="marked($t('page_help_settings_presets_item'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
|
||||
<portal-target class="layout-drawer" name="drawer" />
|
||||
<portal-target class="layout-sidebar" name="sidebar" />
|
||||
|
||||
<drawer-detail class="layout-drawer" icon="layers" :title="$t('layout_options')">
|
||||
<sidebar-detail class="layout-sidebar" icon="layers" :title="$t('layout_options')">
|
||||
<div class="layout-options">
|
||||
<portal-target name="layout-options" class="portal-contents" />
|
||||
</div>
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
</private-view>
|
||||
</template>
|
||||
@@ -547,10 +547,10 @@ export default defineComponent({
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.layout-drawer {
|
||||
--drawer-detail-icon-color: var(--warning);
|
||||
--drawer-detail-color: var(--warning);
|
||||
--drawer-detail-color-active: var(--warning);
|
||||
.layout-sidebar {
|
||||
--sidebar-detail-icon-color: var(--warning);
|
||||
--sidebar-detail-color: var(--warning);
|
||||
--sidebar-detail-color-active: var(--warning);
|
||||
--v-form-vertical-gap: 24px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<dl v-if="parsedInfo">
|
||||
<div>
|
||||
<dt>{{ $t('directus_version') }}</dt>
|
||||
@@ -34,7 +34,7 @@
|
||||
<v-divider />
|
||||
|
||||
<div class="page-description" v-html="marked($t('page_help_settings_project'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -21,8 +21,8 @@
|
||||
<v-form :initial-values="initialValues" v-model="edits" :fields="fields" :primary-key="1" />
|
||||
</div>
|
||||
|
||||
<template #drawer>
|
||||
<project-info-drawer-detail />
|
||||
<template #sidebar>
|
||||
<project-info-sidebar-detail />
|
||||
</template>
|
||||
</private-view>
|
||||
</template>
|
||||
@@ -32,11 +32,11 @@ import { defineComponent, ref, computed } from '@vue/composition-api';
|
||||
import SettingsNavigation from '../../components/navigation.vue';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import { useSettingsStore } from '@/stores';
|
||||
import ProjectInfoDrawerDetail from './components/project-info-drawer-detail.vue';
|
||||
import ProjectInfoSidebarDetail from './components/project-info-sidebar-detail.vue';
|
||||
import { clone } from 'lodash';
|
||||
|
||||
export default defineComponent({
|
||||
components: { SettingsNavigation, ProjectInfoDrawerDetail },
|
||||
components: { SettingsNavigation, ProjectInfoSidebarDetail },
|
||||
setup() {
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
<settings-navigation />
|
||||
</template>
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="page-description" v-html="marked($t('page_help_settings_roles_collection'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
|
||||
<div class="roles">
|
||||
|
||||
@@ -136,15 +136,15 @@ export default defineComponent({
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get('/permissions', {
|
||||
params: {
|
||||
filter: {
|
||||
role: {
|
||||
_eq: props.role,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const params: any = { filter: { role: {} } };
|
||||
|
||||
if (props.role === null) {
|
||||
params.filter.role = { _null: true };
|
||||
} else {
|
||||
params.filter.role = { _eq: props.role };
|
||||
}
|
||||
|
||||
const response = await api.get('/permissions', params);
|
||||
|
||||
permissions.value = response.data.data;
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<dl v-if="!isNew && role">
|
||||
<div>
|
||||
<dt>{{ $t('primary_key') }}</dt>
|
||||
@@ -10,7 +10,7 @@
|
||||
<v-divider />
|
||||
|
||||
<div class="page-description" v-html="marked($t('page_help_settings_roles_item'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -71,8 +71,8 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #drawer>
|
||||
<role-info-drawer-detail :role="item" />
|
||||
<template #sidebar>
|
||||
<role-info-sidebar-detail :role="item" />
|
||||
<revisions-drawer-detail collection="directus_roles" :primary-key="primaryKey" />
|
||||
</template>
|
||||
</private-view>
|
||||
@@ -86,7 +86,7 @@ import router from '@/router';
|
||||
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail';
|
||||
import useItem from '@/composables/use-item';
|
||||
import { useUserStore } from '@/stores/';
|
||||
import RoleInfoDrawerDetail from './components/role-info-drawer-detail.vue';
|
||||
import RoleInfoSidebarDetail from './components/role-info-sidebar-detail.vue';
|
||||
import PermissionsOverview from './components/permissions-overview.vue';
|
||||
|
||||
type Values = {
|
||||
@@ -95,7 +95,7 @@ type Values = {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'roles-item',
|
||||
components: { SettingsNavigation, RevisionsDrawerDetail, RoleInfoDrawerDetail, PermissionsOverview },
|
||||
components: { SettingsNavigation, RevisionsDrawerDetail, RoleInfoSidebarDetail, PermissionsOverview },
|
||||
props: {
|
||||
primaryKey: {
|
||||
type: String,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div class="actions">
|
||||
<v-button @click="save" :loading="loading">{{ $t('save') }}</v-button>
|
||||
<v-button @click="save" :loading="loading" icon rounded v-tooltip.bottom="$t('save')">
|
||||
<v-icon name="check" />
|
||||
</v-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-modal-heading
|
||||
:heading="
|
||||
<v-notice type="info">
|
||||
{{
|
||||
$t('fields_for_role', {
|
||||
role: role ? role.name : $t('public'),
|
||||
action: $t(permission.action).toLowerCase(),
|
||||
})
|
||||
"
|
||||
/>
|
||||
}}
|
||||
</v-notice>
|
||||
|
||||
<p class="type-label">{{ $tc('field', 0) }}</p>
|
||||
<interface-checkboxes v-model="fields" type="json" :choices="fieldsInCollection" />
|
||||
</div>
|
||||
@@ -83,4 +84,8 @@ export default defineComponent({
|
||||
.type-label {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-modal-heading
|
||||
:heading="
|
||||
<v-notice type="info">
|
||||
{{
|
||||
$t('permissions_for_role', {
|
||||
action: $t(permission.action).toLowerCase(),
|
||||
role: role ? role.name : $t('public'),
|
||||
})
|
||||
"
|
||||
/>
|
||||
}}
|
||||
</v-notice>
|
||||
|
||||
<interface-code v-model="permissions" language="json" type="json" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -47,3 +48,9 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-modal-heading
|
||||
:heading="
|
||||
<v-notice type="info">
|
||||
{{
|
||||
$t('presets_for_role', {
|
||||
action: $t(permission.action).toLowerCase(),
|
||||
role: role ? role.name : $t('public'),
|
||||
})
|
||||
"
|
||||
/>
|
||||
}}
|
||||
</v-notice>
|
||||
<interface-code v-model="presets" language="json" type="json" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -47,3 +47,9 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-modal-heading
|
||||
:heading="
|
||||
<v-notice type="info">
|
||||
{{
|
||||
$t('validation_for_role', {
|
||||
action: $t(permission.action).toLowerCase(),
|
||||
role: role ? role.name : $t('public'),
|
||||
})
|
||||
"
|
||||
/>
|
||||
}}
|
||||
</v-notice>
|
||||
|
||||
<interface-code v-model="validation" language="json" type="json" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -47,3 +48,9 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-notice {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<template>
|
||||
<v-modal :title="modalTitle" :active="true" class="new-collection" persistent>
|
||||
<v-drawer :title="modalTitle" :active="true" class="new-collection" persistent>
|
||||
<template #sidebar v-if="!loading">
|
||||
<tabs :current-tab.sync="currentTab" :tabs="tabs" />
|
||||
</template>
|
||||
|
||||
<template v-if="!loading">
|
||||
<div class="content" v-if="!loading">
|
||||
<permissions v-if="currentTab[0] === 'permissions'" :permission.sync="permission" :role="role" />
|
||||
<fields v-if="currentTab[0] === 'fields'" :permission.sync="permission" :role="role" />
|
||||
<validation v-if="currentTab[0] === 'validation'" :permission.sync="permission" :role="role" />
|
||||
<presets v-if="currentTab[0] === 'presets'" :permission.sync="permission" :role="role" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template #footer v-if="!loading">
|
||||
<template #actions v-if="!loading">
|
||||
<actions :role-key="roleKey" :permission="permission" @refresh="$emit('refresh', +permissionKey)" />
|
||||
</template>
|
||||
</v-modal>
|
||||
</v-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -149,3 +149,11 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
padding: var(--content-padding);
|
||||
padding-top: 0;
|
||||
padding-bottom: var(--content-padding);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -85,12 +85,12 @@
|
||||
</template>
|
||||
</component>
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="page-description" v-html="marked($t('page_help_settings_webhooks_collection'))" />
|
||||
</drawer-detail>
|
||||
<layout-drawer-detail />
|
||||
<portal-target name="drawer" />
|
||||
</sidebar-detail>
|
||||
<layout-sidebar-detail />
|
||||
<portal-target name="sidebar" />
|
||||
</template>
|
||||
</private-view>
|
||||
</template>
|
||||
@@ -98,7 +98,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from '@vue/composition-api';
|
||||
import SettingsNavigation from '../../components/navigation.vue';
|
||||
import LayoutDrawerDetail from '@/views/private/components/layout-drawer-detail';
|
||||
import LayoutSidebarDetail from '@/views/private/components/layout-sidebar-detail';
|
||||
import marked from 'marked';
|
||||
import { LayoutComponent } from '@/layouts/types';
|
||||
import { usePreset } from '@/composables/use-preset';
|
||||
@@ -112,7 +112,7 @@ type Item = {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'webhooks-collection',
|
||||
components: { SettingsNavigation, LayoutDrawerDetail, SearchInput },
|
||||
components: { SettingsNavigation, LayoutSidebarDetail, SearchInput },
|
||||
setup(props) {
|
||||
const layoutRef = ref<LayoutComponent | null>(null);
|
||||
|
||||
|
||||
@@ -58,10 +58,10 @@
|
||||
v-model="edits"
|
||||
/>
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="page-description" v-html="marked($t('page_help_settings_webhooks_item'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
<revisions-drawer-detail v-if="isNew === false" collection="directus_webhooks" :primary-key="primaryKey" />
|
||||
</template>
|
||||
</private-view>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<dl v-if="isNew === false && user">
|
||||
<div v-if="user.id">
|
||||
<dt>{{ $t('key') }}</dt>
|
||||
@@ -32,7 +32,7 @@
|
||||
<v-divider />
|
||||
|
||||
<div class="page-description" v-html="marked($t('page_help_users_item'))" />
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -91,12 +91,12 @@
|
||||
</template>
|
||||
</component>
|
||||
|
||||
<template #drawer>
|
||||
<drawer-detail icon="info_outline" :title="$t('information')" close>
|
||||
<template #sidebar>
|
||||
<sidebar-detail icon="info_outline" :title="$t('information')" close>
|
||||
<div class="page-description" v-html="marked($t('page_help_users_collection'))" />
|
||||
</drawer-detail>
|
||||
<layout-drawer-detail @input="layout = $event" :value="layout" />
|
||||
<portal-target name="drawer" />
|
||||
</sidebar-detail>
|
||||
<layout-sidebar-detail @input="layout = $event" :value="layout" />
|
||||
<portal-target name="sidebar" />
|
||||
</template>
|
||||
</private-view>
|
||||
</template>
|
||||
@@ -109,7 +109,7 @@ import { i18n } from '@/lang';
|
||||
import api from '@/api';
|
||||
import { LayoutComponent } from '@/layouts/types';
|
||||
import usePreset from '@/composables/use-preset';
|
||||
import LayoutDrawerDetail from '@/views/private/components/layout-drawer-detail';
|
||||
import LayoutSidebarDetail from '@/views/private/components/layout-sidebar-detail';
|
||||
import SearchInput from '@/views/private/components/search-input';
|
||||
import marked from 'marked';
|
||||
import useNavigation from '../composables/use-navigation';
|
||||
@@ -120,7 +120,7 @@ type Item = {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'users-collection',
|
||||
components: { UsersNavigation, LayoutDrawerDetail, SearchInput },
|
||||
components: { UsersNavigation, LayoutSidebarDetail, SearchInput },
|
||||
props: {
|
||||
queryFilters: {
|
||||
type: Object as PropType<Record<string, string>>,
|
||||
|
||||
@@ -143,15 +143,15 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<template #drawer>
|
||||
<user-info-drawer-detail :is-new="isNew" :user="item" />
|
||||
<template #sidebar>
|
||||
<user-info-sidebar-detail :is-new="isNew" :user="item" />
|
||||
<revisions-drawer-detail
|
||||
v-if="isBatch === false && isNew === false"
|
||||
collection="directus_users"
|
||||
:primary-key="primaryKey"
|
||||
ref="revisionsDrawerDetail"
|
||||
/>
|
||||
<comments-drawer-detail
|
||||
<comments-sidebar-detail
|
||||
v-if="isBatch === false && isNew === false"
|
||||
collection="directus_users"
|
||||
:primary-key="primaryKey"
|
||||
@@ -167,14 +167,14 @@ import UsersNavigation from '../components/navigation.vue';
|
||||
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 CommentsSidebarDetail from '@/views/private/components/comments-sidebar-detail';
|
||||
import useItem from '@/composables/use-item';
|
||||
import SaveOptions from '@/views/private/components/save-options';
|
||||
import api from '@/api';
|
||||
import { useFieldsStore, useUserStore } from '@/stores/';
|
||||
import useFormFields from '@/composables/use-form-fields';
|
||||
import { Field } from '@/types';
|
||||
import UserInfoDrawerDetail from '../components/user-info-drawer-detail.vue';
|
||||
import UserInfoSidebarDetail from '../components/user-info-sidebar-detail.vue';
|
||||
import { getRootPath } from '@/utils/get-root-path';
|
||||
import useShortcut from '@/composables/use-shortcut';
|
||||
import { isAllowed } from '@/utils/is-allowed';
|
||||
@@ -198,7 +198,7 @@ export default defineComponent({
|
||||
|
||||
return next();
|
||||
},
|
||||
components: { UsersNavigation, RevisionsDrawerDetail, SaveOptions, CommentsDrawerDetail, UserInfoDrawerDetail },
|
||||
components: { UsersNavigation, RevisionsDrawerDetail, SaveOptions, CommentsSidebarDetail, UserInfoSidebarDetail },
|
||||
props: {
|
||||
primaryKey: {
|
||||
type: String,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createStore } from 'pinia';
|
||||
export const useAppStore = createStore({
|
||||
id: 'appStore',
|
||||
state: () => ({
|
||||
drawerOpen: false,
|
||||
sidebarOpen: false,
|
||||
hydrated: false,
|
||||
hydrating: false,
|
||||
error: null,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
font-family: var(--family-sans-serif);
|
||||
font-style: normal;
|
||||
line-height: 29px;
|
||||
letter-spacing: -0.8px;
|
||||
letter-spacing: 0;
|
||||
|
||||
@include breakpoint(small) {
|
||||
font-size: 24px;
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import CommentsDrawerDetail from './comments-drawer-detail.vue';
|
||||
|
||||
export { CommentsDrawerDetail };
|
||||
export default CommentsDrawerDetail;
|
||||
@@ -1,9 +0,0 @@
|
||||
# Comments Drawer
|
||||
|
||||
Renders an comment timeline in a drawer section meant to be used in the drawer sidebar.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<comments-drawer-detail collection="authors" primary-key="15" />
|
||||
```
|
||||
@@ -28,7 +28,7 @@
|
||||
<template #activator="{ toggle, active }">
|
||||
<v-icon class="more" :class="{ active }" name="more_horiz" @click="toggle" />
|
||||
<div class="time">
|
||||
<span class="dot" v-if="activity.revisions.length > 0" v-tooltip="editedOnFormatted" />
|
||||
<span class="dot" v-tooltip="editedOnFormatted" />
|
||||
{{ formattedTime }}
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<drawer-detail :title="$t('comments')" icon="chat_bubble_outline" :badge="count || null">
|
||||
<sidebar-detail :title="$t('comments')" icon="chat_bubble_outline" :badge="count || null">
|
||||
<comment-input :refresh="refresh" :collection="collection" :primary-key="primaryKey" />
|
||||
|
||||
<v-progress-linear indeterminate v-if="loading" />
|
||||
@@ -15,7 +15,7 @@
|
||||
<comment-item :refresh="refresh" :activity="item" :key="item.id" />
|
||||
</template>
|
||||
</template>
|
||||
</drawer-detail>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -138,7 +138,7 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drawer-detail {
|
||||
.sidebar-detail {
|
||||
--v-badge-background-color: var(--foreground-normal);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import CommentsSidebarDetail from './comments-sidebar-detail.vue';
|
||||
|
||||
export { CommentsSidebarDetail };
|
||||
export default CommentsSidebarDetail;
|
||||
@@ -0,0 +1,9 @@
|
||||
# Comments Sidebar
|
||||
|
||||
Renders an comment timeline in a sidebar section meant to be used in the sidebar sidebar.
|
||||
|
||||
## Usage
|
||||
|
||||
```html
|
||||
<comments-sidebar-detail collection="authors" primary-key="15" />
|
||||
```
|
||||
@@ -1,4 +0,0 @@
|
||||
import DrawerButton from './drawer-button.vue';
|
||||
|
||||
export { DrawerButton };
|
||||
export default DrawerButton;
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-modal v-model="_active" :title="$t('select_item')" no-padding @esc="cancel">
|
||||
<v-drawer v-model="_active" :title="$t('select_item')" @cancel="cancel">
|
||||
<component
|
||||
:is="`layout-${localLayout}`"
|
||||
:collection="collection"
|
||||
@@ -20,11 +20,12 @@
|
||||
</template>
|
||||
</component>
|
||||
|
||||
<template #footer>
|
||||
<v-button @click="cancel" secondary>{{ $t('cancel') }}</v-button>
|
||||
<v-button @click="save">{{ $t('save') }}</v-button>
|
||||
<template #actions>
|
||||
<v-button @click="save" icon rounded v-tooltip.bottom="$t('save')">
|
||||
<v-icon name="check" />
|
||||
</v-button>
|
||||
</template>
|
||||
</v-modal>
|
||||
</v-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user