304 lines
No EOL
12 KiB
JavaScript
304 lines
No EOL
12 KiB
JavaScript
import {createRouter, createWebHistory} from 'vue-router'
|
|
import Dashboard from '@/views/Dashboard.vue';
|
|
import Login from '@/views/Login.vue';
|
|
import Register from '@/views/Register.vue';
|
|
import Pairing from '@/views/Pairing.vue';
|
|
import store from '@/store';
|
|
import Profile from '@/views/Profile.vue';
|
|
import Settings from '@/views/Settings.vue';
|
|
import Friends from '@/views/Friends.vue';
|
|
import Groups from '@/views/Groups.vue';
|
|
import GroupNew from '@/views/GroupNew.vue';
|
|
import GroupDetail from '@/views/GroupDetail.vue';
|
|
import Inventory from '@/views/Inventory.vue';
|
|
import Search from '@/views/Search.vue';
|
|
import InventoryDetail from '@/views/InventoryDetail.vue';
|
|
import InventoryNew from '@/views/InventoryNew.vue';
|
|
import InventoryEdit from '@/views/InventoryEdit.vue';
|
|
import StorageLocation from '@/views/StorageLocation.vue';
|
|
import StorageLocationDetail from '@/views/StorageLocationDetail.vue';
|
|
import StorageLocationNew from '@/views/StorageLocationNew.vue';
|
|
import StorageLocationEdit from '@/views/StorageLocationEdit.vue';
|
|
import Admin from '@/views/Admin.vue';
|
|
import Swatch from '@/views/Swatch.vue';
|
|
import Files from '@/views/Files.vue';
|
|
import Print from "@/views/Print.vue";
|
|
import Scan from "@/views/Scan.vue";
|
|
import Workflows from '@/views/Workflows.vue';
|
|
import WorkflowDetail from '@/views/WorkflowDetail.vue';
|
|
import ShortId from '@/views/ShortId.vue';
|
|
import {
|
|
encodeShortId, serializeShortId, decodeShortId, deserializeShortId,
|
|
isDomainQualifiedShortId, decodeDomainQualifiedShortId
|
|
} from '@/lib/short-id';
|
|
|
|
import Account from '@/views/settings/Account.vue';
|
|
import Password from '@/views/settings/Password.vue';
|
|
import Privacy from '@/views/settings/Privacy.vue';
|
|
import Email from '@/views/settings/Email.vue';
|
|
import Notifications from '@/views/settings/Notifications.vue';
|
|
import Data from '@/views/settings/Data.vue';
|
|
import Preferences from '@/views/settings/Preferences.vue';
|
|
|
|
|
|
export function encodeHandleForUrl(handle) {
|
|
return handle.replace(/#/g, "+");
|
|
}
|
|
|
|
export function decodeHandleFromUrl(segment) {
|
|
return segment.replace(/\+/g, "#");
|
|
}
|
|
|
|
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
|
export function ownerOverviewRoute(item) {
|
|
return item.owner_group
|
|
? {name: 'inventory', params: {owner: encodeHandleForUrl(item.owner_group)}}
|
|
: {name: 'inventory'};
|
|
}
|
|
|
|
export function ownerLocationOverviewRoute(location) {
|
|
return location.owner_group
|
|
? {name: 'locations', params: {owner: encodeHandleForUrl(location.owner_group)}}
|
|
: {name: 'locations'};
|
|
}
|
|
|
|
const EXPANDED_ROUTE_BUILDERS = {
|
|
item: ({owner_identity_id, item_local_id}) => {
|
|
const handle = store.getters.identityHandleById[owner_identity_id];
|
|
return handle ? {name: 'inventory-detail', params: {handle: encodeHandleForUrl(handle), id: item_local_id}} : null;
|
|
},
|
|
group_item: ({owner_group_id, item_local_id}) => {
|
|
const handle = store.getters.groupHandleById[owner_group_id];
|
|
return handle ? {name: 'inventory-detail', params: {handle: encodeHandleForUrl(handle), id: item_local_id}} : null;
|
|
},
|
|
group: ({group_id}) => {
|
|
const handle = store.getters.groupHandleById[group_id];
|
|
return handle ? {name: 'group-detail', params: {handle: encodeHandleForUrl(handle)}} : null;
|
|
},
|
|
storage_location: ({owner_identity_id, storage_location_id}) => {
|
|
const handle = store.getters.identityHandleById[owner_identity_id];
|
|
return handle ? {name: 'locations-detail', params: {handle: encodeHandleForUrl(handle), id: storage_location_id}} : null;
|
|
},
|
|
group_storage_location: ({owner_group_id, storage_location_id}) => {
|
|
const handle = store.getters.groupHandleById[owner_group_id];
|
|
return handle ? {name: 'locations-detail', params: {handle: encodeHandleForUrl(handle), id: storage_location_id}} : null;
|
|
},
|
|
workflow: ({workflow_id}) => ({name: 'workflow-detail', params: {id: workflow_id}}),
|
|
};
|
|
|
|
// Kinds whose route needs identityHandleById/groupHandleById (from state.idmap); ShortId.vue only waits on an idmap fetch for these.
|
|
export const NEEDS_IDMAP = new Set(['item', 'group_item', 'group', 'storage_location', 'group_storage_location']);
|
|
|
|
export function expandedRoute({kind, ...fields}) {
|
|
const buildRoute = EXPANDED_ROUTE_BUILDERS[kind];
|
|
return buildRoute ? buildRoute(fields) : null;
|
|
}
|
|
|
|
export function shortenedRoute(named) {
|
|
return '/' + encodeShortId(serializeShortId(named));
|
|
}
|
|
|
|
// Owned kinds only - matches handles-and-shortids.md's User-Qualified ID split (group/category/file have no owner id to hand a foreign backend).
|
|
export const OWNED_KIND_FIELDS = {
|
|
item: {ownerField: 'owner_identity_id', localField: 'item_local_id', isLocation: false},
|
|
group_item: {ownerField: 'owner_group_id', localField: 'item_local_id', isLocation: false},
|
|
storage_location: {ownerField: 'owner_identity_id', localField: 'storage_location_id', isLocation: true},
|
|
group_storage_location: {ownerField: 'owner_group_id', localField: 'storage_location_id', isLocation: true},
|
|
};
|
|
|
|
// Resolves a Domain-Qualified Short ID to a route, locally or via a network round-trip. See docs/implementation.md#domain-qualified-short-id-resolution.
|
|
export async function domainQualifiedRoute(domain, ints) {
|
|
const homeDomain = store.getters.isLoggedIn && store.state.user ? store.state.user.split('@')[1] : null;
|
|
const deserialized = deserializeShortId(ints);
|
|
if (domain === homeDomain) {
|
|
return expandedRoute(deserialized) || undefined;
|
|
}
|
|
const owned = OWNED_KIND_FIELDS[deserialized.kind];
|
|
if (!owned) {
|
|
return undefined; // no owner id to resolve a foreign backend against yet (group/category/file/workflow)
|
|
}
|
|
const resolved = await store.dispatch('resolveShortId', {
|
|
domain, kind: deserialized.kind,
|
|
ownerId: deserialized[owned.ownerField], localId: deserialized[owned.localField],
|
|
});
|
|
if (!resolved || !resolved.handle) {
|
|
return undefined;
|
|
}
|
|
return owned.isLocation
|
|
? {name: 'locations-detail', params: {handle: encodeHandleForUrl(resolved.handle), id: resolved.id}}
|
|
: {name: 'inventory-detail', params: {handle: encodeHandleForUrl(resolved.handle), id: resolved.id}};
|
|
}
|
|
|
|
const routes = [{path: '/', name: 'dashboard', component: Dashboard, meta: {requiresAuth: true}}, {
|
|
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
|
path: '/inventory/:owner?',
|
|
name: 'inventory',
|
|
component: Inventory,
|
|
meta: {requiresAuth: true},
|
|
props: true
|
|
}, {
|
|
path: '/inventory/:handle/:id',
|
|
name: 'inventory-detail',
|
|
component: InventoryDetail,
|
|
meta: {requiresAuth: true},
|
|
props: true
|
|
}, {
|
|
path: '/inventory/:handle/:id/edit',
|
|
name: 'inventory-edit',
|
|
component: InventoryEdit,
|
|
meta: {requiresAuth: true},
|
|
props: true
|
|
}, {
|
|
// Label/short-link entry point; alias of /inventory/:handle/:id. See docs/implementation.md#item-short-link-redirect-route.
|
|
path: '/i/:handle/:id',
|
|
redirect: to => ({name: 'inventory-detail', params: to.params})
|
|
}, {
|
|
// beforeEnter, not redirect: falls through to ShortId.vue when the route can't resolve synchronously. See docs/implementation.md#beforeenter-guard-vs-redirect-for-short_id.
|
|
path: '/:short_id',
|
|
component: ShortId,
|
|
props: true,
|
|
beforeEnter: async to => {
|
|
if (isDomainQualifiedShortId(to.params.short_id)) {
|
|
const {domain, ints} = decodeDomainQualifiedShortId(to.params.short_id);
|
|
return await domainQualifiedRoute(domain, ints);
|
|
}
|
|
return expandedRoute(deserializeShortId(decodeShortId(to.params.short_id)));
|
|
}
|
|
}, {
|
|
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
|
path: '/inventory/new/:group?',
|
|
name: 'inventory-new',
|
|
component: InventoryNew,
|
|
meta: {requiresAuth: true},
|
|
props: true
|
|
}, {
|
|
path: '/friends',
|
|
name: 'friends',
|
|
component: Friends,
|
|
meta: {requiresAuth: true}
|
|
}, {
|
|
path: '/groups',
|
|
name: 'groups',
|
|
component: Groups,
|
|
meta: {requiresAuth: true}
|
|
}, {
|
|
path: '/groups/new',
|
|
name: 'groups-new',
|
|
component: GroupNew,
|
|
meta: {requiresAuth: true}
|
|
}, {
|
|
path: '/groups/:handle',
|
|
name: 'group-detail',
|
|
component: GroupDetail,
|
|
meta: {requiresAuth: true},
|
|
props: true
|
|
}, {path: '/files', name: 'files', component: Files, meta: {requiresAuth: true}
|
|
}, {path: '/workflows',
|
|
name: 'workflows',
|
|
component: Workflows,
|
|
meta: {requiresAuth: true},
|
|
}, {path: '/workflows/:id/:step?',
|
|
name: 'workflow-detail',
|
|
component: WorkflowDetail,
|
|
meta: {requiresAuth: true},
|
|
props: true
|
|
}, {path: '/admin',
|
|
name: 'admin',
|
|
component: Admin,
|
|
meta: {requiresAuth: true}
|
|
}, {path: '/swatch', name: 'swatch', component: Swatch, meta: {requiresAuth: true}}, {
|
|
path: '/print',
|
|
name: 'print',
|
|
component: Print,
|
|
meta: {requiresAuth: true},
|
|
props: route => {
|
|
const {kind, ...components} = route.query;
|
|
// queryFields lets any base var (e.g. ?text=...) be set directly, without going through
|
|
// a `kind` builder - see Print.vue's varValues.
|
|
return {prefill: kind ? {kind, components} : null, queryFields: components};
|
|
}
|
|
}, {
|
|
path: '/scan',
|
|
name: 'scan',
|
|
component: Scan,
|
|
meta: {requiresAuth: true},
|
|
}, {
|
|
path: '/search/:query',
|
|
name: 'search',
|
|
component: Search,
|
|
meta: {requiresAuth: true},
|
|
props: true
|
|
}, {path: '/login', name: 'login', component: Login, meta: {requiresAuth: false}}, {
|
|
path: '/register',
|
|
name: 'register',
|
|
component: Register,
|
|
meta: {requiresAuth: false}
|
|
}, {path: '/pairing', name: 'pairing', component: Pairing, meta: {requiresAuth: false}}, {
|
|
path: '/profile',
|
|
name: 'profile',
|
|
component: Profile,
|
|
meta: {requiresAuth: true}
|
|
}, {
|
|
path: '/settings', component: Settings, meta: {requiresAuth: true}, children: [{
|
|
path: '', name: 'account', component: Account, meta: {requiresAuth: true}
|
|
},{
|
|
path: 'password/', name: 'password', component: Password, meta: {requiresAuth: true}
|
|
}, {
|
|
path: 'privacy/', name: 'privacy', component: Privacy, meta: {requiresAuth: true}
|
|
}, {
|
|
path: 'email/', name: 'email', component: Email, meta: {requiresAuth: true}
|
|
}, {
|
|
path: 'notifications/', name: 'notifications', component: Notifications, meta: {requiresAuth: true}
|
|
}, {
|
|
path: 'data/', name: 'data', component: Data, meta: {requiresAuth: true}
|
|
}, {
|
|
path: 'preferences/', name: 'preferences', component: Preferences, meta: {requiresAuth: true}
|
|
}]
|
|
}, {
|
|
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
|
path: '/locations/:owner?',
|
|
name: 'locations',
|
|
component: StorageLocation,
|
|
meta: {requiresAuth: true},
|
|
props: true
|
|
}, {
|
|
path: '/locations/:handle/:id',
|
|
name: 'locations-detail',
|
|
component: StorageLocationDetail,
|
|
meta: {requiresAuth: true},
|
|
props: true
|
|
}, {
|
|
path: '/locations/:handle/:id/edit',
|
|
name: 'locations-edit',
|
|
component: StorageLocationEdit,
|
|
meta: {requiresAuth: true},
|
|
props: true
|
|
}, {
|
|
// Label/short-link entry point for storage locations, the counterpart of /i/:handle/:id. See docs/implementation.md#item-short-link-redirect-route.
|
|
path: '/s/:handle/:id',
|
|
redirect: to => ({name: 'locations-detail', params: to.params})
|
|
}, {
|
|
// See docs/implementation.md#owner-filtered-overview-routes-use-path-segments-not-query-strings.
|
|
path: '/locations/new/:group?',
|
|
name: 'locations-new',
|
|
component: StorageLocationNew,
|
|
meta: {requiresAuth: true},
|
|
props: true
|
|
}, {path: '/:pathMatch(.*)*', redirect: '/'}]
|
|
|
|
const router = createRouter({
|
|
history: createWebHistory(), linkActiveClass: "active", routes,
|
|
})
|
|
|
|
router.beforeEach((to/*, from*/) => {
|
|
// if this route requires auth, check if logged in, if not, redirect to login page.
|
|
if (to.meta.requiresAuth && !store.getters.isLoggedIn) {
|
|
console.log("Not logged in, redirecting to login page")
|
|
return {
|
|
path: '/login', // save the location we were at to come back later
|
|
query: {redirect: to.fullPath},
|
|
}
|
|
}
|
|
})
|
|
|
|
export default router |