Add multi-workspace selector scope

This commit is contained in:
2026-05-05 13:20:44 -04:00
parent 78a7517de7
commit 0d4188b64e
11 changed files with 270 additions and 37 deletions

View 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`

View File

@@ -15,7 +15,7 @@ export const useCampaignsStore = defineStore('campaigns', () => {
const error = ref(null); const error = ref(null);
async function fetchCampaigns() { async function fetchCampaigns() {
if (!authStore.isAuthenticated || !workspaceStore.activeWorkspaceId) { if (!authStore.isAuthenticated) {
campaigns.value = []; campaigns.value = [];
error.value = null; error.value = null;
return; return;
@@ -27,11 +27,13 @@ export const useCampaignsStore = defineStore('campaigns', () => {
try { try {
const response = await client.get('/api/campaigns', { const response = await client.get('/api/campaigns', {
params: { params: {
workspaceId: workspaceStore.activeWorkspaceId, workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
}, },
}); });
campaigns.value = response.data ?? []; campaigns.value = (response.data ?? []).filter(campaign =>
workspaceStore.isWorkspaceVisible(campaign.workspaceId)
);
} catch (fetchError) { } catch (fetchError) {
console.error('Failed to fetch campaigns:', fetchError); console.error('Failed to fetch campaigns:', fetchError);
campaigns.value = []; campaigns.value = [];
@@ -75,9 +77,9 @@ export const useCampaignsStore = defineStore('campaigns', () => {
} }
watch( watch(
() => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId], () => [authStore.isAuthenticated, workspaceStore.workspaceScopeKey],
async ([isAuthenticated, workspaceId]) => { async ([isAuthenticated]) => {
if (!isAuthenticated || !workspaceId) { if (!isAuthenticated) {
campaigns.value = []; campaigns.value = [];
error.value = null; error.value = null;
return; return;

View File

@@ -14,6 +14,7 @@ export const useChannelsStore = defineStore('channels', () => {
const isCreating = ref(false); const isCreating = ref(false);
const error = ref(null); const error = ref(null);
const loadedWorkspaceId = ref(null); const loadedWorkspaceId = ref(null);
const allWorkspacesKey = '__all__';
const availableNetworks = [ const availableNetworks = [
'Instagram', 'Instagram',
@@ -28,15 +29,16 @@ export const useChannelsStore = defineStore('channels', () => {
async function fetchChannels({ force = false } = {}) { async function fetchChannels({ force = false } = {}) {
const currentWorkspaceId = workspaceStore.activeWorkspaceId; const currentWorkspaceId = workspaceStore.activeWorkspaceId;
const currentScopeKey = currentWorkspaceId ?? workspaceStore.workspaceScopeKey ?? allWorkspacesKey;
if (!authStore.isAuthenticated || !currentWorkspaceId) { if (!authStore.isAuthenticated) {
channels.value = []; channels.value = [];
error.value = null; error.value = null;
loadedWorkspaceId.value = null; loadedWorkspaceId.value = null;
return; return;
} }
if (!force && loadedWorkspaceId.value === currentWorkspaceId) { if (!force && loadedWorkspaceId.value === currentScopeKey) {
return; return;
} }
@@ -46,12 +48,14 @@ export const useChannelsStore = defineStore('channels', () => {
try { try {
const response = await client.get('/api/channels', { const response = await client.get('/api/channels', {
params: { params: {
workspaceId: currentWorkspaceId, workspaceId: currentWorkspaceId ?? undefined,
}, },
}); });
channels.value = response.data ?? []; channels.value = (response.data ?? []).filter(channel =>
loadedWorkspaceId.value = currentWorkspaceId; workspaceStore.isWorkspaceVisible(channel.workspaceId)
);
loadedWorkspaceId.value = currentScopeKey;
} catch (fetchError) { } catch (fetchError) {
console.error('Failed to fetch channels:', fetchError); console.error('Failed to fetch channels:', fetchError);
channels.value = []; channels.value = [];
@@ -101,9 +105,9 @@ export const useChannelsStore = defineStore('channels', () => {
} }
watch( watch(
() => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId], () => [authStore.isAuthenticated, workspaceStore.workspaceScopeKey],
async ([isAuthenticated, workspaceId]) => { async ([isAuthenticated]) => {
if (!isAuthenticated || !workspaceId) { if (!isAuthenticated) {
channels.value = []; channels.value = [];
error.value = null; error.value = null;
loadedWorkspaceId.value = null; loadedWorkspaceId.value = null;

View File

@@ -47,10 +47,12 @@
.filter(channel => channel.network) .filter(channel => channel.network)
.map(channel => { .map(channel => {
const metrics = buildMetrics(channel.name); const metrics = buildMetrics(channel.name);
const workspace = workspaceStore.workspaces.find(candidate => candidate.id === channel.workspaceId);
return { return {
...channel, ...channel,
...metrics, ...metrics,
workspaceName: workspace?.name ?? t('nav.noWorkspace'),
}; };
}) })
); );
@@ -215,7 +217,7 @@
> >
<div class="channel-header"> <div class="channel-header">
<strong>{{ channel.name }}</strong> <strong>{{ channel.name }}</strong>
<span>{{ workspaceStore.activeWorkspace?.name || t('nav.noWorkspace') }}</span> <span>{{ channel.workspaceName }}</span>
</div> </div>
<div class="channel-metrics"> <div class="channel-metrics">

View File

@@ -25,7 +25,7 @@ export const useClientsStore = defineStore('clients', () => {
}); });
async function fetchClients() { async function fetchClients() {
if (!authStore.isAuthenticated || !workspaceStore.activeWorkspaceId) { if (!authStore.isAuthenticated) {
clients.value = []; clients.value = [];
error.value = null; error.value = null;
return; return;
@@ -37,11 +37,13 @@ export const useClientsStore = defineStore('clients', () => {
try { try {
const response = await client.get('/api/clients', { const response = await client.get('/api/clients', {
params: { params: {
workspaceId: workspaceStore.activeWorkspaceId, workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
}, },
}); });
clients.value = response.data ?? []; clients.value = (response.data ?? []).filter(candidate =>
workspaceStore.isWorkspaceVisible(candidate.workspaceId)
);
} catch (fetchError) { } catch (fetchError) {
console.error('Failed to fetch clients:', fetchError); console.error('Failed to fetch clients:', fetchError);
clients.value = []; clients.value = [];
@@ -153,9 +155,9 @@ export const useClientsStore = defineStore('clients', () => {
} }
watch( watch(
() => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId], () => [authStore.isAuthenticated, workspaceStore.workspaceScopeKey],
async ([isAuthenticated, workspaceId]) => { async ([isAuthenticated]) => {
if (!isAuthenticated || !workspaceId) { if (!isAuthenticated) {
clients.value = []; clients.value = [];
error.value = null; error.value = null;
return; return;

View File

@@ -24,6 +24,10 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
status: false, status: false,
}); });
function currentItemWorkspaceId() {
return item.value?.workspaceId ?? workspaceStore.activeWorkspaceId;
}
function reset() { function reset() {
item.value = null; item.value = null;
revisions.value = []; revisions.value = [];
@@ -54,7 +58,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
client.get('/api/approvals', { params: { contentItemId } }), client.get('/api/approvals', { params: { contentItemId } }),
client.get('/api/notifications', { client.get('/api/notifications', {
params: { params: {
workspaceId: workspaceStore.activeWorkspaceId, workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
contentItemId, contentItemId,
}, },
}), }),
@@ -97,7 +101,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
const response = await client.post('/api/assets/google-drive', { const response = await client.post('/api/assets/google-drive', {
...payload, ...payload,
contentItemId, contentItemId,
workspaceId: workspaceStore.activeWorkspaceId, workspaceId: currentItemWorkspaceId(),
}); });
if (response.data) { if (response.data) {
assets.value = [...assets.value, 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', { const response = await client.post('/api/comments', {
...payload, ...payload,
contentItemId, contentItemId,
workspaceId: workspaceStore.activeWorkspaceId, workspaceId: currentItemWorkspaceId(),
}); });
if (response.data) { if (response.data) {
comments.value = [...comments.value, response.data]; comments.value = [...comments.value, response.data];
@@ -202,7 +206,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
async function fetchNotifications(contentItemId) { async function fetchNotifications(contentItemId) {
const response = await client.get('/api/notifications', { const response = await client.get('/api/notifications', {
params: { params: {
workspaceId: workspaceStore.activeWorkspaceId, workspaceId: currentItemWorkspaceId() ?? undefined,
contentItemId, contentItemId,
}, },
}); });

View File

@@ -20,7 +20,7 @@ export const useContentItemsStore = defineStore('content-items', () => {
); );
async function fetchContentItems(filters = {}) { async function fetchContentItems(filters = {}) {
if (!authStore.isAuthenticated || !workspaceStore.activeWorkspaceId) { if (!authStore.isAuthenticated) {
items.value = []; items.value = [];
error.value = null; error.value = null;
return; return;
@@ -32,13 +32,15 @@ export const useContentItemsStore = defineStore('content-items', () => {
try { try {
const response = await client.get('/api/content-items', { const response = await client.get('/api/content-items', {
params: { params: {
workspaceId: workspaceStore.activeWorkspaceId, workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
clientId: filters.clientId, clientId: filters.clientId,
campaignId: filters.campaignId, campaignId: filters.campaignId,
}, },
}); });
items.value = response.data ?? []; items.value = (response.data ?? []).filter(item =>
workspaceStore.isWorkspaceVisible(item.workspaceId)
);
} catch (fetchError) { } catch (fetchError) {
console.error('Failed to fetch content items:', fetchError); console.error('Failed to fetch content items:', fetchError);
items.value = []; items.value = [];
@@ -86,9 +88,9 @@ export const useContentItemsStore = defineStore('content-items', () => {
} }
watch( watch(
() => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId], () => [authStore.isAuthenticated, workspaceStore.workspaceScopeKey],
async ([isAuthenticated, workspaceId]) => { async ([isAuthenticated]) => {
if (!isAuthenticated || !workspaceId) { if (!isAuthenticated) {
items.value = []; items.value = [];
error.value = null; error.value = null;
return; return;

View File

@@ -11,6 +11,7 @@ export const useWorkspaceStore = defineStore('workspace', () => {
const workspaces = ref([]); const workspaces = ref([]);
const activeWorkspaceId = ref(null); const activeWorkspaceId = ref(null);
const visibleWorkspaceIds = ref([]);
const isLoading = ref(false); const isLoading = ref(false);
const isCreating = ref(false); const isCreating = ref(false);
const isUpdating = ref(false); const isUpdating = ref(false);
@@ -25,11 +26,43 @@ export const useWorkspaceStore = defineStore('workspace', () => {
const activeWorkspace = computed(() => const activeWorkspace = computed(() =>
workspaces.value.find(workspace => workspace.id === activeWorkspaceId.value) ?? null 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() { async function fetchWorkspaces() {
if (!authStore.isAuthenticated) { if (!authStore.isAuthenticated) {
workspaces.value = []; workspaces.value = [];
activeWorkspaceId.value = null; activeWorkspaceId.value = null;
visibleWorkspaceIds.value = [];
error.value = null; error.value = null;
return; return;
} }
@@ -40,9 +73,10 @@ export const useWorkspaceStore = defineStore('workspace', () => {
try { try {
const response = await client.get('/api/workspaces'); const response = await client.get('/api/workspaces');
workspaces.value = response.data ?? []; workspaces.value = response.data ?? [];
normalizeVisibleWorkspaces();
if (!workspaces.value.some(workspace => workspace.id === activeWorkspaceId.value)) { 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); organizationStore.setSelectedOrganizationFromWorkspace(activeWorkspace.value);
@@ -75,6 +109,7 @@ export const useWorkspaceStore = defineStore('workspace', () => {
workspaces.value = [...workspaces.value, response.data] workspaces.value = [...workspaces.value, response.data]
.sort((left, right) => left.name.localeCompare(right.name)); .sort((left, right) => left.name.localeCompare(right.name));
activeWorkspaceId.value = response.data.id; activeWorkspaceId.value = response.data.id;
visibleWorkspaceIds.value = [response.data.id];
try { try {
await client.post('/api/clients', { await client.post('/api/clients', {
@@ -172,10 +207,62 @@ export const useWorkspaceStore = defineStore('workspace', () => {
if (workspaces.value.some(workspace => workspace.id === workspaceId)) { if (workspaces.value.some(workspace => workspace.id === workspaceId)) {
activeWorkspaceId.value = workspaceId; activeWorkspaceId.value = workspaceId;
visibleWorkspaceIds.value = [workspaceId];
organizationStore.setSelectedOrganizationFromWorkspace(activeWorkspace.value); 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) { async function fetchInvites(workspaceId = activeWorkspaceId.value) {
if (!authStore.isAuthenticated || !workspaceId) { if (!authStore.isAuthenticated || !workspaceId) {
invitesByWorkspace.value = {}; invitesByWorkspace.value = {};
@@ -257,6 +344,7 @@ export const useWorkspaceStore = defineStore('workspace', () => {
if (!isAuthenticated) { if (!isAuthenticated) {
workspaces.value = []; workspaces.value = [];
activeWorkspaceId.value = null; activeWorkspaceId.value = null;
visibleWorkspaceIds.value = [];
error.value = null; error.value = null;
return; return;
} }
@@ -270,6 +358,11 @@ export const useWorkspaceStore = defineStore('workspace', () => {
workspaces, workspaces,
activeWorkspaceId, activeWorkspaceId,
activeWorkspace, activeWorkspace,
visibleWorkspaceIds,
isAllWorkspacesSelected,
visibleWorkspaceCount,
areAllWorkspacesVisible,
workspaceScopeKey,
isLoading, isLoading,
isCreating, isCreating,
isUpdating, isUpdating,
@@ -288,5 +381,8 @@ export const useWorkspaceStore = defineStore('workspace', () => {
fetchMembers, fetchMembers,
inviteMember, inviteMember,
setActiveWorkspace, setActiveWorkspace,
setAllWorkspaces,
isWorkspaceVisible,
toggleWorkspaceVisibility,
}; };
}); });

View File

@@ -12,6 +12,8 @@
import { import {
mdiChevronDown, mdiChevronDown,
mdiCogOutline, mdiCogOutline,
mdiEyeOffOutline,
mdiEyeOutline,
mdiPlus, mdiPlus,
mdiSwapHorizontal, mdiSwapHorizontal,
} from '@mdi/js'; } from '@mdi/js';
@@ -37,6 +39,7 @@
); );
}); });
const canSwitchWorkspaces = computed(() => visibleWorkspaces.value.length > 1); const canSwitchWorkspaces = computed(() => visibleWorkspaces.value.length > 1);
const canSelectAllWorkspaces = computed(() => visibleWorkspaces.value.length > 1);
const canSwitchOrganizations = computed(() => organizationStore.organizations.length > 1); const canSwitchOrganizations = computed(() => organizationStore.organizations.length > 1);
const switchableOrganizations = computed(() => const switchableOrganizations = computed(() =>
organizationStore.organizations.filter( organizationStore.organizations.filter(
@@ -51,8 +54,19 @@
const canOpenWorkspaceMenu = computed(() => const canOpenWorkspaceMenu = computed(() =>
canSwitchWorkspaces.value || canSwitchOrganizations.value || canManageWorkspaces.value || Boolean(activeOrganization.value) canSwitchWorkspaces.value || canSwitchOrganizations.value || canManageWorkspaces.value || Boolean(activeOrganization.value)
); );
const activeWorkspaceName = computed(() => const activeWorkspaceName = computed(() => {
workspaceStore.activeWorkspace?.name || t('nav.noWorkspace') 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(() => const activeOrganizationName = computed(() =>
activeOrganization.value?.name || t('workspaceSelector.noOrganization') activeOrganization.value?.name || t('workspaceSelector.noOrganization')
@@ -73,13 +87,30 @@
isOrganizationListOpen.value = false; isOrganizationListOpen.value = false;
} }
function chooseAllWorkspaces() {
workspaceStore.setAllWorkspaces();
isWorkspaceMenuOpen.value = false;
isOrganizationListOpen.value = false;
}
function toggleWorkspaceVisibility(workspaceId) {
workspaceStore.toggleWorkspaceVisibility(workspaceId);
}
function chooseOrganization(organizationId) { function chooseOrganization(organizationId) {
organizationStore.setSelectedOrganization(organizationId); organizationStore.setSelectedOrganization(organizationId);
const nextWorkspace = workspaceStore.workspaces.find( const nextWorkspace = workspaceStore.workspaces.find(
workspace => workspace.organizationId === organizationId 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; isOrganizationListOpen.value = false;
} }
@@ -137,7 +168,7 @@
> >
<AppAvatar <AppAvatar
:name="activeWorkspaceName" :name="activeWorkspaceName"
:src="workspaceStore.activeWorkspace?.logoUrl" :src="activeWorkspaceLogoUrl"
size="sm" size="sm"
/> />
<span class="label workspace-trigger-label">{{ activeWorkspaceName }}</span> <span class="label workspace-trigger-label">{{ activeWorkspaceName }}</span>
@@ -153,11 +184,31 @@
v-if="isWorkspaceMenuOpen" v-if="isWorkspaceMenuOpen"
class="user-menu" 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 <div
v-for="workspace in visibleWorkspaces" v-for="workspace in visibleWorkspaces"
:key="workspace.id" :key="workspace.id"
class="workspace-menu-row" 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 <button
class="user-menu-item workspace-menu-select" class="user-menu-item workspace-menu-select"
@@ -175,6 +226,16 @@
</span> </span>
</button> </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 <button
v-if="canManageWorkspaces" v-if="canManageWorkspaces"
class="workspace-settings-button" class="workspace-settings-button"
@@ -326,6 +387,16 @@
color: #172033; 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 { .workspace-menu-select {
@apply min-w-0 flex-1; @apply min-w-0 flex-1;
} }
@@ -339,11 +410,18 @@
color: #526178; 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 { .workspace-settings-button:hover {
background: rgba(23, 32, 51, 0.1); background: rgba(23, 32, 51, 0.1);
color: #172033; color: #172033;
} }
.workspace-visibility-button :deep(.v-icon),
.workspace-settings-button :deep(.v-icon) { .workspace-settings-button :deep(.v-icon) {
font-size: 1rem; font-size: 1rem;
} }

View File

@@ -360,10 +360,15 @@
"saving": "Saving..." "saving": "Saving..."
}, },
"workspaceSelector": { "workspaceSelector": {
"allWorkspaces": "All Workspaces",
"allWorkspacesDescription": "Show every workspace",
"createAction": "Add workspace", "createAction": "Add workspace",
"hideWorkspace": "Hide workspace",
"multipleWorkspaces": "Multiple Workspaces",
"organizationLabel": "Organization", "organizationLabel": "Organization",
"organizationSettings": "Organization settings", "organizationSettings": "Organization settings",
"noOrganization": "No organization", "noOrganization": "No organization",
"showWorkspace": "Show workspace",
"workspaceSettings": "Workspace settings" "workspaceSettings": "Workspace settings"
}, },
"workspaceCreate": { "workspaceCreate": {

View File

@@ -360,10 +360,15 @@
"saving": "Enregistrement..." "saving": "Enregistrement..."
}, },
"workspaceSelector": { "workspaceSelector": {
"allWorkspaces": "Tous les espaces",
"allWorkspacesDescription": "Afficher tous les espaces",
"createAction": "Ajouter un espace", "createAction": "Ajouter un espace",
"hideWorkspace": "Masquer l'espace",
"multipleWorkspaces": "Plusieurs espaces",
"organizationLabel": "Organisation", "organizationLabel": "Organisation",
"organizationSettings": "Parametres de l'organisation", "organizationSettings": "Parametres de l'organisation",
"noOrganization": "Aucune organisation", "noOrganization": "Aucune organisation",
"showWorkspace": "Afficher l'espace",
"workspaceSettings": "Parametres de l'espace" "workspaceSettings": "Parametres de l'espace"
}, },
"workspaceCreate": { "workspaceCreate": {