Skip to content

Commit b8069ff

Browse files
fabienSvtrMax-7fabienSvstr
authored
[MS] Show modal hidden workspace (#11711)
* [MS] Show modal to confirm hiding workspace and unmount it --------- Co-authored-by: Maxime Grandcolas <[email protected]> Co-authored-by: fabienSvstr <[email protected]>
1 parent cdaed6d commit b8069ff

File tree

13 files changed

+376
-32
lines changed

13 files changed

+376
-32
lines changed

client/src/components/sidebar/SidebarWorkspaceItem.vue

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
@contextmenu="onContextMenu"
1111
>
1212
<div class="sidebar-item-workspace">
13+
<ion-icon
14+
v-if="isHidden"
15+
:icon="eyeOff"
16+
class="sidebar-item-workspace__hide"
17+
/>
1318
<ion-text
1419
class="sidebar-item-workspace__label"
1520
:title="workspace.currentName"
@@ -30,10 +35,11 @@
3035
import { WorkspaceInfo } from '@/parsec';
3136
import { currentRouteIsWorkspaceRoute } from '@/router';
3237
import { IonIcon, IonItem, IonText } from '@ionic/vue';
33-
import { ellipsisHorizontal } from 'ionicons/icons';
38+
import { ellipsisHorizontal, eyeOff } from 'ionicons/icons';
3439
3540
defineProps<{
3641
workspace: WorkspaceInfo;
42+
isHidden: boolean;
3743
}>();
3844
3945
const emits = defineEmits<{
@@ -69,6 +75,13 @@ async function onContextMenu(event: Event): Promise<void> {
6975
justify-content: space-between;
7076
width: 100%;
7177
78+
&__hide {
79+
margin-right: 0.5rem;
80+
font-size: 1rem;
81+
color: var(--parsec-color-light-primary-30);
82+
flex-shrink: 0;
83+
}
84+
7285
&__label {
7386
text-overflow: ellipsis;
7487
white-space: nowrap;

client/src/components/workspaces/WorkspaceCard.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
:icon="workspace.availableOffline ? cloudDone : cloudOffline"
4141
/>
4242
</ion-text>
43-
4443
<ion-text
4544
class="workspace-card-content__size body-sm"
4645
v-if="false"

client/src/components/workspaces/WorkspaceListItem.vue

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,21 @@ async function onOptionsClick(event: Event): Promise<void> {
204204
}
205205
}
206206
207+
.workspace-list-item--hidden {
208+
border: 1px solid var(--parsec-color-light-secondary-medium);
209+
210+
.workspace-list-item-content {
211+
opacity: 0.8;
212+
filter: brightness(0.8);
213+
}
214+
215+
&:hover {
216+
.workspace-list-item-content {
217+
background: var(--parsec-color-light-secondary-premiere);
218+
}
219+
}
220+
}
221+
207222
.workspace-favorite-icon {
208223
display: flex;
209224
align-items: center;

client/src/components/workspaces/utils.ts

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,34 @@
33
import { workspaceNameValidator } from '@/common/validators';
44
import {
55
ClientRenameWorkspaceErrorTag,
6+
StartedWorkspaceInfo,
67
UserProfile,
78
WorkspaceID,
89
WorkspaceInfo,
910
WorkspaceName,
1011
WorkspaceRole,
1112
getClientProfile,
1213
getSystemPath,
14+
getWorkspaceInfo,
1315
isDesktop,
1416
mountWorkspace,
1517
getPathLink as parsecGetPathLink,
1618
renameWorkspace as parsecRenameWorkspace,
1719
unmountWorkspace,
1820
} from '@/parsec';
1921
import { Routes, navigateTo } from '@/router';
20-
import { EventDistributor } from '@/services/eventDistributor';
22+
import { EventDistributor, Events } from '@/services/eventDistributor';
2123
import { Information, InformationLevel, InformationManager, PresentationMode } from '@/services/informationManager';
2224
import { recentDocumentManager } from '@/services/recentDocuments';
25+
import { StorageManager } from '@/services/storageManager';
2326
import { WorkspaceAttributes } from '@/services/workspaceAttributes';
2427
import SmallDisplayWorkspaceContextMenu from '@/views/workspaces/SmallDisplayWorkspaceContextMenu.vue';
2528
import { WorkspaceAction } from '@/views/workspaces/types';
2629
import WorkspaceContextMenu from '@/views/workspaces/WorkspaceContextMenu.vue';
30+
import WorkspaceHiddenModal from '@/views/workspaces/WorkspaceHiddenModal.vue';
2731
import WorkspaceSharingModal from '@/views/workspaces/WorkspaceSharingModal.vue';
2832
import { modalController, popoverController } from '@ionic/vue';
29-
import { Clipboard, DisplayState, Translatable, getTextFromUser } from 'megashark-lib';
33+
import { Answer, Clipboard, DisplayState, MsModalResult, Translatable, askQuestion, getTextFromUser } from 'megashark-lib';
3034

3135
export const WORKSPACES_PAGE_DATA_KEY = 'WorkspacesPage';
3236

@@ -121,6 +125,7 @@ export async function openWorkspaceContextMenu(
121125
workspaceAttributes: WorkspaceAttributes,
122126
eventDistributor: EventDistributor,
123127
informationManager: InformationManager,
128+
storageManager: StorageManager,
124129
fromSidebar = false,
125130
isLargeDisplay = true,
126131
): Promise<void> {
@@ -177,7 +182,7 @@ export async function openWorkspaceContextMenu(
177182
await copyLinkToClipboard(workspace, informationManager);
178183
break;
179184
case WorkspaceAction.OpenInExplorer:
180-
await openWorkspace(workspace, informationManager);
185+
await seeInExplorer(workspace, informationManager, workspaceAttributes, eventDistributor);
181186
break;
182187
case WorkspaceAction.Rename:
183188
await openRenameWorkspaceModal(workspace, informationManager, isLargeDisplay);
@@ -190,10 +195,23 @@ export async function openWorkspaceContextMenu(
190195
await navigateTo(Routes.History, { query: { documentPath: '/', workspaceHandle: workspace.handle } });
191196
break;
192197
case WorkspaceAction.Mount:
193-
await showWorkspace(workspace, workspaceAttributes, informationManager);
198+
await showWorkspace(workspace, workspaceAttributes, informationManager, eventDistributor);
194199
break;
195200
case WorkspaceAction.UnMount:
196-
await hideWorkspace(workspace, workspaceAttributes, informationManager);
201+
if (isDesktop()) {
202+
const refreshWorkspaces = await getWorkspaceInfo(workspace.handle);
203+
if (refreshWorkspaces.ok) {
204+
await unmountWorkspaceConfirmation(
205+
workspaceAttributes,
206+
refreshWorkspaces.value,
207+
informationManager,
208+
eventDistributor,
209+
storageManager,
210+
);
211+
}
212+
} else {
213+
await hideWorkspace(workspace, workspaceAttributes, informationManager, eventDistributor);
214+
}
197215
break;
198216
default:
199217
console.warn('No WorkspaceAction match found');
@@ -202,10 +220,11 @@ export async function openWorkspaceContextMenu(
202220
}
203221

204222
export async function showWorkspace(
205-
workspace: WorkspaceInfo,
223+
workspace: WorkspaceInfo | StartedWorkspaceInfo,
206224
workspaceAttributes: WorkspaceAttributes,
207225
informationManager: InformationManager,
208-
): Promise<void> {
226+
eventDistributor: EventDistributor,
227+
): Promise<boolean> {
209228
let ok = true;
210229
if (isDesktop()) {
211230
const result = await mountWorkspace(workspace.handle);
@@ -214,6 +233,8 @@ export async function showWorkspace(
214233

215234
if (ok) {
216235
workspaceAttributes.removeHidden(workspace.id);
236+
await workspaceAttributes.save();
237+
217238
informationManager.present(
218239
new Information({
219240
message: {
@@ -224,6 +245,10 @@ export async function showWorkspace(
224245
}),
225246
PresentationMode.Toast,
226247
);
248+
await eventDistributor.dispatchEvent(Events.WorkspaceMountpointsSync, {
249+
workspaceId: workspace.id,
250+
isMounted: true,
251+
});
227252
} else {
228253
informationManager.present(
229254
new Information({
@@ -236,12 +261,15 @@ export async function showWorkspace(
236261
PresentationMode.Toast,
237262
);
238263
}
264+
265+
return ok;
239266
}
240267

241268
export async function hideWorkspace(
242-
workspace: WorkspaceInfo,
269+
workspace: WorkspaceInfo | StartedWorkspaceInfo,
243270
workspaceAttributes: WorkspaceAttributes,
244271
informationManager: InformationManager,
272+
eventDistributor: EventDistributor,
245273
): Promise<void> {
246274
let ok = true;
247275
if (isDesktop()) {
@@ -251,6 +279,8 @@ export async function hideWorkspace(
251279

252280
if (ok) {
253281
workspaceAttributes.addHidden(workspace.id);
282+
await workspaceAttributes.save();
283+
254284
informationManager.present(
255285
new Information({
256286
message: {
@@ -261,6 +291,10 @@ export async function hideWorkspace(
261291
}),
262292
PresentationMode.Toast,
263293
);
294+
await eventDistributor.dispatchEvent(Events.WorkspaceMountpointsSync, {
295+
workspaceId: workspace.id,
296+
isMounted: false,
297+
});
264298
} else {
265299
informationManager.present(
266300
new Information({
@@ -275,16 +309,35 @@ export async function hideWorkspace(
275309
}
276310
}
277311

278-
async function openWorkspace(workspace: WorkspaceInfo, informationManager: InformationManager): Promise<void> {
279-
const result = await getSystemPath(workspace.handle, '/');
312+
async function seeInExplorer(
313+
workspace: WorkspaceInfo,
314+
informationManager: InformationManager,
315+
workspaceAttributes: WorkspaceAttributes,
316+
eventDistributor: EventDistributor,
317+
): Promise<void> {
318+
if (workspaceAttributes.isHidden(workspace.id)) {
319+
const answer = await askQuestion(
320+
'WorkspacesPage.openInExplorerModal.workspace.title',
321+
'WorkspacesPage.openInExplorerModal.workspace.description',
322+
{
323+
yesText: 'WorkspacesPage.openInExplorerModal.actionConfirm',
324+
noText: 'WorkspacesPage.openInExplorerModal.actionCancel',
325+
},
326+
);
327+
328+
if (answer === Answer.Yes) {
329+
await showWorkspace(workspace, workspaceAttributes, informationManager, eventDistributor);
330+
}
331+
}
280332

333+
const result = await getSystemPath(workspace.handle, '/');
281334
if (!result.ok) {
282335
await informationManager.present(
283336
new Information({
284337
message: { key: 'FoldersPage.open.folderFailed', data: { name: workspace.currentName } },
285338
level: InformationLevel.Error,
286339
}),
287-
PresentationMode.Modal,
340+
PresentationMode.Toast,
288341
);
289342
} else {
290343
window.electronAPI.openFile(result.value);
@@ -329,6 +382,42 @@ async function renameWorkspace(workspace: WorkspaceInfo, newName: WorkspaceName,
329382
}
330383
}
331384

385+
async function unmountWorkspaceConfirmation(
386+
workspaceAttributes: WorkspaceAttributes,
387+
workspace: WorkspaceInfo | StartedWorkspaceInfo,
388+
informationManager: InformationManager,
389+
eventDistributor: EventDistributor,
390+
storageManager: StorageManager,
391+
): Promise<void> {
392+
const config = await storageManager.retrieveConfig();
393+
394+
if (config.skipWorkspaceHiddenWarning === true) {
395+
await hideWorkspace(workspace as WorkspaceInfo, workspaceAttributes, informationManager, eventDistributor);
396+
return;
397+
}
398+
399+
const modal = await modalController.create({
400+
component: WorkspaceHiddenModal,
401+
cssClass: 'workspace-hidden-modal',
402+
componentProps: {
403+
workspaceName: workspace.currentName,
404+
},
405+
});
406+
407+
await modal.present();
408+
const { data, role } = await modal.onWillDismiss();
409+
await modal.dismiss();
410+
411+
if (role === MsModalResult.Confirm) {
412+
if (data?.skipWorkspaceHiddenWarning === true) {
413+
config.skipWorkspaceHiddenWarning = true;
414+
await storageManager.storeConfig(config);
415+
}
416+
417+
await hideWorkspace(workspace as WorkspaceInfo, workspaceAttributes, informationManager, eventDistributor);
418+
}
419+
}
420+
332421
async function openRenameWorkspaceModal(
333422
workspace: WorkspaceInfo,
334423
informationManager: InformationManager,

client/src/locales/en-US.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,34 @@
710710
"successDesktopShown": "The workspace is now visible in Parsec and your explorer.",
711711
"successWebShown": "The workspace is now visible in Parsec.",
712712
"failedShown": "Failed to show the workspace. Please try again."
713+
},
714+
"workspaceHiddenModal": {
715+
"title": "Hide a workspace",
716+
"affectedWorkspaces": "You are about to hide the workspace {workspace} and it will no longer be visible from the file explorer.",
717+
"info": "You can check “Show hidden workspaces” to show it on Parsec.",
718+
"actionConfirm": "Hide workspace",
719+
"actionCancel": "Cancel",
720+
"noReminder": "Don't remind me again",
721+
"showWorkspace": {
722+
"toastSuccess": "The workspace {workspace} is now visible.",
723+
"toastFailure": "Failed to show the workspace {workspace}. Please try again."
724+
},
725+
"hideWorkspace": {
726+
"toastSuccess": "The workspace {workspace} is now hidden.",
727+
"toastFailure": "Failed to hide the workspace {workspace}. Please try again."
728+
}
729+
},
730+
"openInExplorerModal": {
731+
"file": {
732+
"title": "Failed to open the file in your explorer",
733+
"description": "You must make this workspace visible in order to view your file in your explorer."
734+
},
735+
"workspace": {
736+
"title": "Failed to open the workspace in your explorer",
737+
"description": "You must make this workspace visible in order to view it from your file explorer."
738+
},
739+
"actionConfirm": "Make this workspace visible",
740+
"actionCancel": "Cancel"
713741
}
714742
},
715743
"FoldersPage": {

client/src/locales/fr-FR.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,34 @@
709709
"successDesktopShown": "L'espace de travail est maintenant visible dans Parsec et votre explorateur.",
710710
"successWebShown": "L'espace de travail est maintenant visible dans Parsec.",
711711
"failedShown": "Impossible d'afficher l'espace de travail. Veuillez réessayer."
712+
},
713+
"workspaceHiddenModal": {
714+
"title": "Masquer un espace de travail",
715+
"affectedWorkspaces": "Vous êtes sur le point de masquer l'espace de travail {workspace}. Cet espace de travail ne sera plus visible depuis l'explorateur de fichiers.",
716+
"info": "Vous pouvez cocher “Afficher les espaces masqués” pour le voir sur Parsec.",
717+
"actionConfirm": "Masquer l'espace de travail",
718+
"actionCancel": "Annuler",
719+
"noReminder": "Ne plus me rappeler",
720+
"showWorkspace": {
721+
"toastSuccess": "L'espace de travail {workspace} est maintenant visible.",
722+
"toastFailure": "Impossible d'afficher l'espace de travail {workspace}. Veuillez réessayer."
723+
},
724+
"hideWorkspace": {
725+
"toastSuccess": "L'espace de travail {workspace} est maintenant masqué.",
726+
"toastFailure": "Impossible de masquer l'espace de travail {workspace}. Veuillez réessayer."
727+
}
728+
},
729+
"openInExplorerModal": {
730+
"file": {
731+
"title": "Impossible d'ouvrir le fichier",
732+
"description": "Vous devez rendre visible cet espace de travail pour pouvoir le consulter depuis votre explorateur de fichiers."
733+
},
734+
"workspace": {
735+
"title": "Impossible d'ouvrir l'espace de travail",
736+
"description": "Vous devez rendre visible cet espace de travail pour pouvoir le consulter depuis votre explorateur de fichiers."
737+
},
738+
"actionConfirm": "Rendre cet espace de travail visible",
739+
"actionCancel": "Annuler"
712740
}
713741
},
714742
"FoldersPage": {

client/src/parsec/workspace.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,12 @@ export async function mountWorkspace(
249249
return await libparsec.workspaceMount(workspaceHandle);
250250
}
251251

252-
export async function unmountWorkspace(workspace: WorkspaceInfo): Promise<Result<null, MountpointUnmountError>> {
252+
export async function unmountWorkspace(workspace: WorkspaceInfo | StartedWorkspaceInfo): Promise<Result<null, MountpointUnmountError>> {
253253
let error: MountpointUnmountError | null = null;
254254

255255
for (let i = workspace.mountpoints.length - 1; i >= 0; i--) {
256256
const result = await libparsec.mountpointUnmount(workspace.mountpoints[i][0]);
257+
257258
if (result.ok) {
258259
workspace.mountpoints.splice(i, 1);
259260
} else {

0 commit comments

Comments
 (0)