Skip to content

Commit

Permalink
feat: option to sign out all workspaces (#1005)
Browse files Browse the repository at this point in the history
* feat: option to sign out all workspaces

* style: prettier width 120

* chore: bump snjs
  • Loading branch information
moughxyz authored Apr 26, 2022
1 parent c6ed953 commit bce8c5f
Show file tree
Hide file tree
Showing 13 changed files with 132 additions and 111 deletions.
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"printWidth": 120,
"semi": false
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,19 @@ export const WorkspaceMenuItem: FunctionComponent<Props> = ({
<div>
<button
className="w-5 h-5 p-0 mr-3 border-0 bg-transparent hover:bg-contrast cursor-pointer"
onClick={() => {
onClick={(e) => {
e.stopPropagation()
setIsRenaming((isRenaming) => !isRenaming)
}}
>
<Icon type="pencil" className="sn-icon--mid color-neutral" />
</button>
<button
className="w-5 h-5 p-0 border-0 bg-transparent hover:bg-contrast cursor-pointer"
onClick={onDelete}
onClick={(e) => {
e.stopPropagation()
onDelete()
}}
>
<Icon type="trash" className="sn-icon--mid color-danger" />
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ApplicationGroup } from '@/UIModels/ApplicationGroup'
import { AppState } from '@/UIModels/AppState'
import { ApplicationDescriptor } from '@standardnotes/snjs'
import { ApplicationDescriptor, ButtonType } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact'
import { useEffect, useState } from 'preact/hooks'
import { useCallback, useEffect, useState } from 'preact/hooks'
import { Icon } from '@/Components/Icon'
import { Menu } from '@/Components/Menu/Menu'
import { MenuItem, MenuItemSeparator, MenuItemType } from '@/Components/Menu/MenuItem'
Expand All @@ -18,9 +18,7 @@ type Props = {

export const WorkspaceSwitcherMenu: FunctionComponent<Props> = observer(
({ mainApplicationGroup, appState, isOpen, hideWorkspaceOptions = false }) => {
const [applicationDescriptors, setApplicationDescriptors] = useState<ApplicationDescriptor[]>(
[],
)
const [applicationDescriptors, setApplicationDescriptors] = useState<ApplicationDescriptor[]>([])

useEffect(() => {
const removeAppGroupObserver = mainApplicationGroup.addApplicationChangeObserver(() => {
Expand All @@ -33,6 +31,19 @@ export const WorkspaceSwitcherMenu: FunctionComponent<Props> = observer(
}
}, [mainApplicationGroup])

const signoutAll = useCallback(async () => {
const confirmed = await appState.application.alertService.confirm(
'Are you sure you want to sign out of all workspaces on this device?',
undefined,
'Sign out all',
ButtonType.Danger,
)
if (!confirmed) {
return
}
mainApplicationGroup.signOutAllWorkspaces().catch(console.error)
}, [mainApplicationGroup, appState.application.alertService])

return (
<Menu a11yLabel="Workspace switcher menu" className="px-0 focus:shadow-none" isOpen={isOpen}>
{applicationDescriptors.map((descriptor) => (
Expand All @@ -43,23 +54,29 @@ export const WorkspaceSwitcherMenu: FunctionComponent<Props> = observer(
appState.accountMenu.setSigningOut(true)
}}
onClick={() => {
mainApplicationGroup.loadApplicationForDescriptor(descriptor).catch(console.error)
mainApplicationGroup.loadApplicationForDescriptor(descriptor)
}}
renameDescriptor={(label: string) =>
mainApplicationGroup.renameDescriptor(descriptor, label)
}
renameDescriptor={(label: string) => mainApplicationGroup.renameDescriptor(descriptor, label)}
/>
))}
<MenuItemSeparator />

<MenuItem
type={MenuItemType.IconButton}
onClick={() => {
mainApplicationGroup.addNewApplication().catch(console.error)
mainApplicationGroup.addNewApplication()
}}
>
<Icon type="user-add" className="color-neutral mr-2" />
Add another workspace
</MenuItem>

{!hideWorkspaceOptions && (
<MenuItem type={MenuItemType.IconButton} onClick={signoutAll}>
<Icon type="signOut" className="color-neutral mr-2" />
Sign out all workspaces
</MenuItem>
)}
</Menu>
)
},
Expand Down
19 changes: 9 additions & 10 deletions app/assets/javascripts/Components/ApplicationView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ export class ApplicationView extends PureComponent<Props, State> {

override componentDidMount(): void {
super.componentDidMount()
this.loadApplication().catch(console.error)

void this.loadApplication()
}

async loadApplication() {
Expand Down Expand Up @@ -180,16 +181,10 @@ export class ApplicationView extends PureComponent<Props, State> {
)}
{renderAppContents && (
<>
<Footer
application={this.application}
applicationGroup={this.props.mainApplicationGroup}
/>
<Footer application={this.application} applicationGroup={this.props.mainApplicationGroup} />
<SessionsModal application={this.application} appState={this.appState} />
<PreferencesViewWrapper appState={this.appState} application={this.application} />
<RevisionHistoryModalWrapper
application={this.application}
appState={this.appState}
/>
<RevisionHistoryModalWrapper application={this.application} appState={this.appState} />
</>
)}
{this.state.challenges.map((challenge) => {
Expand All @@ -211,7 +206,11 @@ export class ApplicationView extends PureComponent<Props, State> {
<NotesContextMenu application={this.application} appState={this.appState} />
<TagsContextMenu appState={this.appState} />
<PurchaseFlowWrapper application={this.application} appState={this.appState} />
<ConfirmSignoutContainer appState={this.appState} application={this.application} />
<ConfirmSignoutContainer
applicationGroup={this.props.mainApplicationGroup}
appState={this.appState}
application={this.application}
/>
<ToastContainer />
</>
)}
Expand Down
27 changes: 22 additions & 5 deletions app/assets/javascripts/Components/ConfirmSignoutModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import { STRING_SIGN_OUT_CONFIRMATION } from '@/Strings'
import { WebApplication } from '@/UIModels/Application'
import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite'
import { ApplicationGroup } from '@/UIModels/ApplicationGroup'
import { isDesktopApplication } from '@/Utils'

type Props = {
application: WebApplication
appState: AppState
applicationGroup: ApplicationGroup
}

export const ConfirmSignoutContainer = observer((props: Props) => {
Expand All @@ -17,7 +20,7 @@ export const ConfirmSignoutContainer = observer((props: Props) => {
return <ConfirmSignoutModal {...props} />
})

export const ConfirmSignoutModal = observer(({ application, appState }: Props) => {
export const ConfirmSignoutModal = observer(({ application, appState, applicationGroup }: Props) => {
const [deleteLocalBackups, setDeleteLocalBackups] = useState(false)

const cancelRef = useRef<HTMLButtonElement>(null)
Expand All @@ -31,18 +34,32 @@ export const ConfirmSignoutModal = observer(({ application, appState }: Props) =
application.desktopDevice?.localBackupsCount().then(setLocalBackupsCount).catch(console.error)
}, [appState.accountMenu.signingOut, application.desktopDevice])

const workspaces = applicationGroup.getDescriptors()
const showWorkspaceWarning = workspaces.length > 1 && isDesktopApplication()

return (
<AlertDialog onDismiss={closeDialog} leastDestructiveRef={cancelRef}>
<div className="sk-modal-content">
<div className="sn-component">
<div className="sk-panel">
<div className="sk-panel-content">
<div className="sk-panel-section">
<AlertDialogLabel className="sk-h3 sk-panel-section-title">
Sign out workspace?
</AlertDialogLabel>
<AlertDialogLabel className="sk-h3 sk-panel-section-title">Sign out workspace?</AlertDialogLabel>
<AlertDialogDescription className="sk-panel-row">
<p className="color-foreground">{STRING_SIGN_OUT_CONFIRMATION}</p>
<div>
<p className="color-foreground">{STRING_SIGN_OUT_CONFIRMATION}</p>
{showWorkspaceWarning && (
<>
<br />
<p className="color-foreground">
<strong>Note: </strong>
Because you have other workspaces signed in, this sign out may leave logs and other metadata
of your session on this device. For a more robust sign out that performs a hard clear of all
app-related data, use the <i>Sign out all workspaces</i> option under <i>Switch workspace</i>.
</p>
</>
)}
</div>
</AlertDialogDescription>

{localBackupsCount > 0 && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface PreferencesViewWrapperProps {

export const PreferencesViewWrapper: FunctionComponent<PreferencesViewWrapperProps> = observer(
({ appState, application }) => {
if (!appState.preferences.isOpen) {
if (!appState.preferences?.isOpen) {
return null
}

Expand Down
4 changes: 2 additions & 2 deletions app/assets/javascripts/Device/DesktopDeviceInterface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DeviceInterface, Environment } from '@standardnotes/snjs'
import { WebCommunicationReceiver } from './DesktopWebCommunication'
import { WebClientRequiresDesktopMethods } from './DesktopWebCommunication'
import { WebOrDesktopDeviceInterface } from './WebOrDesktopDeviceInterface'

export function isDesktopDevice(x: DeviceInterface): x is DesktopDeviceInterface {
Expand All @@ -8,6 +8,6 @@ export function isDesktopDevice(x: DeviceInterface): x is DesktopDeviceInterface

export interface DesktopDeviceInterface
extends WebOrDesktopDeviceInterface,
WebCommunicationReceiver {
WebClientRequiresDesktopMethods {
environment: Environment.Desktop
}
11 changes: 6 additions & 5 deletions app/assets/javascripts/Device/DesktopWebCommunication.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { DecryptedTransferPayload } from '@standardnotes/snjs'

/** Receives communications emitted by Web Core. This would be the Desktop client. */
export interface WebCommunicationReceiver {
export interface WebClientRequiresDesktopMethods {
localBackupsCount(): Promise<number>

viewlocalBackups(): void
Expand All @@ -14,7 +13,10 @@ export interface WebCommunicationReceiver {

onInitialDataLoad(): void

onSignOut(): void
/**
* Destroys all sensitive storage data, such as localStorage, IndexedDB, and other log files.
*/
destroyAllData(): void

onSearch(text?: string): void

Expand All @@ -23,8 +25,7 @@ export interface WebCommunicationReceiver {
get extensionsServerHost(): string
}

/** Receives communications emitted by the desktop client. This would be Web Core. */
export interface DesktopCommunicationReceiver {
export interface DesktopClientRequiresWebMethods {
updateAvailable(): void

windowGainedFocus(): void
Expand Down
4 changes: 2 additions & 2 deletions app/assets/javascripts/Services/DesktopManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import {
} from '@standardnotes/snjs'
import { WebAppEvent, WebApplication } from '@/UIModels/Application'
import { DesktopDeviceInterface } from '../Device/DesktopDeviceInterface'
import { DesktopCommunicationReceiver } from '@/Device/DesktopWebCommunication'
import { DesktopClientRequiresWebMethods } from '@/Device/DesktopWebCommunication'

export class DesktopManager
extends ApplicationService
implements DesktopManagerInterface, DesktopCommunicationReceiver
implements DesktopManagerInterface, DesktopClientRequiresWebMethods
{
updateObservers: {
callback: (component: SNComponent) => void
Expand Down
4 changes: 0 additions & 4 deletions app/assets/javascripts/UIModels/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,6 @@ export class WebApplication extends SNApplication {
this.webServices = {} as WebServices
this.noteControllerGroup.deinit()
this.webEventObservers.length = 0

if (source === DeinitSource.SignOut) {
isDesktopDevice(this.deviceInterface) && this.deviceInterface.onSignOut()
}
} catch (error) {
console.error('Error while deiniting application', error)
}
Expand Down
25 changes: 10 additions & 15 deletions app/assets/javascripts/UIModels/ApplicationGroup.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { WebApplication } from './Application'
import {
ApplicationDescriptor,
SNApplicationGroup,
Platform,
Runtime,
InternalEventBus,
} from '@standardnotes/snjs'
import { ApplicationDescriptor, SNApplicationGroup, Platform, Runtime, InternalEventBus } from '@standardnotes/snjs'
import { AppState } from '@/UIModels/AppState'
import { getPlatform, isDesktopApplication } from '@/Utils'
import { ArchiveManager } from '@/Services/ArchiveManager'
Expand Down Expand Up @@ -33,17 +27,19 @@ export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
})

if (isDesktopApplication()) {
Object.defineProperty(window, 'desktopCommunicationReceiver', {
Object.defineProperty(window, 'webClient', {
get: () => (this.primaryApplication as WebApplication).getDesktopService(),
})
}
}

private createApplication = (
descriptor: ApplicationDescriptor,
deviceInterface: WebOrDesktopDevice,
) => {
override handleAllWorkspacesSignedOut(): void {
isDesktopDevice(this.deviceInterface) && this.deviceInterface.destroyAllData()
}

private createApplication = (descriptor: ApplicationDescriptor, deviceInterface: WebOrDesktopDevice) => {
const platform = getPlatform()

const application = new WebApplication(
deviceInterface,
platform,
Expand All @@ -52,6 +48,7 @@ export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
this.webSocketUrl,
this.runtime,
)

const appState = new AppState(application, this.device)
const archiveService = new ArchiveManager(application)
const io = new IOService(platform === Platform.MacWeb || platform === Platform.MacDesktop)
Expand All @@ -62,9 +59,7 @@ export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
application.setWebServices({
appState,
archiveService,
desktopService: isDesktopDevice(this.device)
? new DesktopManager(application, this.device)
: undefined,
desktopService: isDesktopDevice(this.device) ? new DesktopManager(application, this.device) : undefined,
io,
autolockService,
statusManager,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"@standardnotes/components": "1.7.15",
"@standardnotes/filepicker": "1.13.3",
"@standardnotes/sncrypto-web": "1.8.4",
"@standardnotes/snjs": "2.99.1",
"@standardnotes/snjs": "2.100.0",
"@standardnotes/stylekit": "5.23.0",
"@zip.js/zip.js": "^2.4.10",
"mobx": "^6.5.0",
Expand Down
Loading

0 comments on commit bce8c5f

Please sign in to comment.