Add multi-workspace selector scope
This commit is contained in:
33
docs/TASKS/workspaces/001-all-workspaces-selector.md
Normal file
33
docs/TASKS/workspaces/001-all-workspaces-selector.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# All Workspaces Selector
|
||||
|
||||
## Feature
|
||||
|
||||
Workspace navigation and cross-workspace content visibility.
|
||||
|
||||
## Goal
|
||||
|
||||
Allow users with access to multiple workspaces to select an "All Workspaces" scope from the workspace selector and view combined workspace data in list/calendar style views.
|
||||
|
||||
## Scope
|
||||
|
||||
- Add an explicit all-workspaces selection state to the frontend workspace store.
|
||||
- Add an "All Workspaces" entry as the first workspace selector item.
|
||||
- Add per-workspace visibility toggles for the all-workspaces aggregate scope.
|
||||
- Fetch list data without `workspaceId` when all workspaces are selected.
|
||||
- Filter all-workspaces list data to the currently visible workspace set.
|
||||
- Keep creation and workspace settings actions scoped to a concrete workspace.
|
||||
|
||||
## Likely Files
|
||||
|
||||
- `frontend/src/features/workspaces/stores/workspaceStore.js`
|
||||
- `frontend/src/layouts/main/WorkspaceSelector.vue`
|
||||
- `frontend/src/features/content/stores/contentItemsStore.js`
|
||||
- `frontend/src/features/campaigns/stores/campaignsStore.js`
|
||||
- `frontend/src/features/clients/stores/clientsStore.js`
|
||||
- `frontend/src/features/channels/stores/channelsStore.js`
|
||||
- `frontend/src/locales/en.json`
|
||||
- `frontend/src/locales/fr.json`
|
||||
|
||||
## Validation
|
||||
|
||||
- `cd frontend && npm run build`
|
||||
@@ -15,7 +15,7 @@ export const useCampaignsStore = defineStore('campaigns', () => {
|
||||
const error = ref(null);
|
||||
|
||||
async function fetchCampaigns() {
|
||||
if (!authStore.isAuthenticated || !workspaceStore.activeWorkspaceId) {
|
||||
if (!authStore.isAuthenticated) {
|
||||
campaigns.value = [];
|
||||
error.value = null;
|
||||
return;
|
||||
@@ -27,11 +27,13 @@ export const useCampaignsStore = defineStore('campaigns', () => {
|
||||
try {
|
||||
const response = await client.get('/api/campaigns', {
|
||||
params: {
|
||||
workspaceId: workspaceStore.activeWorkspaceId,
|
||||
workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
|
||||
},
|
||||
});
|
||||
|
||||
campaigns.value = response.data ?? [];
|
||||
campaigns.value = (response.data ?? []).filter(campaign =>
|
||||
workspaceStore.isWorkspaceVisible(campaign.workspaceId)
|
||||
);
|
||||
} catch (fetchError) {
|
||||
console.error('Failed to fetch campaigns:', fetchError);
|
||||
campaigns.value = [];
|
||||
@@ -75,9 +77,9 @@ export const useCampaignsStore = defineStore('campaigns', () => {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId],
|
||||
async ([isAuthenticated, workspaceId]) => {
|
||||
if (!isAuthenticated || !workspaceId) {
|
||||
() => [authStore.isAuthenticated, workspaceStore.workspaceScopeKey],
|
||||
async ([isAuthenticated]) => {
|
||||
if (!isAuthenticated) {
|
||||
campaigns.value = [];
|
||||
error.value = null;
|
||||
return;
|
||||
|
||||
@@ -14,6 +14,7 @@ export const useChannelsStore = defineStore('channels', () => {
|
||||
const isCreating = ref(false);
|
||||
const error = ref(null);
|
||||
const loadedWorkspaceId = ref(null);
|
||||
const allWorkspacesKey = '__all__';
|
||||
|
||||
const availableNetworks = [
|
||||
'Instagram',
|
||||
@@ -28,15 +29,16 @@ export const useChannelsStore = defineStore('channels', () => {
|
||||
|
||||
async function fetchChannels({ force = false } = {}) {
|
||||
const currentWorkspaceId = workspaceStore.activeWorkspaceId;
|
||||
const currentScopeKey = currentWorkspaceId ?? workspaceStore.workspaceScopeKey ?? allWorkspacesKey;
|
||||
|
||||
if (!authStore.isAuthenticated || !currentWorkspaceId) {
|
||||
if (!authStore.isAuthenticated) {
|
||||
channels.value = [];
|
||||
error.value = null;
|
||||
loadedWorkspaceId.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force && loadedWorkspaceId.value === currentWorkspaceId) {
|
||||
if (!force && loadedWorkspaceId.value === currentScopeKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -46,12 +48,14 @@ export const useChannelsStore = defineStore('channels', () => {
|
||||
try {
|
||||
const response = await client.get('/api/channels', {
|
||||
params: {
|
||||
workspaceId: currentWorkspaceId,
|
||||
workspaceId: currentWorkspaceId ?? undefined,
|
||||
},
|
||||
});
|
||||
|
||||
channels.value = response.data ?? [];
|
||||
loadedWorkspaceId.value = currentWorkspaceId;
|
||||
channels.value = (response.data ?? []).filter(channel =>
|
||||
workspaceStore.isWorkspaceVisible(channel.workspaceId)
|
||||
);
|
||||
loadedWorkspaceId.value = currentScopeKey;
|
||||
} catch (fetchError) {
|
||||
console.error('Failed to fetch channels:', fetchError);
|
||||
channels.value = [];
|
||||
@@ -101,9 +105,9 @@ export const useChannelsStore = defineStore('channels', () => {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId],
|
||||
async ([isAuthenticated, workspaceId]) => {
|
||||
if (!isAuthenticated || !workspaceId) {
|
||||
() => [authStore.isAuthenticated, workspaceStore.workspaceScopeKey],
|
||||
async ([isAuthenticated]) => {
|
||||
if (!isAuthenticated) {
|
||||
channels.value = [];
|
||||
error.value = null;
|
||||
loadedWorkspaceId.value = null;
|
||||
|
||||
@@ -47,10 +47,12 @@
|
||||
.filter(channel => channel.network)
|
||||
.map(channel => {
|
||||
const metrics = buildMetrics(channel.name);
|
||||
const workspace = workspaceStore.workspaces.find(candidate => candidate.id === channel.workspaceId);
|
||||
|
||||
return {
|
||||
...channel,
|
||||
...metrics,
|
||||
workspaceName: workspace?.name ?? t('nav.noWorkspace'),
|
||||
};
|
||||
})
|
||||
);
|
||||
@@ -215,7 +217,7 @@
|
||||
>
|
||||
<div class="channel-header">
|
||||
<strong>{{ channel.name }}</strong>
|
||||
<span>{{ workspaceStore.activeWorkspace?.name || t('nav.noWorkspace') }}</span>
|
||||
<span>{{ channel.workspaceName }}</span>
|
||||
</div>
|
||||
|
||||
<div class="channel-metrics">
|
||||
|
||||
@@ -25,7 +25,7 @@ export const useClientsStore = defineStore('clients', () => {
|
||||
});
|
||||
|
||||
async function fetchClients() {
|
||||
if (!authStore.isAuthenticated || !workspaceStore.activeWorkspaceId) {
|
||||
if (!authStore.isAuthenticated) {
|
||||
clients.value = [];
|
||||
error.value = null;
|
||||
return;
|
||||
@@ -37,11 +37,13 @@ export const useClientsStore = defineStore('clients', () => {
|
||||
try {
|
||||
const response = await client.get('/api/clients', {
|
||||
params: {
|
||||
workspaceId: workspaceStore.activeWorkspaceId,
|
||||
workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
|
||||
},
|
||||
});
|
||||
|
||||
clients.value = response.data ?? [];
|
||||
clients.value = (response.data ?? []).filter(candidate =>
|
||||
workspaceStore.isWorkspaceVisible(candidate.workspaceId)
|
||||
);
|
||||
} catch (fetchError) {
|
||||
console.error('Failed to fetch clients:', fetchError);
|
||||
clients.value = [];
|
||||
@@ -153,9 +155,9 @@ export const useClientsStore = defineStore('clients', () => {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId],
|
||||
async ([isAuthenticated, workspaceId]) => {
|
||||
if (!isAuthenticated || !workspaceId) {
|
||||
() => [authStore.isAuthenticated, workspaceStore.workspaceScopeKey],
|
||||
async ([isAuthenticated]) => {
|
||||
if (!isAuthenticated) {
|
||||
clients.value = [];
|
||||
error.value = null;
|
||||
return;
|
||||
|
||||
@@ -24,6 +24,10 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
|
||||
status: false,
|
||||
});
|
||||
|
||||
function currentItemWorkspaceId() {
|
||||
return item.value?.workspaceId ?? workspaceStore.activeWorkspaceId;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
item.value = null;
|
||||
revisions.value = [];
|
||||
@@ -54,7 +58,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
|
||||
client.get('/api/approvals', { params: { contentItemId } }),
|
||||
client.get('/api/notifications', {
|
||||
params: {
|
||||
workspaceId: workspaceStore.activeWorkspaceId,
|
||||
workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
|
||||
contentItemId,
|
||||
},
|
||||
}),
|
||||
@@ -97,7 +101,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
|
||||
const response = await client.post('/api/assets/google-drive', {
|
||||
...payload,
|
||||
contentItemId,
|
||||
workspaceId: workspaceStore.activeWorkspaceId,
|
||||
workspaceId: currentItemWorkspaceId(),
|
||||
});
|
||||
if (response.data) {
|
||||
assets.value = [...assets.value, response.data];
|
||||
@@ -131,7 +135,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
|
||||
const response = await client.post('/api/comments', {
|
||||
...payload,
|
||||
contentItemId,
|
||||
workspaceId: workspaceStore.activeWorkspaceId,
|
||||
workspaceId: currentItemWorkspaceId(),
|
||||
});
|
||||
if (response.data) {
|
||||
comments.value = [...comments.value, response.data];
|
||||
@@ -202,7 +206,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
|
||||
async function fetchNotifications(contentItemId) {
|
||||
const response = await client.get('/api/notifications', {
|
||||
params: {
|
||||
workspaceId: workspaceStore.activeWorkspaceId,
|
||||
workspaceId: currentItemWorkspaceId() ?? undefined,
|
||||
contentItemId,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ export const useContentItemsStore = defineStore('content-items', () => {
|
||||
);
|
||||
|
||||
async function fetchContentItems(filters = {}) {
|
||||
if (!authStore.isAuthenticated || !workspaceStore.activeWorkspaceId) {
|
||||
if (!authStore.isAuthenticated) {
|
||||
items.value = [];
|
||||
error.value = null;
|
||||
return;
|
||||
@@ -32,13 +32,15 @@ export const useContentItemsStore = defineStore('content-items', () => {
|
||||
try {
|
||||
const response = await client.get('/api/content-items', {
|
||||
params: {
|
||||
workspaceId: workspaceStore.activeWorkspaceId,
|
||||
workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
|
||||
clientId: filters.clientId,
|
||||
campaignId: filters.campaignId,
|
||||
},
|
||||
});
|
||||
|
||||
items.value = response.data ?? [];
|
||||
items.value = (response.data ?? []).filter(item =>
|
||||
workspaceStore.isWorkspaceVisible(item.workspaceId)
|
||||
);
|
||||
} catch (fetchError) {
|
||||
console.error('Failed to fetch content items:', fetchError);
|
||||
items.value = [];
|
||||
@@ -86,9 +88,9 @@ export const useContentItemsStore = defineStore('content-items', () => {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId],
|
||||
async ([isAuthenticated, workspaceId]) => {
|
||||
if (!isAuthenticated || !workspaceId) {
|
||||
() => [authStore.isAuthenticated, workspaceStore.workspaceScopeKey],
|
||||
async ([isAuthenticated]) => {
|
||||
if (!isAuthenticated) {
|
||||
items.value = [];
|
||||
error.value = null;
|
||||
return;
|
||||
|
||||
@@ -11,6 +11,7 @@ export const useWorkspaceStore = defineStore('workspace', () => {
|
||||
|
||||
const workspaces = ref([]);
|
||||
const activeWorkspaceId = ref(null);
|
||||
const visibleWorkspaceIds = ref([]);
|
||||
const isLoading = ref(false);
|
||||
const isCreating = ref(false);
|
||||
const isUpdating = ref(false);
|
||||
@@ -25,11 +26,43 @@ export const useWorkspaceStore = defineStore('workspace', () => {
|
||||
const activeWorkspace = computed(() =>
|
||||
workspaces.value.find(workspace => workspace.id === activeWorkspaceId.value) ?? null
|
||||
);
|
||||
const isAllWorkspacesSelected = computed(() =>
|
||||
activeWorkspaceId.value === null && workspaces.value.length > 1
|
||||
);
|
||||
const visibleWorkspaceCount = computed(() =>
|
||||
activeWorkspaceId.value ? 1 : visibleWorkspaceIds.value.length
|
||||
);
|
||||
const areAllWorkspacesVisible = computed(() =>
|
||||
activeWorkspaceId.value === null &&
|
||||
workspaces.value.length > 1 &&
|
||||
visibleWorkspaceIds.value.length === workspaces.value.length
|
||||
);
|
||||
const visibleWorkspaceIdSet = computed(() => new Set(visibleWorkspaceIds.value));
|
||||
const workspaceScopeKey = computed(() =>
|
||||
activeWorkspaceId.value ?? visibleWorkspaceIds.value.slice().sort().join(',')
|
||||
);
|
||||
|
||||
function allWorkspaceIds() {
|
||||
return workspaces.value.map(workspace => workspace.id);
|
||||
}
|
||||
|
||||
function normalizeVisibleWorkspaces() {
|
||||
const workspaceIds = allWorkspaceIds();
|
||||
const knownWorkspaceIds = new Set(workspaceIds);
|
||||
const nextVisibleWorkspaceIds = visibleWorkspaceIds.value.filter(workspaceId =>
|
||||
knownWorkspaceIds.has(workspaceId)
|
||||
);
|
||||
|
||||
visibleWorkspaceIds.value = nextVisibleWorkspaceIds.length > 0
|
||||
? nextVisibleWorkspaceIds
|
||||
: workspaceIds;
|
||||
}
|
||||
|
||||
async function fetchWorkspaces() {
|
||||
if (!authStore.isAuthenticated) {
|
||||
workspaces.value = [];
|
||||
activeWorkspaceId.value = null;
|
||||
visibleWorkspaceIds.value = [];
|
||||
error.value = null;
|
||||
return;
|
||||
}
|
||||
@@ -40,9 +73,10 @@ export const useWorkspaceStore = defineStore('workspace', () => {
|
||||
try {
|
||||
const response = await client.get('/api/workspaces');
|
||||
workspaces.value = response.data ?? [];
|
||||
normalizeVisibleWorkspaces();
|
||||
|
||||
if (!workspaces.value.some(workspace => workspace.id === activeWorkspaceId.value)) {
|
||||
activeWorkspaceId.value = workspaces.value[0]?.id ?? null;
|
||||
activeWorkspaceId.value = workspaces.value.length > 1 ? null : workspaces.value[0]?.id ?? null;
|
||||
}
|
||||
|
||||
organizationStore.setSelectedOrganizationFromWorkspace(activeWorkspace.value);
|
||||
@@ -75,6 +109,7 @@ export const useWorkspaceStore = defineStore('workspace', () => {
|
||||
workspaces.value = [...workspaces.value, response.data]
|
||||
.sort((left, right) => left.name.localeCompare(right.name));
|
||||
activeWorkspaceId.value = response.data.id;
|
||||
visibleWorkspaceIds.value = [response.data.id];
|
||||
|
||||
try {
|
||||
await client.post('/api/clients', {
|
||||
@@ -172,10 +207,62 @@ export const useWorkspaceStore = defineStore('workspace', () => {
|
||||
|
||||
if (workspaces.value.some(workspace => workspace.id === workspaceId)) {
|
||||
activeWorkspaceId.value = workspaceId;
|
||||
visibleWorkspaceIds.value = [workspaceId];
|
||||
organizationStore.setSelectedOrganizationFromWorkspace(activeWorkspace.value);
|
||||
}
|
||||
}
|
||||
|
||||
function setAllWorkspaces() {
|
||||
if (workspaces.value.length > 1) {
|
||||
activeWorkspaceId.value = null;
|
||||
visibleWorkspaceIds.value = allWorkspaceIds();
|
||||
}
|
||||
}
|
||||
|
||||
function isWorkspaceVisible(workspaceId) {
|
||||
if (activeWorkspaceId.value) {
|
||||
return workspaceId === activeWorkspaceId.value;
|
||||
}
|
||||
|
||||
if (visibleWorkspaceIds.value.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return visibleWorkspaceIdSet.value.has(workspaceId);
|
||||
}
|
||||
|
||||
function toggleWorkspaceVisibility(workspaceId) {
|
||||
if (!workspaces.value.some(workspace => workspace.id === workspaceId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wasFocusedOnSingleWorkspace = Boolean(activeWorkspaceId.value);
|
||||
activeWorkspaceId.value = null;
|
||||
const visibleIds = new Set(
|
||||
wasFocusedOnSingleWorkspace || visibleWorkspaceIds.value.length === 0
|
||||
? allWorkspaceIds()
|
||||
: visibleWorkspaceIds.value
|
||||
);
|
||||
|
||||
if (visibleIds.has(workspaceId)) {
|
||||
visibleIds.delete(workspaceId);
|
||||
} else {
|
||||
visibleIds.add(workspaceId);
|
||||
}
|
||||
|
||||
if (visibleIds.size === 0) {
|
||||
visibleIds.add(workspaceId);
|
||||
}
|
||||
|
||||
const nextVisibleWorkspaceIds = allWorkspaceIds().filter(id => visibleIds.has(id));
|
||||
if (nextVisibleWorkspaceIds.length === 1) {
|
||||
setActiveWorkspace(nextVisibleWorkspaceIds[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
visibleWorkspaceIds.value = nextVisibleWorkspaceIds;
|
||||
}
|
||||
|
||||
async function fetchInvites(workspaceId = activeWorkspaceId.value) {
|
||||
if (!authStore.isAuthenticated || !workspaceId) {
|
||||
invitesByWorkspace.value = {};
|
||||
@@ -257,6 +344,7 @@ export const useWorkspaceStore = defineStore('workspace', () => {
|
||||
if (!isAuthenticated) {
|
||||
workspaces.value = [];
|
||||
activeWorkspaceId.value = null;
|
||||
visibleWorkspaceIds.value = [];
|
||||
error.value = null;
|
||||
return;
|
||||
}
|
||||
@@ -270,6 +358,11 @@ export const useWorkspaceStore = defineStore('workspace', () => {
|
||||
workspaces,
|
||||
activeWorkspaceId,
|
||||
activeWorkspace,
|
||||
visibleWorkspaceIds,
|
||||
isAllWorkspacesSelected,
|
||||
visibleWorkspaceCount,
|
||||
areAllWorkspacesVisible,
|
||||
workspaceScopeKey,
|
||||
isLoading,
|
||||
isCreating,
|
||||
isUpdating,
|
||||
@@ -288,5 +381,8 @@ export const useWorkspaceStore = defineStore('workspace', () => {
|
||||
fetchMembers,
|
||||
inviteMember,
|
||||
setActiveWorkspace,
|
||||
setAllWorkspaces,
|
||||
isWorkspaceVisible,
|
||||
toggleWorkspaceVisibility,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
import {
|
||||
mdiChevronDown,
|
||||
mdiCogOutline,
|
||||
mdiEyeOffOutline,
|
||||
mdiEyeOutline,
|
||||
mdiPlus,
|
||||
mdiSwapHorizontal,
|
||||
} from '@mdi/js';
|
||||
@@ -37,6 +39,7 @@
|
||||
);
|
||||
});
|
||||
const canSwitchWorkspaces = computed(() => visibleWorkspaces.value.length > 1);
|
||||
const canSelectAllWorkspaces = computed(() => visibleWorkspaces.value.length > 1);
|
||||
const canSwitchOrganizations = computed(() => organizationStore.organizations.length > 1);
|
||||
const switchableOrganizations = computed(() =>
|
||||
organizationStore.organizations.filter(
|
||||
@@ -51,8 +54,19 @@
|
||||
const canOpenWorkspaceMenu = computed(() =>
|
||||
canSwitchWorkspaces.value || canSwitchOrganizations.value || canManageWorkspaces.value || Boolean(activeOrganization.value)
|
||||
);
|
||||
const activeWorkspaceName = computed(() =>
|
||||
workspaceStore.activeWorkspace?.name || t('nav.noWorkspace')
|
||||
const activeWorkspaceName = computed(() => {
|
||||
if (workspaceStore.areAllWorkspacesVisible) {
|
||||
return t('workspaceSelector.allWorkspaces');
|
||||
}
|
||||
|
||||
if (workspaceStore.isAllWorkspacesSelected) {
|
||||
return t('workspaceSelector.multipleWorkspaces');
|
||||
}
|
||||
|
||||
return workspaceStore.activeWorkspace?.name || t('nav.noWorkspace');
|
||||
});
|
||||
const activeWorkspaceLogoUrl = computed(() =>
|
||||
workspaceStore.isAllWorkspacesSelected ? null : workspaceStore.activeWorkspace?.logoUrl
|
||||
);
|
||||
const activeOrganizationName = computed(() =>
|
||||
activeOrganization.value?.name || t('workspaceSelector.noOrganization')
|
||||
@@ -73,13 +87,30 @@
|
||||
isOrganizationListOpen.value = false;
|
||||
}
|
||||
|
||||
function chooseAllWorkspaces() {
|
||||
workspaceStore.setAllWorkspaces();
|
||||
isWorkspaceMenuOpen.value = false;
|
||||
isOrganizationListOpen.value = false;
|
||||
}
|
||||
|
||||
function toggleWorkspaceVisibility(workspaceId) {
|
||||
workspaceStore.toggleWorkspaceVisibility(workspaceId);
|
||||
}
|
||||
|
||||
function chooseOrganization(organizationId) {
|
||||
organizationStore.setSelectedOrganization(organizationId);
|
||||
|
||||
const nextWorkspace = workspaceStore.workspaces.find(
|
||||
workspace => workspace.organizationId === organizationId
|
||||
);
|
||||
workspaceStore.setActiveWorkspace(nextWorkspace?.id ?? null);
|
||||
const organizationWorkspaceCount = workspaceStore.workspaces.filter(
|
||||
workspace => workspace.organizationId === organizationId
|
||||
).length;
|
||||
if (organizationWorkspaceCount > 1) {
|
||||
workspaceStore.setAllWorkspaces();
|
||||
} else {
|
||||
workspaceStore.setActiveWorkspace(nextWorkspace?.id ?? null);
|
||||
}
|
||||
isOrganizationListOpen.value = false;
|
||||
}
|
||||
|
||||
@@ -137,7 +168,7 @@
|
||||
>
|
||||
<AppAvatar
|
||||
:name="activeWorkspaceName"
|
||||
:src="workspaceStore.activeWorkspace?.logoUrl"
|
||||
:src="activeWorkspaceLogoUrl"
|
||||
size="sm"
|
||||
/>
|
||||
<span class="label workspace-trigger-label">{{ activeWorkspaceName }}</span>
|
||||
@@ -153,11 +184,31 @@
|
||||
v-if="isWorkspaceMenuOpen"
|
||||
class="user-menu"
|
||||
>
|
||||
<button
|
||||
v-if="canSelectAllWorkspaces"
|
||||
class="user-menu-item all-workspaces-item"
|
||||
:class="{ 'user-menu-item-active': workspaceStore.isAllWorkspacesSelected }"
|
||||
type="button"
|
||||
@click="chooseAllWorkspaces"
|
||||
>
|
||||
<AppAvatar
|
||||
:name="t('workspaceSelector.allWorkspaces')"
|
||||
size="sm"
|
||||
/>
|
||||
<span class="user-menu-item-copy">
|
||||
<span>{{ t('workspaceSelector.allWorkspaces') }}</span>
|
||||
<small>{{ t('workspaceSelector.allWorkspacesDescription') }}</small>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div
|
||||
v-for="workspace in visibleWorkspaces"
|
||||
:key="workspace.id"
|
||||
class="workspace-menu-row"
|
||||
:class="{ 'user-menu-item-active': workspace.id === workspaceStore.activeWorkspaceId }"
|
||||
:class="{
|
||||
'user-menu-item-active': workspace.id === workspaceStore.activeWorkspaceId,
|
||||
'workspace-menu-row-muted': workspaceStore.isAllWorkspacesSelected && !workspaceStore.isWorkspaceVisible(workspace.id),
|
||||
}"
|
||||
>
|
||||
<button
|
||||
class="user-menu-item workspace-menu-select"
|
||||
@@ -175,6 +226,16 @@
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="canSelectAllWorkspaces"
|
||||
class="workspace-visibility-button"
|
||||
type="button"
|
||||
:aria-label="workspaceStore.isWorkspaceVisible(workspace.id) ? t('workspaceSelector.hideWorkspace') : t('workspaceSelector.showWorkspace')"
|
||||
@click.stop="toggleWorkspaceVisibility(workspace.id)"
|
||||
>
|
||||
<v-icon :icon="workspaceStore.isWorkspaceVisible(workspace.id) ? mdiEyeOutline : mdiEyeOffOutline" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="canManageWorkspaces"
|
||||
class="workspace-settings-button"
|
||||
@@ -326,6 +387,16 @@
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.workspace-menu-row-muted {
|
||||
opacity: 0.58;
|
||||
}
|
||||
|
||||
.all-workspaces-item {
|
||||
@apply mb-1 border;
|
||||
border-color: rgba(23, 32, 51, 0.08);
|
||||
background: rgba(23, 32, 51, 0.03);
|
||||
}
|
||||
|
||||
.workspace-menu-select {
|
||||
@apply min-w-0 flex-1;
|
||||
}
|
||||
@@ -339,11 +410,18 @@
|
||||
color: #526178;
|
||||
}
|
||||
|
||||
.workspace-visibility-button {
|
||||
@apply flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full transition-colors;
|
||||
color: #526178;
|
||||
}
|
||||
|
||||
.workspace-visibility-button:hover,
|
||||
.workspace-settings-button:hover {
|
||||
background: rgba(23, 32, 51, 0.1);
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.workspace-visibility-button :deep(.v-icon),
|
||||
.workspace-settings-button :deep(.v-icon) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@@ -360,10 +360,15 @@
|
||||
"saving": "Saving..."
|
||||
},
|
||||
"workspaceSelector": {
|
||||
"allWorkspaces": "All Workspaces",
|
||||
"allWorkspacesDescription": "Show every workspace",
|
||||
"createAction": "Add workspace",
|
||||
"hideWorkspace": "Hide workspace",
|
||||
"multipleWorkspaces": "Multiple Workspaces",
|
||||
"organizationLabel": "Organization",
|
||||
"organizationSettings": "Organization settings",
|
||||
"noOrganization": "No organization",
|
||||
"showWorkspace": "Show workspace",
|
||||
"workspaceSettings": "Workspace settings"
|
||||
},
|
||||
"workspaceCreate": {
|
||||
|
||||
@@ -360,10 +360,15 @@
|
||||
"saving": "Enregistrement..."
|
||||
},
|
||||
"workspaceSelector": {
|
||||
"allWorkspaces": "Tous les espaces",
|
||||
"allWorkspacesDescription": "Afficher tous les espaces",
|
||||
"createAction": "Ajouter un espace",
|
||||
"hideWorkspace": "Masquer l'espace",
|
||||
"multipleWorkspaces": "Plusieurs espaces",
|
||||
"organizationLabel": "Organisation",
|
||||
"organizationSettings": "Parametres de l'organisation",
|
||||
"noOrganization": "Aucune organisation",
|
||||
"showWorkspace": "Afficher l'espace",
|
||||
"workspaceSettings": "Parametres de l'espace"
|
||||
},
|
||||
"workspaceCreate": {
|
||||
|
||||
Reference in New Issue
Block a user