mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 12:42:16 +03:00
New user menu (#7581)
- Closes https://github.com/enso-org/cloud-v2/issues/610 - New user menu - Remove "go to profile" action that does not currently have an action, and does not exist in new design - Add placeholder icons for existing actions - Re-style "change password" modal to fit in with the design # Important Notes There are many differences from the design - none are visual differences though: - The list of actions is completely different - there are no menu entries in common between the design and the current - This also means that *all* current icons are placeholders. There are no appropriate icons in the "icons" Figma tab either. - The user icon is still a placeholder, as there is no backend support for user icons yet. - The user menu entries are highlighted on hover (not specified in the design), but to make this look nice, some of the padding has been moved from the outer container to the individual menu entries. - The menu entries use the same component as the context menu, so they *do* support shortcuts, and adding shortcuts to them will be very easy, *however* no shortcuts have been set for the new actions, because they are different from the actions in the Figma design (and so they don't have an official default shortcut)
This commit is contained in:
parent
e224eb350a
commit
06b2280ee6
9
app/ide-desktop/lib/assets/change_password.svg
Normal file
9
app/ide-desktop/lib/assets/change_password.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect opacity="0.2" y="3" width="16" height="10" rx="2" fill="black" />
|
||||||
|
<path d="M10 0H12V16H10V0Z" fill="black" />
|
||||||
|
<path d="M8 0H14V2H8V0Z" fill="black" />
|
||||||
|
<path d="M8 14H14V16H8V14Z" fill="black" />
|
||||||
|
<path d="M2 8L8 8" stroke="black" />
|
||||||
|
<path d="M6.5 10.6L3.5 5.4" stroke="black" />
|
||||||
|
<path d="M3.5 10.6L6.5 5.4" stroke="black" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 460 B |
4
app/ide-desktop/lib/assets/sign_in.svg
Normal file
4
app/ide-desktop/lib/assets/sign_in.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect opacity="0.25" x="6" width="10" height="16" rx="2" fill="black" />
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 8L7.36364 5V7L1 7L1 9H7.36364V11L11 8Z" fill="black" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 292 B |
5
app/ide-desktop/lib/assets/sign_out.svg
Normal file
5
app/ide-desktop/lib/assets/sign_out.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect opacity="0.25" x="6" width="10" height="16" rx="2" fill="black" />
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M-9.53674e-07 8L3.63636 5V7L10 7V9H3.63636V11L-9.53674e-07 8Z"
|
||||||
|
fill="black" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 319 B |
@ -19,11 +19,11 @@ import * as assetsTable from './assetsTable'
|
|||||||
import * as tableRow from './tableRow'
|
import * as tableRow from './tableRow'
|
||||||
import ConfirmDeleteModal from './confirmDeleteModal'
|
import ConfirmDeleteModal from './confirmDeleteModal'
|
||||||
import ContextMenu from './contextMenu'
|
import ContextMenu from './contextMenu'
|
||||||
import ContextMenuEntry from './contextMenuEntry'
|
|
||||||
import ContextMenuSeparator from './contextMenuSeparator'
|
import ContextMenuSeparator from './contextMenuSeparator'
|
||||||
import ContextMenus from './contextMenus'
|
import ContextMenus from './contextMenus'
|
||||||
import GlobalContextMenu from './globalContextMenu'
|
import GlobalContextMenu from './globalContextMenu'
|
||||||
import ManagePermissionsModal from './managePermissionsModal'
|
import ManagePermissionsModal from './managePermissionsModal'
|
||||||
|
import MenuEntry from './menuEntry'
|
||||||
|
|
||||||
// ========================
|
// ========================
|
||||||
// === AssetContextMenu ===
|
// === AssetContextMenu ===
|
||||||
@ -85,7 +85,7 @@ export default function AssetContextMenu(props: AssetContextMenuProps) {
|
|||||||
<ContextMenus hidden={hidden} key={asset.id} event={event}>
|
<ContextMenus hidden={hidden} key={asset.id} event={event}>
|
||||||
<ContextMenu hidden={hidden}>
|
<ContextMenu hidden={hidden}>
|
||||||
{asset.type === backendModule.AssetType.project && (
|
{asset.type === backendModule.AssetType.project && (
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
action={shortcuts.KeyboardAction.open}
|
action={shortcuts.KeyboardAction.open}
|
||||||
doAction={() => {
|
doAction={() => {
|
||||||
@ -99,7 +99,7 @@ export default function AssetContextMenu(props: AssetContextMenuProps) {
|
|||||||
)}
|
)}
|
||||||
{asset.type === backendModule.AssetType.project &&
|
{asset.type === backendModule.AssetType.project &&
|
||||||
backend.type === backendModule.BackendType.local && (
|
backend.type === backendModule.BackendType.local && (
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
action={shortcuts.KeyboardAction.uploadToCloud}
|
action={shortcuts.KeyboardAction.uploadToCloud}
|
||||||
doAction={async () => {
|
doAction={async () => {
|
||||||
@ -145,7 +145,7 @@ export default function AssetContextMenu(props: AssetContextMenuProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
disabled={
|
disabled={
|
||||||
asset.type !== backendModule.AssetType.project &&
|
asset.type !== backendModule.AssetType.project &&
|
||||||
@ -160,7 +160,7 @@ export default function AssetContextMenu(props: AssetContextMenuProps) {
|
|||||||
unsetModal()
|
unsetModal()
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
disabled
|
disabled
|
||||||
action={shortcuts.KeyboardAction.snapshot}
|
action={shortcuts.KeyboardAction.snapshot}
|
||||||
@ -168,7 +168,7 @@ export default function AssetContextMenu(props: AssetContextMenuProps) {
|
|||||||
// No backend support yet.
|
// No backend support yet.
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
action={shortcuts.KeyboardAction.moveToTrash}
|
action={shortcuts.KeyboardAction.moveToTrash}
|
||||||
doAction={() => {
|
doAction={() => {
|
||||||
@ -182,7 +182,7 @@ export default function AssetContextMenu(props: AssetContextMenuProps) {
|
|||||||
/>
|
/>
|
||||||
<ContextMenuSeparator hidden={hidden} />
|
<ContextMenuSeparator hidden={hidden} />
|
||||||
{managesThisAsset && (
|
{managesThisAsset && (
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
action={shortcuts.KeyboardAction.share}
|
action={shortcuts.KeyboardAction.share}
|
||||||
doAction={() => {
|
doAction={() => {
|
||||||
@ -203,7 +203,7 @@ export default function AssetContextMenu(props: AssetContextMenuProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
disabled
|
disabled
|
||||||
action={shortcuts.KeyboardAction.label}
|
action={shortcuts.KeyboardAction.label}
|
||||||
@ -212,7 +212,7 @@ export default function AssetContextMenu(props: AssetContextMenuProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ContextMenuSeparator hidden={hidden} />
|
<ContextMenuSeparator hidden={hidden} />
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
disabled
|
disabled
|
||||||
action={shortcuts.KeyboardAction.duplicate}
|
action={shortcuts.KeyboardAction.duplicate}
|
||||||
@ -220,7 +220,7 @@ export default function AssetContextMenu(props: AssetContextMenuProps) {
|
|||||||
// No backend support yet.
|
// No backend support yet.
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
disabled
|
disabled
|
||||||
action={shortcuts.KeyboardAction.copy}
|
action={shortcuts.KeyboardAction.copy}
|
||||||
@ -228,7 +228,7 @@ export default function AssetContextMenu(props: AssetContextMenuProps) {
|
|||||||
// No backend support yet.
|
// No backend support yet.
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
disabled
|
disabled
|
||||||
action={shortcuts.KeyboardAction.cut}
|
action={shortcuts.KeyboardAction.cut}
|
||||||
@ -236,7 +236,7 @@ export default function AssetContextMenu(props: AssetContextMenuProps) {
|
|||||||
// No backend support yet.
|
// No backend support yet.
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
disabled
|
disabled
|
||||||
action={shortcuts.KeyboardAction.download}
|
action={shortcuts.KeyboardAction.download}
|
||||||
|
@ -28,9 +28,9 @@ import AssetRow from './assetRow'
|
|||||||
import Button from './button'
|
import Button from './button'
|
||||||
import ConfirmDeleteModal from './confirmDeleteModal'
|
import ConfirmDeleteModal from './confirmDeleteModal'
|
||||||
import ContextMenu from './contextMenu'
|
import ContextMenu from './contextMenu'
|
||||||
import ContextMenuEntry from './contextMenuEntry'
|
|
||||||
import ContextMenus from './contextMenus'
|
import ContextMenus from './contextMenus'
|
||||||
import GlobalContextMenu from './globalContextMenu'
|
import GlobalContextMenu from './globalContextMenu'
|
||||||
|
import MenuEntry from './menuEntry'
|
||||||
import Table from './table'
|
import Table from './table'
|
||||||
|
|
||||||
// =================
|
// =================
|
||||||
@ -775,7 +775,7 @@ export default function AssetsTable(props: AssetsTableProps) {
|
|||||||
<ContextMenus key={uniqueString.uniqueString()} event={event}>
|
<ContextMenus key={uniqueString.uniqueString()} event={event}>
|
||||||
{innerSelectedKeys.size !== 0 && (
|
{innerSelectedKeys.size !== 0 && (
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
action={shortcuts.KeyboardAction.moveAllToTrash}
|
action={shortcuts.KeyboardAction.moveAllToTrash}
|
||||||
doAction={doDeleteAll}
|
doAction={doDeleteAll}
|
||||||
/>
|
/>
|
||||||
|
@ -34,13 +34,12 @@ export default function ChangePasswordModal() {
|
|||||||
onClick={event => {
|
onClick={event => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
}}
|
}}
|
||||||
className="flex flex-col bg-white shadow-md px-4 sm:px-6 md:px-8 lg:px-10 py-8 rounded-md w-full max-w-md"
|
className="flex flex-col bg-frame-selected backdrop-blur-3xl rounded-2xl px-4 py-8 w-full max-w-md"
|
||||||
>
|
>
|
||||||
<div className="font-medium self-center text-xl sm:text-2xl uppercase text-gray-800">
|
<div className="self-center text-xl">Change Your Password</div>
|
||||||
Change Your Password
|
|
||||||
</div>
|
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
<form
|
<form
|
||||||
|
className="flex flex-col gap-6"
|
||||||
onSubmit={async event => {
|
onSubmit={async event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
@ -51,13 +50,8 @@ export default function ChangePasswordModal() {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col mb-6">
|
<div className="flex flex-col gap-1">
|
||||||
<label
|
<label htmlFor="old_password">Old Password:</label>
|
||||||
htmlFor="old_password"
|
|
||||||
className="mb-1 text-xs sm:text-sm tracking-wide text-gray-600"
|
|
||||||
>
|
|
||||||
Old Password:
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<SvgIcon>
|
<SvgIcon>
|
||||||
<SvgMask src={LockIcon} />
|
<SvgMask src={LockIcon} />
|
||||||
@ -74,17 +68,12 @@ export default function ChangePasswordModal() {
|
|||||||
error={validation.PASSWORD_ERROR}
|
error={validation.PASSWORD_ERROR}
|
||||||
value={oldPassword}
|
value={oldPassword}
|
||||||
setValue={setOldPassword}
|
setValue={setOldPassword}
|
||||||
className="text-sm sm:text-base placeholder-gray-500 pl-10 pr-4 rounded-lg border border-gray-400 w-full py-2 focus:outline-none focus:border-blue-400"
|
className="text-sm sm:text-base placeholder-gray-500 pl-10 pr-4 rounded-2xl w-full py-2 focus:outline-none focus:border-blue-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col mb-6">
|
<div className="flex flex-col gap-1">
|
||||||
<label
|
<label htmlFor="new_password">New Password:</label>
|
||||||
htmlFor="new_password"
|
|
||||||
className="mb-1 text-xs sm:text-sm tracking-wide text-gray-600"
|
|
||||||
>
|
|
||||||
New Password:
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<SvgIcon>
|
<SvgIcon>
|
||||||
<SvgMask src={LockIcon} />
|
<SvgMask src={LockIcon} />
|
||||||
@ -100,17 +89,12 @@ export default function ChangePasswordModal() {
|
|||||||
error={validation.PASSWORD_ERROR}
|
error={validation.PASSWORD_ERROR}
|
||||||
value={newPassword}
|
value={newPassword}
|
||||||
setValue={setNewPassword}
|
setValue={setNewPassword}
|
||||||
className="text-sm sm:text-base placeholder-gray-500 pl-10 pr-4 rounded-lg border border-gray-400 w-full py-2 focus:outline-none focus:border-blue-400"
|
className="text-sm placeholder-gray-500 pl-10 pr-4 rounded-full w-full py-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col mb-6">
|
<div className="flex flex-col gap-1">
|
||||||
<label
|
<label htmlFor="new_password_confirm">Confirm New Password:</label>
|
||||||
htmlFor="new_password_confirm"
|
|
||||||
className="mb-1 text-xs sm:text-sm tracking-wide text-gray-600"
|
|
||||||
>
|
|
||||||
Confirm New Password:
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<SvgIcon>
|
<SvgIcon>
|
||||||
<SvgMask src={LockIcon} />
|
<SvgMask src={LockIcon} />
|
||||||
@ -126,22 +110,18 @@ export default function ChangePasswordModal() {
|
|||||||
error={validation.CONFIRM_PASSWORD_ERROR}
|
error={validation.CONFIRM_PASSWORD_ERROR}
|
||||||
value={confirmNewPassword}
|
value={confirmNewPassword}
|
||||||
setValue={setConfirmNewPassword}
|
setValue={setConfirmNewPassword}
|
||||||
className="text-sm sm:text-base placeholder-gray-500 pl-10 pr-4 rounded-lg border border-gray-400 w-full py-2 focus:outline-none focus:border-blue-400"
|
className="text-sm placeholder-gray-500 pl-10 pr-4 rounded-full w-full py-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-full">
|
|
||||||
<button
|
<button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
type="submit"
|
type="submit"
|
||||||
className="flex items-center justify-center focus:outline-none text-white text-sm sm:text-base bg-blue-600 hover:bg-blue-700 rounded py-2 w-full transition duration-150 ease-in disabled:opacity-50"
|
className="flex items-center justify-center text-white text-sm bg-cloud rounded-full gap-2 h-10 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<span className="mr-2 uppercase">Reset</span>
|
Reset
|
||||||
<span>
|
|
||||||
<SvgMask src={ArrowRightIcon} />
|
<SvgMask src={ArrowRightIcon} />
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,7 @@ import * as modalProvider from '../../providers/modal'
|
|||||||
import * as shortcuts from '../shortcuts'
|
import * as shortcuts from '../shortcuts'
|
||||||
|
|
||||||
import ContextMenu from './contextMenu'
|
import ContextMenu from './contextMenu'
|
||||||
import ContextMenuEntry from './contextMenuEntry'
|
import MenuEntry from './menuEntry'
|
||||||
|
|
||||||
/** Props for a {@link GlobalContextMenu}. */
|
/** Props for a {@link GlobalContextMenu}. */
|
||||||
export interface GlobalContextMenuProps {
|
export interface GlobalContextMenuProps {
|
||||||
@ -49,7 +49,7 @@ export default function GlobalContextMenu(props: GlobalContextMenuProps) {
|
|||||||
}}
|
}}
|
||||||
></input>
|
></input>
|
||||||
)}
|
)}
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
action={shortcuts.KeyboardAction.uploadFiles}
|
action={shortcuts.KeyboardAction.uploadFiles}
|
||||||
doAction={() => {
|
doAction={() => {
|
||||||
@ -76,7 +76,7 @@ export default function GlobalContextMenu(props: GlobalContextMenuProps) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
action={shortcuts.KeyboardAction.newProject}
|
action={shortcuts.KeyboardAction.newProject}
|
||||||
doAction={() => {
|
doAction={() => {
|
||||||
@ -91,7 +91,7 @@ export default function GlobalContextMenu(props: GlobalContextMenuProps) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{backend.type !== backendModule.BackendType.local && (
|
{backend.type !== backendModule.BackendType.local && (
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
action={shortcuts.KeyboardAction.newFolder}
|
action={shortcuts.KeyboardAction.newFolder}
|
||||||
doAction={() => {
|
doAction={() => {
|
||||||
@ -105,7 +105,7 @@ export default function GlobalContextMenu(props: GlobalContextMenuProps) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{backend.type !== backendModule.BackendType.local && (
|
{backend.type !== backendModule.BackendType.local && (
|
||||||
<ContextMenuEntry
|
<MenuEntry
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
disabled
|
disabled
|
||||||
action={shortcuts.KeyboardAction.newDataConnector}
|
action={shortcuts.KeyboardAction.newDataConnector}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/** @file An entry in a context menu. */
|
/** @file An entry in a menu. */
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
import * as shortcutsModule from '../shortcuts'
|
import * as shortcutsModule from '../shortcuts'
|
||||||
@ -7,22 +7,23 @@ import * as shortcutsProvider from '../../providers/shortcuts'
|
|||||||
import KeyboardShortcut from './keyboardShortcut'
|
import KeyboardShortcut from './keyboardShortcut'
|
||||||
import SvgMask from '../../authentication/components/svgMask'
|
import SvgMask from '../../authentication/components/svgMask'
|
||||||
|
|
||||||
// ========================
|
// =================
|
||||||
// === ContextMenuEntry ===
|
// === MenuEntry ===
|
||||||
// ========================
|
// =================
|
||||||
|
|
||||||
/** Props for a {@link ContextMenuEntry}. */
|
/** Props for a {@link MenuEntry}. */
|
||||||
export interface ContextMenuEntryProps {
|
export interface MenuEntryProps {
|
||||||
hidden?: boolean
|
hidden?: boolean
|
||||||
action: shortcutsModule.KeyboardAction
|
action: shortcutsModule.KeyboardAction
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
title?: string
|
title?: string
|
||||||
|
paddingClassName?: string
|
||||||
doAction: () => void
|
doAction: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An item in a `ContextMenu`. */
|
/** An item in a menu. */
|
||||||
export default function ContextMenuEntry(props: ContextMenuEntryProps) {
|
export default function MenuEntry(props: MenuEntryProps) {
|
||||||
const { hidden = false, action, disabled = false, title, doAction } = props
|
const { hidden = false, action, disabled = false, title, paddingClassName, doAction } = props
|
||||||
const { shortcuts } = shortcutsProvider.useShortcuts()
|
const { shortcuts } = shortcutsProvider.useShortcuts()
|
||||||
const info = shortcuts.keyboardShortcutInfo[action]
|
const info = shortcuts.keyboardShortcutInfo[action]
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -39,7 +40,9 @@ export default function ContextMenuEntry(props: ContextMenuEntryProps) {
|
|||||||
<button
|
<button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
title={title}
|
title={title}
|
||||||
className="flex items-center place-content-between h-8 px-3 py-1 hover:bg-black-a10 disabled:bg-transparent rounded-lg text-left disabled:opacity-50"
|
className={`flex items-center place-content-between h-8 disabled:bg-transparent rounded-lg text-left disabled:opacity-50 hover:bg-black-a10 ${
|
||||||
|
paddingClassName ?? 'px-3 py-1'
|
||||||
|
}`}
|
||||||
onClick={event => {
|
onClick={event => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
doAction()
|
doAction()
|
@ -1,42 +1,22 @@
|
|||||||
/** @file The UserMenu component provides a dropdown menu of user actions and settings. */
|
/** @file The UserMenu component provides a dropdown menu of user actions and settings. */
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import DefaultUserIcon from 'enso-assets/default_user.svg'
|
||||||
|
|
||||||
import * as app from '../../components/app'
|
import * as app from '../../components/app'
|
||||||
import * as auth from '../../authentication/providers/auth'
|
import * as auth from '../../authentication/providers/auth'
|
||||||
import * as hooks from '../../hooks'
|
import * as hooks from '../../hooks'
|
||||||
import * as modalProvider from '../../providers/modal'
|
import * as modalProvider from '../../providers/modal'
|
||||||
|
import * as shortcuts from '../shortcuts'
|
||||||
|
|
||||||
import ChangePasswordModal from './changePasswordModal'
|
import ChangePasswordModal from './changePasswordModal'
|
||||||
|
import MenuEntry from './menuEntry'
|
||||||
|
import Modal from './modal'
|
||||||
|
|
||||||
// ================
|
// ================
|
||||||
// === UserMenu ===
|
// === UserMenu ===
|
||||||
// ================
|
// ================
|
||||||
|
|
||||||
/** This is the UI component for a `UserMenu` list item.
|
|
||||||
* The main interaction logic is in the `onClick` injected by `UserMenu`. */
|
|
||||||
export interface UserMenuItemProps {
|
|
||||||
disabled?: boolean
|
|
||||||
onClick?: React.MouseEventHandler<HTMLDivElement>
|
|
||||||
}
|
|
||||||
|
|
||||||
/** User menu item. */
|
|
||||||
function UserMenuItem(props: React.PropsWithChildren<UserMenuItemProps>) {
|
|
||||||
const { children, disabled = false, onClick } = props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`whitespace-nowrap first:rounded-t-2xl last:rounded-b-2xl px-4 py-2 ${
|
|
||||||
disabled ? 'opacity-50' : ''
|
|
||||||
} ${onClick && !disabled ? 'hover:bg-black-a10' : ''} ${
|
|
||||||
onClick != null && !disabled ? 'cursor-pointer' : ''
|
|
||||||
}`}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Props for a {@link UserMenu}. */
|
/** Props for a {@link UserMenu}. */
|
||||||
export interface UserMenuProps {
|
export interface UserMenuProps {
|
||||||
onSignOut: () => void
|
onSignOut: () => void
|
||||||
@ -45,20 +25,11 @@ export interface UserMenuProps {
|
|||||||
/** Handling the UserMenuItem click event logic and displaying its content. */
|
/** Handling the UserMenuItem click event logic and displaying its content. */
|
||||||
export default function UserMenu(props: UserMenuProps) {
|
export default function UserMenu(props: UserMenuProps) {
|
||||||
const { onSignOut } = props
|
const { onSignOut } = props
|
||||||
|
const navigate = hooks.useNavigate()
|
||||||
const { signOut } = auth.useAuth()
|
const { signOut } = auth.useAuth()
|
||||||
const { accessToken, organization } = auth.useNonPartialUserSession()
|
const { accessToken, organization } = auth.useNonPartialUserSession()
|
||||||
const navigate = hooks.useNavigate()
|
|
||||||
|
|
||||||
const { setModal } = modalProvider.useSetModal()
|
const { setModal } = modalProvider.useSetModal()
|
||||||
|
|
||||||
const goToProfile = () => {
|
|
||||||
// TODO: Implement this when the backend endpoints are implemented.
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToLoginPage = () => {
|
|
||||||
navigate(app.LOGIN_PATH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The shape of the JWT payload is statically known.
|
// The shape of the JWT payload is statically known.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
const username: string | null =
|
const username: string | null =
|
||||||
@ -67,47 +38,59 @@ export default function UserMenu(props: UserMenuProps) {
|
|||||||
const canChangePassword = username != null ? !/^Github_|^Google_/.test(username) : false
|
const canChangePassword = username != null ? !/^Github_|^Google_/.test(username) : false
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Modal className="absolute overflow-hidden bg-dim w-full h-full z-10">
|
||||||
<div
|
<div
|
||||||
className="absolute bg-frame-selected backdrop-blur-3xl right-2.25 top-11 z-1 flex flex-col rounded-2xl"
|
className="absolute flex flex-col bg-frame-selected backdrop-blur-3xl rounded-2xl gap-3 right-2.25 top-2.25 w-51.5 px-2 py-2.25"
|
||||||
onClick={event => {
|
onClick={event => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{organization != null ? (
|
{organization != null ? (
|
||||||
<>
|
<>
|
||||||
<UserMenuItem>
|
<div className="flex items-center gap-3 px-1">
|
||||||
Signed in as <span className="font-bold">{organization.name}</span>
|
<img src={DefaultUserIcon} height={28} width={28} />
|
||||||
</UserMenuItem>
|
<span className="leading-170 h-6 py-px">{organization.name}</span>
|
||||||
<UserMenuItem disabled onClick={goToProfile}>
|
</div>
|
||||||
Your profile
|
<div className="flex flex-col">
|
||||||
</UserMenuItem>
|
|
||||||
{canChangePassword && (
|
{canChangePassword && (
|
||||||
<UserMenuItem
|
<MenuEntry
|
||||||
onClick={() => {
|
action={shortcuts.KeyboardAction.changeYourPassword}
|
||||||
|
paddingClassName="p-1"
|
||||||
|
doAction={() => {
|
||||||
setModal(<ChangePasswordModal />)
|
setModal(<ChangePasswordModal />)
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
Change your password
|
|
||||||
</UserMenuItem>
|
|
||||||
)}
|
)}
|
||||||
<UserMenuItem
|
<MenuEntry
|
||||||
onClick={() => {
|
action={shortcuts.KeyboardAction.signOut}
|
||||||
|
paddingClassName="p-1"
|
||||||
|
doAction={() => {
|
||||||
onSignOut()
|
onSignOut()
|
||||||
// Wait until React has switched back to drive view, before signing out.
|
// Wait until React has switched back to drive view, before signing out.
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
void signOut()
|
void signOut()
|
||||||
}, 0)
|
}, 0)
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
Sign out
|
</div>
|
||||||
</UserMenuItem>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<UserMenuItem>You are not signed in.</UserMenuItem>
|
<div className="flex items-center h-7">
|
||||||
<UserMenuItem onClick={goToLoginPage}>Login</UserMenuItem>
|
<span className="leading-170 h-6 py-px">You are not signed in.</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<MenuEntry
|
||||||
|
action={shortcuts.KeyboardAction.signIn}
|
||||||
|
paddingClassName="py-1"
|
||||||
|
doAction={() => {
|
||||||
|
navigate(app.LOGIN_PATH)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import AddFolderIcon from 'enso-assets/add_folder.svg'
|
|||||||
import AddNetworkIcon from 'enso-assets/add_network.svg'
|
import AddNetworkIcon from 'enso-assets/add_network.svg'
|
||||||
import BlankIcon from 'enso-assets/blank_16.svg'
|
import BlankIcon from 'enso-assets/blank_16.svg'
|
||||||
import CameraIcon from 'enso-assets/camera.svg'
|
import CameraIcon from 'enso-assets/camera.svg'
|
||||||
|
import ChangePasswordIcon from 'enso-assets/change_password.svg'
|
||||||
import CloudToIcon from 'enso-assets/cloud_to.svg'
|
import CloudToIcon from 'enso-assets/cloud_to.svg'
|
||||||
import CopyIcon from 'enso-assets/copy.svg'
|
import CopyIcon from 'enso-assets/copy.svg'
|
||||||
import DataDownloadIcon from 'enso-assets/data_download.svg'
|
import DataDownloadIcon from 'enso-assets/data_download.svg'
|
||||||
@ -15,6 +16,8 @@ import OpenIcon from 'enso-assets/open.svg'
|
|||||||
import PenIcon from 'enso-assets/pen.svg'
|
import PenIcon from 'enso-assets/pen.svg'
|
||||||
import PeopleIcon from 'enso-assets/people.svg'
|
import PeopleIcon from 'enso-assets/people.svg'
|
||||||
import ScissorsIcon from 'enso-assets/scissors.svg'
|
import ScissorsIcon from 'enso-assets/scissors.svg'
|
||||||
|
import SignInIcon from 'enso-assets/sign_in.svg'
|
||||||
|
import SignOutIcon from 'enso-assets/sign_out.svg'
|
||||||
import TagIcon from 'enso-assets/tag.svg'
|
import TagIcon from 'enso-assets/tag.svg'
|
||||||
import TrashIcon from 'enso-assets/trash.svg'
|
import TrashIcon from 'enso-assets/trash.svg'
|
||||||
|
|
||||||
@ -61,6 +64,9 @@ export enum KeyboardAction {
|
|||||||
newDataConnector = 'new-data-connector',
|
newDataConnector = 'new-data-connector',
|
||||||
closeModal = 'close-modal',
|
closeModal = 'close-modal',
|
||||||
cancelEditName = 'cancel-edit-name',
|
cancelEditName = 'cancel-edit-name',
|
||||||
|
changeYourPassword = 'change-your-password',
|
||||||
|
signIn = 'sign-in',
|
||||||
|
signOut = 'sign-out',
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Valid mouse buttons. The values of each enum member is its corresponding value of
|
/** Valid mouse buttons. The values of each enum member is its corresponding value of
|
||||||
@ -155,6 +161,9 @@ function makeKeyboardActionMap<T>(make: () => T): Record<KeyboardAction, T> {
|
|||||||
[KeyboardAction.newDataConnector]: make(),
|
[KeyboardAction.newDataConnector]: make(),
|
||||||
[KeyboardAction.closeModal]: make(),
|
[KeyboardAction.closeModal]: make(),
|
||||||
[KeyboardAction.cancelEditName]: make(),
|
[KeyboardAction.cancelEditName]: make(),
|
||||||
|
[KeyboardAction.changeYourPassword]: make(),
|
||||||
|
[KeyboardAction.signIn]: make(),
|
||||||
|
[KeyboardAction.signOut]: make(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,6 +428,9 @@ const DEFAULT_KEYBOARD_SHORTCUTS: Record<KeyboardAction, KeyboardShortcut[]> = {
|
|||||||
],
|
],
|
||||||
[KeyboardAction.closeModal]: [keybind(KeyboardAction.closeModal, [], 'Escape')],
|
[KeyboardAction.closeModal]: [keybind(KeyboardAction.closeModal, [], 'Escape')],
|
||||||
[KeyboardAction.cancelEditName]: [keybind(KeyboardAction.cancelEditName, [], 'Escape')],
|
[KeyboardAction.cancelEditName]: [keybind(KeyboardAction.cancelEditName, [], 'Escape')],
|
||||||
|
[KeyboardAction.changeYourPassword]: [],
|
||||||
|
[KeyboardAction.signIn]: [],
|
||||||
|
[KeyboardAction.signOut]: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The default UI data for every keyboard shortcut. */
|
/** The default UI data for every keyboard shortcut. */
|
||||||
@ -450,6 +462,9 @@ const DEFAULT_KEYBOARD_SHORTCUT_INFO: Record<KeyboardAction, ShortcutInfo> = {
|
|||||||
// These should not appear in any context menus.
|
// These should not appear in any context menus.
|
||||||
[KeyboardAction.closeModal]: { name: 'Close', icon: BlankIcon },
|
[KeyboardAction.closeModal]: { name: 'Close', icon: BlankIcon },
|
||||||
[KeyboardAction.cancelEditName]: { name: 'Cancel Editing', icon: BlankIcon },
|
[KeyboardAction.cancelEditName]: { name: 'Cancel Editing', icon: BlankIcon },
|
||||||
|
[KeyboardAction.changeYourPassword]: { name: 'Change Your Password', icon: ChangePasswordIcon },
|
||||||
|
[KeyboardAction.signIn]: { name: 'Sign In', icon: SignInIcon },
|
||||||
|
[KeyboardAction.signOut]: { name: 'Sign Out', icon: SignOutIcon, colorClass: 'text-delete' },
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The default mouse shortcuts. */
|
/** The default mouse shortcuts. */
|
||||||
|
@ -76,6 +76,7 @@ export const theme = {
|
|||||||
'30': '7.5rem',
|
'30': '7.5rem',
|
||||||
'30.25': '7.5625rem',
|
'30.25': '7.5625rem',
|
||||||
'42': '10.5rem',
|
'42': '10.5rem',
|
||||||
|
'51.5': '12.875rem',
|
||||||
'54': '13.5rem',
|
'54': '13.5rem',
|
||||||
'57.5': '14.375rem',
|
'57.5': '14.375rem',
|
||||||
'62': '15.5rem',
|
'62': '15.5rem',
|
||||||
|
Loading…
Reference in New Issue
Block a user