Skip to content

Commit dc14c31

Browse files
committed
[MS] Open contextuel menu from breadcrumb
1 parent 94b0f89 commit dc14c31

11 files changed

Lines changed: 421 additions & 37 deletions

File tree

client/src/components/header/HeaderBreadcrumbs.vue

Lines changed: 116 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,22 @@
44
<div class="header-breadcrumbs">
55
<ion-breadcrumbs
66
v-if="isLargeDisplay"
7-
class="breadcrumb"
7+
class="breadcrumb-container"
88
@ion-collapsed-click="openPopover($event)"
99
:max-items="maxShown"
1010
:items-before-collapse="itemsBeforeCollapse"
1111
:items-after-collapse="itemsAfterCollapse"
1212
>
1313
<ion-breadcrumb
1414
v-for="path in pathNodes"
15-
@click="navigateTo(path)"
15+
@click="path.display !== currentFolderName ? navigateTo(path) : null"
16+
@contextmenu.prevent="onRightClick"
1617
:path="path"
1718
class="breadcrumb-element breadcrumb-normal"
19+
:class="{
20+
'breadcrumb-element--home': props.pathNodes.length === 1,
21+
'breadcrumb-element--active': path.display === currentFolderName || (props.pathNodes.length === 1 && props.fromHeaderPage),
22+
}"
1823
:key="path.id"
1924
ref="breadcrumb"
2025
>
@@ -26,6 +31,12 @@
2631
<div class="breadcrumb-text">
2732
{{ path.display ? path.display : $msTranslate(path.title) }}
2833
</div>
34+
<ion-icon
35+
v-if="currentFolderName === path.display"
36+
:icon="ellipsisHorizontal"
37+
class="option-icon"
38+
@click="breadcrumbOptionClick(path, $event)"
39+
/>
2940
</ion-breadcrumb>
3041
</ion-breadcrumbs>
3142
<div
@@ -59,12 +70,17 @@ export interface RouterPathNode {
5970

6071
<script setup lang="ts">
6172
import HeaderBreadcrumbPopover from '@/components/header/HeaderBreadcrumbPopover.vue';
62-
import { WorkspaceName } from '@/parsec';
73+
import { openWorkspaceContextMenu } from '@/components/workspaces';
74+
import { StartedWorkspaceInfo, WorkspaceInfo, WorkspaceName, WorkspaceRole } from '@/parsec';
6375
import { Query, Routes } from '@/router';
76+
import { EventDistributor, EventDistributorKey, Events, OpenFolderBreadcrumbContextMenuData } from '@/services/eventDistributor';
77+
import { InformationManager } from '@/services/informationManager';
78+
import { StorageManager } from '@/services/storageManager';
79+
import { WorkspaceAttributes } from '@/services/workspaceAttributes';
6480
import { IonBreadcrumb, IonBreadcrumbs, IonIcon, IonText, popoverController } from '@ionic/vue';
65-
import { chevronDown } from 'ionicons/icons';
81+
import { chevronDown, ellipsisHorizontal } from 'ionicons/icons';
6682
import { Translatable, useWindowSize } from 'megashark-lib';
67-
import { computed, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
83+
import { computed, inject, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
6884
6985
const props = withDefaults(
7086
defineProps<{
@@ -73,21 +89,29 @@ const props = withDefaults(
7389
itemsBeforeCollapse?: number;
7490
itemsAfterCollapse?: number;
7591
maxShown?: number;
76-
fromHeaderPage?: boolean;
92+
fromHeaderPage?: {
93+
workspaceAttributes: WorkspaceAttributes;
94+
workspace: StartedWorkspaceInfo | WorkspaceInfo;
95+
ownRole: WorkspaceRole;
96+
eventDistributor: EventDistributor;
97+
informationManager: InformationManager;
98+
storageManager: StorageManager;
99+
};
77100
availableWidth?: number;
78101
showParentNode?: boolean;
79102
}>(),
80103
{
81104
itemsBeforeCollapse: 2,
82105
itemsAfterCollapse: 1,
83106
maxShown: 3,
84-
fromHeaderPage: false,
107+
fromHeaderPage: undefined,
85108
availableWidth: 0,
86109
showParentNode: true,
87110
},
88111
);
89112
90113
const { windowWidth, isLargeDisplay, isSmallDisplay } = useWindowSize();
114+
const eventDistributor: EventDistributor = inject(EventDistributorKey)!;
91115
const breadcrumbRef = useTemplateRef<HTMLIonBreadcrumbElement>('breadcrumb');
92116
const breadcrumbWidthProperty = ref('');
93117
let ignoreNextEvent = false;
@@ -121,14 +145,37 @@ const currentFolderName = computed(() => {
121145
return '';
122146
}
123147
124-
if (props.pathNodes.length === 1) {
125-
return props.workspaceName;
148+
if (props.pathNodes.length === 1 && props.fromHeaderPage) {
149+
return props.fromHeaderPage.workspace.currentName;
126150
}
127151
128152
const lastNode = props.pathNodes[props.pathNodes.length - 1];
129153
return lastNode.display || props.workspaceName;
130154
});
131155
156+
async function breadcrumbOptionClick(path: RouterPathNode, event: Event): Promise<void> {
157+
if (!props.fromHeaderPage) {
158+
return;
159+
}
160+
161+
if (path.display === props.workspaceName) {
162+
await openWorkspaceContextMenu(
163+
event,
164+
props.fromHeaderPage.workspace as WorkspaceInfo,
165+
props.fromHeaderPage.workspaceAttributes,
166+
props.fromHeaderPage.eventDistributor,
167+
props.fromHeaderPage.informationManager,
168+
props.fromHeaderPage.storageManager,
169+
true,
170+
);
171+
} else {
172+
await eventDistributor.dispatchEvent(Events.OpenFolderBreadcrumbContextMenu, {
173+
event,
174+
role: props.fromHeaderPage.ownRole,
175+
} as OpenFolderBreadcrumbContextMenuData);
176+
}
177+
}
178+
132179
function setBreadcrumbWidth(): void {
133180
if (props.availableWidth > 0 && breadcrumbRef.value) {
134181
let visibleNodes = Math.min(props.pathNodes.length, props.maxShown);
@@ -192,6 +239,12 @@ function navigateTo(path: RouterPathNode): void {
192239
}
193240
emits('change', path);
194241
}
242+
243+
async function onRightClick(event: Event): Promise<void> {
244+
event.preventDefault();
245+
event.stopPropagation();
246+
await breadcrumbOptionClick(props.pathNodes[props.pathNodes.length - 1], event);
247+
}
195248
</script>
196249

197250
<style scoped lang="scss">
@@ -207,13 +260,13 @@ function navigateTo(path: RouterPathNode): void {
207260
}
208261
}
209262
210-
.breadcrumb {
263+
.breadcrumb-container {
211264
padding: 0;
212265
color: var(--parsec-color-light-secondary-grey);
213266
display: flex;
214267
flex-wrap: nowrap;
215268
216-
&-element {
269+
.breadcrumb-element {
217270
.main-icon {
218271
font-size: 1.125rem;
219272
}
@@ -240,6 +293,7 @@ function navigateTo(path: RouterPathNode): void {
240293
&:hover:not(.breadcrumb-collapsed) {
241294
color: var(--parsec-color-light-secondary-text);
242295
position: relative;
296+
cursor: default;
243297
244298
&::after {
245299
content: '';
@@ -259,28 +313,65 @@ function navigateTo(path: RouterPathNode): void {
259313
}
260314
}
261315
}
262-
}
263-
264-
// Defined by ionic
265-
// eslint-disable-next-line vue-scoped-css/no-unused-selector
266-
&-active {
267-
color: var(--parsec-color-light-primary-700);
268-
pointer-events: none;
269316
270-
&::part(native) {
317+
.breadcrumb-text {
318+
overflow-x: hidden;
319+
text-overflow: ellipsis;
320+
white-space: nowrap;
321+
font-size: 1rem;
322+
pointer-events: none;
271323
cursor: default;
272324
}
273325
274-
.main-icon {
326+
&--active:not(.breadcrumb-element--home) {
327+
color: var(--parsec-color-light-secondary-text);
328+
--background: none;
329+
330+
&::part(native) {
331+
border-radius: var(--parsec-radius-6);
332+
}
333+
334+
.breadcrumb-text {
335+
pointer-events: none;
336+
cursor: default;
337+
}
338+
339+
&:hover:not(.breadcrumb-collapsed) {
340+
color: var(--parsec-color-light-primary-700);
341+
position: relative;
342+
343+
&::after {
344+
content: none;
345+
}
346+
}
347+
348+
.option-icon:hover {
349+
background: var(--parsec-color-light-secondary-medium);
350+
border-radius: var(--parsec-radius-8);
351+
pointer-events: all;
352+
opacity: 1;
353+
cursor: pointer;
354+
}
355+
}
356+
357+
&--home {
275358
color: var(--parsec-color-light-primary-700);
276-
margin-right: 0.5rem;
359+
360+
.main-icon {
361+
color: var(--parsec-color-light-primary-700);
362+
margin-right: 0.5rem;
363+
}
277364
}
278365
}
279366
280-
&-text {
281-
overflow-x: hidden;
282-
text-overflow: ellipsis;
283-
white-space: nowrap;
367+
.option-icon {
368+
font-size: 1.25rem;
369+
padding: 0.25rem;
370+
color: var(--parsec-color-light-secondary-text);
371+
margin-left: 0.25rem;
372+
opacity: 0.3;
373+
pointer-events: all;
374+
flex-shrink: 0;
284375
}
285376
}
286377

client/src/services/eventDistributor.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ enum Events {
4949
EntryDeleted = 1 << 26,
5050
EntrySyncProgress = 1 << 27,
5151
WorkspaceMountpointsSync = 1 << 28,
52-
OpenContextMenu = 1 << 28,
52+
OpenContextMenu = 1 << 29,
53+
OpenFolderBreadcrumbContextMenu = 1 << 30,
5354
}
5455

5556
interface WorkspaceCreatedData {
@@ -135,6 +136,11 @@ interface OpenContextualMenuData {
135136
event: Event;
136137
}
137138

139+
interface OpenFolderBreadcrumbContextMenuData {
140+
event: Event;
141+
role: WorkspaceRole;
142+
}
143+
138144
type EventData =
139145
| WorkspaceCreatedData
140146
| InvitationUpdatedData
@@ -151,7 +157,8 @@ type EventData =
151157
| EntryRenamedData
152158
| EntryDeletedData
153159
| WorkspaceMountpointInfo
154-
| OpenContextualMenuData;
160+
| OpenContextualMenuData
161+
| OpenFolderBreadcrumbContextMenuData;
155162

156163
interface Callback {
157164
id: string;
@@ -242,6 +249,7 @@ export {
242249
InvitationUpdatedData,
243250
MenuActionData,
244251
OpenContextualMenuData,
252+
OpenFolderBreadcrumbContextMenuData,
245253
UpdateAvailabilityData,
246254
WorkspaceCreatedData,
247255
WorkspaceMountpointInfo,

client/src/theme/components/_popovers.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858

5959
#workspace-context-menu,
6060
#file-context-menu,
61+
#folder-breadcrumb-context-menu,
6162
#user-context-menu,
6263
#workspace-context-sheet-modal,
6364
#file-context-sheet-modal,
@@ -213,6 +214,7 @@
213214

214215
#workspace-context-menu,
215216
#file-context-menu,
217+
#folder-breadcrumb-context-menu,
216218
#user-context-menu {
217219
.list-group {
218220
gap: 0.375rem;

0 commit comments

Comments
 (0)