mirror of
https://github.com/directus/directus.git
synced 2026-01-29 16:28:02 -05:00
Squashed commit of the following:
commit aabfcdb9fc75789e8b68a026297651dc3b9a432d Author: rijkvanzanten <rijkvanzanten@me.com> Date: Fri Jul 3 11:40:44 2020 -0400 Fix more projects stuff commit 5df3364e0887627fca8361297b92a9ff5b36c98f Author: rijkvanzanten <rijkvanzanten@me.com> Date: Fri Jul 3 11:36:28 2020 -0400 More project deletions commit 75d49100adffee5ad94736e62470cffa89a44aa3 Author: rijkvanzanten <rijkvanzanten@me.com> Date: Fri Jul 3 11:32:04 2020 -0400 Batch get rid of projects commit 02a64a5d4c6ae02ece42878dc10560b662b1dfca Author: rijkvanzanten <rijkvanzanten@me.com> Date: Fri Jul 3 11:06:20 2020 -0400 Some more things commit 617e22e0abd5d6bf57e1eac95b9a68287cd0044b Author: rijkvanzanten <rijkvanzanten@me.com> Date: Fri Jul 3 10:35:43 2020 -0400 Fix module bar logo projects check commit a8fbf0be75055c31075f2352b150309fea424e8a Author: rijkvanzanten <rijkvanzanten@me.com> Date: Fri Jul 3 10:35:24 2020 -0400 Set auth token in auth handler commit c0c1c1bb164bd49d252c6dd1d944d7bc4b6aede5 Author: rijkvanzanten <rijkvanzanten@me.com> Date: Fri Jul 3 10:18:30 2020 -0400 public login
This commit is contained in:
159
src/api.test.ts
159
src/api.test.ts
@@ -1,159 +0,0 @@
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { onRequest, onResponse, onError, RequestError } from './api';
|
||||
import * as auth from '@/auth';
|
||||
import { useRequestsStore } from '@/stores/requests';
|
||||
|
||||
const defaultError: RequestError = {
|
||||
config: {},
|
||||
isAxiosError: false,
|
||||
toJSON: () => ({}),
|
||||
name: 'error',
|
||||
message: '',
|
||||
response: {
|
||||
data: null,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config: {
|
||||
id: 'abc',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('API', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(auth, 'logout');
|
||||
jest.spyOn(auth, 'checkAuth');
|
||||
Vue.use(VueCompositionAPI);
|
||||
window = Object.create(window);
|
||||
});
|
||||
|
||||
it('Calls startRequest on the store on any request', () => {
|
||||
const store = useRequestsStore({});
|
||||
const spy = jest.spyOn(store, 'startRequest');
|
||||
spy.mockImplementation(() => 'abc');
|
||||
const newRequest = onRequest({});
|
||||
expect(spy).toHaveBeenCalled();
|
||||
expect(newRequest.id).toBe('abc');
|
||||
});
|
||||
|
||||
it('Calls endRequest on responses', () => {
|
||||
const store = useRequestsStore({});
|
||||
const spy = jest.spyOn(store, 'endRequest');
|
||||
onResponse({
|
||||
data: null,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {},
|
||||
config: {
|
||||
id: 'abc',
|
||||
},
|
||||
});
|
||||
expect(spy).toHaveBeenCalledWith('abc');
|
||||
});
|
||||
|
||||
it('Calls endRequest on errors', async () => {
|
||||
const store = useRequestsStore({});
|
||||
const spy = jest.spyOn(store, 'endRequest');
|
||||
try {
|
||||
await onError({
|
||||
...defaultError,
|
||||
});
|
||||
} catch {}
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('abc');
|
||||
});
|
||||
|
||||
it('Passes the error on to the next catch handler on unrelated 401 errors', async () => {
|
||||
const error = {
|
||||
...defaultError,
|
||||
response: {
|
||||
...defaultError.response,
|
||||
status: 401,
|
||||
config: {
|
||||
id: 'abc',
|
||||
},
|
||||
data: {
|
||||
error: {
|
||||
code: -5,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(onError(error)).rejects.toEqual(error);
|
||||
});
|
||||
|
||||
it('Checks the auth status on 401+3 errors', async () => {
|
||||
try {
|
||||
await onError({
|
||||
...defaultError,
|
||||
response: {
|
||||
...defaultError.response,
|
||||
config: {
|
||||
id: 'abc',
|
||||
},
|
||||
status: 401,
|
||||
data: {
|
||||
error: {
|
||||
code: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
expect(auth.checkAuth).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it('Forces a logout when the users is not logged in on 401+3 errors', async () => {
|
||||
(auth.checkAuth as jest.Mock).mockImplementation(async () => false);
|
||||
|
||||
try {
|
||||
await onError({
|
||||
...defaultError,
|
||||
response: {
|
||||
...defaultError.response,
|
||||
config: {
|
||||
id: 'abc',
|
||||
},
|
||||
status: 401,
|
||||
data: {
|
||||
error: {
|
||||
code: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
expect(auth.logout).toHaveBeenCalledWith({
|
||||
reason: auth.LogoutReason.ERROR_SESSION_EXPIRED,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('Does not call logout if the user is logged in on 401+3 error', async () => {
|
||||
(auth.checkAuth as jest.Mock).mockImplementation(async () => true);
|
||||
|
||||
try {
|
||||
await onError({
|
||||
...defaultError,
|
||||
response: {
|
||||
...defaultError.response,
|
||||
config: {
|
||||
id: 'abc',
|
||||
},
|
||||
status: 401,
|
||||
data: {
|
||||
error: {
|
||||
code: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
expect(auth.logout).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -15,7 +15,8 @@
|
||||
import { defineComponent, toRefs, watch, computed } from '@vue/composition-api';
|
||||
import { useAppStore } from '@/stores/app';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
|
||||
import useWindowSize from '@/composables/use-window-size';
|
||||
import setFavicon from '@/utils/set-favicon';
|
||||
|
||||
@@ -23,17 +24,17 @@ export default defineComponent({
|
||||
setup() {
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const { hydrating, drawerOpen } = toRefs(appStore.state);
|
||||
|
||||
const brandStyle = computed(() => {
|
||||
return {
|
||||
'--brand': projectsStore.currentProject.value?.color || 'var(--primary)',
|
||||
'--brand': settingsStore.state.settings?.project_color || 'var(--primary)',
|
||||
};
|
||||
});
|
||||
|
||||
watch(() => projectsStore.currentProject.value?.color, setFavicon);
|
||||
watch(() => settingsStore.state.settings?.project_color, setFavicon);
|
||||
|
||||
const { width } = useWindowSize();
|
||||
|
||||
|
||||
151
src/auth.test.ts
151
src/auth.test.ts
@@ -1,151 +0,0 @@
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import { checkAuth, login, logout, LogoutReason } from './auth';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
import router from '@/router';
|
||||
import { hydrate, dehydrate } from '@/hydrate';
|
||||
|
||||
jest.mock('@/api');
|
||||
jest.mock('@/router');
|
||||
jest.mock('@/hydrate');
|
||||
|
||||
describe('Auth', () => {
|
||||
beforeAll(() => {
|
||||
Vue.config.productionTip = false;
|
||||
Vue.config.devtools = false;
|
||||
Vue.use(VueCompositionAPI);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(api, 'get');
|
||||
jest.spyOn(api, 'post');
|
||||
});
|
||||
|
||||
describe('checkAuth', () => {
|
||||
it('Does not ping the API if the curent project key is null', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.currentProjectKey = null;
|
||||
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({ data: { data: { authenticated: true } } })
|
||||
);
|
||||
await checkAuth();
|
||||
expect(api.get).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Calls the api with the correct endpoint', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.currentProjectKey = 'test-project';
|
||||
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({ data: { data: { authenticated: true } } })
|
||||
);
|
||||
await checkAuth();
|
||||
expect(api.get).toHaveBeenCalledWith('/test-project/auth/check');
|
||||
});
|
||||
|
||||
it('Returns true if user is logged in', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.currentProjectKey = 'test-project';
|
||||
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({ data: { data: { authenticated: true } } })
|
||||
);
|
||||
const loggedIn = await checkAuth();
|
||||
expect(loggedIn).toBe(true);
|
||||
});
|
||||
|
||||
it('Returns false if user is logged out', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.currentProjectKey = 'test-project';
|
||||
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({ data: { data: { authenticated: false } } })
|
||||
);
|
||||
const loggedIn = await checkAuth();
|
||||
expect(loggedIn).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('login', () => {
|
||||
it('Calls /auth/authenticate with the provided credentials', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.currentProjectKey = 'test-project';
|
||||
|
||||
await login({ email: 'test', password: 'test' });
|
||||
expect(api.post).toHaveBeenCalledWith('/test-project/auth/authenticate', {
|
||||
mode: 'cookie',
|
||||
email: 'test',
|
||||
password: 'test',
|
||||
});
|
||||
});
|
||||
|
||||
it('Calls hydrate on successful login', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.currentProjectKey = 'test-project';
|
||||
|
||||
await login({ email: 'test', password: 'test' });
|
||||
|
||||
expect(hydrate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('logout', () => {
|
||||
it('Does not do anything when there is no current project', async () => {
|
||||
useProjectsStore({});
|
||||
await logout();
|
||||
expect(dehydrate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Calls dehydrate', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.currentProjectKey = 'test-project';
|
||||
|
||||
await logout();
|
||||
expect(dehydrate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Posts to /logout on regular sign out', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.currentProjectKey = 'test-project';
|
||||
|
||||
await logout();
|
||||
expect(api.post).toHaveBeenCalledWith('/test-project/auth/logout');
|
||||
});
|
||||
|
||||
it('Navigates to the current projects login page', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
|
||||
await logout();
|
||||
expect(router.push).toHaveBeenCalledWith({
|
||||
path: '/my-project/login',
|
||||
query: {
|
||||
reason: LogoutReason.SIGN_OUT,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('Does not navigate when the navigate option is false', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
|
||||
await logout({ navigate: false });
|
||||
expect(router.push).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Adds the reason query param if any non-default reason is given', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
|
||||
await logout({ reason: LogoutReason.ERROR_SESSION_EXPIRED });
|
||||
expect(router.push).toHaveBeenCalledWith({
|
||||
path: '/my-project/login',
|
||||
query: {
|
||||
reason: LogoutReason.ERROR_SESSION_EXPIRED,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
36
src/auth.ts
36
src/auth.ts
@@ -1,5 +1,4 @@
|
||||
import { RawLocation } from 'vue-router';
|
||||
import { useProjectsStore } from '@/stores/projects/';
|
||||
import api from '@/api';
|
||||
import { hydrate, dehydrate } from '@/hydrate';
|
||||
import router from '@/router';
|
||||
@@ -8,16 +7,14 @@ import router from '@/router';
|
||||
* Check if the current user is authenticated to the current project
|
||||
*/
|
||||
export async function checkAuth() {
|
||||
const { currentProjectKey } = useProjectsStore().state;
|
||||
|
||||
if (!currentProjectKey) return false;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/auth/check`);
|
||||
return response.data.data.authenticated;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
/** @todo base this on existence of access token / response token */
|
||||
return true;
|
||||
// try {
|
||||
// const response = await api.get(`/auth/check`);
|
||||
// return response.data.data.authenticated;
|
||||
// } catch {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
export type LoginCredentials = {
|
||||
@@ -26,14 +23,13 @@ export type LoginCredentials = {
|
||||
};
|
||||
|
||||
export async function login(credentials: LoginCredentials) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
await api.post(`/${currentProjectKey}/auth/authenticate`, {
|
||||
const response = await api.post(`/auth/authenticate`, {
|
||||
...credentials,
|
||||
mode: 'cookie',
|
||||
});
|
||||
|
||||
api.defaults.headers['Authorization'] = `Bearer ${response.data.data.token}`;
|
||||
|
||||
await hydrate();
|
||||
}
|
||||
|
||||
@@ -58,22 +54,16 @@ export async function logout(optionsRaw: LogoutOptions = {}) {
|
||||
|
||||
const options = { ...defaultOptions, ...optionsRaw };
|
||||
|
||||
const projectsStore = useProjectsStore();
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
// You can't logout of a project if you're not in a project
|
||||
if (currentProjectKey === null) return;
|
||||
|
||||
// Only if the user manually signed out should we kill the session by hitting the logout endpoint
|
||||
if (options.reason === LogoutReason.SIGN_OUT) {
|
||||
await api.post(`/${currentProjectKey}/auth/logout`);
|
||||
await api.post(`/auth/logout`);
|
||||
}
|
||||
|
||||
await dehydrate();
|
||||
|
||||
if (options.navigate === true) {
|
||||
const location: RawLocation = {
|
||||
path: `/${currentProjectKey}/login`,
|
||||
path: `/login`,
|
||||
query: { reason: options.reason },
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import api from '@/api';
|
||||
import { Ref, ref, watch, computed } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import notify from '@/utils/notify';
|
||||
import i18n from '@/lang';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
@@ -20,10 +19,9 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
const isBatch = computed(() => typeof primaryKey.value === 'string' && primaryKey.value.includes(','));
|
||||
|
||||
const endpoint = computed(() => {
|
||||
const currentProjectKey = useProjectsStore().state.currentProjectKey;
|
||||
return collection.value.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${collection.value.substring(9)}`
|
||||
: `/${currentProjectKey}/items/${collection.value}`;
|
||||
? `/${collection.value.substring(9)}`
|
||||
: `/items/${collection.value}`;
|
||||
});
|
||||
|
||||
watch([collection, primaryKey], refresh, { immediate: true });
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { computed, ref, Ref, watch } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import Vue from 'vue';
|
||||
import { isEqual } from 'lodash';
|
||||
@@ -19,16 +18,14 @@ type Query = {
|
||||
};
|
||||
|
||||
export function useItems(collection: Ref<string>, query: Query) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { primaryKeyField, sortField } = useCollection(collection);
|
||||
|
||||
const { limit, fields, sort, page, filters, searchQuery } = query;
|
||||
|
||||
const endpoint = computed(() => {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
return collection.value.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${collection.value.substring(9)}`
|
||||
: `/${currentProjectKey}/items/${collection.value}`;
|
||||
? `/${collection.value.substring(9)}`
|
||||
: `/items/${collection.value}`;
|
||||
});
|
||||
|
||||
const items = ref<any>([]);
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, PropType, Ref } from '@vue/composition-api';
|
||||
import getRelatedCollection from '@/utils/get-related-collection';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import ValueNull from '@/views/private/components/value-null';
|
||||
|
||||
@@ -50,8 +49,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const relatedCollection = computed(() => {
|
||||
return getRelatedCollection(props.collection, props.field);
|
||||
});
|
||||
@@ -66,10 +63,9 @@ export default defineComponent({
|
||||
|
||||
function getLinkForItem(item: any) {
|
||||
if (!relatedCollection.value || !primaryKeyField.value) return null;
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
const primaryKey = item[primaryKeyField.value.field];
|
||||
|
||||
return `/${currentProjectKey}/collections/${relatedCollection.value}/${primaryKey}`;
|
||||
return `/collections/${relatedCollection.value}/${primaryKey}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { useAppStore } from '@/stores/app';
|
||||
import { useStores, hydrate, dehydrate } from './hydrate';
|
||||
|
||||
jest.mock('@/api');
|
||||
|
||||
describe('Stores / App', () => {
|
||||
beforeAll(() => {
|
||||
Vue.use(VueCompositionAPI);
|
||||
});
|
||||
|
||||
describe('useStores', () => {
|
||||
it('Calls all functions', () => {
|
||||
const mockStores: any = [jest.fn(), jest.fn()];
|
||||
useStores(mockStores);
|
||||
expect(mockStores[0]).toHaveBeenCalled();
|
||||
expect(mockStores[1]).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Defaults to a global set of stores', () => {
|
||||
expect(useStores).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Hydrate', () => {
|
||||
it('Sets hydrating state during hydration', async () => {
|
||||
const appStore = useAppStore({});
|
||||
|
||||
expect(appStore.state.hydrating).toBe(false);
|
||||
|
||||
const promise = hydrate([
|
||||
{
|
||||
id: 'test1',
|
||||
hydrate() {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(), 15);
|
||||
});
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
expect(appStore.state.hydrating).toBe(true);
|
||||
|
||||
promise.then(() => {
|
||||
expect(appStore.state.hydrating).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('Calls the hydrate function for all stores', async () => {
|
||||
const mockStores: any = [
|
||||
{
|
||||
hydrate: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
{
|
||||
dehydrate: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
{
|
||||
hydrate: jest.fn(() => Promise.resolve()),
|
||||
dehydrate: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
];
|
||||
|
||||
useAppStore({});
|
||||
|
||||
await hydrate(mockStores);
|
||||
|
||||
expect(mockStores[0].hydrate).toHaveBeenCalled();
|
||||
expect(mockStores[2].hydrate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Sets the hydrated state to true when done', async () => {
|
||||
const appStore = useAppStore({});
|
||||
|
||||
await hydrate([]);
|
||||
|
||||
expect(appStore.state.hydrated).toBe(true);
|
||||
});
|
||||
|
||||
it('Does not hydrate when hydrated is true', async () => {
|
||||
const appStore = useAppStore({});
|
||||
appStore.state.hydrated = true;
|
||||
|
||||
const mockStores: any = [
|
||||
{
|
||||
hydrate: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
];
|
||||
|
||||
await hydrate([]);
|
||||
|
||||
expect(mockStores[0].hydrate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Does not hydrate when hydrating is true', async () => {
|
||||
const appStore = useAppStore({});
|
||||
appStore.state.hydrating = true;
|
||||
|
||||
const mockStores: any = [
|
||||
{
|
||||
hydrate: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
];
|
||||
|
||||
await hydrate(mockStores);
|
||||
|
||||
expect(mockStores[0].hydrate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Sets the error state when one of the hydration functions fails', async () => {
|
||||
const mockStores: any = [
|
||||
{
|
||||
hydrate: jest.fn(() => {
|
||||
throw 'test';
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const appStore = useAppStore({});
|
||||
|
||||
await hydrate(mockStores);
|
||||
|
||||
expect(appStore.state.error).toBe('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dehydrate', () => {
|
||||
it('Calls the dehydrate function for all stores', async () => {
|
||||
const mockStores: any = [
|
||||
{
|
||||
dehydrate: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
{},
|
||||
{
|
||||
dehydrate: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
];
|
||||
|
||||
const appStore = useAppStore({});
|
||||
appStore.state.hydrated = true;
|
||||
|
||||
await dehydrate(mockStores);
|
||||
|
||||
expect(mockStores[0].dehydrate).toHaveBeenCalled();
|
||||
expect(mockStores[2].dehydrate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Sets the hydrated state to false when done', async () => {
|
||||
const mockStores: any = [{}];
|
||||
|
||||
const appStore = useAppStore({});
|
||||
appStore.state.hydrated = true;
|
||||
|
||||
await dehydrate(mockStores);
|
||||
|
||||
expect(appStore.state.hydrated).toBe(false);
|
||||
});
|
||||
|
||||
it('Does not dehydrate when store is already dehydrated', async () => {
|
||||
const mockStores: any = [
|
||||
{
|
||||
dehydrate: jest.fn(() => Promise.resolve()),
|
||||
},
|
||||
];
|
||||
|
||||
const appStore = useAppStore({});
|
||||
appStore.state.hydrated = false;
|
||||
|
||||
await dehydrate(mockStores);
|
||||
|
||||
expect(mockStores[0].dehydrate).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,9 +5,8 @@ import { useUserStore } from '@/stores/user/';
|
||||
import { useRequestsStore } from '@/stores/requests/';
|
||||
import { useCollectionPresetsStore } from '@/stores/collection-presets/';
|
||||
import { useSettingsStore } from '@/stores/settings/';
|
||||
import { useProjectsStore } from '@/stores/projects/';
|
||||
//
|
||||
import { useLatencyStore } from '@/stores/latency';
|
||||
import { usePermissionsStore } from '@/stores/permissions';
|
||||
import { useRelationsStore } from '@/stores/relations';
|
||||
import { setLanguage, Language } from '@/lang';
|
||||
|
||||
@@ -27,9 +26,7 @@ export function useStores(
|
||||
useRequestsStore,
|
||||
useCollectionPresetsStore,
|
||||
useSettingsStore,
|
||||
useProjectsStore,
|
||||
useLatencyStore,
|
||||
usePermissionsStore,
|
||||
useRelationsStore,
|
||||
]
|
||||
) {
|
||||
|
||||
@@ -108,7 +108,6 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, computed } from '@vue/composition-api';
|
||||
import ModalBrowse from '@/views/private/components/modal-browse';
|
||||
import useProjectsStore from '@/stores/projects/';
|
||||
import api from '@/api';
|
||||
import readableMimeType from '@/utils/readable-mime-type';
|
||||
|
||||
@@ -138,7 +137,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const activeDialog = ref<'upload' | 'choose' | 'url' | null>(null);
|
||||
const { loading, error, file, fetchFile } = useFile();
|
||||
|
||||
@@ -190,10 +188,8 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/files/${props.value}`, {
|
||||
const response = await api.get(`/files/${props.value}`, {
|
||||
params: {
|
||||
fields: ['title', 'data', 'type', 'filename_download'],
|
||||
},
|
||||
@@ -241,10 +237,8 @@ export default defineComponent({
|
||||
async function importFromURL() {
|
||||
loading.value = true;
|
||||
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
try {
|
||||
const response = await api.post(`/${currentProjectKey}/files`, {
|
||||
const response = await api.post(`/files`, {
|
||||
data: url.value,
|
||||
});
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, computed } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import formatFilesize from '@/utils/format-filesize';
|
||||
import i18n from '@/lang';
|
||||
import FileLightbox from '@/views/private/components/file-lightbox';
|
||||
@@ -87,8 +86,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const image = ref<Image | null>(null);
|
||||
const error = ref(null);
|
||||
@@ -149,12 +146,10 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
async function fetchImage() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/files/${props.value}`, {
|
||||
const response = await api.get(`/files/${props.value}`, {
|
||||
params: {
|
||||
fields: ['id', 'data', 'title', 'width', 'height', 'filesize', 'type', 'filename_download'],
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Ref, ref, watch } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import { Field } from '@/stores/fields/types';
|
||||
import { merge } from 'lodash';
|
||||
import { Relation } from '@/stores/relations/types';
|
||||
@@ -36,8 +35,6 @@ export default function usePreview({
|
||||
relatedCollection,
|
||||
fields,
|
||||
}: PreviewParam) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const previewItems = ref<readonly any[]>([]);
|
||||
const error = ref(null);
|
||||
@@ -107,7 +104,6 @@ export default function usePreview({
|
||||
// yet, as they can't have been saved yet.
|
||||
if (primaryKey.value === '+') return [];
|
||||
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
const junctionTable = relationCurrentToJunction.value.collection_many;
|
||||
|
||||
// The stuff we want to fetch is the related junction row, and the content of the
|
||||
@@ -128,7 +124,7 @@ export default function usePreview({
|
||||
if (fieldsToFetch.includes(`${junctionField}.${relatedPrimaryKey}`) === false)
|
||||
fieldsToFetch.push(`${junctionField}.${relatedPrimaryKey}`);
|
||||
|
||||
const response = await api.get(`/${currentProjectKey}/items/${junctionTable}`, {
|
||||
const response = await api.get(`/items/${junctionTable}`, {
|
||||
params: {
|
||||
fields: adjustFieldsForDisplay(fieldsToFetch, junctionCollection.value),
|
||||
[`filter[${currentInJunction}][eq]`]: primaryKey.value,
|
||||
@@ -188,8 +184,6 @@ export default function usePreview({
|
||||
if (!relationJunctionToRelated.value) return [];
|
||||
if (!relationJunctionToRelated.value.junction_field) return [];
|
||||
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
const junctionPrimaryKey = junctionCollectionPrimaryKeyField.value.field;
|
||||
const junctionField = relationCurrentToJunction.value.junction_field;
|
||||
const relatedPrimaryKey = relatedCollectionPrimaryKeyField.value.field;
|
||||
@@ -223,8 +217,8 @@ export default function usePreview({
|
||||
if (fieldsToFetch.includes(relatedPrimaryKey) === false) fieldsToFetch.push(relatedPrimaryKey);
|
||||
|
||||
const endpoint = relatedCollection.value.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${relatedCollection.value.substring(9)}/${newlySelectedRelatedKeys.join(',')}`
|
||||
: `/${currentProjectKey}/items/${relatedCollection.value}/${newlySelectedRelatedKeys.join(',')}`;
|
||||
? `/${relatedCollection.value.substring(9)}/${newlySelectedRelatedKeys.join(',')}`
|
||||
: `/items/${relatedCollection.value}/${newlySelectedRelatedKeys.join(',')}`;
|
||||
|
||||
const response = await api.get(endpoint, {
|
||||
params: {
|
||||
|
||||
@@ -107,7 +107,6 @@ import { useRelationsStore } from '@/stores/relations';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import useCollectionsStore from '@/stores/collections';
|
||||
import ModalDetail from '@/views/private/components/modal-detail';
|
||||
import ModalBrowse from '@/views/private/components/modal-browse';
|
||||
@@ -151,7 +150,6 @@ export default defineComponent({
|
||||
setup(props, { emit }) {
|
||||
const { collection } = toRefs(props);
|
||||
|
||||
const projectsStore = useProjectsStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
|
||||
@@ -243,7 +241,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
async function fetchCurrent() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
const fields = requiredFields.value || [];
|
||||
@@ -254,8 +251,8 @@ export default defineComponent({
|
||||
|
||||
try {
|
||||
const endpoint = relatedCollection.value.collection.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${relatedCollection.value.collection.substring(9)}/${props.value}`
|
||||
: `/${currentProjectKey}/items/${relatedCollection.value.collection}/${props.value}`;
|
||||
? `/${relatedCollection.value.collection.substring(9)}/${props.value}`
|
||||
: `/items/${relatedCollection.value.collection}/${props.value}`;
|
||||
|
||||
const response = await api.get(endpoint, {
|
||||
params: {
|
||||
@@ -289,7 +286,6 @@ export default defineComponent({
|
||||
async function fetchItems() {
|
||||
if (items.value !== null) return;
|
||||
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
const fields = requiredFields.value || [];
|
||||
@@ -300,8 +296,8 @@ export default defineComponent({
|
||||
|
||||
try {
|
||||
const endpoint = relatedCollection.value.collection.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${relatedCollection.value.collection.substring(9)}`
|
||||
: `/${currentProjectKey}/items/${relatedCollection.value.collection}`;
|
||||
? `/${relatedCollection.value.collection.substring(9)}`
|
||||
: `/items/${relatedCollection.value.collection}`;
|
||||
|
||||
const response = await api.get(endpoint, {
|
||||
params: {
|
||||
@@ -319,11 +315,9 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
async function fetchTotalCount() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
const endpoint = relatedCollection.value.collection.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${relatedCollection.value.collection.substring(9)}`
|
||||
: `/${currentProjectKey}/items/${relatedCollection.value.collection}`;
|
||||
? `/${relatedCollection.value.collection.substring(9)}`
|
||||
: `/items/${relatedCollection.value.collection}`;
|
||||
|
||||
const response = await api.get(endpoint, {
|
||||
params: {
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch, toRefs, Ref, PropType } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import useRelationsStore from '@/stores/relations';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import useCollectionsStore from '@/stores/collections';
|
||||
@@ -102,7 +101,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
@@ -199,7 +197,6 @@ export default defineComponent({
|
||||
* run on first load (or when the parent form primary key changes)
|
||||
*/
|
||||
async function fetchCurrent() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
let fields = [...props.fields];
|
||||
@@ -214,8 +211,8 @@ export default defineComponent({
|
||||
|
||||
try {
|
||||
const endpoint = props.collection.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${props.collection.substring(9)}/${props.primaryKey}`
|
||||
: `/${currentProjectKey}/items/${props.collection}/${props.primaryKey}`;
|
||||
? `/${props.collection.substring(9)}/${props.primaryKey}`
|
||||
: `/items/${props.collection}/${props.primaryKey}`;
|
||||
|
||||
const response = await api.get(endpoint, {
|
||||
params: {
|
||||
@@ -238,7 +235,6 @@ export default defineComponent({
|
||||
* will only have a pk in the array of changes)
|
||||
*/
|
||||
async function mergeWithItems(changes: any[]) {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
const pkField = relatedPrimaryKeyField.value.field;
|
||||
@@ -284,10 +280,8 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const endpoint = props.collection.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${props.collection.substring(9)}/${selectedPrimaryKeys.join(',')}`
|
||||
: `/${currentProjectKey}/items/${relatedCollection.value.collection}/${selectedPrimaryKeys.join(
|
||||
','
|
||||
)}`;
|
||||
? `/${props.collection.substring(9)}/${selectedPrimaryKeys.join(',')}`
|
||||
: `/items/${relatedCollection.value.collection}/${selectedPrimaryKeys.join(',')}`;
|
||||
|
||||
const response = await api.get(endpoint, {
|
||||
params: {
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref, toRefs, Ref, watch, PropType } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import useRelationsStore from '@/stores/relations';
|
||||
import useCollectionsStore from '@/stores/collections';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
@@ -73,7 +72,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
|
||||
@@ -137,7 +135,6 @@ export default defineComponent({
|
||||
return { languages, loading, error, primaryKeyField };
|
||||
|
||||
async function fetchLanguages() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
const fields = getFieldsFromTemplate(props.template);
|
||||
@@ -147,7 +144,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/items/${props.languagesCollection}`, {
|
||||
const response = await api.get(`/items/${props.languagesCollection}`, {
|
||||
params: {
|
||||
fields: fields,
|
||||
limit: -1,
|
||||
@@ -180,18 +177,14 @@ export default defineComponent({
|
||||
return { loading, items, error };
|
||||
|
||||
async function fetchCurrent() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(
|
||||
`/${currentProjectKey}/items/${props.collection}/${props.primaryKey}`,
|
||||
{
|
||||
params: {
|
||||
fields: props.field + '.*',
|
||||
},
|
||||
}
|
||||
);
|
||||
const response = await api.get(`/items/${props.collection}/${props.primaryKey}`, {
|
||||
params: {
|
||||
fields: props.field + '.*',
|
||||
},
|
||||
});
|
||||
|
||||
items.value = response.data.data[props.field];
|
||||
} catch (err) {
|
||||
|
||||
@@ -95,7 +95,6 @@
|
||||
import { defineComponent, computed, ref, watch, PropType } from '@vue/composition-api';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import ModalDetail from '@/views/private/components/modal-detail';
|
||||
import ModalBrowse from '@/views/private/components/modal-browse';
|
||||
|
||||
@@ -120,8 +119,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const { usesMenu, menuActive } = useMenu();
|
||||
const { info: collectionInfo } = useCollection(ref('directus_users'));
|
||||
const { selection, stageSelection, selectModalActive } = useSelection();
|
||||
@@ -201,7 +198,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
async function fetchCurrent() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
const fields = requiredFields;
|
||||
@@ -211,7 +207,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/users/${props.value}`, {
|
||||
const response = await api.get(`/users/${props.value}`, {
|
||||
params: {
|
||||
fields: fields,
|
||||
},
|
||||
@@ -241,7 +237,6 @@ export default defineComponent({
|
||||
async function fetchUsers() {
|
||||
if (users.value !== null) return;
|
||||
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
const fields = requiredFields;
|
||||
@@ -251,7 +246,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/users`, {
|
||||
const response = await api.get(`/users`, {
|
||||
params: {
|
||||
fields: fields,
|
||||
limit: -1,
|
||||
@@ -267,8 +262,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
async function fetchTotalCount() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
const response = await api.get(`/${currentProjectKey}/users`, {
|
||||
const response = await api.get(`/users`, {
|
||||
params: {
|
||||
limit: 0,
|
||||
meta: 'total_count',
|
||||
|
||||
@@ -137,7 +137,7 @@ import useItems from '@/composables/use-items';
|
||||
import Card from './components/card.vue';
|
||||
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
|
||||
import { render } from 'micromustache';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import CardsHeader from './components/header.vue';
|
||||
import i18n from '@/lang';
|
||||
import adjustFieldsForDisplays from '@/utils/adjust-fields-for-displays';
|
||||
@@ -208,7 +208,6 @@ export default defineComponent({
|
||||
setup(props, { emit }) {
|
||||
const layoutElement = ref<HTMLElement | null>(null);
|
||||
const mainElement = inject('main-element', ref<Element | null>(null));
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const _selection = useSync(props, 'selection', emit);
|
||||
const _viewOptions = useSync(props, 'viewOptions', emit);
|
||||
@@ -241,7 +240,6 @@ export default defineComponent({
|
||||
|
||||
const newLink = computed(() => {
|
||||
return render(props.detailRoute, {
|
||||
project: projectsStore.state.currentProjectKey,
|
||||
collection: collection.value,
|
||||
primaryKey: '+',
|
||||
item: null,
|
||||
@@ -397,12 +395,9 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function getLinkForItem(item: Record<string, any>) {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
return render(props.detailRoute, {
|
||||
item: item,
|
||||
collection: props.collection,
|
||||
project: currentProjectKey,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
primaryKey: item[primaryKeyField.value!.field],
|
||||
});
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { defineComponent, PropType, ref, computed, inject, toRefs, Ref } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import { HeaderRaw, Item } from '@/components/v-table/types';
|
||||
import { Field } from '@/stores/fields/types';
|
||||
import router from '@/router';
|
||||
@@ -214,8 +214,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { currentProjectKey } = toRefs(useProjectsStore().state);
|
||||
|
||||
const table = ref<Vue | null>(null);
|
||||
const mainElement = inject('main-element', ref<Element | null>(null));
|
||||
|
||||
@@ -255,7 +253,6 @@ export default defineComponent({
|
||||
|
||||
const newLink = computed(() => {
|
||||
return render(props.detailRoute, {
|
||||
project: currentProjectKey.value,
|
||||
collection: collection.value,
|
||||
primaryKey: '+',
|
||||
item: null,
|
||||
@@ -500,7 +497,6 @@ export default defineComponent({
|
||||
const primaryKey = item[primaryKeyField.value!.field];
|
||||
router.push(
|
||||
render(props.detailRoute, {
|
||||
project: currentProjectKey.value,
|
||||
collection: collection.value,
|
||||
primaryKey,
|
||||
item,
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from '@vue/composition-api';
|
||||
import ActivityNavigation from '../../components/navigation/';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import { i18n } from '@/lang';
|
||||
import { LayoutComponent } from '@/layouts/types';
|
||||
import useCollectionPreset from '@/composables/use-collection-preset';
|
||||
@@ -50,7 +49,6 @@ export default defineComponent({
|
||||
props: {},
|
||||
setup() {
|
||||
const layout = ref<LayoutComponent | null>(null);
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const { viewOptions, viewQuery } = useCollectionPreset(ref('directus_activity'));
|
||||
const { breadcrumb } = useBreadcrumb();
|
||||
@@ -65,12 +63,10 @@ export default defineComponent({
|
||||
|
||||
function useBreadcrumb() {
|
||||
const breadcrumb = computed(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
return [
|
||||
{
|
||||
name: i18n.tc('collection', 2),
|
||||
to: `/${currentProjectKey}/collections`,
|
||||
to: `/collections`,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, toRefs, ref } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import ActivityNavigation from '../../components/navigation/';
|
||||
import { i18n } from '@/lang';
|
||||
import useItem from '@/composables/use-item';
|
||||
@@ -50,8 +50,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { currentProjectKey } = toRefs(projectsStore.state);
|
||||
const { primaryKey } = toRefs(props);
|
||||
const { breadcrumb } = useBreadcrumb();
|
||||
|
||||
@@ -69,7 +67,7 @@ export default defineComponent({
|
||||
const breadcrumb = computed(() => [
|
||||
{
|
||||
name: i18n.t('activity_log'),
|
||||
to: `/${currentProjectKey.value}/activity/`,
|
||||
to: `/activity/`,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
import { defineComponent, computed } from '@vue/composition-api';
|
||||
import useNavigation from '../../composables/use-navigation';
|
||||
import useCollectionPresetsStore from '@/stores/collection-presets';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -42,12 +41,9 @@ export default defineComponent({
|
||||
},
|
||||
setup() {
|
||||
const collectionPresetsStore = useCollectionPresetsStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const { customNavItems, navItems } = useNavigation();
|
||||
|
||||
const bookmarks = computed(() => {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
return collectionPresetsStore.state.collectionPresets
|
||||
.filter((preset) => {
|
||||
return preset.title !== null && preset.collection.startsWith('directus_') === false;
|
||||
@@ -55,7 +51,7 @@ export default defineComponent({
|
||||
.map((preset) => {
|
||||
return {
|
||||
...preset,
|
||||
to: `/${currentProjectKey}/collections/${preset.collection}?bookmark=${preset.id}`,
|
||||
to: `/collections/${preset.collection}?bookmark=${preset.id}`,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { computed } from '@vue/composition-api';
|
||||
import { useProjectsStore } from '@/stores/projects/';
|
||||
|
||||
import { useCollectionsStore } from '@/stores/collections/';
|
||||
import { Collection } from '@/stores/collections/types';
|
||||
import VueI18n from 'vue-i18n';
|
||||
@@ -19,7 +19,7 @@ export type NavItemGroup = {
|
||||
|
||||
export default function useNavigation() {
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const customNavItems = computed<NavItemGroup[] | null>(() => {
|
||||
@@ -39,7 +39,7 @@ export default function useNavigation() {
|
||||
collection: collection,
|
||||
name: collectionInfo.name,
|
||||
icon: collectionInfo.icon,
|
||||
to: `/${projectsStore.state.currentProjectKey}/collections/${collection}`,
|
||||
to: `/collections/${collection}`,
|
||||
};
|
||||
|
||||
return navItem;
|
||||
@@ -58,7 +58,7 @@ export default function useNavigation() {
|
||||
collection: collection.collection,
|
||||
name: collection.name,
|
||||
icon: collection.icon,
|
||||
to: `/${projectsStore.state.currentProjectKey}/collections/${collection.collection}`,
|
||||
to: `/collections/${collection.collection}`,
|
||||
};
|
||||
|
||||
return navItem;
|
||||
|
||||
@@ -137,7 +137,6 @@ import { NavigationGuard } from 'vue-router';
|
||||
import CollectionsNavigation from '../../components/navigation/';
|
||||
import useCollectionsStore from '@/stores/collections';
|
||||
import useFieldsStore from '@/stores/fields';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import api from '@/api';
|
||||
import { LayoutComponent } from '@/layouts/types';
|
||||
import CollectionsNotFound from '../not-found/';
|
||||
@@ -209,8 +208,6 @@ export default defineComponent({
|
||||
const { collection } = toRefs(props);
|
||||
const bookmarkID = computed(() => (props.bookmark ? +props.bookmark : null));
|
||||
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const { selection } = useSelection();
|
||||
const { info: currentCollection } = useCollection(collection);
|
||||
const { addNewLink, batchLink, collectionsLink, currentCollectionLink } = useLinks();
|
||||
@@ -274,7 +271,7 @@ export default defineComponent({
|
||||
const breadcrumb = computed(() => [
|
||||
{
|
||||
name: currentCollection.value?.name,
|
||||
to: `/${projectsStore.state.currentProjectKey}/collections/${props.collection}`,
|
||||
to: `/collections/${props.collection}`,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -300,8 +297,6 @@ export default defineComponent({
|
||||
return { confirmDelete, deleting, batchDelete };
|
||||
|
||||
async function batchDelete() {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
deleting.value = true;
|
||||
|
||||
confirmDelete.value = false;
|
||||
@@ -309,7 +304,7 @@ export default defineComponent({
|
||||
const batchPrimaryKeys = selection.value;
|
||||
|
||||
try {
|
||||
await api.delete(`/${currentProjectKey}/items/${props.collection}/${batchPrimaryKeys}`);
|
||||
await api.delete(`/items/${props.collection}/${batchPrimaryKeys}`);
|
||||
|
||||
await layout.value?.refresh?.();
|
||||
|
||||
@@ -325,26 +320,20 @@ export default defineComponent({
|
||||
|
||||
function useLinks() {
|
||||
const addNewLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
return `/${currentProjectKey}/collections/${props.collection}/+`;
|
||||
return `/collections/${props.collection}/+`;
|
||||
});
|
||||
|
||||
const batchLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
const batchPrimaryKeys = selection.value.join();
|
||||
return `/${currentProjectKey}/collections/${props.collection}/${batchPrimaryKeys}`;
|
||||
return `/collections/${props.collection}/${batchPrimaryKeys}`;
|
||||
});
|
||||
|
||||
const collectionsLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
return `/${currentProjectKey}/collections`;
|
||||
return `/collections`;
|
||||
});
|
||||
|
||||
const currentCollectionLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
return `/${currentProjectKey}/collections/${props.collection}`;
|
||||
return `/collections/${props.collection}`;
|
||||
});
|
||||
|
||||
return { addNewLink, batchLink, collectionsLink, currentCollectionLink };
|
||||
@@ -364,15 +353,11 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
async function createBookmark(name: string) {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
creatingBookmark.value = true;
|
||||
|
||||
try {
|
||||
const newBookmark = await saveCurrentAsBookmark({ title: name });
|
||||
router.push(
|
||||
`/${currentProjectKey}/collections/${newBookmark.collection}?bookmark=${newBookmark.id}`
|
||||
);
|
||||
router.push(`/collections/${newBookmark.collection}?bookmark=${newBookmark.id}`);
|
||||
|
||||
bookmarkDialogActive.value = false;
|
||||
} catch (error) {
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, toRefs, ref } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import CollectionsNavigation from '../../components/navigation/';
|
||||
import router from '@/router';
|
||||
import CollectionsNotFound from '../not-found/';
|
||||
@@ -215,8 +215,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { currentProjectKey } = toRefs(projectsStore.state);
|
||||
const { collection, primaryKey } = toRefs(props);
|
||||
const { breadcrumb } = useBreadcrumb();
|
||||
|
||||
@@ -248,7 +246,7 @@ export default defineComponent({
|
||||
const confirmLeave = ref(false);
|
||||
const leaveTo = ref<string | null>(null);
|
||||
|
||||
const backLink = computed(() => `/${currentProjectKey.value}/collections/${collection.value}/`);
|
||||
const backLink = computed(() => `/collections/${collection.value}/`);
|
||||
|
||||
const templateValues = computed(() => {
|
||||
return {
|
||||
@@ -307,7 +305,7 @@ export default defineComponent({
|
||||
const breadcrumb = computed(() => [
|
||||
{
|
||||
name: collectionInfo.value?.name,
|
||||
to: `/${currentProjectKey.value}/collections/${props.collection}`,
|
||||
to: `/collections/${props.collection}`,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -316,7 +314,7 @@ export default defineComponent({
|
||||
|
||||
async function saveAndQuit() {
|
||||
await save();
|
||||
router.push(`/${currentProjectKey.value}/collections/${props.collection}`);
|
||||
router.push(`/collections/${props.collection}`);
|
||||
}
|
||||
|
||||
async function saveAndStay() {
|
||||
@@ -327,23 +325,23 @@ export default defineComponent({
|
||||
if (props.primaryKey === '+') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const newPrimaryKey = savedItem[primaryKeyField.value!.field];
|
||||
router.replace(`/${currentProjectKey.value}/collections/${props.collection}/${newPrimaryKey}`);
|
||||
router.replace(`/collections/${props.collection}/${newPrimaryKey}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAndAddNew() {
|
||||
await save();
|
||||
router.push(`/${currentProjectKey.value}/collections/${props.collection}/+`);
|
||||
router.push(`/collections/${props.collection}/+`);
|
||||
}
|
||||
|
||||
async function saveAsCopyAndNavigate() {
|
||||
const newPrimaryKey = await saveAsCopy();
|
||||
router.push(`/${currentProjectKey.value}/collections/${props.collection}/${newPrimaryKey}`);
|
||||
router.push(`/collections/${props.collection}/${newPrimaryKey}`);
|
||||
}
|
||||
|
||||
async function deleteAndQuit(soft = false) {
|
||||
await remove(soft);
|
||||
router.push(`/${currentProjectKey.value}/collections/${props.collection}`);
|
||||
router.push(`/collections/${props.collection}`);
|
||||
}
|
||||
|
||||
function discardAndLeave() {
|
||||
|
||||
@@ -46,7 +46,7 @@ import { i18n } from '@/lang';
|
||||
import useNavigation, { NavItem } from '../../composables/use-navigation';
|
||||
import router from '@/router';
|
||||
import useUserStore from '@/stores/user';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import marked from 'marked';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -57,7 +57,6 @@ export default defineComponent({
|
||||
props: {},
|
||||
setup() {
|
||||
const userStore = useUserStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const tableHeaders = [
|
||||
{
|
||||
@@ -82,7 +81,7 @@ export default defineComponent({
|
||||
const isAdmin = computed(() => userStore.state.currentUser?.role.id === 1);
|
||||
|
||||
const dataModelLink = computed(() => {
|
||||
return `/${projectsStore.state.currentProjectKey}/settings/data-model`;
|
||||
return `/settings/data-model`;
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
import { defineComponent, ref } from '@vue/composition-api';
|
||||
import useFolders from '../../composables/use-folders';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -35,7 +34,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const dialogActive = ref(false);
|
||||
const saving = ref(false);
|
||||
const newFolderName = ref(null);
|
||||
@@ -46,12 +44,10 @@ export default defineComponent({
|
||||
return { addFolder, dialogActive, newFolderName, saving, savingError };
|
||||
|
||||
async function addFolder() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
await api.post(`/${currentProjectKey}/folders`, {
|
||||
await api.post(`/folders`, {
|
||||
name: newFolderName.value,
|
||||
parent_folder: props.parent,
|
||||
});
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import api from '@/api';
|
||||
import FolderPickerListItem from './folder-picker-list-item.vue';
|
||||
|
||||
@@ -49,8 +48,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const folders = ref<FolderRaw[]>([]);
|
||||
const error = ref<any>(null);
|
||||
@@ -88,12 +85,10 @@ export default defineComponent({
|
||||
|
||||
async function fetchFolders() {
|
||||
if (folders.value.length > 0) return;
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/folders`, {
|
||||
const response = await api.get(`/folders`, {
|
||||
params: {
|
||||
limit: -1,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import api from '@/api';
|
||||
import { ref, Ref } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
type FolderRaw = {
|
||||
id: number;
|
||||
@@ -20,8 +19,6 @@ let folders: Ref<Folder[] | null> | null = null;
|
||||
let error: Ref<any> | null = null;
|
||||
|
||||
export default function useFolders() {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
if (loading === null) loading = ref(false);
|
||||
if (folders === null) folders = ref<Folder[] | null>(null);
|
||||
if (error === null) error = ref(null);
|
||||
@@ -40,7 +37,7 @@ export default function useFolders() {
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${projectsStore.state.currentProjectKey}/folders`, {
|
||||
const response = await api.get(`/folders`, {
|
||||
params: {
|
||||
limit: -1,
|
||||
sort: 'name',
|
||||
|
||||
@@ -104,7 +104,6 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from '@vue/composition-api';
|
||||
import FilesNavigation from '../../components/navigation/';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import { i18n } from '@/lang';
|
||||
import api from '@/api';
|
||||
import { LayoutComponent } from '@/layouts/types';
|
||||
@@ -126,8 +125,6 @@ export default defineComponent({
|
||||
props: {},
|
||||
setup() {
|
||||
const layout = ref<LayoutComponent | null>(null);
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const selection = ref<Item[]>([]);
|
||||
|
||||
const { viewType, viewOptions, viewQuery, filters, searchQuery } = useCollectionPreset(ref('directus_files'));
|
||||
@@ -211,15 +208,13 @@ export default defineComponent({
|
||||
return { confirmDelete, deleting, batchDelete };
|
||||
|
||||
async function batchDelete() {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
deleting.value = true;
|
||||
|
||||
confirmDelete.value = false;
|
||||
|
||||
const batchPrimaryKeys = selection.value;
|
||||
|
||||
await api.delete(`/${currentProjectKey}/files/${batchPrimaryKeys}`);
|
||||
await api.delete(`/files/${batchPrimaryKeys}`);
|
||||
|
||||
await layout.value?.refresh();
|
||||
|
||||
@@ -231,14 +226,12 @@ export default defineComponent({
|
||||
|
||||
function useLinks() {
|
||||
const addNewLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
return `/${currentProjectKey}/files/+`;
|
||||
return `/files/+`;
|
||||
});
|
||||
|
||||
const batchLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
const batchPrimaryKeys = selection.value;
|
||||
return `/${currentProjectKey}/files/${batchPrimaryKeys}`;
|
||||
return `/files/${batchPrimaryKeys}`;
|
||||
});
|
||||
|
||||
return { addNewLink, batchLink };
|
||||
@@ -246,12 +239,10 @@ export default defineComponent({
|
||||
|
||||
function useBreadcrumb() {
|
||||
const breadcrumb = computed(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
return [
|
||||
{
|
||||
name: i18n.tc('collection', 2),
|
||||
to: `/${currentProjectKey}/collections`,
|
||||
to: `/collections`,
|
||||
},
|
||||
];
|
||||
});
|
||||
@@ -268,10 +259,8 @@ export default defineComponent({
|
||||
|
||||
async function moveToFolder() {
|
||||
moving.value = true;
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
try {
|
||||
await api.patch(`/${currentProjectKey}/files/${selection.value}`, {
|
||||
await api.patch(`/files/${selection.value}`, {
|
||||
folder: selectedFolder.value,
|
||||
});
|
||||
|
||||
|
||||
@@ -54,7 +54,6 @@ import readableMimeType from '@/utils/readable-mime-type';
|
||||
import bytes from 'bytes';
|
||||
import i18n from '@/lang';
|
||||
import localizedFormat from '@/utils/localized-format';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import api from '@/api';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -70,8 +69,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const size = computed(() => {
|
||||
if (props.isNew) return null;
|
||||
if (!props.file) return null;
|
||||
@@ -123,10 +120,9 @@ export default defineComponent({
|
||||
if (!props.file) return null;
|
||||
|
||||
loading.value = true;
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/users/${props.file.uploaded_by}`, {
|
||||
const response = await api.get(`/users/${props.file.uploaded_by}`, {
|
||||
params: {
|
||||
fields: ['id', 'first_name', 'last_name', 'role'],
|
||||
},
|
||||
@@ -137,7 +133,7 @@ export default defineComponent({
|
||||
user.value = {
|
||||
id: props.file.uploaded_by,
|
||||
name: first_name + ' ' + last_name,
|
||||
link: `/${currentProjectKey}/users/${role}/${id}`,
|
||||
link: `/users/${role}/${id}`,
|
||||
};
|
||||
} finally {
|
||||
loading.value = false;
|
||||
@@ -162,10 +158,8 @@ export default defineComponent({
|
||||
if (!props.file) return null;
|
||||
if (!props.file.folder) return;
|
||||
loading.value = true;
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/folders/${props.file.folder}`, {
|
||||
const response = await api.get(`/folders/${props.file.folder}`, {
|
||||
params: {
|
||||
fields: ['id', 'name'],
|
||||
},
|
||||
|
||||
@@ -144,7 +144,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, toRefs, ref } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import FilesNavigation from '../../components/navigation/';
|
||||
import { i18n } from '@/lang';
|
||||
import router from '@/router';
|
||||
@@ -200,8 +199,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { currentProjectKey } = toRefs(projectsStore.state);
|
||||
const { primaryKey } = toRefs(props);
|
||||
const { breadcrumb } = useBreadcrumb();
|
||||
const fieldsStore = useFieldsStore();
|
||||
@@ -295,7 +292,7 @@ export default defineComponent({
|
||||
const breadcrumb = computed(() => [
|
||||
{
|
||||
name: i18n.t('file_library'),
|
||||
to: `/${currentProjectKey.value}/files/`,
|
||||
to: `/files/`,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -304,7 +301,7 @@ export default defineComponent({
|
||||
|
||||
async function saveAndQuit() {
|
||||
await save();
|
||||
router.push(`/${currentProjectKey.value}/files`);
|
||||
router.push(`/files`);
|
||||
}
|
||||
|
||||
async function saveAndStay() {
|
||||
@@ -315,23 +312,23 @@ export default defineComponent({
|
||||
if (props.primaryKey === '+') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const newPrimaryKey = savedItem.id;
|
||||
router.replace(`/${currentProjectKey.value}/collections/files/${newPrimaryKey}`);
|
||||
router.replace(`/collections/files/${newPrimaryKey}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAndAddNew() {
|
||||
await save();
|
||||
router.push(`/${currentProjectKey.value}/files/+`);
|
||||
router.push(`/files/+`);
|
||||
}
|
||||
|
||||
async function saveAsCopyAndNavigate() {
|
||||
const newPrimaryKey = await saveAsCopy();
|
||||
router.push(`/${currentProjectKey.value}/files/${newPrimaryKey}`);
|
||||
router.push(`/files/${newPrimaryKey}`);
|
||||
}
|
||||
|
||||
async function deleteAndQuit() {
|
||||
await remove();
|
||||
router.push(`/${currentProjectKey.value}/files`);
|
||||
router.push(`/files`);
|
||||
}
|
||||
|
||||
function discardAndLeave() {
|
||||
@@ -349,10 +346,8 @@ export default defineComponent({
|
||||
|
||||
async function moveToFolder() {
|
||||
moving.value = true;
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
try {
|
||||
await api.patch(`/${currentProjectKey}/files/${props.primaryKey}`, {
|
||||
await api.patch(`/files/${props.primaryKey}`, {
|
||||
folder: selectedFolder.value,
|
||||
});
|
||||
await refresh();
|
||||
|
||||
@@ -21,38 +21,34 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs } from '@vue/composition-api';
|
||||
import { i18n } from '@/lang';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { currentProjectKey } = toRefs(projectsStore.state);
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
icon: 'public',
|
||||
name: i18n.t('settings_project'),
|
||||
to: `/${currentProjectKey.value}/settings/project`,
|
||||
to: `/settings/project`,
|
||||
},
|
||||
{
|
||||
icon: 'list_alt',
|
||||
name: i18n.t('settings_data_model'),
|
||||
to: `/${currentProjectKey.value}/settings/data-model`,
|
||||
to: `/settings/data-model`,
|
||||
},
|
||||
{
|
||||
icon: 'people',
|
||||
name: i18n.t('settings_permissions'),
|
||||
to: `/${currentProjectKey.value}/settings/roles`,
|
||||
to: `/settings/roles`,
|
||||
},
|
||||
{
|
||||
icon: 'bookmark',
|
||||
name: i18n.t('settings_presets'),
|
||||
to: `/${currentProjectKey.value}/settings/presets`,
|
||||
to: `/settings/presets`,
|
||||
},
|
||||
{
|
||||
icon: 'send',
|
||||
name: i18n.t('settings_webhooks'),
|
||||
to: `/${currentProjectKey.value}/settings/webhooks`,
|
||||
to: `/settings/webhooks`,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -95,7 +95,6 @@ import { HeaderRaw } from '../../../../../components/v-table/types';
|
||||
import { i18n } from '@/lang/';
|
||||
import useCollectionsStore from '@/stores/collections';
|
||||
import { Collection } from '@/stores/collections/types';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import router from '@/router';
|
||||
import { sortBy } from 'lodash';
|
||||
import CollectionOptions from './components/collection-options';
|
||||
@@ -129,8 +128,7 @@ export default defineComponent({
|
||||
]);
|
||||
|
||||
function openCollection({ collection }: Collection) {
|
||||
const { currentProjectKey } = useProjectsStore().state;
|
||||
router.push(`/${currentProjectKey}/settings/data-model/${collection}`);
|
||||
router.push(`/settings/data-model/${collection}`);
|
||||
}
|
||||
|
||||
const { items } = useItems();
|
||||
|
||||
@@ -106,7 +106,6 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import { Field } from '@/stores/fields/types';
|
||||
import useCollectionsStore from '@/stores/collections';
|
||||
import useFieldsStore from '@/stores/fields';
|
||||
@@ -124,7 +123,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
|
||||
@@ -194,11 +192,10 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
async function save() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
await api.post(`/${currentProjectKey}/collections`, {
|
||||
await api.post(`/collections`, {
|
||||
collection: collectionName.value,
|
||||
fields: [getPrimaryKeyField(), ...getSystemFields()],
|
||||
});
|
||||
|
||||
@@ -78,7 +78,7 @@ import SetupActions from './setup-actions.vue';
|
||||
import useFieldsStore from '@/stores/fields/';
|
||||
import { Relation } from '@/stores/relations/types';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import { LocalType } from './types';
|
||||
import { localTypeGroups } from './index';
|
||||
import { Type } from '@/stores/fields/types';
|
||||
@@ -114,7 +114,6 @@ export default defineComponent({
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const fieldsStore = useFieldsStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const { collection } = toRefs(props);
|
||||
|
||||
@@ -280,8 +279,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
async function createRelation(relation: Partial<Relation>) {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
await api.post(`/${currentProjectKey}/relations`, relation);
|
||||
await api.post(`/relations`, relation);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -73,7 +73,7 @@ import { defineComponent, computed, toRefs, ref } from '@vue/composition-api';
|
||||
import SettingsNavigation from '../../../components/navigation/';
|
||||
import useCollection from '@/composables/use-collection/';
|
||||
import FieldsManagement from './components/fields-management';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import useItem from '@/composables/use-item';
|
||||
import router from '@/router';
|
||||
import useCollectionsStore from '@/stores/collections';
|
||||
@@ -88,10 +88,8 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { collection } = toRefs(props);
|
||||
const { info: collectionInfo, fields } = useCollection(collection);
|
||||
const { currentProjectKey } = toRefs(projectsStore.state);
|
||||
const collectionsStore = useCollectionsStore();
|
||||
|
||||
const { isNew, edits, item, saving, loading, error, save, remove, deleting, saveAsCopy, isBatch } = useItem(
|
||||
@@ -126,13 +124,13 @@ export default defineComponent({
|
||||
|
||||
async function deleteAndQuit() {
|
||||
await remove();
|
||||
router.push(`/${currentProjectKey.value}/settings/data-model`);
|
||||
router.push(`/settings/data-model`);
|
||||
}
|
||||
|
||||
async function saveAndQuit() {
|
||||
await save();
|
||||
await collectionsStore.hydrate();
|
||||
router.push(`/${currentProjectKey.value}/settings/data-model`);
|
||||
router.push(`/settings/data-model`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from '@vue/composition-api';
|
||||
import SettingsNavigation from '../../../components/navigation';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import api from '@/api';
|
||||
import { Header } from '@/components/v-table/types';
|
||||
import i18n from '@/lang';
|
||||
@@ -128,7 +128,6 @@ type Preset = {
|
||||
export default defineComponent({
|
||||
components: { SettingsNavigation, ValueNull, PresetsInfoDrawerDetail },
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
|
||||
const selection = ref<Preset[]>([]);
|
||||
@@ -158,8 +157,7 @@ export default defineComponent({
|
||||
|
||||
function useLinks() {
|
||||
const addNewLink = computed(() => {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
return `/${currentProjectKey}/settings/presets/+`;
|
||||
return `/settings/presets/+`;
|
||||
});
|
||||
|
||||
return { addNewLink };
|
||||
@@ -198,12 +196,10 @@ export default defineComponent({
|
||||
return { loading, presetsRaw, error, getPresets, presets };
|
||||
|
||||
async function getPresets() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/collection_presets`, {
|
||||
const response = await api.get(`/collection_presets`, {
|
||||
params: {
|
||||
fields: [
|
||||
'id',
|
||||
@@ -262,9 +258,8 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function onRowClick(item: Preset) {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
if (selection.value.length === 0) {
|
||||
router.push(`/${currentProjectKey}/settings/presets/${item.id}`);
|
||||
router.push(`/settings/presets/${item.id}`);
|
||||
} else {
|
||||
if (selection.value.includes(item)) {
|
||||
selection.value = selection.value.filter((i) => i !== item);
|
||||
@@ -281,12 +276,11 @@ export default defineComponent({
|
||||
return { confirmDelete, deleting, deleteSelection };
|
||||
|
||||
async function deleteSelection() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
deleting.value = true;
|
||||
|
||||
try {
|
||||
const IDs = selection.value.map((item) => item.id).join(',');
|
||||
await api.delete(`/${currentProjectKey}/collection_presets/${IDs}`);
|
||||
await api.delete(`/collection_presets/${IDs}`);
|
||||
selection.value = [];
|
||||
await getPresets();
|
||||
confirmDelete.value = false;
|
||||
|
||||
@@ -16,12 +16,9 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const error = ref<any>(null);
|
||||
const bookmarksCount = ref<number | null>(null);
|
||||
@@ -32,12 +29,10 @@ export default defineComponent({
|
||||
return { bookmarksCount, presetsCount };
|
||||
|
||||
async function fetchCounts() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/collection_presets`, {
|
||||
const response = await api.get(`/collection_presets`, {
|
||||
params: {
|
||||
[`filter[title][nnull]`]: 1,
|
||||
fields: ['id'],
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import SettingsNavigation from '../../../components/navigation';
|
||||
import { CollectionPreset, Filter } from '@/stores/collection-presets/types';
|
||||
import api from '@/api';
|
||||
@@ -122,7 +122,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const collectionsStore = useCollectionsStore();
|
||||
const collectionPresetsStore = useCollectionPresetsStore();
|
||||
const { backLink } = useLinks();
|
||||
@@ -165,8 +164,6 @@ export default defineComponent({
|
||||
return { saving, save };
|
||||
|
||||
async function save() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
saving.value = true;
|
||||
|
||||
const editsParsed: Partial<CollectionPreset> = {};
|
||||
@@ -189,9 +186,9 @@ export default defineComponent({
|
||||
|
||||
try {
|
||||
if (isNew.value === true) {
|
||||
await api.post(`/${currentProjectKey}/collection_presets`, editsParsed);
|
||||
await api.post(`/collection_presets`, editsParsed);
|
||||
} else {
|
||||
await api.patch(`/${currentProjectKey}/collection_presets/${props.id}`, editsParsed);
|
||||
await api.patch(`/collection_presets/${props.id}`, editsParsed);
|
||||
}
|
||||
|
||||
await collectionPresetsStore.hydrate();
|
||||
@@ -201,7 +198,7 @@ export default defineComponent({
|
||||
console.error(err);
|
||||
} finally {
|
||||
saving.value = false;
|
||||
router.push(`/${currentProjectKey}/settings/presets`);
|
||||
router.push(`/settings/presets`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,13 +210,11 @@ export default defineComponent({
|
||||
return { deleting, confirmDelete, deleteAndQuit };
|
||||
|
||||
async function deleteAndQuit() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
deleting.value = true;
|
||||
|
||||
try {
|
||||
await api.delete(`/${currentProjectKey}/collection_presets/${props.id}`);
|
||||
router.push(`/${currentProjectKey}/settings/presets`);
|
||||
await api.delete(`/collection_presets/${props.id}`);
|
||||
router.push(`/settings/presets`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
@@ -316,11 +311,10 @@ export default defineComponent({
|
||||
return { loading, error, preset, fetchPreset };
|
||||
|
||||
async function fetchPreset() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/collection_presets/${props.id}`);
|
||||
const response = await api.get(`/collection_presets/${props.id}`);
|
||||
|
||||
preset.value = response.data.data;
|
||||
} catch (err) {
|
||||
@@ -333,9 +327,7 @@ export default defineComponent({
|
||||
|
||||
function useLinks() {
|
||||
const backLink = computed(() => {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
return `/${currentProjectKey}/settings/presets`;
|
||||
return `/settings/presets`;
|
||||
});
|
||||
|
||||
return { backLink };
|
||||
@@ -351,11 +343,10 @@ export default defineComponent({
|
||||
return { loading, error, users };
|
||||
|
||||
async function fetchUsers() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/users`, {
|
||||
const response = await api.get(`/users`, {
|
||||
params: {
|
||||
fields: ['first_name', 'last_name', 'id'],
|
||||
},
|
||||
@@ -383,11 +374,10 @@ export default defineComponent({
|
||||
return { loading, error, roles };
|
||||
|
||||
async function fetchRoles() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/roles`, {
|
||||
const response = await api.get(`/roles`, {
|
||||
params: {
|
||||
fields: ['name', 'id'],
|
||||
},
|
||||
|
||||
@@ -19,14 +19,17 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import bytes from 'bytes';
|
||||
|
||||
/**
|
||||
* @TODO
|
||||
* retrieve server info somewhere separate
|
||||
*/
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
return { project: projectsStore.currentProject, bytes };
|
||||
return { project: {}, bytes };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from '@vue/composition-api';
|
||||
import SettingsNavigation from '../../../components/navigation/';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import { i18n } from '@/lang';
|
||||
import api from '@/api';
|
||||
import marked from 'marked';
|
||||
@@ -82,7 +82,6 @@ export default defineComponent({
|
||||
components: { SettingsNavigation, ValueNull },
|
||||
props: {},
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const roles = ref<Role[]>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref<any>(null);
|
||||
@@ -121,19 +120,16 @@ export default defineComponent({
|
||||
fetchRoles();
|
||||
|
||||
const addNewLink = computed(() => {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
return `/${currentProjectKey}/settings/roles/+`;
|
||||
return `/settings/roles/+`;
|
||||
});
|
||||
|
||||
return { marked, loading, roles, error, tableHeaders, addNewLink, navigateToRole };
|
||||
|
||||
async function fetchRoles() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/roles`, {
|
||||
const response = await api.get(`/roles`, {
|
||||
params: { limit: -1, fields: 'id,name,description,icon,users.id' },
|
||||
});
|
||||
|
||||
@@ -151,8 +147,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function navigateToRole(item: Role) {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
router.push(`/${currentProjectKey}/settings/roles/${item.id}`);
|
||||
router.push(`/settings/roles/${item.id}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ref, Ref, watch } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
export type Permission = {
|
||||
id?: number;
|
||||
@@ -22,8 +21,6 @@ export default function usePermissions(role: Ref<number>) {
|
||||
const error = ref(null);
|
||||
const permissions = ref<Permission[] | null>(null);
|
||||
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
watch(role, (newRole, oldRole) => {
|
||||
if (newRole !== oldRole) {
|
||||
reset();
|
||||
@@ -40,12 +37,10 @@ export default function usePermissions(role: Ref<number>) {
|
||||
}
|
||||
|
||||
async function fetchPermissions() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/permissions`, {
|
||||
const response = await api.get(`/permissions`, {
|
||||
params: {
|
||||
'filter[role][eq]': role.value,
|
||||
},
|
||||
@@ -60,14 +55,13 @@ export default function usePermissions(role: Ref<number>) {
|
||||
}
|
||||
|
||||
async function savePermission(updates: Partial<Permission>) {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
try {
|
||||
if (updates.id !== undefined) {
|
||||
await api.patch(`/${currentProjectKey}/permissions/${updates.id}`, {
|
||||
await api.patch(`/permissions/${updates.id}`, {
|
||||
...updates,
|
||||
});
|
||||
} else {
|
||||
await api.post(`/${currentProjectKey}/permissions`, updates);
|
||||
await api.post(`/permissions`, updates);
|
||||
}
|
||||
|
||||
await fetchPermissions();
|
||||
@@ -78,14 +72,13 @@ export default function usePermissions(role: Ref<number>) {
|
||||
}
|
||||
|
||||
async function saveAll(create: Partial<Permission>[], update: Partial<Permission>[]) {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
try {
|
||||
if (create.length > 0) {
|
||||
await api.post(`/${currentProjectKey}/permissions`, create);
|
||||
await api.post(`/permissions`, create);
|
||||
}
|
||||
|
||||
if (update.length > 0) {
|
||||
await api.patch(`/${currentProjectKey}/permissions`, update);
|
||||
await api.patch(`/permissions`, update);
|
||||
}
|
||||
|
||||
await fetchPermissions();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<private-view :title="loading ? $t('loading') : $t('editing_role', { role: item && item.name })">
|
||||
<template #headline>{{ $t('settings_permissions') }}</template>
|
||||
<template #title-outer:prepend>
|
||||
<v-button class="header-icon" rounded icon exact :to="`/${currentProjectKey}/settings/roles/`">
|
||||
<v-button class="header-icon" rounded icon exact :to="`/settings/roles/`">
|
||||
<v-icon name="arrow_back" />
|
||||
</v-button>
|
||||
</template>
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, toRefs, ref } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import SettingsNavigation from '../../../components/navigation/';
|
||||
import router from '@/router';
|
||||
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail';
|
||||
@@ -101,10 +101,8 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const { currentProjectKey } = toRefs(projectsStore.state);
|
||||
const { primaryKey } = toRefs(props);
|
||||
|
||||
const { isNew, edits, item, saving, loading, error, save, remove, deleting, saveAsCopy, isBatch } = useItem(
|
||||
@@ -132,7 +130,6 @@ export default defineComponent({
|
||||
saveAndAddNew,
|
||||
saveAsCopyAndNavigate,
|
||||
isBatch,
|
||||
currentProjectKey,
|
||||
marked,
|
||||
};
|
||||
|
||||
@@ -146,7 +143,7 @@ export default defineComponent({
|
||||
async function saveAndQuit() {
|
||||
await save();
|
||||
await userStore.hydrate();
|
||||
router.push(`/${currentProjectKey.value}/settings/roles`);
|
||||
router.push(`/settings/roles`);
|
||||
}
|
||||
|
||||
async function saveAndStay() {
|
||||
@@ -157,17 +154,17 @@ export default defineComponent({
|
||||
async function saveAndAddNew() {
|
||||
await save();
|
||||
await userStore.hydrate();
|
||||
router.push(`/${currentProjectKey.value}/settings/roles/+`);
|
||||
router.push(`/settings/roles/+`);
|
||||
}
|
||||
|
||||
async function saveAsCopyAndNavigate() {
|
||||
const newPrimaryKey = await saveAsCopy();
|
||||
router.push(`/${currentProjectKey.value}/settings/roles/${newPrimaryKey}`);
|
||||
router.push(`/settings/roles/${newPrimaryKey}`);
|
||||
}
|
||||
|
||||
async function deleteAndQuit() {
|
||||
await remove();
|
||||
router.push(`/${currentProjectKey.value}/settings/roles`);
|
||||
router.push(`/settings/roles`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from '@vue/composition-api';
|
||||
import SettingsNavigation from '../../../components/navigation/';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import { i18n } from '@/lang';
|
||||
import api from '@/api';
|
||||
import { LayoutComponent } from '@/layouts/types';
|
||||
@@ -87,7 +87,6 @@ export default defineComponent({
|
||||
props: {},
|
||||
setup() {
|
||||
const layout = ref<LayoutComponent | null>(null);
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const selection = ref<Item[]>([]);
|
||||
|
||||
@@ -138,15 +137,13 @@ export default defineComponent({
|
||||
return { confirmDelete, deleting, batchDelete };
|
||||
|
||||
async function batchDelete() {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
deleting.value = true;
|
||||
|
||||
confirmDelete.value = false;
|
||||
|
||||
const batchPrimaryKeys = selection.value.map((item) => item.id).join();
|
||||
|
||||
await api.delete(`/${currentProjectKey}/settings/webhooks/${batchPrimaryKeys}`);
|
||||
await api.delete(`/settings/webhooks/${batchPrimaryKeys}`);
|
||||
|
||||
await layout.value?.refresh();
|
||||
|
||||
@@ -158,14 +155,12 @@ export default defineComponent({
|
||||
|
||||
function useLinks() {
|
||||
const addNewLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
return `/${currentProjectKey}/settings/webhooks/+`;
|
||||
return `/settings/webhooks/+`;
|
||||
});
|
||||
|
||||
const batchLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
const batchPrimaryKeys = selection.value.map((item) => item.id).join();
|
||||
return `/${currentProjectKey}/settings/webhooks/${batchPrimaryKeys}`;
|
||||
return `/settings/webhooks/${batchPrimaryKeys}`;
|
||||
});
|
||||
|
||||
return { addNewLink, batchLink };
|
||||
@@ -173,12 +168,10 @@ export default defineComponent({
|
||||
|
||||
function useBreadcrumb() {
|
||||
const breadcrumb = computed(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
return [
|
||||
{
|
||||
name: i18n.tc('collection', 2),
|
||||
to: `/${currentProjectKey}/collections`,
|
||||
to: `/collections`,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<template #headline>{{ $t('settings_webhooks') }}</template>
|
||||
|
||||
<template #title-outer:prepend>
|
||||
<v-button class="header-icon" rounded icon exact :to="`/${currentProjectKey}/settings/webhooks/`">
|
||||
<v-button class="header-icon" rounded icon exact :to="`/settings/webhooks/`">
|
||||
<v-icon name="arrow_back" />
|
||||
</v-button>
|
||||
</template>
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, toRefs, ref } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import SettingsNavigation from '../../../components/navigation/';
|
||||
import router from '@/router';
|
||||
import RevisionsDrawerDetail from '@/views/private/components/revisions-drawer-detail';
|
||||
@@ -94,8 +94,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { currentProjectKey } = toRefs(projectsStore.state);
|
||||
const { primaryKey } = toRefs(props);
|
||||
|
||||
const { isNew, edits, item, saving, loading, error, save, remove, deleting, saveAsCopy, isBatch } = useItem(
|
||||
@@ -128,14 +126,13 @@ export default defineComponent({
|
||||
saveAndAddNew,
|
||||
saveAsCopyAndNavigate,
|
||||
isBatch,
|
||||
currentProjectKey,
|
||||
marked,
|
||||
title,
|
||||
};
|
||||
|
||||
async function saveAndQuit() {
|
||||
await save();
|
||||
router.push(`/${currentProjectKey.value}/settings/webhooks`);
|
||||
router.push(`/settings/webhooks`);
|
||||
}
|
||||
|
||||
async function saveAndStay() {
|
||||
@@ -144,17 +141,17 @@ export default defineComponent({
|
||||
|
||||
async function saveAndAddNew() {
|
||||
await save();
|
||||
router.push(`/${currentProjectKey.value}/settings/webhooks/+`);
|
||||
router.push(`/settings/webhooks/+`);
|
||||
}
|
||||
|
||||
async function saveAsCopyAndNavigate() {
|
||||
const newPrimaryKey = await saveAsCopy();
|
||||
router.push(`/${currentProjectKey.value}/settings/webhooks/${newPrimaryKey}`);
|
||||
router.push(`/settings/webhooks/${newPrimaryKey}`);
|
||||
}
|
||||
|
||||
async function deleteAndQuit() {
|
||||
await remove();
|
||||
router.push(`/${currentProjectKey.value}/settings/webhooks`);
|
||||
router.push(`/settings/webhooks`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -22,15 +22,14 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import useNavigation from '../../composables/use-navigation';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { roles, loading } = useNavigation();
|
||||
|
||||
return { roles, loading, project: projectsStore.state.currentProjectKey };
|
||||
return { roles, loading };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ref, Ref } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import api from '@/api';
|
||||
import { Role } from '@/stores/user/types';
|
||||
|
||||
@@ -24,10 +24,8 @@ export default function useNavigation() {
|
||||
async function fetchRoles() {
|
||||
if (!loading || !roles) return;
|
||||
loading.value = true;
|
||||
const projectsStore = useProjectsStore();
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
const rolesResponse = await api.get(`/${currentProjectKey}/roles`);
|
||||
const rolesResponse = await api.get(`/roles`);
|
||||
roles.value = rolesResponse.data.data;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from '@vue/composition-api';
|
||||
import UsersNavigation from '../../components/navigation/';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import { i18n } from '@/lang';
|
||||
import api from '@/api';
|
||||
import { LayoutComponent } from '@/layouts/types';
|
||||
@@ -102,7 +102,6 @@ export default defineComponent({
|
||||
},
|
||||
setup(props) {
|
||||
const layout = ref<LayoutComponent | null>(null);
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const selection = ref<Item[]>([]);
|
||||
|
||||
@@ -177,15 +176,13 @@ export default defineComponent({
|
||||
return { confirmDelete, deleting, batchDelete };
|
||||
|
||||
async function batchDelete() {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
deleting.value = true;
|
||||
|
||||
confirmDelete.value = false;
|
||||
|
||||
const batchPrimaryKeys = selection.value;
|
||||
|
||||
await api.delete(`/${currentProjectKey}/users/${batchPrimaryKeys}`);
|
||||
await api.delete(`/users/${batchPrimaryKeys}`);
|
||||
|
||||
await layout.value?.refresh();
|
||||
|
||||
@@ -197,14 +194,12 @@ export default defineComponent({
|
||||
|
||||
function useLinks() {
|
||||
const addNewLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
return `/${currentProjectKey}/users/+`;
|
||||
return `/users/+`;
|
||||
});
|
||||
|
||||
const batchLink = computed<string>(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
const batchPrimaryKeys = selection.value;
|
||||
return `/${currentProjectKey}/users/${batchPrimaryKeys}`;
|
||||
return `/users/${batchPrimaryKeys}`;
|
||||
});
|
||||
|
||||
return { addNewLink, batchLink };
|
||||
@@ -212,12 +207,10 @@ export default defineComponent({
|
||||
|
||||
function useBreadcrumb() {
|
||||
const breadcrumb = computed(() => {
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
return [
|
||||
{
|
||||
name: i18n.tc('collection', 2),
|
||||
to: `/${currentProjectKey}/collections`,
|
||||
to: `/collections`,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
@@ -116,7 +116,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, toRefs, ref, watch } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import UsersNavigation from '../../components/navigation/';
|
||||
import { i18n } from '@/lang';
|
||||
import router from '@/router';
|
||||
@@ -157,9 +157,8 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const { currentProjectKey } = toRefs(projectsStore.state);
|
||||
|
||||
const { primaryKey } = toRefs(props);
|
||||
const { breadcrumb } = useBreadcrumb();
|
||||
|
||||
@@ -243,7 +242,7 @@ export default defineComponent({
|
||||
const breadcrumb = computed(() => [
|
||||
{
|
||||
name: i18n.t('user_directory'),
|
||||
to: `/${currentProjectKey.value}/users/`,
|
||||
to: `/users/`,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -252,7 +251,7 @@ export default defineComponent({
|
||||
|
||||
async function saveAndQuit() {
|
||||
await save();
|
||||
router.push(`/${currentProjectKey.value}/users`);
|
||||
router.push(`/users`);
|
||||
}
|
||||
|
||||
async function saveAndStay() {
|
||||
@@ -263,23 +262,23 @@ export default defineComponent({
|
||||
if (props.primaryKey === '+') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const newPrimaryKey = savedItem.id;
|
||||
router.replace(`/${currentProjectKey.value}/collections/users/${newPrimaryKey}`);
|
||||
router.replace(`/collections/users/${newPrimaryKey}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveAndAddNew() {
|
||||
await save();
|
||||
router.push(`/${currentProjectKey.value}/users/+`);
|
||||
router.push(`/users/+`);
|
||||
}
|
||||
|
||||
async function saveAsCopyAndNavigate() {
|
||||
const newPrimaryKey = await saveAsCopy();
|
||||
router.push(`/${currentProjectKey.value}/users/${newPrimaryKey}`);
|
||||
router.push(`/users/${newPrimaryKey}`);
|
||||
}
|
||||
|
||||
async function deleteAndQuit() {
|
||||
await remove();
|
||||
router.push(`/${currentProjectKey.value}/users`);
|
||||
router.push(`/users`);
|
||||
}
|
||||
|
||||
function useUserPreview() {
|
||||
@@ -298,7 +297,7 @@ export default defineComponent({
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey.value}/users/${props.primaryKey}`, {
|
||||
const response = await api.get(`/users/${props.primaryKey}`, {
|
||||
params: {
|
||||
fields: ['role.name', 'avatar.data'],
|
||||
},
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { Route } from 'vue-router';
|
||||
import { onBeforeEach, onAfterEach, onBeforeEnterProjectChooser, replaceRoutes, defaultRoutes } from './router';
|
||||
import api from '@/api';
|
||||
import * as auth from '@/auth';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
import { hydrate } from '@/hydrate';
|
||||
import useUserStore from '@/stores/user';
|
||||
|
||||
jest.mock('@/auth');
|
||||
jest.mock('@/hydrate');
|
||||
jest.mock('@/api');
|
||||
|
||||
const route: Route = {
|
||||
name: undefined,
|
||||
path: '',
|
||||
query: {},
|
||||
hash: '',
|
||||
params: {},
|
||||
fullPath: '',
|
||||
matched: [],
|
||||
};
|
||||
|
||||
describe('Router', () => {
|
||||
beforeAll(() => {
|
||||
Vue.config.productionTip = false;
|
||||
Vue.config.devtools = false;
|
||||
Vue.use(VueCompositionAPI);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(api, 'get');
|
||||
jest.spyOn(api, 'post');
|
||||
});
|
||||
|
||||
it('Fetches the projects using projectsStore on first load', async () => {
|
||||
const toRoute = route;
|
||||
|
||||
const fromRoute = {
|
||||
...route,
|
||||
name: null,
|
||||
};
|
||||
|
||||
const callback = jest.fn();
|
||||
|
||||
const projectsStore = useProjectsStore({});
|
||||
jest.spyOn(projectsStore, 'getProjects');
|
||||
|
||||
await onBeforeEach(toRoute, fromRoute as any, callback);
|
||||
|
||||
expect(projectsStore.getProjects).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Redirects to /install if projectsStore indicates that an install is needed', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.needsInstall = true;
|
||||
|
||||
const toRoute = route;
|
||||
const fromRoute = route;
|
||||
const callback = jest.fn();
|
||||
|
||||
await onBeforeEach(toRoute, fromRoute, callback);
|
||||
|
||||
expect(callback).toHaveBeenCalledWith('/install');
|
||||
});
|
||||
|
||||
it('Does not redirect to /install if we try to open /install', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.needsInstall = true;
|
||||
|
||||
const toRoute = {
|
||||
...route,
|
||||
path: '/install',
|
||||
};
|
||||
const fromRoute = route;
|
||||
const callback = jest.fn();
|
||||
|
||||
await onBeforeEach(toRoute, fromRoute, callback);
|
||||
|
||||
expect(callback).not.toHaveBeenCalledWith('/install');
|
||||
});
|
||||
|
||||
it('Keeps projects store in sync with project in route', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
|
||||
jest.spyOn(projectsStore, 'setCurrentProject');
|
||||
|
||||
const toRoute = {
|
||||
...route,
|
||||
params: {
|
||||
project: 'my-project',
|
||||
},
|
||||
};
|
||||
const fromRoute = route;
|
||||
const callback = jest.fn();
|
||||
|
||||
await onBeforeEach(toRoute, fromRoute, callback);
|
||||
|
||||
expect(projectsStore.setCurrentProject).toHaveBeenCalledWith('my-project');
|
||||
});
|
||||
|
||||
it('Redirects to / when trying to open non-existing project', async () => {
|
||||
useProjectsStore({});
|
||||
|
||||
const toRoute = {
|
||||
...route,
|
||||
path: '/test',
|
||||
params: {
|
||||
project: 'my-project',
|
||||
},
|
||||
};
|
||||
const fromRoute = route;
|
||||
const callback = jest.fn();
|
||||
|
||||
await onBeforeEach(toRoute, fromRoute, callback);
|
||||
|
||||
expect(callback).toHaveBeenCalledWith('/');
|
||||
});
|
||||
|
||||
it('Does not redirect to / when trying to open /', async () => {
|
||||
useProjectsStore({});
|
||||
|
||||
const toRoute = {
|
||||
...route,
|
||||
path: '/',
|
||||
params: {
|
||||
project: 'my-project',
|
||||
},
|
||||
};
|
||||
const fromRoute = route;
|
||||
const callback = jest.fn();
|
||||
|
||||
await onBeforeEach(toRoute, fromRoute, callback);
|
||||
|
||||
expect(callback).not.toHaveBeenCalledWith('/');
|
||||
});
|
||||
|
||||
it('Checks if you are authenticated on first load', async () => {
|
||||
jest.spyOn(auth, 'checkAuth').mockImplementation(() => Promise.resolve(false));
|
||||
|
||||
const projectsStore = useProjectsStore({});
|
||||
jest.spyOn(projectsStore, 'getProjects').mockResolvedValue();
|
||||
|
||||
projectsStore.state.projects = [
|
||||
{
|
||||
key: 'my-project',
|
||||
},
|
||||
] as any;
|
||||
|
||||
const to = {
|
||||
...route,
|
||||
params: {
|
||||
project: 'my-project',
|
||||
},
|
||||
};
|
||||
|
||||
const from = { ...route, name: null };
|
||||
const next = jest.fn();
|
||||
|
||||
await onBeforeEach(to, from as any, next);
|
||||
|
||||
expect(auth.checkAuth).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Hydrates the store on first load when logged in', async () => {
|
||||
jest.spyOn(auth, 'checkAuth').mockImplementation(() => Promise.resolve(true));
|
||||
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.projects = [
|
||||
{
|
||||
key: 'my-project',
|
||||
},
|
||||
] as any;
|
||||
jest.spyOn(projectsStore, 'getProjects').mockResolvedValue();
|
||||
|
||||
const to = {
|
||||
...route,
|
||||
params: {
|
||||
project: 'my-project',
|
||||
},
|
||||
};
|
||||
|
||||
const from = { ...route, name: null };
|
||||
const next = jest.fn();
|
||||
|
||||
await onBeforeEach(to, from as any, next);
|
||||
|
||||
expect(hydrate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Calls next when trying to open public route while being logged in', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
jest.spyOn(projectsStore, 'getProjects').mockResolvedValue();
|
||||
|
||||
const toRoute = {
|
||||
...route,
|
||||
meta: {
|
||||
public: true,
|
||||
},
|
||||
};
|
||||
const fromRoute = {
|
||||
...route,
|
||||
name: null,
|
||||
};
|
||||
const next = jest.fn();
|
||||
|
||||
await onBeforeEach(toRoute, fromRoute as any, next);
|
||||
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
it('Calls next when all checks are done', async () => {
|
||||
jest.spyOn(auth, 'checkAuth').mockImplementation(() => Promise.resolve(true));
|
||||
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.projects = [
|
||||
{
|
||||
key: 'my-project',
|
||||
},
|
||||
] as any;
|
||||
jest.spyOn(projectsStore, 'getProjects').mockResolvedValue();
|
||||
|
||||
const to = {
|
||||
...route,
|
||||
params: {
|
||||
project: 'my-project',
|
||||
},
|
||||
};
|
||||
|
||||
const from = { ...route, name: null };
|
||||
const next = jest.fn();
|
||||
|
||||
await onBeforeEach(to, from as any, next);
|
||||
|
||||
expect(auth.checkAuth).toHaveBeenCalled();
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
describe('onBeforeEnterProjectChooser', () => {
|
||||
it('Sets the current project to null on open', () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
jest.spyOn(projectsStore, 'getProjects').mockResolvedValue();
|
||||
|
||||
const to = { ...route, path: '/' };
|
||||
const from = route;
|
||||
const next = jest.fn();
|
||||
onBeforeEnterProjectChooser(to, from, next);
|
||||
expect(projectsStore.state.currentProjectKey).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceRoutes', () => {
|
||||
it('Calls the handler with the default routes', async () => {
|
||||
const handler = jest.fn(() => []);
|
||||
replaceRoutes(handler);
|
||||
expect(handler).toHaveBeenCalledWith(defaultRoutes);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onAfterEach', () => {
|
||||
it('Calls the userStore trackPage method after some time', () => {
|
||||
jest.useFakeTimers();
|
||||
const userStore = useUserStore({});
|
||||
|
||||
jest.spyOn(userStore, 'trackPage');
|
||||
|
||||
const to = {
|
||||
fullPath: '/test',
|
||||
meta: {
|
||||
public: false,
|
||||
},
|
||||
} as any;
|
||||
|
||||
onAfterEach(to);
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(userStore.trackPage).toHaveBeenCalledWith('/test');
|
||||
});
|
||||
|
||||
it('Does not track the page for public pages', () => {
|
||||
jest.useFakeTimers();
|
||||
const userStore = useUserStore({});
|
||||
|
||||
jest.spyOn(userStore, 'trackPage');
|
||||
|
||||
const to = {
|
||||
fullPath: '/test',
|
||||
meta: {
|
||||
public: true,
|
||||
},
|
||||
} as any;
|
||||
|
||||
onAfterEach(to);
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(userStore.trackPage).not.toHaveBeenCalledWith('/test');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,6 +8,7 @@ import { hydrate } from '@/hydrate';
|
||||
import useAppStore from '@/stores/app';
|
||||
import useUserStore from '@/stores/user';
|
||||
import PrivateNotFoundRoute from '@/routes/private-not-found';
|
||||
import useSettingsStore from '@/stores/settings';
|
||||
|
||||
import getRootPath from '@/utils/get-root-path';
|
||||
|
||||
@@ -93,11 +94,11 @@ export function replaceRoutes(routeFilter: (routes: RouteConfig[]) => RouteConfi
|
||||
|
||||
export const onBeforeEach: NavigationGuard = async (to, from, next) => {
|
||||
const appStore = useAppStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
// Make sure the projects store is aware of all projects that exist
|
||||
// if (projectsStore.state.projects === null) {
|
||||
// await projectsStore.getProjects();
|
||||
// }
|
||||
if (settingsStore.state.settings === null) {
|
||||
await settingsStore.hydrate();
|
||||
}
|
||||
|
||||
// When there aren't any projects, we should redirect to the install page to force the
|
||||
// user to setup a project.
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref, reactive } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import InstallWelcome from './install-welcome.vue';
|
||||
import InstallRequirements from './install-requirements.vue';
|
||||
import InstallProject from './install-project.vue';
|
||||
@@ -46,10 +46,11 @@ export default defineComponent({
|
||||
InstallFinal,
|
||||
},
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const first = computed(() => {
|
||||
return projectsStore.state.needsInstall;
|
||||
/**
|
||||
* @todo remove difference between first or not (it's always first now)
|
||||
*/
|
||||
return true;
|
||||
});
|
||||
|
||||
const panes = ['welcome', 'requirements', 'project', 'database', 'final'];
|
||||
@@ -95,7 +96,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
async function finish() {
|
||||
await projectsStore.hydrate();
|
||||
router.push('/');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -13,17 +13,15 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, watch, ref } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import api from '@/api';
|
||||
import { hydrate } from '@/hydrate';
|
||||
import router from '@/router';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const signOutLink = computed<string>(() => {
|
||||
return `/${projectsStore.state.currentProjectKey}/logout`;
|
||||
return `/logout`;
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
@@ -31,16 +29,16 @@ export default defineComponent({
|
||||
const name = ref<string | null>(null);
|
||||
const lastPage = ref<string | null>(null);
|
||||
|
||||
watch(() => projectsStore.state.currentProjectKey, fetchUser);
|
||||
fetchUser();
|
||||
|
||||
return { name, lastPage, signOutLink, loading, error, hydrateAndLogin };
|
||||
|
||||
async function fetchUser(projectKey: string | null) {
|
||||
async function fetchUser() {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${projectKey}/users/me`, {
|
||||
const response = await api.get(`/users/me`, {
|
||||
params: {
|
||||
fields: ['first_name', 'last_name', 'last_page'],
|
||||
},
|
||||
@@ -57,7 +55,7 @@ export default defineComponent({
|
||||
|
||||
async function hydrateAndLogin() {
|
||||
await hydrate();
|
||||
router.push(`/${projectsStore.state.currentProjectKey}/collections/`);
|
||||
router.push(`/collections/`);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
</v-notice>
|
||||
<div class="buttons">
|
||||
<v-button type="submit" :loading="loggingIn" large>{{ $t('sign_in') }}</v-button>
|
||||
<router-link :to="forgotLink" class="forgot-password">
|
||||
<router-link to="/reset-password" class="forgot-password">
|
||||
{{ $t('forgot_password') }}
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<template v-if="ssoProviders">
|
||||
<!-- <template v-if="ssoProviders">
|
||||
<v-divider class="sso-divider" />
|
||||
|
||||
<v-button
|
||||
@@ -35,18 +35,17 @@
|
||||
<v-notice class="sso-notice" type="danger" v-if="ssoError">
|
||||
{{ translateAPIError(ssoError) }}
|
||||
</v-notice>
|
||||
</template>
|
||||
</template> -->
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch } from '@vue/composition-api';
|
||||
import router from '@/router';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
//
|
||||
import { login } from '@/auth';
|
||||
import { RequestError } from '@/api';
|
||||
import { translateAPIError } from '@/lang';
|
||||
import getRootPath from '@/utils/get-root-path';
|
||||
|
||||
type Credentials = {
|
||||
email: string;
|
||||
@@ -62,8 +61,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const loggingIn = ref(false);
|
||||
const email = ref<string | null>(null);
|
||||
const password = ref<string | null>(null);
|
||||
@@ -82,29 +79,25 @@ export default defineComponent({
|
||||
return null;
|
||||
});
|
||||
|
||||
const forgotLink = computed(() => {
|
||||
return `/${projectsStore.state.currentProjectKey}/reset-password`;
|
||||
});
|
||||
|
||||
const ssoProviders = computed(() => {
|
||||
const redirectURL = getRootPath() + `admin/${projectsStore.state.currentProjectKey}/login`;
|
||||
return projectsStore.currentProject.value.sso.map((provider: { icon: string; name: string }) => {
|
||||
return {
|
||||
...provider,
|
||||
link: `/${projectsStore.state.currentProjectKey}/auth/sso/${provider.name}?mode=cookie&redirect_url=${redirectURL}`,
|
||||
};
|
||||
});
|
||||
});
|
||||
/** @todo fetch these from /auth/sso */
|
||||
// const ssoProviders = computed(() => {
|
||||
// const redirectURL = getRootPath() + `admin/login`;
|
||||
// return projectsStore.currentProject.value.sso.map((provider: { icon: string; name: string }) => {
|
||||
// return {
|
||||
// ...provider,
|
||||
// link: `/auth/sso/${provider.name}?mode=cookie&redirect_url=${redirectURL}`,
|
||||
// };
|
||||
// });
|
||||
// });
|
||||
|
||||
return {
|
||||
ssoProviders,
|
||||
// ssoProviders,
|
||||
errorFormatted,
|
||||
error,
|
||||
email,
|
||||
password,
|
||||
onSubmit,
|
||||
loggingIn,
|
||||
forgotLink,
|
||||
translateAPIError,
|
||||
otp,
|
||||
requiresTFA,
|
||||
@@ -113,8 +106,6 @@ export default defineComponent({
|
||||
async function onSubmit() {
|
||||
if (email.value === null || password.value === null) return;
|
||||
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
try {
|
||||
loggingIn.value = true;
|
||||
|
||||
@@ -129,8 +120,9 @@ export default defineComponent({
|
||||
|
||||
await login(credentials);
|
||||
|
||||
router.push(`/${currentProjectKey}/collections/`);
|
||||
router.push(`/collections/`);
|
||||
} catch (err) {
|
||||
/** @todo use new error code */
|
||||
if (err.response?.data?.error?.code === 111) {
|
||||
requiresTFA.value = true;
|
||||
} else {
|
||||
|
||||
@@ -2,10 +2,8 @@
|
||||
<public-view>
|
||||
<h1 class="type-title">{{ $t('sign_in') }}</h1>
|
||||
|
||||
<continue-as v-if="currentProject.authenticated" />
|
||||
<v-notice type="danger" v-else-if="currentProject && currentProject.error">
|
||||
{{ errorFormatted }}
|
||||
</v-notice>
|
||||
<continue-as v-if="authenticated" />
|
||||
|
||||
<login-form v-else :sso-error="ssoErrorCode" />
|
||||
|
||||
<template #notice>
|
||||
@@ -16,12 +14,11 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from '@vue/composition-api';
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import LoginForm from './components/login-form/';
|
||||
import ContinueAs from './components/continue-as/';
|
||||
import useProjectsStore from '../../stores/projects';
|
||||
|
||||
import { translateAPIError } from '@/lang';
|
||||
import useAppStore from '../../stores/app';
|
||||
import useSettingsStore from '../../stores/settings';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -32,17 +29,13 @@ export default defineComponent({
|
||||
},
|
||||
components: { LoginForm, ContinueAs },
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const appStore = useAppStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const errorFormatted = computed(() => {
|
||||
if (projectsStore.currentProject.value?.error) {
|
||||
return translateAPIError(projectsStore.currentProject.value.error.code);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
return { errorFormatted, currentProject: projectsStore.currentProject };
|
||||
return {
|
||||
authenticated: appStore.state.authenticated,
|
||||
currentProject: settingsStore.state.settings,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import ProjectChooserRoute from './project-chooser.vue';
|
||||
|
||||
export { ProjectChooserRoute };
|
||||
export default ProjectChooserRoute;
|
||||
@@ -1,96 +0,0 @@
|
||||
<template>
|
||||
<public-view>
|
||||
<router-link class="project" v-for="project in projects" :to="project.link" :key="project.key">
|
||||
<div class="logo" v-if="project && project.logo" :style="{ backgroundColor: project.color }">
|
||||
<img :src="project.logo" :alt="project.name || project.key" />
|
||||
</div>
|
||||
<img v-else class="default-logo" src="@/assets/logo-dark.svg" alt="Directus" />
|
||||
<div class="name type-title">
|
||||
{{ project.name || project.key }}
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<router-link to="/install" class="project new">
|
||||
<div class="logo">
|
||||
<v-icon name="add" />
|
||||
</div>
|
||||
<div class="name type-title">{{ $t('create_project') }}</div>
|
||||
</router-link>
|
||||
</public-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'project-chooser',
|
||||
props: {},
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const projects = projectsStore.formatted.value?.map((project) => ({
|
||||
...project,
|
||||
link: `/${project.key}/login`,
|
||||
}));
|
||||
|
||||
return { projects };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.v-button:not(:last-child) {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 44px;
|
||||
}
|
||||
|
||||
.project {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 64px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.default-logo {
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-left: 12px;
|
||||
color: var(--foreground-subdued);
|
||||
transition: color var(--fast) var(--transition);
|
||||
}
|
||||
|
||||
.v-divider {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.project.new {
|
||||
.logo {
|
||||
background-color: var(--background-normal);
|
||||
}
|
||||
|
||||
.v-icon {
|
||||
color: var(--foreground-subdued);
|
||||
}
|
||||
}
|
||||
|
||||
.project:hover .name {
|
||||
color: var(--foreground);
|
||||
}
|
||||
</style>
|
||||
@@ -14,15 +14,12 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed } from '@vue/composition-api';
|
||||
import useProjectsStore from '../../stores/projects';
|
||||
import api from '@/api';
|
||||
import { translateAPIError } from '@/lang';
|
||||
import { RequestError } from '@/api';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const email = ref(null);
|
||||
|
||||
const sending = ref(false);
|
||||
@@ -36,14 +33,13 @@ export default defineComponent({
|
||||
return null;
|
||||
});
|
||||
|
||||
const signInLink = computed(() => `/${projectsStore.state.currentProjectKey}/login`);
|
||||
const signInLink = computed(() => `/login`);
|
||||
|
||||
return {
|
||||
sending,
|
||||
error,
|
||||
done,
|
||||
email,
|
||||
currentProject: projectsStore.currentProject,
|
||||
onSubmit,
|
||||
signInLink,
|
||||
errorFormatted,
|
||||
@@ -54,7 +50,7 @@ export default defineComponent({
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
await api.post(`/${projectsStore.state.currentProjectKey}/auth/password/request`, {
|
||||
await api.post(`/auth/password/request`, {
|
||||
email: email.value,
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed } from '@vue/composition-api';
|
||||
import useProjectsStore from '../../stores/projects';
|
||||
import api from '@/api';
|
||||
import { translateAPIError } from '@/lang';
|
||||
import { RequestError } from '@/api';
|
||||
@@ -34,8 +33,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const password = ref(null);
|
||||
|
||||
const resetting = ref(false);
|
||||
@@ -49,7 +46,7 @@ export default defineComponent({
|
||||
return null;
|
||||
});
|
||||
|
||||
const signInLink = computed(() => `/${projectsStore.state.currentProjectKey}/login`);
|
||||
const signInLink = computed(() => `/login`);
|
||||
|
||||
const email = computed(() => jwtPayload(props.token).email);
|
||||
|
||||
@@ -58,7 +55,6 @@ export default defineComponent({
|
||||
error,
|
||||
done,
|
||||
password,
|
||||
currentProject: projectsStore.currentProject,
|
||||
onSubmit,
|
||||
signInLink,
|
||||
errorFormatted,
|
||||
@@ -70,7 +66,7 @@ export default defineComponent({
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
await api.post(`/${projectsStore.state.currentProjectKey}/auth/password/reset`, {
|
||||
await api.post(`/auth/password/reset`, {
|
||||
password: password.value,
|
||||
token: props.token,
|
||||
});
|
||||
|
||||
5
src/shims.d.ts
vendored
5
src/shims.d.ts
vendored
@@ -13,6 +13,11 @@ declare module '*.md' {
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module '*.json' {
|
||||
const value: { [key: string]: any };
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module 'vuedraggable' {
|
||||
import Vue from 'vue';
|
||||
export default Vue;
|
||||
|
||||
@@ -7,5 +7,6 @@ export const useAppStore = createStore({
|
||||
hydrated: false,
|
||||
hydrating: false,
|
||||
error: null,
|
||||
authenticated: false,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,356 +0,0 @@
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { useUserStore } from '@/stores/user/';
|
||||
import { useProjectsStore } from '@/stores/projects/';
|
||||
import { useCollectionPresetsStore } from './collection-presets';
|
||||
import defaultCollectionPreset from './default-collection-preset';
|
||||
import api from '@/api';
|
||||
|
||||
jest.mock('@/api');
|
||||
|
||||
describe('Composables / Collection Presets', () => {
|
||||
let req: any;
|
||||
|
||||
beforeAll(() => {
|
||||
Vue.use(VueCompositionAPI);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
req = {};
|
||||
});
|
||||
|
||||
describe('Hydrate', () => {
|
||||
it('Calls api.get with the correct parameters', async () => {
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
data: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const userStore = useUserStore(req);
|
||||
(userStore.state.currentUser as any) = { id: 15, role: { id: 25 } };
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
|
||||
await collectionPresetsStore.hydrate();
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith(`/my-project/collection_presets`, {
|
||||
params: {
|
||||
'filter[user][eq]': 15,
|
||||
},
|
||||
});
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith(`/my-project/collection_presets`, {
|
||||
params: {
|
||||
'filter[role][eq]': 25,
|
||||
'filter[user][null]': 1,
|
||||
},
|
||||
});
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith(`/my-project/collection_presets`, {
|
||||
params: {
|
||||
'filter[role][null]': 1,
|
||||
'filter[user][null]': 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dehydrate', () => {
|
||||
it('Calls reset', async () => {
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
jest.spyOn(collectionPresetsStore as any, 'reset');
|
||||
await collectionPresetsStore.dehydrate();
|
||||
expect(collectionPresetsStore.reset).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Create Preset', () => {
|
||||
it('Calls the right endpoint', async () => {
|
||||
(api.post as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
data: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
|
||||
await collectionPresetsStore.create({
|
||||
title: 'test',
|
||||
});
|
||||
|
||||
expect(api.post).toHaveBeenCalledWith('/my-project/collection_presets', {
|
||||
title: 'test',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update Preset', () => {
|
||||
it('Calls the right endpoint', async () => {
|
||||
(api.patch as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
data: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
|
||||
await collectionPresetsStore.update(15, {
|
||||
title: 'test',
|
||||
});
|
||||
|
||||
expect(api.patch).toHaveBeenCalledWith('/my-project/collection_presets/15', {
|
||||
title: 'test',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete Preset', () => {
|
||||
it('Calls the right endpoint', async () => {
|
||||
(api.delete as jest.Mock).mockImplementation(() => Promise.resolve());
|
||||
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
|
||||
await (collectionPresetsStore as any).delete(15);
|
||||
|
||||
expect(api.delete).toHaveBeenCalledWith('/my-project/collection_presets/15');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Get Collection Preset for Collection', () => {
|
||||
it('Returns null if userStore currentUser is null', () => {
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = null;
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
const preset = collectionPresetsStore.getPresetForCollection('articles');
|
||||
expect(preset).toBe(null);
|
||||
});
|
||||
|
||||
it('Returns the default preset if there are no available presets', () => {
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = { id: 5, role: 5 } as any;
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
const preset = collectionPresetsStore.getPresetForCollection('articles');
|
||||
expect(preset).toEqual({
|
||||
...defaultCollectionPreset,
|
||||
collection: 'articles',
|
||||
user: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it('Ignores bookmarks', () => {
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = { id: 5, role: 5 } as any;
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
collectionPresetsStore.state.collectionPresets = [
|
||||
{
|
||||
collection: 'articles',
|
||||
user: null,
|
||||
role: null,
|
||||
title: 'should be ignored',
|
||||
},
|
||||
] as any;
|
||||
|
||||
const preset = collectionPresetsStore.getPresetForCollection('articles');
|
||||
expect(preset).toEqual({
|
||||
...defaultCollectionPreset,
|
||||
collection: 'articles',
|
||||
user: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns the preset immediately if there is only 1', () => {
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = { id: 5, role: 5 } as any;
|
||||
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
collectionPresetsStore.state.collectionPresets = [
|
||||
{
|
||||
collection: 'articles',
|
||||
user: null,
|
||||
role: null,
|
||||
},
|
||||
] as any;
|
||||
|
||||
const preset = collectionPresetsStore.getPresetForCollection('articles');
|
||||
expect(preset).toEqual({
|
||||
collection: 'articles',
|
||||
user: null,
|
||||
role: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('Prefers the user preset if it exists', () => {
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = { id: 5, role: 5 } as any;
|
||||
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
collectionPresetsStore.state.collectionPresets = [
|
||||
{
|
||||
collection: 'articles',
|
||||
user: null,
|
||||
role: 5,
|
||||
},
|
||||
{
|
||||
collection: 'articles',
|
||||
user: 5,
|
||||
role: null,
|
||||
},
|
||||
{
|
||||
collection: 'articles',
|
||||
user: null,
|
||||
role: null,
|
||||
},
|
||||
] as any;
|
||||
|
||||
const preset = collectionPresetsStore.getPresetForCollection('articles');
|
||||
expect(preset).toEqual({
|
||||
collection: 'articles',
|
||||
user: 5,
|
||||
role: null,
|
||||
});
|
||||
});
|
||||
|
||||
it('Prefers the role preset if user does not exist', () => {
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = { id: 5, role: 5 } as any;
|
||||
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
collectionPresetsStore.state.collectionPresets = [
|
||||
{
|
||||
collection: 'articles',
|
||||
user: null,
|
||||
role: null,
|
||||
},
|
||||
{
|
||||
collection: 'articles',
|
||||
user: null,
|
||||
role: 5,
|
||||
},
|
||||
] as any;
|
||||
|
||||
const preset = collectionPresetsStore.getPresetForCollection('articles');
|
||||
expect(preset).toEqual({
|
||||
collection: 'articles',
|
||||
user: null,
|
||||
role: 5,
|
||||
});
|
||||
});
|
||||
|
||||
it('Returns the last collection preset if more than 1 exist', () => {
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = { id: 5, role: 5 } as any;
|
||||
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
collectionPresetsStore.state.collectionPresets = [
|
||||
{
|
||||
collection: 'articles',
|
||||
user: null,
|
||||
role: null,
|
||||
test: false,
|
||||
},
|
||||
{
|
||||
collection: 'articles',
|
||||
user: null,
|
||||
role: null,
|
||||
test: false,
|
||||
},
|
||||
{
|
||||
collection: 'articles',
|
||||
user: null,
|
||||
role: null,
|
||||
test: true,
|
||||
},
|
||||
] as any;
|
||||
|
||||
const preset = collectionPresetsStore.getPresetForCollection('articles');
|
||||
expect(preset).toEqual({
|
||||
collection: 'articles',
|
||||
user: null,
|
||||
role: null,
|
||||
test: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Save Preset', () => {
|
||||
it('Returns null immediately if userStore is empty', async () => {
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = null;
|
||||
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
|
||||
const result = await collectionPresetsStore.savePreset();
|
||||
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
it('Calls create if id is undefined or null', async () => {
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = { id: 5 } as any;
|
||||
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
jest.spyOn(collectionPresetsStore, 'create').mockImplementation(() => ({}));
|
||||
|
||||
await collectionPresetsStore.savePreset({
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
expect(collectionPresetsStore.create).toHaveBeenCalledWith({ id: undefined, user: 5 });
|
||||
|
||||
await collectionPresetsStore.savePreset({
|
||||
id: null,
|
||||
});
|
||||
|
||||
expect(collectionPresetsStore.create).toHaveBeenCalledWith({ id: null, user: 5 });
|
||||
});
|
||||
|
||||
it('Calls create when the user is not the current user', async () => {
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = { id: 5 } as any;
|
||||
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
jest.spyOn(collectionPresetsStore, 'create').mockImplementation(() => ({}));
|
||||
|
||||
await collectionPresetsStore.savePreset({
|
||||
id: 15,
|
||||
test: 'value',
|
||||
user: null,
|
||||
});
|
||||
|
||||
expect(collectionPresetsStore.create).toHaveBeenCalledWith({ test: 'value', user: 5 });
|
||||
});
|
||||
|
||||
it('Calls update if the user field is already set', async () => {
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = { id: 5 } as any;
|
||||
|
||||
const collectionPresetsStore = useCollectionPresetsStore(req);
|
||||
jest.spyOn(collectionPresetsStore, 'update').mockImplementation(() => ({}));
|
||||
|
||||
await collectionPresetsStore.savePreset({
|
||||
id: 15,
|
||||
test: 'value',
|
||||
user: 5,
|
||||
});
|
||||
|
||||
expect(collectionPresetsStore.update).toHaveBeenCalledWith(15, {
|
||||
test: 'value',
|
||||
user: 5,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createStore } from 'pinia';
|
||||
import { CollectionPreset } from './types';
|
||||
import { useUserStore } from '@/stores/user/';
|
||||
import { useProjectsStore } from '@/stores/projects/';
|
||||
import api from '@/api';
|
||||
|
||||
import defaultCollectionPreset from './default-collection-preset';
|
||||
@@ -16,24 +15,23 @@ export const useCollectionPresetsStore = createStore({
|
||||
// Hydrate is only called for logged in users, therefore, currentUser exists
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const { id, role } = useUserStore().state.currentUser!;
|
||||
const { currentProjectKey } = useProjectsStore().state;
|
||||
|
||||
const values = await Promise.all([
|
||||
// All user saved bookmarks and presets
|
||||
api.get(`/${currentProjectKey}/collection_presets`, {
|
||||
api.get(`/collection_presets`, {
|
||||
params: {
|
||||
'filter[user][eq]': id,
|
||||
},
|
||||
}),
|
||||
// All role saved bookmarks and presets
|
||||
api.get(`/${currentProjectKey}/collection_presets`, {
|
||||
api.get(`/collection_presets`, {
|
||||
params: {
|
||||
'filter[role][eq]': role.id,
|
||||
'filter[user][null]': 1,
|
||||
},
|
||||
}),
|
||||
// All global saved bookmarks and presets
|
||||
api.get(`/${currentProjectKey}/collection_presets`, {
|
||||
api.get(`/collection_presets`, {
|
||||
params: {
|
||||
'filter[role][null]': 1,
|
||||
'filter[user][null]': 1,
|
||||
@@ -47,18 +45,14 @@ export const useCollectionPresetsStore = createStore({
|
||||
this.reset();
|
||||
},
|
||||
async create(newPreset: Partial<CollectionPreset>) {
|
||||
const { currentProjectKey } = useProjectsStore().state;
|
||||
|
||||
const response = await api.post(`/${currentProjectKey}/collection_presets`, newPreset);
|
||||
const response = await api.post(`/collection_presets`, newPreset);
|
||||
|
||||
this.state.collectionPresets.push(response.data.data);
|
||||
|
||||
return response.data.data;
|
||||
},
|
||||
async update(id: number, updates: Partial<CollectionPreset>) {
|
||||
const { currentProjectKey } = useProjectsStore().state;
|
||||
|
||||
const response = await api.patch(`/${currentProjectKey}/collection_presets/${id}`, updates);
|
||||
const response = await api.patch(`/collection_presets/${id}`, updates);
|
||||
|
||||
this.state.collectionPresets = this.state.collectionPresets.map((preset) => {
|
||||
const updatedPreset = response.data.data;
|
||||
@@ -72,9 +66,7 @@ export const useCollectionPresetsStore = createStore({
|
||||
return response.data.data;
|
||||
},
|
||||
async delete(id: number) {
|
||||
const { currentProjectKey } = useProjectsStore().state;
|
||||
|
||||
await api.delete(`/${currentProjectKey}/collection_presets/${id}`);
|
||||
await api.delete(`/collection_presets/${id}`);
|
||||
|
||||
this.state.collectionPresets = this.state.collectionPresets.filter((preset) => {
|
||||
return preset.id !== id;
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
import api from '@/api';
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import formatTitle from '@directus/format-title';
|
||||
import i18n from '@/lang';
|
||||
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
import { useCollectionsStore } from './collections';
|
||||
|
||||
jest.mock('@directus/format-title');
|
||||
jest.mock('@/api');
|
||||
jest.mock('@/lang');
|
||||
|
||||
describe('Stores / collections', () => {
|
||||
let req: any = {};
|
||||
|
||||
beforeAll(() => {
|
||||
Vue.config.productionTip = false;
|
||||
Vue.config.devtools = false;
|
||||
Vue.use(VueCompositionAPI);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
req = {};
|
||||
});
|
||||
|
||||
describe('Getters / visibleCollections', () => {
|
||||
it('Filters collections starting with directus_', () => {
|
||||
const collectionsStore = useCollectionsStore(req);
|
||||
collectionsStore.state.collections = [
|
||||
{
|
||||
collection: 'test-1',
|
||||
},
|
||||
{
|
||||
collection: 'test-2',
|
||||
},
|
||||
{
|
||||
collection: 'directus_test',
|
||||
},
|
||||
{
|
||||
collection: 'test-3',
|
||||
},
|
||||
] as any;
|
||||
|
||||
expect(collectionsStore.visibleCollections.value).toEqual([
|
||||
{
|
||||
collection: 'test-1',
|
||||
},
|
||||
{
|
||||
collection: 'test-2',
|
||||
},
|
||||
{
|
||||
collection: 'test-3',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('Filters collections that have the hidden flag true', () => {
|
||||
const collectionsStore = useCollectionsStore(req);
|
||||
collectionsStore.state.collections = [
|
||||
{
|
||||
collection: 'test-1',
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
collection: 'test-2',
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
collection: 'test-3',
|
||||
hidden: null,
|
||||
},
|
||||
] as any;
|
||||
|
||||
expect(collectionsStore.visibleCollections.value).toEqual([
|
||||
{
|
||||
collection: 'test-2',
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
collection: 'test-3',
|
||||
hidden: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Actions / Hydrate', () => {
|
||||
it('Calls the right endpoint', async () => {
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
data: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const projectsStore = useProjectsStore(req);
|
||||
const collectionsStore = useCollectionsStore(req);
|
||||
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
await collectionsStore.hydrate();
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith('/my-project/collections');
|
||||
});
|
||||
|
||||
it('Formats the title to use as name', async () => {
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
collection: 'test_collection',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const projectsStore = useProjectsStore(req);
|
||||
const collectionsStore = useCollectionsStore(req);
|
||||
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
await collectionsStore.hydrate();
|
||||
|
||||
expect(formatTitle).toHaveBeenCalledWith('test_collection');
|
||||
expect(collectionsStore.state.collections[0].hasOwnProperty('name')).toBe(true);
|
||||
});
|
||||
|
||||
it('Registers the passed translations to i18n to be registered', async () => {
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
collection: 'test_collection',
|
||||
translation: [
|
||||
{
|
||||
locale: 'en-US',
|
||||
translation: 'Test collection',
|
||||
},
|
||||
{
|
||||
locale: 'nl-NL',
|
||||
translation: 'Test verzameling',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
|
||||
const collectionsStore = useCollectionsStore(req);
|
||||
await collectionsStore.hydrate();
|
||||
|
||||
expect(i18n.mergeLocaleMessage).toHaveBeenCalledWith('en-US', {
|
||||
collections: {
|
||||
test_collection: 'Test collection',
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.mergeLocaleMessage).toHaveBeenCalledWith('nl-NL', {
|
||||
collections: {
|
||||
test_collection: 'Test verzameling',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Actions/ Dehydrate', () => {
|
||||
it('Calls reset on dehydrate', async () => {
|
||||
const collectionsStore = useCollectionsStore(req);
|
||||
jest.spyOn(collectionsStore, 'reset');
|
||||
await collectionsStore.dehydrate();
|
||||
expect(collectionsStore.reset).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createStore } from 'pinia';
|
||||
import api from '@/api';
|
||||
import { Collection, CollectionRaw } from './types';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
import i18n from '@/lang/';
|
||||
import { notEmpty } from '@/utils/is-empty/';
|
||||
import VueI18n from 'vue-i18n';
|
||||
@@ -22,10 +21,7 @@ export const useCollectionsStore = createStore({
|
||||
},
|
||||
actions: {
|
||||
async hydrate() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
const response = await api.get(`/${currentProjectKey}/collections`);
|
||||
const response = await api.get(`/collections`);
|
||||
|
||||
const collections: CollectionRaw[] = response.data.data;
|
||||
|
||||
@@ -60,10 +56,8 @@ export const useCollectionsStore = createStore({
|
||||
this.reset();
|
||||
},
|
||||
async updateCollection(collection: string, updates: Partial<Collection>) {
|
||||
const { currentProjectKey } = useProjectsStore().state;
|
||||
|
||||
try {
|
||||
await api.patch(`${currentProjectKey}/collections/${collection}`, updates);
|
||||
await api.patch(`/collections/${collection}`, updates);
|
||||
await this.hydrate();
|
||||
notify({
|
||||
type: 'success',
|
||||
@@ -80,10 +74,8 @@ export const useCollectionsStore = createStore({
|
||||
}
|
||||
},
|
||||
async deleteCollection(collection: string) {
|
||||
const { currentProjectKey } = useProjectsStore().state;
|
||||
|
||||
try {
|
||||
await api.delete(`${currentProjectKey}/collections/${collection}`);
|
||||
await api.delete(`/collections/${collection}`);
|
||||
await this.hydrate();
|
||||
notify({
|
||||
type: 'success',
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
import api from '@/api';
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import formatTitle from '@directus/format-title';
|
||||
import i18n from '@/lang';
|
||||
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
import { useFieldsStore } from './fields';
|
||||
|
||||
jest.mock('@directus/format-title');
|
||||
jest.mock('@/api');
|
||||
jest.mock('@/lang');
|
||||
|
||||
describe('Stores / Fields', () => {
|
||||
let req: any = {};
|
||||
|
||||
beforeAll(() => {
|
||||
Vue.config.productionTip = false;
|
||||
Vue.config.devtools = false;
|
||||
Vue.use(VueCompositionAPI);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
req = {};
|
||||
});
|
||||
|
||||
describe('Hydrate', () => {
|
||||
it('Calls the right endpoint', () => {
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
data: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
const fieldsStore = useFieldsStore(req);
|
||||
|
||||
fieldsStore.hydrate().then(() => {
|
||||
expect(api.get).toHaveBeenCalledWith('/my-project/fields');
|
||||
});
|
||||
});
|
||||
|
||||
it('Formats the title to use as name', async () => {
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
field: 'test_field',
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
const fieldsStore = useFieldsStore(req);
|
||||
|
||||
await fieldsStore.hydrate();
|
||||
|
||||
expect(formatTitle).toHaveBeenCalledWith('test_field');
|
||||
expect(fieldsStore.state.fields[0].hasOwnProperty('name')).toBe(true);
|
||||
});
|
||||
|
||||
it('Registers the passed translations to i18n to be registered', async () => {
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
field: 'test_field',
|
||||
translation: [
|
||||
{
|
||||
locale: 'en-US',
|
||||
translation: 'Test field',
|
||||
},
|
||||
{
|
||||
locale: 'nl-NL',
|
||||
translation: 'Test veld',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
const fieldsStore = useFieldsStore(req);
|
||||
|
||||
await fieldsStore.hydrate();
|
||||
|
||||
expect(i18n.mergeLocaleMessage).toHaveBeenCalledWith('en-US', {
|
||||
fields: {
|
||||
test_field: 'Test field',
|
||||
},
|
||||
});
|
||||
|
||||
expect(i18n.mergeLocaleMessage).toHaveBeenCalledWith('nl-NL', {
|
||||
fields: {
|
||||
test_field: 'Test veld',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dehydrate', () => {
|
||||
it('Calls reset on dehydrate', async () => {
|
||||
const fieldsStore: any = useFieldsStore(req);
|
||||
jest.spyOn(fieldsStore, 'reset');
|
||||
await fieldsStore.dehydrate();
|
||||
expect(fieldsStore.reset).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createStore } from 'pinia';
|
||||
import { FieldRaw, Field } from './types';
|
||||
import api from '@/api';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
import VueI18n from 'vue-i18n';
|
||||
import { notEmpty } from '@/utils/is-empty/';
|
||||
import { i18n } from '@/lang';
|
||||
@@ -47,10 +46,7 @@ export const useFieldsStore = createStore({
|
||||
}),
|
||||
actions: {
|
||||
async hydrate() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
const fieldsResponse = await api.get(`/${currentProjectKey}/fields`);
|
||||
const fieldsResponse = await api.get(`/fields`);
|
||||
|
||||
const fields: FieldRaw[] = fieldsResponse.data.data.filter(
|
||||
({ collection }: FieldRaw) => collection !== 'directus_settings'
|
||||
@@ -59,23 +55,7 @@ export const useFieldsStore = createStore({
|
||||
/**
|
||||
* @NOTE
|
||||
*
|
||||
* directus_settings is a bit of a special case. It's actual fields (key / value) are not
|
||||
* what we're looking for here. Instead, we want all the "fake" fields that make up the
|
||||
* form. This extra bit of logic is needed to make sure the app doesn't differentiate
|
||||
* between settings and regular collections.
|
||||
*/
|
||||
|
||||
const settingsResponse = await api.get(`/${currentProjectKey}/settings/fields`, {
|
||||
params: {
|
||||
limit: -1,
|
||||
},
|
||||
});
|
||||
fields.push(...settingsResponse.data.data);
|
||||
|
||||
/**
|
||||
* @NOTE
|
||||
*
|
||||
* directus_fields is another special case. For it to play nice with layouts, we need to
|
||||
* directus_files is a special case. For it to play nice with layouts, we need to
|
||||
* treat the actual image as a separate available field, instead of part of the regular
|
||||
* item (normally all file related info is nested within a separate column). This allows
|
||||
* layouts to render out files as it if were a "normal" collection, where the actual file
|
||||
@@ -112,9 +92,6 @@ export const useFieldsStore = createStore({
|
||||
};
|
||||
},
|
||||
async createField(collectionKey: string, newField: Field) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
const stateClone = [...this.state.fields];
|
||||
|
||||
// Update locally first, so the changes are visible immediately
|
||||
@@ -123,7 +100,7 @@ export const useFieldsStore = createStore({
|
||||
// Save to API, and update local state again to make sure everything is in sync with the
|
||||
// API
|
||||
try {
|
||||
const response = await api.post(`/${currentProjectKey}/fields/${collectionKey}`, newField);
|
||||
const response = await api.post(`/fields/${collectionKey}`, newField);
|
||||
|
||||
this.state.fields = this.state.fields.map((field) => {
|
||||
if (field.collection === collectionKey && field.field === newField.field) {
|
||||
@@ -149,9 +126,6 @@ export const useFieldsStore = createStore({
|
||||
}
|
||||
},
|
||||
async updateField(collectionKey: string, fieldKey: string, updates: Record<string, Partial<Field>>) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
const stateClone = [...this.state.fields];
|
||||
|
||||
// Update locally first, so the changes are visible immediately
|
||||
@@ -169,7 +143,7 @@ export const useFieldsStore = createStore({
|
||||
// Save to API, and update local state again to make sure everything is in sync with the
|
||||
// API
|
||||
try {
|
||||
const response = await api.patch(`/${currentProjectKey}/fields/${collectionKey}/${fieldKey}`, updates);
|
||||
const response = await api.patch(`/fields/${collectionKey}/${fieldKey}`, updates);
|
||||
|
||||
this.state.fields = this.state.fields.map((field) => {
|
||||
if (field.collection === collectionKey && field.field === fieldKey) {
|
||||
@@ -195,8 +169,6 @@ export const useFieldsStore = createStore({
|
||||
}
|
||||
},
|
||||
async updateFields(collectionKey: string, updates: Partial<Field>[]) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
const stateClone = [...this.state.fields];
|
||||
|
||||
// Update locally first, so the changes are visible immediately
|
||||
@@ -218,7 +190,7 @@ export const useFieldsStore = createStore({
|
||||
try {
|
||||
// Save to API, and update local state again to make sure everything is in sync with the
|
||||
// API
|
||||
const response = await api.patch(`/${currentProjectKey}/fields/${collectionKey}`, updates);
|
||||
const response = await api.patch(`/fields/${collectionKey}`, updates);
|
||||
|
||||
this.state.fields = this.state.fields.map((field) => {
|
||||
if (field.collection === collectionKey) {
|
||||
@@ -248,8 +220,6 @@ export const useFieldsStore = createStore({
|
||||
}
|
||||
},
|
||||
async deleteField(collectionKey: string, fieldKey: string) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
const stateClone = [...this.state.fields];
|
||||
|
||||
this.state.fields = this.state.fields.filter((field) => {
|
||||
@@ -258,7 +228,7 @@ export const useFieldsStore = createStore({
|
||||
});
|
||||
|
||||
try {
|
||||
await api.delete(`/${currentProjectKey}/fields/${collectionKey}/${fieldKey}`);
|
||||
await api.delete(`/fields/${collectionKey}/${fieldKey}`);
|
||||
|
||||
notify({
|
||||
title: i18n.t('field_delete_success', { field: fieldKey }),
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import { usePermissionsStore } from './permissions';
|
||||
|
||||
export { usePermissionsStore };
|
||||
export default usePermissionsStore;
|
||||
@@ -1,64 +0,0 @@
|
||||
import { createStore } from 'pinia';
|
||||
import useUserStore from '@/stores/user';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import { Permission } from './types';
|
||||
|
||||
const defaultAdminPermission: Partial<Permission> = {
|
||||
role: 1,
|
||||
create: 'full',
|
||||
read: 'full',
|
||||
update: 'full',
|
||||
delete: 'full',
|
||||
comment: 'full',
|
||||
explain: 'none',
|
||||
read_field_blacklist: [],
|
||||
write_field_blacklist: [],
|
||||
status_blacklist: [],
|
||||
};
|
||||
|
||||
const defaultPermission: Partial<Permission> = {
|
||||
create: 'none',
|
||||
read: 'none',
|
||||
update: 'none',
|
||||
delete: 'none',
|
||||
comment: 'none',
|
||||
explain: 'none',
|
||||
read_field_blacklist: [],
|
||||
write_field_blacklist: [],
|
||||
status_blacklist: [],
|
||||
};
|
||||
|
||||
export const usePermissionsStore = createStore({
|
||||
id: 'permissionsStore',
|
||||
state: () => ({
|
||||
permissions: [] as Permission[],
|
||||
}),
|
||||
actions: {
|
||||
getPermissionsForCollection(collection: string) {
|
||||
const userStore = useUserStore();
|
||||
|
||||
if (userStore.isAdmin.value) return defaultAdminPermission;
|
||||
|
||||
const permissions = this.state.permissions.filter((permission) => permission.collection === collection);
|
||||
|
||||
return permissions ? permissions : defaultPermission;
|
||||
},
|
||||
async hydrate() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
if (userStore.isAdmin.value) return;
|
||||
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
const permissionsResponse = await api.get(`/${currentProjectKey}/permissions`);
|
||||
|
||||
this.state.permissions = permissionsResponse.data.data;
|
||||
},
|
||||
async dehydrate() {
|
||||
this.reset();
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
export type Permission = {
|
||||
id: number;
|
||||
collection: string;
|
||||
role: number;
|
||||
status: string | null;
|
||||
create: 'full' | 'none';
|
||||
read: 'none' | 'mine' | 'role' | 'full';
|
||||
update: 'none' | 'mine' | 'role' | 'full';
|
||||
delete: 'none' | 'mine' | 'role' | 'full';
|
||||
comment: 'none' | 'read' | 'create' | 'update' | 'full';
|
||||
explain: 'none' | 'create' | 'update' | 'always';
|
||||
read_field_blacklist: string[];
|
||||
write_field_blacklist: string[];
|
||||
status_blacklist: string[];
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
import { useProjectsStore } from './projects';
|
||||
|
||||
export { useProjectsStore };
|
||||
export default useProjectsStore;
|
||||
@@ -1,207 +0,0 @@
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import { useProjectsStore } from './projects';
|
||||
|
||||
describe('Stores / Projects', () => {
|
||||
beforeAll(() => {
|
||||
Vue.use(VueCompositionAPI);
|
||||
});
|
||||
|
||||
describe('Actions / setCurrentProject', () => {
|
||||
it('Sets the currentProjectKey state to the passed key if project exists', async () => {
|
||||
const spy = jest.spyOn(api, 'get');
|
||||
|
||||
const projectsStore = useProjectsStore({});
|
||||
(projectsStore.state.projects as any) = [{ key: 'my-project' }];
|
||||
await projectsStore.setCurrentProject('my-project');
|
||||
expect(projectsStore.state.currentProjectKey).toBe('my-project');
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Retrieves project information for given project key', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
const spy = jest.spyOn(api, 'get').mockImplementation(() => Promise.resolve({ data: { data: {} } }));
|
||||
await projectsStore.setCurrentProject('my-project');
|
||||
expect(spy).toHaveBeenCalledWith('/my-project/');
|
||||
expect(projectsStore.state.currentProjectKey).toBe('my-project');
|
||||
});
|
||||
|
||||
it('Adds the returned project info the the state ', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
const spy = jest.spyOn(api, 'get').mockImplementation(() => Promise.resolve({ data: { data: {} } }));
|
||||
await projectsStore.setCurrentProject('my-project');
|
||||
expect(spy).toHaveBeenCalledWith('/my-project/');
|
||||
expect(projectsStore.state.projects?.[0]).toEqual({ key: 'my-project' });
|
||||
});
|
||||
|
||||
it('Returns true if the project exists', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
jest.spyOn(api, 'get').mockImplementation(() => Promise.resolve({ data: { data: {} } }));
|
||||
const result = await projectsStore.setCurrentProject('my-project');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('Returns false if the project does not exist', async () => {
|
||||
const projectsStore = useProjectsStore({});
|
||||
jest.spyOn(api, 'get').mockImplementation(() => Promise.reject());
|
||||
const result = await projectsStore.setCurrentProject('my-project');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Actions / getProjects', () => {
|
||||
it('Fetches the project info for each individual project', async () => {
|
||||
const spy = jest.spyOn(api, 'get');
|
||||
|
||||
spy.mockImplementation((path: string) => {
|
||||
switch (path) {
|
||||
case '/server/projects':
|
||||
return Promise.resolve({
|
||||
data: { data: ['my-project', 'another-project'] },
|
||||
});
|
||||
case '/my-project/':
|
||||
case '/another-project/':
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
data: {},
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
const projectsStore = useProjectsStore({});
|
||||
await projectsStore.getProjects();
|
||||
|
||||
expect(spy).toHaveBeenCalledWith('/server/projects');
|
||||
expect(spy).toHaveBeenCalledWith('/my-project/');
|
||||
expect(spy).toHaveBeenCalledWith('/another-project/');
|
||||
|
||||
expect(projectsStore.state.error).toBe(null);
|
||||
expect(projectsStore.state.projects).toEqual([
|
||||
{ key: 'my-project', authenticated: false },
|
||||
{ key: 'another-project', authenticated: false },
|
||||
]);
|
||||
});
|
||||
|
||||
it('Sets the error state if the API errors out while fetching project keys', async () => {
|
||||
const spy = jest.spyOn(api, 'get');
|
||||
|
||||
spy.mockImplementation((path: string) => {
|
||||
switch (path) {
|
||||
case '/server/projects':
|
||||
return Promise.reject({ response: { status: 500 }, message: 'Error' });
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
const projectsStore = useProjectsStore({});
|
||||
await projectsStore.getProjects();
|
||||
|
||||
expect(projectsStore.state.error).toEqual({ status: 500, error: 'Error' });
|
||||
});
|
||||
|
||||
it('Sets the needsInstall boolean to true if the API returns a 503 error on projects retrieving', async () => {
|
||||
const spy = jest.spyOn(api, 'get');
|
||||
|
||||
spy.mockImplementation((path: string) => {
|
||||
switch (path) {
|
||||
case '/server/projects':
|
||||
return Promise.reject({ response: { status: 503 } });
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
const projectsStore = useProjectsStore({});
|
||||
await projectsStore.getProjects();
|
||||
|
||||
expect(projectsStore.state.error).toBe(null);
|
||||
expect(projectsStore.state.needsInstall).toBe(true);
|
||||
});
|
||||
|
||||
it('Adds an error key to the individual project if one of them fails', async () => {
|
||||
const spy = jest.spyOn(api, 'get');
|
||||
|
||||
spy.mockImplementation((path: string) => {
|
||||
switch (path) {
|
||||
case '/server/projects':
|
||||
return Promise.resolve({
|
||||
data: { data: ['my-project', 'another-project'] },
|
||||
});
|
||||
case '/my-project/':
|
||||
return Promise.resolve({ data: {} });
|
||||
case '/another-project/':
|
||||
return Promise.reject({
|
||||
response: {
|
||||
status: 500,
|
||||
data: {
|
||||
error: {
|
||||
code: 10,
|
||||
message: 'error message',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
const projectsStore = useProjectsStore({});
|
||||
await projectsStore.getProjects();
|
||||
|
||||
expect(projectsStore.state.projects).toEqual([
|
||||
{ key: 'my-project', authenticated: false },
|
||||
{
|
||||
key: 'another-project',
|
||||
error: {
|
||||
code: 10,
|
||||
message: 'error message',
|
||||
},
|
||||
status: 500,
|
||||
authenticated: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('Uses the error message of the request if API did not return any data', async () => {
|
||||
const spy = jest.spyOn(api, 'get');
|
||||
|
||||
spy.mockImplementation((path: string) => {
|
||||
switch (path) {
|
||||
case '/server/projects':
|
||||
return Promise.resolve({
|
||||
data: { data: ['my-project', 'another-project'] },
|
||||
});
|
||||
case '/my-project/':
|
||||
return Promise.resolve({ data: {} });
|
||||
case '/another-project/':
|
||||
return Promise.reject({
|
||||
message: 'Error fallback',
|
||||
response: {
|
||||
status: 500,
|
||||
data: {},
|
||||
},
|
||||
});
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
const projectsStore = useProjectsStore({});
|
||||
await projectsStore.getProjects();
|
||||
|
||||
expect(projectsStore.state.projects).toEqual([
|
||||
{ key: 'my-project', authenticated: false },
|
||||
{
|
||||
key: 'another-project',
|
||||
error: {
|
||||
message: 'Error fallback',
|
||||
code: null,
|
||||
},
|
||||
status: 500,
|
||||
authenticated: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,126 +0,0 @@
|
||||
import { createStore } from 'pinia';
|
||||
import { ProjectWithKey } from './types';
|
||||
import api from '@/api';
|
||||
|
||||
type LoadingError = null | {
|
||||
status: number;
|
||||
error: string;
|
||||
};
|
||||
|
||||
export const useProjectsStore = createStore({
|
||||
id: 'projectsStore',
|
||||
state: () => ({
|
||||
needsInstall: false,
|
||||
error: null as LoadingError,
|
||||
projects: null as ProjectWithKey[] | null,
|
||||
currentProjectKey: null as string | null,
|
||||
}),
|
||||
getters: {
|
||||
formatted: (state) => {
|
||||
return state.projects?.map((project) => {
|
||||
return {
|
||||
key: project.key,
|
||||
authenticated: project?.authenticated || false,
|
||||
name: project?.api?.project_name || null,
|
||||
error: project?.error || null,
|
||||
foregroundImage: project?.api?.project_foreground?.asset_url || null,
|
||||
backgroundImage: project?.api?.project_background?.asset_url || null,
|
||||
logo: project?.api?.project_logo?.asset_url || null,
|
||||
color: project?.api?.project_color || null,
|
||||
note: project?.api?.project_public_note || null,
|
||||
sso: project?.api?.sso || [],
|
||||
server: project?.server,
|
||||
};
|
||||
});
|
||||
},
|
||||
currentProject: (state, getters) => {
|
||||
return getters.formatted.value?.find(({ key }: { key: string }) => key === state.currentProjectKey) || null;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* Sets the current project to the one with the provided key. If the project with the key
|
||||
* doesn't exist in the public projects, try fetching the project information. If that
|
||||
* succeeds, we'll use the private project as the current project, and add it to the pool
|
||||
* of available projects.
|
||||
* Returns a boolean if the operation succeeded or not.
|
||||
*/
|
||||
async setCurrentProject(key: string): Promise<boolean> {
|
||||
const projects = this.state.projects || ([] as ProjectWithKey[]);
|
||||
const projectKeys = projects.map((project) => project.key);
|
||||
|
||||
if (projectKeys.includes(key) === false) {
|
||||
try {
|
||||
const projectInfoResponse = await api.get(`/${key}/`);
|
||||
const project: ProjectWithKey = {
|
||||
key: key,
|
||||
...projectInfoResponse.data.data,
|
||||
};
|
||||
this.state.projects = [...projects, project];
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.state.currentProjectKey = key;
|
||||
return true;
|
||||
},
|
||||
|
||||
// Even though the projects are fetched on first load, we have to refresh them to make sure
|
||||
// we have the updated server information for the current project. It also gives us a chance
|
||||
// to update the authenticated state, for smoother project switching in the private view
|
||||
async hydrate() {
|
||||
await this.getProjects();
|
||||
},
|
||||
|
||||
// This is the only store that's supposed to load data on dehydration. By re-fetching the
|
||||
// projects, we make sure the login views and authenticated states will be up to date. It
|
||||
// also ensures that the potentially private server info is purged from the store.
|
||||
async dehydrate() {
|
||||
await this.getProjects();
|
||||
},
|
||||
|
||||
async getProjects() {
|
||||
try {
|
||||
const projectsResponse = await api.get('/server/projects');
|
||||
const projectKeys: string[] = projectsResponse.data.data;
|
||||
const projects: ProjectWithKey[] = [];
|
||||
|
||||
for (let index = 0; index < projectKeys.length; index++) {
|
||||
try {
|
||||
const projectInfoResponse = await api.get(`/${projectKeys[index]}/`);
|
||||
projects.push({
|
||||
key: projectKeys[index],
|
||||
...projectInfoResponse.data.data,
|
||||
authenticated: projectInfoResponse?.data?.data?.hasOwnProperty('server') || false,
|
||||
});
|
||||
} catch (error) {
|
||||
/* istanbul ignore next */
|
||||
projects.push({
|
||||
key: projectKeys[index],
|
||||
status: error.response?.status,
|
||||
error: {
|
||||
message: error.response?.data?.error?.message ?? error.message,
|
||||
code: error.response?.data?.error?.code || null,
|
||||
},
|
||||
authenticated: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.state.projects = projects;
|
||||
} catch (error) {
|
||||
/* istanbul ignore next */
|
||||
if (error.response?.status === 503) {
|
||||
this.state.needsInstall = true;
|
||||
} else {
|
||||
this.state.error = {
|
||||
/* istanbul ignore next */
|
||||
status: error.response?.status,
|
||||
error: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,55 +0,0 @@
|
||||
/**
|
||||
* @NOTE
|
||||
*
|
||||
* api, server don't exist when the project errored out.
|
||||
* status, error don't exist for working projects.
|
||||
*/
|
||||
|
||||
export interface Project {
|
||||
api?: {
|
||||
version?: string;
|
||||
database?: string;
|
||||
requires2FA: boolean;
|
||||
project_color: string;
|
||||
project_logo: {
|
||||
full_url: string;
|
||||
asset_url: string;
|
||||
url: string;
|
||||
} | null;
|
||||
project_foreground: {
|
||||
full_url: string;
|
||||
asset_url: string;
|
||||
url: string;
|
||||
} | null;
|
||||
project_background: {
|
||||
full_url: string;
|
||||
asset_url: string;
|
||||
url: string;
|
||||
} | null;
|
||||
project_public_note: string | null;
|
||||
default_locale: string;
|
||||
telemetry: boolean;
|
||||
project_name: string;
|
||||
sso: {
|
||||
name: string;
|
||||
icon: string;
|
||||
}[];
|
||||
};
|
||||
server?: {
|
||||
max_upload_size: number;
|
||||
general: {
|
||||
php_version: string;
|
||||
php_api: string;
|
||||
};
|
||||
};
|
||||
status?: number;
|
||||
error?: {
|
||||
code: number;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ProjectWithKey extends Project {
|
||||
key: string;
|
||||
authenticated: boolean;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createStore } from 'pinia';
|
||||
import { Relation } from './types';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import api from '@/api';
|
||||
import useFieldsStore from '@/stores/fields';
|
||||
|
||||
@@ -11,10 +10,7 @@ export const useRelationsStore = createStore({
|
||||
}),
|
||||
actions: {
|
||||
async hydrate() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
const response = await api.get(`/${currentProjectKey}/relations`);
|
||||
const response = await api.get(`/relations`);
|
||||
|
||||
this.state.relations = response.data.data;
|
||||
},
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import { useSettingsStore } from './settings';
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
import api from '@/api';
|
||||
|
||||
jest.mock('@/api');
|
||||
|
||||
describe('Stores / Settings', () => {
|
||||
let req = {};
|
||||
|
||||
beforeAll(() => {
|
||||
Vue.use(VueCompositionAPI);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
req = {};
|
||||
});
|
||||
|
||||
it('Fetches the settings on hydrate', async () => {
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
|
||||
const settingsStore = useSettingsStore(req);
|
||||
|
||||
(api.get as jest.Mock).mockImplementation(() => Promise.resolve({ data: { data: [] } }));
|
||||
|
||||
await settingsStore.hydrate();
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith('/my-project/settings', { params: { limit: -1 } });
|
||||
});
|
||||
|
||||
it('Calls reset on dehydrate', async () => {
|
||||
const settingsStore = useSettingsStore(req);
|
||||
jest.spyOn(settingsStore, 'reset');
|
||||
|
||||
await settingsStore.dehydrate();
|
||||
|
||||
expect(settingsStore.reset).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import { createStore } from 'pinia';
|
||||
import { Setting } from './types';
|
||||
import { keyBy, mapValues } from 'lodash';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
//
|
||||
import notify from '@/utils/notify';
|
||||
import { i18n } from '@/lang';
|
||||
|
||||
@@ -17,99 +17,69 @@ import { i18n } from '@/lang';
|
||||
export const useSettingsStore = createStore({
|
||||
id: 'settingsStore',
|
||||
state: () => ({
|
||||
settings: [] as Setting[],
|
||||
settings: null as null | Record<string, any>,
|
||||
}),
|
||||
actions: {
|
||||
async hydrate() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
const response = await api.get(`/${currentProjectKey}/settings`, {
|
||||
params: {
|
||||
limit: -1,
|
||||
},
|
||||
});
|
||||
|
||||
const response = await api.get(`/settings`, { params: { fields: ['*.*'] } });
|
||||
this.state.settings = response.data.data;
|
||||
},
|
||||
|
||||
async dehydrate() {
|
||||
this.reset();
|
||||
},
|
||||
|
||||
async updateSettings(updates: { [key: string]: any }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
const settingsCopy = [...this.state.settings];
|
||||
|
||||
const settingsToBeSaved = Object.keys(updates).map((key) => {
|
||||
const existing = this.state.settings.find((setting) => setting.key === key);
|
||||
|
||||
if (existing === undefined) {
|
||||
throw new Error(`Setting with key '${key}' doesn't exist.`);
|
||||
}
|
||||
|
||||
const { id } = existing;
|
||||
|
||||
return {
|
||||
id: id,
|
||||
value: updates[key],
|
||||
};
|
||||
});
|
||||
|
||||
this.state.settings = this.state.settings.map((existingSetting) => {
|
||||
const updated = settingsToBeSaved.find((update) => update.id === existingSetting.id);
|
||||
|
||||
if (updated !== undefined) {
|
||||
return {
|
||||
...existingSetting,
|
||||
value: updated.value,
|
||||
};
|
||||
}
|
||||
|
||||
return existingSetting;
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await api.patch(`/${currentProjectKey}/settings`, settingsToBeSaved);
|
||||
|
||||
this.state.settings = this.state.settings.map((setting) => {
|
||||
const updated = response.data.data.find((update: any) => update.id === setting.id);
|
||||
|
||||
if (updated !== undefined) {
|
||||
return {
|
||||
...setting,
|
||||
value: updated.value,
|
||||
};
|
||||
}
|
||||
|
||||
return setting;
|
||||
});
|
||||
|
||||
notify({
|
||||
title: i18n.t('settings_update_success'),
|
||||
text: Object.keys(updates).join(', '),
|
||||
type: 'success',
|
||||
});
|
||||
|
||||
this.updateProjectsStore();
|
||||
} catch (error) {
|
||||
this.state.settings = settingsCopy;
|
||||
|
||||
notify({
|
||||
title: i18n.t('settings_update_failed'),
|
||||
text: Object.keys(updates).join(', '),
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
async updateProjectsStore() {
|
||||
const projectsStore = useProjectsStore();
|
||||
await projectsStore.getProjects();
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
formatted(state) {
|
||||
return mapValues(keyBy(state.settings, 'key'), 'value');
|
||||
//
|
||||
//
|
||||
// const settingsCopy = [...this.state.settings];
|
||||
// const settingsToBeSaved = Object.keys(updates).map((key) => {
|
||||
// const existing = this.state.settings.find((setting) => setting.key === key);
|
||||
// if (existing === undefined) {
|
||||
// throw new Error(`Setting with key '${key}' doesn't exist.`);
|
||||
// }
|
||||
// const { id } = existing;
|
||||
// return {
|
||||
// id: id,
|
||||
// value: updates[key],
|
||||
// };
|
||||
// });
|
||||
// this.state.settings = this.state.settings.map((existingSetting) => {
|
||||
// const updated = settingsToBeSaved.find((update) => update.id === existingSetting.id);
|
||||
// if (updated !== undefined) {
|
||||
// return {
|
||||
// ...existingSetting,
|
||||
// value: updated.value,
|
||||
// };
|
||||
// }
|
||||
// return existingSetting;
|
||||
// });
|
||||
// try {
|
||||
// const response = await api.patch(`/settings`, settingsToBeSaved);
|
||||
// this.state.settings = this.state.settings.map((setting) => {
|
||||
// const updated = response.data.data.find((update: any) => update.id === setting.id);
|
||||
// if (updated !== undefined) {
|
||||
// return {
|
||||
// ...setting,
|
||||
// value: updated.value,
|
||||
// };
|
||||
// }
|
||||
// return setting;
|
||||
// });
|
||||
// notify({
|
||||
// title: i18n.t('settings_update_success'),
|
||||
// text: Object.keys(updates).join(', '),
|
||||
// type: 'success',
|
||||
// });
|
||||
// this.updateProjectsStore();
|
||||
// } catch (error) {
|
||||
// this.state.settings = settingsCopy;
|
||||
// notify({
|
||||
// title: i18n.t('settings_update_failed'),
|
||||
// text: Object.keys(updates).join(', '),
|
||||
// type: 'error',
|
||||
// });
|
||||
// }
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import api from '@/api';
|
||||
import Vue from 'vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
import { useUserStore } from './user';
|
||||
|
||||
jest.mock('@directus/format-title');
|
||||
jest.mock('@/api');
|
||||
jest.mock('@/lang');
|
||||
|
||||
describe('Stores / User', () => {
|
||||
let req: any = {};
|
||||
|
||||
beforeAll(() => {
|
||||
Vue.config.productionTip = false;
|
||||
Vue.config.devtools = false;
|
||||
Vue.use(VueCompositionAPI);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
req = {};
|
||||
});
|
||||
|
||||
describe('Hydrate', () => {
|
||||
it('Calls the right endpoint', async () => {
|
||||
(api.get as jest.Mock).mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
data: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
const userStore = useUserStore(req);
|
||||
|
||||
userStore.hydrate();
|
||||
|
||||
expect(api.get).toHaveBeenCalledWith('/my-project/users/me', {
|
||||
params: {
|
||||
fields: '*,avatar.data,role.*',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dehydrate', () => {
|
||||
it('Calls reset on dehydrate', async () => {
|
||||
const userStore: any = useUserStore(req);
|
||||
jest.spyOn(userStore, 'reset');
|
||||
await userStore.dehydrate();
|
||||
expect(userStore.reset).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Track Page', () => {
|
||||
it('Calls the right endpoint on tracking', async () => {
|
||||
const userStore = useUserStore(req);
|
||||
const projectsStore = useProjectsStore(req);
|
||||
|
||||
userStore.state.currentUser = {
|
||||
id: 5,
|
||||
last_page: 'test',
|
||||
} as any;
|
||||
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
|
||||
await userStore.trackPage('/example');
|
||||
|
||||
expect(api.patch).toHaveBeenCalledWith('/my-project/users/me/tracking/page', {
|
||||
last_page: '/example',
|
||||
});
|
||||
});
|
||||
|
||||
it('Updates the store with the new last page', async () => {
|
||||
const userStore = useUserStore(req);
|
||||
const projectsStore = useProjectsStore(req);
|
||||
|
||||
userStore.state.currentUser = {
|
||||
id: 5,
|
||||
last_page: 'test',
|
||||
} as any;
|
||||
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
|
||||
await userStore.trackPage('/example');
|
||||
|
||||
expect(userStore.state.currentUser!.last_page).toBe('/example');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createStore } from 'pinia';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
import api from '@/api';
|
||||
import { useLatencyStore } from '@/stores/latency';
|
||||
|
||||
@@ -18,20 +17,18 @@ export const useUserStore = createStore({
|
||||
return state.currentUser.first_name + ' ' + state.currentUser.last_name;
|
||||
},
|
||||
isAdmin(state) {
|
||||
/** @todo base this on UUID */
|
||||
return state.currentUser?.role.id === 1;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async hydrate() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
this.state.loading = true;
|
||||
|
||||
try {
|
||||
const { data } = await api.get(`/${currentProjectKey}/users/me`, {
|
||||
const { data } = await api.get(`/users/me`, {
|
||||
params: {
|
||||
fields: '*,avatar.data,role.*',
|
||||
fields: '*,avatar.*,role.*',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -46,17 +43,15 @@ export const useUserStore = createStore({
|
||||
this.reset();
|
||||
},
|
||||
async trackPage(page: string) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const latencyStore = useLatencyStore();
|
||||
const currentProjectKey = projectsStore.state.currentProjectKey;
|
||||
|
||||
const start = Date.now();
|
||||
const start = performance.now();
|
||||
|
||||
await api.patch(`/${currentProjectKey}/users/me/tracking/page`, {
|
||||
await api.patch(`/users/me`, {
|
||||
last_page: page,
|
||||
});
|
||||
|
||||
const end = Date.now();
|
||||
const end = performance.now();
|
||||
|
||||
latencyStore.state.latency.push({
|
||||
timestamp: new Date(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import notify from '@/utils/notify';
|
||||
import i18n from '@/lang';
|
||||
|
||||
@@ -9,12 +9,11 @@ export default async function uploadFile(
|
||||
showNotifications = true
|
||||
) {
|
||||
const progressHandler = onProgressChange || (() => undefined);
|
||||
const currentProjectKey = useProjectsStore().state.currentProjectKey;
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const response = await api.post(`/${currentProjectKey}/files`, formData, {
|
||||
const response = await api.post(`/files`, formData, {
|
||||
onUploadProgress,
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, PropType } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import notify from '@/utils/notify';
|
||||
import api from '@/api';
|
||||
import i18n from '@/lang';
|
||||
@@ -39,8 +39,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const newCommentContent = ref(null);
|
||||
const saving = ref(false);
|
||||
|
||||
@@ -50,7 +48,7 @@ export default defineComponent({
|
||||
saving.value = true;
|
||||
|
||||
try {
|
||||
await api.post(`/${projectsStore.state.currentProjectKey}/activity/comment`, {
|
||||
await api.post(`/activity/comment`, {
|
||||
collection: props.collection,
|
||||
item: props.primaryKey,
|
||||
comment: newCommentContent.value,
|
||||
|
||||
@@ -69,7 +69,7 @@ import { defineComponent, PropType, computed, ref, watch } from '@vue/compositio
|
||||
import { Activity } from './types';
|
||||
import format from 'date-fns/format';
|
||||
import i18n from '@/lang';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import api from '@/api';
|
||||
import localizedFormat from '@/utils/localized-format';
|
||||
|
||||
@@ -99,8 +99,6 @@ export default defineComponent({
|
||||
}
|
||||
);
|
||||
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const formattedTime = computed(() => {
|
||||
if (props.activity.action_on) {
|
||||
// action_on is in iso-8601
|
||||
@@ -124,8 +122,6 @@ export default defineComponent({
|
||||
return { formattedTime, avatarSource, confirmDelete, deleting, remove, editedOnFormatted };
|
||||
|
||||
function useDelete() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
const confirmDelete = ref(false);
|
||||
const deleting = ref(false);
|
||||
|
||||
@@ -135,7 +131,7 @@ export default defineComponent({
|
||||
deleting.value = true;
|
||||
|
||||
try {
|
||||
await api.delete(`/${currentProjectKey}/activity/comment/${props.activity.id}`);
|
||||
await api.delete(`/activity/comment/${props.activity.id}`);
|
||||
await props.refresh();
|
||||
confirmDelete.value = false;
|
||||
} catch (error) {
|
||||
|
||||
@@ -32,7 +32,7 @@ import { defineComponent, PropType, ref, computed, watch } from '@vue/compositio
|
||||
import { Activity } from './types';
|
||||
import CommentItemHeader from './comment-item-header.vue';
|
||||
import marked from 'marked';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import api from '@/api';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -48,8 +48,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const htmlContent = computed(() => (props.activity.comment ? marked(props.activity.comment) : null));
|
||||
|
||||
const { edits, editing, savingEdits, saveEdits, cancelEditing } = useEdits();
|
||||
@@ -69,11 +67,10 @@ export default defineComponent({
|
||||
return { edits, editing, savingEdits, saveEdits, cancelEditing };
|
||||
|
||||
async function saveEdits() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
savingEdits.value = true;
|
||||
|
||||
try {
|
||||
await api.patch(`/${currentProjectKey}/activity/comment/${props.activity.id}`, {
|
||||
await api.patch(`/activity/comment/${props.activity.id}`, {
|
||||
comment: edits.value,
|
||||
});
|
||||
await props.refresh();
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import api from '@/api';
|
||||
import { Activity, ActivityByDate } from './types';
|
||||
import CommentInput from './comment-input.vue';
|
||||
@@ -49,8 +49,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const { activity, loading, error, refresh } = useActivity(props.collection, props.primaryKey);
|
||||
|
||||
return {
|
||||
@@ -74,7 +72,7 @@ export default defineComponent({
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${projectsStore.state.currentProjectKey}/activity`, {
|
||||
const response = await api.get(`/activity`, {
|
||||
params: {
|
||||
'filter[collection][eq]': collection,
|
||||
'filter[item][eq]': primaryKey,
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, computed } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import FilePreview from '@/views/private/components/file-preview';
|
||||
|
||||
@@ -57,8 +57,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const localActive = ref(false);
|
||||
|
||||
const _active = computed({
|
||||
@@ -89,13 +87,12 @@ export default defineComponent({
|
||||
return { _active, cacheBuster, loading, error, file };
|
||||
|
||||
async function fetchFile() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
cacheBuster.value = nanoid();
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/files/${props.id}`, {
|
||||
const response = await api.get(`/files/${props.id}`, {
|
||||
params: {
|
||||
fields: ['data', 'type', 'width', 'height', 'title'],
|
||||
},
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, computed, reactive } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import Cropper from 'cropperjs';
|
||||
import { nanoid } from 'nanoid';
|
||||
import throttle from 'lodash/throttle';
|
||||
@@ -155,8 +155,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const localActive = ref(false);
|
||||
|
||||
const _active = computed({
|
||||
@@ -240,11 +238,9 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
async function fetchImage() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
const response = await api.get(`/${currentProjectKey}/files/${props.id}`, {
|
||||
const response = await api.get(`/files/${props.id}`, {
|
||||
params: {
|
||||
fields: ['data', 'type', 'filesize', 'filename_download', 'width', 'height'],
|
||||
},
|
||||
@@ -259,7 +255,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function save() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
saving.value = true;
|
||||
|
||||
cropperInstance.value
|
||||
@@ -276,7 +271,7 @@ export default defineComponent({
|
||||
formData.append('file', blob, imageData.value?.filename_download);
|
||||
|
||||
try {
|
||||
await api.post(`/${currentProjectKey}/files/${props.id}`, formData);
|
||||
await api.post(`/files/${props.id}`, formData);
|
||||
emit('refresh');
|
||||
_active.value = false;
|
||||
} catch (err) {
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, PropType, watch, toRefs } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import useFieldsStore from '@/stores/fields';
|
||||
import i18n from '@/lang';
|
||||
@@ -71,7 +71,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const relationsStore = useRelationsStore();
|
||||
|
||||
@@ -163,13 +162,11 @@ export default defineComponent({
|
||||
return { _edits, loading, error, item, fetchItem };
|
||||
|
||||
async function fetchItem() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
const endpoint = props.collection.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${props.collection.substring(9)}/${props.primaryKey}`
|
||||
: `/${currentProjectKey}/items/${props.collection}/${props.primaryKey}`;
|
||||
? `/${props.collection.substring(9)}/${props.primaryKey}`
|
||||
: `/items/${props.collection}/${props.primaryKey}`;
|
||||
|
||||
let fields = '*';
|
||||
|
||||
@@ -189,15 +186,13 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
async function fetchRelatedItem() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
const collection = junctionRelatedCollection.value;
|
||||
|
||||
const endpoint = collection.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${collection.substring(9)}/${props.relatedPrimaryKey}`
|
||||
: `/${currentProjectKey}/items/${collection}/${props.relatedPrimaryKey}`;
|
||||
? `/${collection.substring(9)}/${props.relatedPrimaryKey}`
|
||||
: `/items/${collection}/${props.relatedPrimaryKey}`;
|
||||
|
||||
try {
|
||||
const response = await api.get(endpoint);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import markdown from './readme.md';
|
||||
import withPadding from '../../../../../.storybook/decorators/with-padding';
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import useUserStore from '@/stores/user';
|
||||
import ModuleBarAvatar from './module-bar-avatar.vue';
|
||||
import { i18n } from '@/lang/';
|
||||
@@ -22,10 +22,8 @@ export const basic = () =>
|
||||
components: { ModuleBarAvatar },
|
||||
setup() {
|
||||
const req = {};
|
||||
const projectsStore = useProjectsStore(req);
|
||||
const userStore = useUserStore(req);
|
||||
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
userStore.state.currentUser = {
|
||||
first_name: 'Admin',
|
||||
last_name: 'User',
|
||||
@@ -46,10 +44,9 @@ export const withAvatar = () =>
|
||||
components: { ModuleBarAvatar },
|
||||
setup() {
|
||||
const req = {};
|
||||
const projectsStore = useProjectsStore(req);
|
||||
|
||||
const userStore = useUserStore(req);
|
||||
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
userStore.state.currentUser = {
|
||||
first_name: 'Admin',
|
||||
last_name: 'User',
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import { shallowMount, createLocalVue } from '@vue/test-utils';
|
||||
import useUserStore from '@/stores/user';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import ModuleBarAvatar from './module-bar-avatar.vue';
|
||||
import VueCompositionAPI from '@vue/composition-api';
|
||||
import { i18n } from '@/lang/';
|
||||
import VueRouter from 'vue-router';
|
||||
|
||||
import VIcon from '@/components/v-icon';
|
||||
import VButton from '@/components/v-button';
|
||||
import VAvatar from '@/components/v-avatar';
|
||||
import VDialog from '@/components/v-dialog';
|
||||
import VOverlay from '@/components/v-dialog';
|
||||
import VCard, { VCardTitle, VCardActions } from '@/components/v-card';
|
||||
|
||||
import Tooltip from '@/directives/tooltip';
|
||||
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(VueCompositionAPI);
|
||||
localVue.use(VueRouter);
|
||||
|
||||
localVue.component('v-icon', VIcon);
|
||||
localVue.component('v-button', VButton);
|
||||
localVue.component('v-avatar', VAvatar);
|
||||
localVue.component('v-dialog', VDialog);
|
||||
localVue.component('v-card', VCard);
|
||||
localVue.component('v-card-title', VCardTitle);
|
||||
localVue.component('v-card-actions', VCardActions);
|
||||
localVue.component('v-overlay', VOverlay);
|
||||
|
||||
localVue.directive('tooltip', Tooltip);
|
||||
|
||||
describe('Views / Private / Module Bar Avatar', () => {
|
||||
let req: any = {};
|
||||
|
||||
beforeEach(() => {
|
||||
req = {};
|
||||
});
|
||||
|
||||
it('Returns correct avatar url for thumbnail key', () => {
|
||||
const userStore = useUserStore(req);
|
||||
useProjectsStore(req);
|
||||
|
||||
userStore.state.currentUser = {
|
||||
id: 1,
|
||||
avatar: {
|
||||
data: {
|
||||
thumbnails: [
|
||||
{
|
||||
key: 'test',
|
||||
url: 'test',
|
||||
},
|
||||
{
|
||||
key: 'directus-small-crop',
|
||||
url: 'test1',
|
||||
},
|
||||
{
|
||||
key: 'directus-medium-crop',
|
||||
url: 'test2',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
const component = shallowMount(ModuleBarAvatar, {
|
||||
localVue,
|
||||
i18n,
|
||||
stubs: {
|
||||
'v-hover': '<div><slot v-bind="{ hover: false }" /></div>',
|
||||
},
|
||||
});
|
||||
|
||||
expect((component.vm as any).avatarURL).toBe('test2');
|
||||
});
|
||||
|
||||
it('Returns null if avatar is null', () => {
|
||||
const userStore = useUserStore(req);
|
||||
useProjectsStore(req);
|
||||
|
||||
userStore.state.currentUser = {
|
||||
id: 1,
|
||||
avatar: null,
|
||||
} as any;
|
||||
|
||||
const component = shallowMount(ModuleBarAvatar, {
|
||||
localVue,
|
||||
i18n,
|
||||
stubs: {
|
||||
'v-hover': '<div><slot v-bind="{ hover: false }" /></div>',
|
||||
},
|
||||
});
|
||||
|
||||
expect((component.vm as any).avatarURL).toBe(null);
|
||||
});
|
||||
|
||||
it('Returns null if thumbnail can not be found', () => {
|
||||
const userStore = useUserStore(req);
|
||||
useProjectsStore(req);
|
||||
|
||||
userStore.state.currentUser = {
|
||||
id: 1,
|
||||
avatar: {
|
||||
data: {
|
||||
thumbnails: [
|
||||
{
|
||||
key: 'fake',
|
||||
url: 'non-existent',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
const component = shallowMount(ModuleBarAvatar, {
|
||||
localVue,
|
||||
i18n,
|
||||
stubs: {
|
||||
'v-hover': '<div><slot v-bind="{ hover: false }" /></div>',
|
||||
},
|
||||
});
|
||||
|
||||
expect((component.vm as any).avatarURL).toBe(null);
|
||||
});
|
||||
|
||||
it('Calculates correct routes for user profile and sign out', () => {
|
||||
const userStore = useUserStore(req);
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
|
||||
userStore.state.currentUser = {
|
||||
id: 1,
|
||||
avatar: null,
|
||||
role: { id: 15 },
|
||||
} as any;
|
||||
|
||||
const component = shallowMount(ModuleBarAvatar, {
|
||||
localVue,
|
||||
i18n,
|
||||
stubs: {
|
||||
'v-hover': '<div><slot v-bind="{ hover: false }" /></div>',
|
||||
},
|
||||
});
|
||||
|
||||
expect((component.vm as any).userProfileLink).toBe('/my-project/users/15/1');
|
||||
expect((component.vm as any).signOutLink).toBe('/my-project/logout');
|
||||
});
|
||||
});
|
||||
@@ -38,12 +38,10 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref } from '@vue/composition-api';
|
||||
import useUserStore from '@/stores/user/';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const userStore = useUserStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const signOutActive = ref(false);
|
||||
|
||||
@@ -59,16 +57,14 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
const userProfileLink = computed<string>(() => {
|
||||
const project = projectsStore.state.currentProjectKey;
|
||||
const id = userStore.state.currentUser?.id;
|
||||
const role = userStore.state.currentUser?.role?.id;
|
||||
|
||||
return `/${project}/users/${role}/${id}`;
|
||||
return `/users/${role}/${id}`;
|
||||
});
|
||||
|
||||
const signOutLink = computed<string>(() => {
|
||||
const project = projectsStore.state.currentProjectKey;
|
||||
return `/${project}/logout`;
|
||||
return `/logout`;
|
||||
});
|
||||
|
||||
const userFullName = userStore.fullName;
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import markdown from './readme.md';
|
||||
import ModuleBarLogo from './module-bar-logo.vue';
|
||||
import withPadding from '../../../../../.storybook/decorators/with-padding';
|
||||
import useRequestsStore from '@/stores/requests';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
|
||||
export default {
|
||||
title: 'Views / Private / Components / Module Bar Logo',
|
||||
decorators: [withPadding],
|
||||
parameters: {
|
||||
notes: markdown,
|
||||
},
|
||||
};
|
||||
|
||||
export const basic = () =>
|
||||
defineComponent({
|
||||
components: { ModuleBarLogo },
|
||||
setup() {
|
||||
useProjectsStore({});
|
||||
useRequestsStore({});
|
||||
},
|
||||
template: `
|
||||
<module-bar-logo />
|
||||
`,
|
||||
});
|
||||
|
||||
export const withQueue = () =>
|
||||
defineComponent({
|
||||
components: { ModuleBarLogo },
|
||||
setup() {
|
||||
useProjectsStore({});
|
||||
const requestsStore = useRequestsStore({});
|
||||
requestsStore.state.queue = ['abc'];
|
||||
},
|
||||
template: `
|
||||
<module-bar-logo />
|
||||
`,
|
||||
});
|
||||
|
||||
export const withCustomLogo = () =>
|
||||
defineComponent({
|
||||
components: { ModuleBarLogo },
|
||||
setup() {
|
||||
useRequestsStore({});
|
||||
const projectsStore = useProjectsStore({});
|
||||
|
||||
projectsStore.state.projects = [
|
||||
{
|
||||
key: 'my-project',
|
||||
api: {
|
||||
version: '8.5.5',
|
||||
requires2FA: false,
|
||||
database: 'mysql',
|
||||
project_name: 'Thumper',
|
||||
project_logo: {
|
||||
full_url:
|
||||
'https://demo.directus.io/uploads/thumper/originals/19acff06-4969-5c75-9cd5-dc3f27506de2.svg',
|
||||
url: '/uploads/thumper/originals/19acff06-4969-5c75-9cd5-dc3f27506de2.svg',
|
||||
asset_url: '/uploads/thumper/originals/19acff06-4969-5c75-9cd5-dc3f27506de2.svg',
|
||||
},
|
||||
project_color: '#4CAF50',
|
||||
project_foreground: null,
|
||||
project_background: null,
|
||||
telemetry: true,
|
||||
default_locale: 'en-US',
|
||||
project_public_note: null,
|
||||
sso: [],
|
||||
},
|
||||
server: {
|
||||
max_upload_size: 104857600,
|
||||
general: {
|
||||
php_version: '7.2.22',
|
||||
php_api: 'fpm-fcgi',
|
||||
},
|
||||
},
|
||||
authenticated: true,
|
||||
},
|
||||
];
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
},
|
||||
template: `
|
||||
<module-bar-logo /> `,
|
||||
});
|
||||
|
||||
export const withCustomColor = () =>
|
||||
defineComponent({
|
||||
components: { ModuleBarLogo },
|
||||
setup() {
|
||||
useProjectsStore({});
|
||||
useRequestsStore({});
|
||||
},
|
||||
template: `
|
||||
<module-bar-logo style="--brand: red;" />
|
||||
`,
|
||||
});
|
||||
@@ -10,19 +10,17 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch } from '@vue/composition-api';
|
||||
import { useProjectsStore } from '@/stores/projects/';
|
||||
import { useRequestsStore } from '@/stores/requests/';
|
||||
import { useSettingsStore } from '@/stores/settings/';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const requestsStore = useRequestsStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const customLogoPath = computed<string | null>(() => {
|
||||
if (projectsStore.currentProject.value === null) return null;
|
||||
return projectsStore.currentProject.value.logo || null;
|
||||
if (settingsStore.state.settings === null) return null;
|
||||
return settingsStore.state.settings.project_logo || null;
|
||||
});
|
||||
|
||||
const showLoader = ref(false);
|
||||
@@ -37,7 +35,7 @@ export default defineComponent({
|
||||
}
|
||||
);
|
||||
|
||||
const url = computed(() => settingsStore.formatted.value.project_url);
|
||||
const url = computed(() => settingsStore.state.settings?.project_url);
|
||||
|
||||
return {
|
||||
customLogoPath,
|
||||
|
||||
@@ -2,7 +2,7 @@ import markdown from './readme.md';
|
||||
import ModuleBar from './module-bar.vue';
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import VueRouter from 'vue-router';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import useRequestsStore from '@/stores/requests';
|
||||
import useUserStore from '@/stores/user';
|
||||
import i18n from '@/lang/';
|
||||
@@ -24,8 +24,6 @@ export const basic = () =>
|
||||
useRequestsStore(req);
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = { id: 1, avatar: null } as any;
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
},
|
||||
template: `
|
||||
<module-bar />
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, Ref, computed } from '@vue/composition-api';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
|
||||
import { modules } from '@/modules/';
|
||||
import ModuleBarLogo from '../module-bar-logo/';
|
||||
import ModuleBarAvatar from '../module-bar-avatar/';
|
||||
@@ -40,11 +40,8 @@ export default defineComponent({
|
||||
ModuleBarAvatar,
|
||||
},
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
const _modules = computed(() => {
|
||||
const customModuleListing = userStore.state.currentUser?.role.module_listing;
|
||||
|
||||
@@ -68,7 +65,7 @@ export default defineComponent({
|
||||
.map((module) => ({
|
||||
...module,
|
||||
href: module.link || null,
|
||||
to: module.link === undefined ? `/${currentProjectKey}/${module.id}/` : null,
|
||||
to: module.link === undefined ? `/${module.id}/` : null,
|
||||
}))
|
||||
.filter((module) => {
|
||||
if (module.hidden !== undefined) {
|
||||
|
||||
@@ -34,7 +34,6 @@ import { defineComponent, computed, ref, watch } from '@vue/composition-api';
|
||||
import DrawerButton from '../drawer-button';
|
||||
import NotificationItem from '../notification-item';
|
||||
import useNotificationsStore from '@/stores/notifications';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
export default defineComponent({
|
||||
components: { DrawerButton, NotificationItem },
|
||||
@@ -46,8 +45,8 @@ export default defineComponent({
|
||||
},
|
||||
setup(props) {
|
||||
const notificationsStore = useNotificationsStore();
|
||||
const projectsStore = useProjectsStore();
|
||||
const activityLink = computed(() => `/${projectsStore.state.currentProjectKey}/activity`);
|
||||
|
||||
const activityLink = computed(() => `/activity`);
|
||||
const active = ref(false);
|
||||
|
||||
watch(
|
||||
|
||||
@@ -1,84 +1,22 @@
|
||||
<template>
|
||||
<div class="project-chooser" :class="{ active }">
|
||||
<button ref="activator" class="toggle" :disabled="projects.length === 1" @click="active = !active">
|
||||
<latency-indicator />
|
||||
<span class="name">{{ currentProject.name }}</span>
|
||||
<v-icon class="icon" name="expand_more" />
|
||||
</button>
|
||||
<transition-expand>
|
||||
<div
|
||||
v-if="active"
|
||||
class="options-wrapper"
|
||||
v-click-outside="{
|
||||
handler: () => (active = false),
|
||||
middleware: onClickOutsideMiddleware,
|
||||
}"
|
||||
>
|
||||
<div class="options">
|
||||
<v-divider />
|
||||
|
||||
<router-link
|
||||
v-for="project in projects"
|
||||
class="router-link"
|
||||
:key="project.key"
|
||||
:to="`/${project.key}/collections`"
|
||||
>
|
||||
<v-radio
|
||||
:inputValue="currentProjectKey"
|
||||
:value="project.key"
|
||||
:label="project.name || project.key"
|
||||
/>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</transition-expand>
|
||||
<div class="project-chooser">
|
||||
<latency-indicator />
|
||||
<span class="name">{{ name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, ref } from '@vue/composition-api';
|
||||
import { useProjectsStore } from '@/stores/projects';
|
||||
import router from '@/router';
|
||||
import { defineComponent, computed } from '@vue/composition-api';
|
||||
import LatencyIndicator from '../latency-indicator';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
|
||||
export default defineComponent({
|
||||
components: { LatencyIndicator },
|
||||
setup() {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { currentProjectKey } = toRefs(projectsStore.state);
|
||||
const active = ref(false);
|
||||
const activator = ref<Element | null>(null);
|
||||
const settingsStore = useSettingsStore();
|
||||
const name = computed(() => settingsStore.state.settings?.project_name);
|
||||
|
||||
const currentProject = projectsStore.currentProject;
|
||||
|
||||
return {
|
||||
projects: projectsStore.formatted,
|
||||
currentProjectKey,
|
||||
navigateToProject,
|
||||
projectsStore,
|
||||
currentProject,
|
||||
active,
|
||||
onClickOutsideMiddleware,
|
||||
activator,
|
||||
};
|
||||
|
||||
function onClickOutsideMiddleware(event: Event) {
|
||||
return !activator.value?.contains(event.target as Node);
|
||||
}
|
||||
|
||||
function navigateToProject(key: string) {
|
||||
router
|
||||
.push(`/${key}/collections`)
|
||||
/** @NOTE
|
||||
* Vue Router considers a navigation change _in_ the navigation guard a rejection
|
||||
* so when this push goes from /collections to /login, it will throw.
|
||||
* In order to prevent a useless uncaught exception to show up in the console,
|
||||
* we have to catch it here with a no-op. See
|
||||
* https://github.com/vuejs/vue-router/issues/2881#issuecomment-520554378
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
.catch(() => {});
|
||||
}
|
||||
return { name };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -86,68 +24,21 @@ export default defineComponent({
|
||||
<style lang="scss" scoped>
|
||||
.project-chooser {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
padding: 0 20px;
|
||||
color: var(--foreground-normal);
|
||||
text-align: left;
|
||||
background-color: var(--background-normal-alt);
|
||||
|
||||
.toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
padding: 0 20px;
|
||||
text-align: left;
|
||||
|
||||
.latency-indicator {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--foreground-subdued);
|
||||
transform: rotate(0deg);
|
||||
opacity: 0;
|
||||
transition: opacity var(--fast) var(--transition), transform var(--medium) var(--transition);
|
||||
}
|
||||
|
||||
&:hover .icon {
|
||||
opacity: 1;
|
||||
}
|
||||
.latency-indicator {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
&.active .toggle .icon {
|
||||
transform: rotate(180deg);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.options-wrapper {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.options {
|
||||
padding: 20px;
|
||||
padding-top: 0;
|
||||
background-color: var(--background-normal-alt);
|
||||
box-shadow: 0px 8px 12px 0px rgba(38, 50, 56, 0.25);
|
||||
}
|
||||
|
||||
.v-divider {
|
||||
--v-divider-color: var(--background-normal);
|
||||
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.router-link {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed } from '@vue/composition-api';
|
||||
import { Revision, RevisionsByDate } from './types';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
import api from '@/api';
|
||||
import { groupBy, orderBy } from 'lodash';
|
||||
import { isToday, isYesterday, isThisYear } from 'date-fns';
|
||||
@@ -62,8 +62,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const { revisions, revisionsByDate, loading, error, refresh } = useRevisions(
|
||||
props.collection,
|
||||
props.primaryKey
|
||||
@@ -110,33 +108,28 @@ export default defineComponent({
|
||||
return { revisions, revisionsByDate, error, loading, refresh };
|
||||
|
||||
async function getRevisions() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
error.value = null;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const response = await api.get(
|
||||
`/${currentProjectKey}/items/${collection}/${primaryKey}/revisions`,
|
||||
{
|
||||
params: {
|
||||
fields: [
|
||||
'id',
|
||||
'data',
|
||||
'delta',
|
||||
'collection',
|
||||
'item',
|
||||
'activity.action',
|
||||
'activity.action_on',
|
||||
'activity.action_by.id',
|
||||
'activity.action_by.first_name',
|
||||
'activity.action_by.last_name',
|
||||
'activity.ip',
|
||||
'activity.user_agent',
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
const response = await api.get(`/items/${collection}/${primaryKey}/revisions`, {
|
||||
params: {
|
||||
fields: [
|
||||
'id',
|
||||
'data',
|
||||
'delta',
|
||||
'collection',
|
||||
'item',
|
||||
'activity.action',
|
||||
'activity.action_on',
|
||||
'activity.action_by.id',
|
||||
'activity.action_by.first_name',
|
||||
'activity.action_by.last_name',
|
||||
'activity.ip',
|
||||
'activity.user_agent',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const revisionsGroupedByDate = groupBy(response.data.data, (revision: Revision) => {
|
||||
// revision's action_on date is in iso-8601
|
||||
|
||||
@@ -59,7 +59,6 @@ import RevisionsModalPicker from './revisions-modal-picker.vue';
|
||||
import RevisionsModalPreview from './revisions-modal-preview.vue';
|
||||
import RevisionsModalUpdates from './revisions-modal-updates.vue';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
export default defineComponent({
|
||||
components: { RevisionsModalPicker, RevisionsModalPreview, RevisionsModalUpdates },
|
||||
@@ -80,7 +79,6 @@ export default defineComponent({
|
||||
setup(props, { emit }) {
|
||||
const _active = useSync(props, 'active', emit);
|
||||
const _current = useSync(props, 'current', emit);
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const currentTab = ref(['preview']);
|
||||
|
||||
@@ -113,7 +111,6 @@ export default defineComponent({
|
||||
};
|
||||
|
||||
function useRevert() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
const confirmRevert = ref(false);
|
||||
const reverting = ref(false);
|
||||
|
||||
@@ -125,10 +122,10 @@ export default defineComponent({
|
||||
|
||||
try {
|
||||
const endpoint = currentRevision.value.collection.startsWith('directus_')
|
||||
? `/${currentProjectKey}/${currentRevision.value.collection.substring(9)}/${
|
||||
currentRevision.value.item
|
||||
}/revert/${currentRevision.value.id}`
|
||||
: `/${currentProjectKey}/items/${currentRevision.value.collection}/${currentRevision.value.item}/revert/${currentRevision.value.id}`;
|
||||
? `/${currentRevision.value.collection.substring(9)}/${currentRevision.value.item}/revert/${
|
||||
currentRevision.value.id
|
||||
}`
|
||||
: `/items/${currentRevision.value.collection}/${currentRevision.value.item}/revert/${currentRevision.value.id}`;
|
||||
await api.patch(endpoint);
|
||||
confirmRevert.value = false;
|
||||
_active.value = false;
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, onUnmounted, computed } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
|
||||
type User = {
|
||||
first_name: string;
|
||||
@@ -53,8 +52,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
const data = ref<User | null>(null);
|
||||
@@ -85,10 +82,9 @@ export default defineComponent({
|
||||
async function fetchUser() {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/users/${props.user}`, {
|
||||
const response = await api.get(`/users/${props.user}`, {
|
||||
params: {
|
||||
fields: ['first_name', 'last_name', 'avatar.data', 'role.name', 'status', 'email'],
|
||||
},
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import Vue from 'vue';
|
||||
import PrivateView from './private-view.vue';
|
||||
import markdown from './readme.md';
|
||||
import VueRouter from 'vue-router';
|
||||
import { defineComponent } from '@vue/composition-api';
|
||||
import useRequestsStore from '@/stores/requests';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import useUserStore from '@/stores/user';
|
||||
import { i18n } from '@/lang/';
|
||||
|
||||
Vue.component('private-view', PrivateView);
|
||||
Vue.use(VueRouter);
|
||||
|
||||
export default {
|
||||
title: 'Views / Private',
|
||||
parameters: {
|
||||
notes: markdown,
|
||||
},
|
||||
};
|
||||
|
||||
export const basic = () =>
|
||||
defineComponent({
|
||||
i18n,
|
||||
router: new VueRouter(),
|
||||
setup() {
|
||||
const req = {};
|
||||
useRequestsStore(req);
|
||||
const userStore = useUserStore(req);
|
||||
userStore.state.currentUser = { id: 1, avatar: null } as any;
|
||||
const projectsStore = useProjectsStore(req);
|
||||
projectsStore.state.currentProjectKey = 'my-project';
|
||||
},
|
||||
template: `
|
||||
<private-view />
|
||||
`,
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user