mirror of
https://github.com/enso-org/enso.git
synced 2024-12-25 03:43:41 +03:00
Minor dashboard fixes (#8732)
- Use `rootDirectoryId` returned by backend instead of mirroring backend behavior to generate the root directory ID - To test this, move/copy an asset *to* the root directory. - Move the right side of the top bar back to the right edge in editor view (oops) - To test this one just open any project and make sure the top bar doesn't look funny. - Delete the barrel export of `#/hooks` in the dashboard (left over due to an oversight when removing the other barrel exports) - Should not need to be tested; all imports have simply been moved to point to the actual declaring file. As such, as long as the code still typechecks, it should be working fine. - Delete a duplicated (unused) file caused by a bad merge. # Important Notes None
This commit is contained in:
parent
ec2de192ce
commit
b52c81f7e9
@ -42,7 +42,7 @@ import * as detect from 'enso-common/src/detect'
|
||||
|
||||
import * as appUtils from '#/appUtils'
|
||||
import * as authServiceModule from '#/authentication/service'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as navigateHooks from '#/hooks/navigateHooks'
|
||||
import ConfirmRegistration from '#/pages/authentication/ConfirmRegistration'
|
||||
import EnterOfflineMode from '#/pages/authentication/EnterOfflineMode'
|
||||
import ForgotPassword from '#/pages/authentication/ForgotPassword'
|
||||
@ -138,7 +138,7 @@ export default function App(props: AppProps) {
|
||||
function AppRouter(props: AppProps) {
|
||||
const { logger, supportsLocalBackend, isAuthenticationDisabled, shouldShowDashboard } = props
|
||||
const { onAuthenticated, projectManagerUrl } = props
|
||||
const navigate = hooks.useNavigate()
|
||||
const navigate = navigateHooks.useNavigate()
|
||||
if (detect.IS_DEV_MODE) {
|
||||
// @ts-expect-error This is used exclusively for debugging.
|
||||
window.navigate = navigate
|
||||
|
@ -5,7 +5,8 @@ import BlankIcon from 'enso-assets/blank.svg'
|
||||
|
||||
import AssetEventType from '#/events/AssetEventType'
|
||||
import AssetListEventType from '#/events/AssetListEventType'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as eventHooks from '#/hooks/eventHooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import AssetContextMenu from '#/layouts/dashboard/AssetContextMenu'
|
||||
import type * as assetsTable from '#/layouts/dashboard/AssetsTable'
|
||||
import * as authProvider from '#/providers/AuthProvider'
|
||||
@ -62,7 +63,7 @@ export default function AssetRow(props: AssetRowProps) {
|
||||
const { organization, user } = authProvider.useNonPartialUserSession()
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const { setModal, unsetModal } = modalProvider.useSetModal()
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const [isDraggedOver, setIsDraggedOver] = React.useState(false)
|
||||
const [item, setItem] = React.useState(rawItem)
|
||||
const dragOverTimeoutHandle = React.useRef<number | null>(null)
|
||||
@ -102,7 +103,7 @@ export default function AssetRow(props: AssetRowProps) {
|
||||
)
|
||||
const copiedAsset = await backend.copyAsset(
|
||||
asset.id,
|
||||
newParentId ?? backend.rootDirectoryId(organization),
|
||||
newParentId ?? organization?.rootDirectoryId ?? backendModule.DirectoryId(''),
|
||||
asset.title,
|
||||
null
|
||||
)
|
||||
@ -138,7 +139,7 @@ export default function AssetRow(props: AssetRowProps) {
|
||||
newParentKey: backendModule.AssetId | null,
|
||||
newParentId: backendModule.DirectoryId | null
|
||||
) => {
|
||||
const rootDirectoryId = backend.rootDirectoryId(organization)
|
||||
const rootDirectoryId = organization?.rootDirectoryId ?? backendModule.DirectoryId('')
|
||||
const nonNullNewParentKey = newParentKey ?? rootDirectoryId
|
||||
const nonNullNewParentId = newParentId ?? rootDirectoryId
|
||||
try {
|
||||
@ -157,10 +158,7 @@ export default function AssetRow(props: AssetRowProps) {
|
||||
)
|
||||
await backend.updateAsset(
|
||||
asset.id,
|
||||
{
|
||||
parentDirectoryId: newParentId ?? backend.rootDirectoryId(organization),
|
||||
description: null,
|
||||
},
|
||||
{ parentDirectoryId: newParentId ?? rootDirectoryId, description: null },
|
||||
asset.title
|
||||
)
|
||||
} catch (error) {
|
||||
@ -269,7 +267,7 @@ export default function AssetRow(props: AssetRowProps) {
|
||||
/* should never change */ toastAndLog,
|
||||
])
|
||||
|
||||
hooks.useEventHandler(assetEvents, async event => {
|
||||
eventHooks.useEventHandler(assetEvents, async event => {
|
||||
switch (event.type) {
|
||||
// These events are handled in the specific `NameColumn` files.
|
||||
case AssetEventType.newProject:
|
||||
|
@ -6,7 +6,8 @@ import TriangleDownIcon from 'enso-assets/triangle_down.svg'
|
||||
|
||||
import AssetEventType from '#/events/AssetEventType'
|
||||
import AssetListEventType from '#/events/AssetListEventType'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as eventHooks from '#/hooks/eventHooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import * as backendProvider from '#/providers/BackendProvider'
|
||||
import * as shortcutsProvider from '#/providers/ShortcutsProvider'
|
||||
import * as backendModule from '#/services/backend'
|
||||
@ -35,7 +36,7 @@ export default function DirectoryNameColumn(props: DirectoryNameColumnProps) {
|
||||
const { item, setItem, selected, setSelected, state, rowState, setRowState } = props
|
||||
const { numberOfSelectedItems, assetEvents, dispatchAssetListEvent, nodeMap } = state
|
||||
const { doToggleDirectoryExpansion } = state
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const { shortcuts } = shortcutsProvider.useShortcuts()
|
||||
const asset = item.item
|
||||
@ -57,7 +58,7 @@ export default function DirectoryNameColumn(props: DirectoryNameColumnProps) {
|
||||
}
|
||||
}
|
||||
|
||||
hooks.useEventHandler(assetEvents, async event => {
|
||||
eventHooks.useEventHandler(assetEvents, async event => {
|
||||
switch (event.type) {
|
||||
case AssetEventType.newProject:
|
||||
case AssetEventType.uploadFiles:
|
||||
|
@ -3,7 +3,8 @@ import * as React from 'react'
|
||||
|
||||
import AssetEventType from '#/events/AssetEventType'
|
||||
import AssetListEventType from '#/events/AssetListEventType'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as eventHooks from '#/hooks/eventHooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import * as backendProvider from '#/providers/BackendProvider'
|
||||
import * as shortcutsProvider from '#/providers/ShortcutsProvider'
|
||||
import * as backendModule from '#/services/backend'
|
||||
@ -32,7 +33,7 @@ export interface FileNameColumnProps extends column.AssetColumnProps {}
|
||||
export default function FileNameColumn(props: FileNameColumnProps) {
|
||||
const { item, setItem, selected, state, rowState, setRowState } = props
|
||||
const { assetEvents, dispatchAssetListEvent } = state
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const { shortcuts } = shortcutsProvider.useShortcuts()
|
||||
const asset = item.item
|
||||
@ -49,7 +50,7 @@ export default function FileNameColumn(props: FileNameColumnProps) {
|
||||
return await Promise.resolve(null)
|
||||
}
|
||||
|
||||
hooks.useEventHandler(assetEvents, async event => {
|
||||
eventHooks.useEventHandler(assetEvents, async event => {
|
||||
switch (event.type) {
|
||||
case AssetEventType.newProject:
|
||||
case AssetEventType.newFolder:
|
||||
|
@ -9,7 +9,8 @@ import StopIcon from 'enso-assets/stop.svg'
|
||||
|
||||
import type * as assetEvent from '#/events/assetEvent'
|
||||
import AssetEventType from '#/events/AssetEventType'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as eventHooks from '#/hooks/eventHooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import * as authProvider from '#/providers/AuthProvider'
|
||||
import * as backendProvider from '#/providers/BackendProvider'
|
||||
import * as localStorageProvider from '#/providers/LocalStorageProvider'
|
||||
@ -81,7 +82,7 @@ export default function ProjectIcon(props: ProjectIconProps) {
|
||||
const { organization } = authProvider.useNonPartialUserSession()
|
||||
const { unsetModal } = modalProvider.useSetModal()
|
||||
const { localStorage } = localStorageProvider.useLocalStorage()
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const state = item.projectState.type
|
||||
const setState = React.useCallback(
|
||||
(stateOrUpdater: React.SetStateAction<backendModule.ProjectState>) => {
|
||||
@ -234,7 +235,7 @@ export default function ProjectIcon(props: ProjectIconProps) {
|
||||
}
|
||||
}, [onSpinnerStateChange])
|
||||
|
||||
hooks.useEventHandler(assetEvents, event => {
|
||||
eventHooks.useEventHandler(assetEvents, event => {
|
||||
switch (event.type) {
|
||||
case AssetEventType.newFolder:
|
||||
case AssetEventType.uploadFiles:
|
||||
|
@ -5,7 +5,8 @@ import NetworkIcon from 'enso-assets/network.svg'
|
||||
|
||||
import AssetEventType from '#/events/AssetEventType'
|
||||
import AssetListEventType from '#/events/AssetListEventType'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as eventHooks from '#/hooks/eventHooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import * as authProvider from '#/providers/AuthProvider'
|
||||
import * as backendProvider from '#/providers/BackendProvider'
|
||||
import * as shortcutsProvider from '#/providers/ShortcutsProvider'
|
||||
@ -39,7 +40,7 @@ export default function ProjectNameColumn(props: ProjectNameColumnProps) {
|
||||
const { item, setItem, selected, rowState, setRowState, state } = props
|
||||
const { numberOfSelectedItems, assetEvents, dispatchAssetEvent, dispatchAssetListEvent } = state
|
||||
const { nodeMap, doOpenManually, doOpenIde, doCloseIde } = state
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const { organization } = authProvider.useNonPartialUserSession()
|
||||
const { shortcuts } = shortcutsProvider.useShortcuts()
|
||||
@ -86,7 +87,7 @@ export default function ProjectNameColumn(props: ProjectNameColumnProps) {
|
||||
}
|
||||
}
|
||||
|
||||
hooks.useEventHandler(assetEvents, async event => {
|
||||
eventHooks.useEventHandler(assetEvents, async event => {
|
||||
switch (event.type) {
|
||||
case AssetEventType.newFolder:
|
||||
case AssetEventType.newDataConnector:
|
||||
|
@ -5,7 +5,8 @@ import ConnectorIcon from 'enso-assets/connector.svg'
|
||||
|
||||
import AssetEventType from '#/events/AssetEventType'
|
||||
import AssetListEventType from '#/events/AssetListEventType'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as eventHooks from '#/hooks/eventHooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import UpsertSecretModal from '#/layouts/dashboard/UpsertSecretModal'
|
||||
import * as backendProvider from '#/providers/BackendProvider'
|
||||
import * as modalProvider from '#/providers/ModalProvider'
|
||||
@ -34,7 +35,7 @@ export interface SecretNameColumnProps extends column.AssetColumnProps {}
|
||||
export default function SecretNameColumn(props: SecretNameColumnProps) {
|
||||
const { item, setItem, selected, state, rowState, setRowState } = props
|
||||
const { assetEvents, dispatchAssetListEvent } = state
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const { setModal } = modalProvider.useSetModal()
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const { shortcuts } = shortcutsProvider.useShortcuts()
|
||||
@ -52,7 +53,7 @@ export default function SecretNameColumn(props: SecretNameColumnProps) {
|
||||
await Promise.resolve(null)
|
||||
}
|
||||
|
||||
hooks.useEventHandler(assetEvents, async event => {
|
||||
eventHooks.useEventHandler(assetEvents, async event => {
|
||||
switch (event.type) {
|
||||
case AssetEventType.newProject:
|
||||
case AssetEventType.newFolder:
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @file A user and their permissions for a specific asset. */
|
||||
import * as React from 'react'
|
||||
|
||||
import * as hooks from '#/hooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import * as backendProvider from '#/providers/BackendProvider'
|
||||
import * as backendModule from '#/services/backend'
|
||||
import * as object from '#/utilities/object'
|
||||
@ -24,7 +24,7 @@ export default function UserPermissions(props: UserPermissionsProps) {
|
||||
const { userPermission: initialUserPermission, setUserPermission: outerSetUserPermission } =
|
||||
props
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const [userPermissions, setUserPermissions] = React.useState(initialUserPermission)
|
||||
|
||||
React.useEffect(() => {
|
||||
|
@ -3,7 +3,7 @@ import * as React from 'react'
|
||||
|
||||
import Plus2Icon from 'enso-assets/plus2.svg'
|
||||
|
||||
import * as hooks from '#/hooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import Category from '#/layouts/dashboard/CategorySwitcher/Category'
|
||||
import ManageLabelsModal from '#/layouts/dashboard/ManageLabelsModal'
|
||||
import * as authProvider from '#/providers/AuthProvider'
|
||||
@ -36,7 +36,7 @@ export default function LabelsColumn(props: column.AssetColumnProps) {
|
||||
const session = authProvider.useNonPartialUserSession()
|
||||
const { setModal, unsetModal } = modalProvider.useSetModal()
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const [isHovered, setIsHovered] = React.useState(false)
|
||||
const self = asset.permissions?.find(
|
||||
permission => permission.user.user_email === session.organization?.email
|
||||
|
@ -1,7 +0,0 @@
|
||||
/** @file Barrel exports for all React Hooks, other than debug hooks. */
|
||||
export * from '#/hooks/asyncEffectHooks'
|
||||
export * from '#/hooks/eventHooks'
|
||||
export * from '#/hooks/navigateHooks'
|
||||
export * from '#/hooks/refreshHooks'
|
||||
export * from '#/hooks/routerHooks'
|
||||
export * from '#/hooks/toastAndLogHooks'
|
@ -1,17 +0,0 @@
|
||||
/** @file Re-exports of React Router hooks. */
|
||||
import * as router from 'react-router-dom'
|
||||
|
||||
/** Returns the current location object, which represents the current URL in web browsers.
|
||||
*
|
||||
* Note: If you're using this it may mean you're doing some of your own "routing" in your app,
|
||||
* and we'd like to know what your use case is. We may be able to provide something higher-level
|
||||
* to better suit your needs.
|
||||
* @see {@link https://reactrouter.com/hooks/use-location}
|
||||
*/
|
||||
// This is a function, even though it does not look like one.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
export const useLocation = router.useLocation
|
||||
|
||||
/** Returns the context (if provided) for the child route at this level of the route hierarchy.
|
||||
* @see {@link https://reactrouter.com/hooks/use-outlet-context} */
|
||||
export const useOutletContext = router.useOutletContext
|
@ -5,7 +5,7 @@ import * as toast from 'react-toastify'
|
||||
|
||||
import AssetEventType from '#/events/AssetEventType'
|
||||
import AssetListEventType from '#/events/AssetListEventType'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import type * as assetsTable from '#/layouts/dashboard/AssetsTable'
|
||||
import Category from '#/layouts/dashboard/CategorySwitcher/Category'
|
||||
import GlobalContextMenu from '#/layouts/dashboard/GlobalContextMenu'
|
||||
@ -63,7 +63,7 @@ export default function AssetContextMenu(props: AssetContextMenuProps) {
|
||||
const { organization, accessToken } = authProvider.useNonPartialUserSession()
|
||||
const { setModal, unsetModal } = modalProvider.useSetModal()
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const asset = item.item
|
||||
const self = asset.permissions?.find(
|
||||
permission => permission.user.user_email === organization?.email
|
||||
|
@ -4,7 +4,7 @@ import * as React from 'react'
|
||||
import PenIcon from 'enso-assets/pen.svg'
|
||||
|
||||
import type * as assetEvent from '#/events/assetEvent'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import type Category from '#/layouts/dashboard/CategorySwitcher/Category'
|
||||
import type * as pageSwitcher from '#/layouts/dashboard/PageSwitcher'
|
||||
import UserBar from '#/layouts/dashboard/UserBar'
|
||||
@ -55,7 +55,7 @@ export default function AssetSettingsPanel(props: AssetSettingsPanelProps) {
|
||||
const [description, setDescription] = React.useState('')
|
||||
const { organization } = authProvider.useNonPartialUserSession()
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const setItem = React.useCallback(
|
||||
(valueOrUpdater: React.SetStateAction<assetTreeNode.AssetTreeNode>) => {
|
||||
innerSetItem(valueOrUpdater)
|
||||
|
@ -7,7 +7,9 @@ import type * as assetEvent from '#/events/assetEvent'
|
||||
import AssetEventType from '#/events/AssetEventType'
|
||||
import type * as assetListEvent from '#/events/assetListEvent'
|
||||
import AssetListEventType from '#/events/AssetListEventType'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as asyncEffectHooks from '#/hooks/asyncEffectHooks'
|
||||
import * as eventHooks from '#/hooks/eventHooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import type * as assetSearchBar from '#/layouts/dashboard/assetSearchBar'
|
||||
import type * as assetSettingsPanel from '#/layouts/dashboard/AssetSettingsPanel'
|
||||
import Category from '#/layouts/dashboard/CategorySwitcher/Category'
|
||||
@ -354,7 +356,7 @@ export default function AssetsTable(props: AssetsTableProps) {
|
||||
const { setModal, unsetModal } = modalProvider.useSetModal()
|
||||
const { localStorage } = localStorageProvider.useLocalStorage()
|
||||
const { shortcuts } = shortcutsProvider.useShortcuts()
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const [initialized, setInitialized] = React.useState(false)
|
||||
const [isLoading, setIsLoading] = React.useState(true)
|
||||
const [extraColumns, setExtraColumns] = React.useState(() => new Set<columnUtils.ExtraColumn>())
|
||||
@ -367,8 +369,8 @@ export default function AssetsTable(props: AssetsTableProps) {
|
||||
const [, setQueuedAssetEvents] = React.useState<assetEvent.AssetEvent[]>([])
|
||||
const [, setNameOfProjectToImmediatelyOpen] = React.useState(initialProjectName)
|
||||
const rootDirectoryId = React.useMemo(
|
||||
() => backend.rootDirectoryId(organization),
|
||||
[backend, organization]
|
||||
() => organization?.rootDirectoryId ?? backendModule.DirectoryId(''),
|
||||
[organization]
|
||||
)
|
||||
const [assetTree, setAssetTree] = React.useState<assetTreeNode.AssetTreeNode>(() => {
|
||||
const rootParentDirectoryId = backendModule.DirectoryId('')
|
||||
@ -901,7 +903,7 @@ export default function AssetsTable(props: AssetsTableProps) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [backend, category])
|
||||
|
||||
hooks.useAsyncEffect(
|
||||
asyncEffectHooks.useAsyncEffect(
|
||||
null,
|
||||
async signal => {
|
||||
switch (backend.type) {
|
||||
@ -1278,7 +1280,7 @@ export default function AssetsTable(props: AssetsTableProps) {
|
||||
[rootDirectoryId]
|
||||
)
|
||||
|
||||
hooks.useEventHandler(assetListEvents, event => {
|
||||
eventHooks.useEventHandler(assetListEvents, event => {
|
||||
switch (event.type) {
|
||||
case AssetListEventType.newFolder: {
|
||||
const siblings = nodeMapRef.current.get(event.parentKey)?.children ?? []
|
||||
|
@ -6,7 +6,7 @@ import * as reactDom from 'react-dom'
|
||||
import CloseLargeIcon from 'enso-assets/close_large.svg'
|
||||
|
||||
import * as appUtils from '#/appUtils'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as navigateHooks from '#/hooks/navigateHooks'
|
||||
import * as chat from '#/layouts/dashboard/Chat'
|
||||
import * as pageSwitcher from '#/layouts/dashboard/PageSwitcher'
|
||||
import * as loggerProvider from '#/providers/LoggerProvider'
|
||||
@ -24,7 +24,7 @@ export interface ChatPlaceholderProps {
|
||||
export default function ChatPlaceholder(props: ChatPlaceholderProps) {
|
||||
const { page, isOpen, doClose } = props
|
||||
const logger = loggerProvider.useLogger()
|
||||
const navigate = hooks.useNavigate()
|
||||
const navigate = navigateHooks.useNavigate()
|
||||
const [right, setTargetRight] = animations.useInterpolateOverTime(
|
||||
animations.interpolationFunctionEaseInOut,
|
||||
chat.ANIMATION_DURATION_MS,
|
||||
|
@ -8,7 +8,8 @@ import type * as assetEvent from '#/events/assetEvent'
|
||||
import AssetEventType from '#/events/AssetEventType'
|
||||
import type * as assetListEvent from '#/events/assetListEvent'
|
||||
import AssetListEventType from '#/events/AssetListEventType'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as navigateHooks from '#/hooks/navigateHooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import type * as assetSearchBar from '#/layouts/dashboard/assetSearchBar'
|
||||
import type * as assetSettingsPanel from '#/layouts/dashboard/AssetSettingsPanel'
|
||||
import AssetsTable from '#/layouts/dashboard/AssetsTable'
|
||||
@ -77,12 +78,12 @@ export default function Drive(props: DriveProps) {
|
||||
const { loadingProjectManagerDidFail, isListingRemoteDirectoryWhileOffline } = props
|
||||
const { isListingLocalDirectoryAndWillFail, isListingRemoteDirectoryAndWillFail } = props
|
||||
|
||||
const navigate = hooks.useNavigate()
|
||||
const navigate = navigateHooks.useNavigate()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const { organization } = authProvider.useNonPartialUserSession()
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const { localStorage } = localStorageProvider.useLocalStorage()
|
||||
const { modalRef } = modalProvider.useModalRef()
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const [isFileBeingDragged, setIsFileBeingDragged] = React.useState(false)
|
||||
const [category, setCategory] = React.useState(
|
||||
() => localStorage.get(localStorageModule.LocalStorageKey.driveCategory) ?? Category.home
|
||||
@ -96,8 +97,8 @@ export default function Drive(props: DriveProps) {
|
||||
[labels]
|
||||
)
|
||||
const rootDirectoryId = React.useMemo(
|
||||
() => backend.rootDirectoryId(organization),
|
||||
[backend, organization]
|
||||
() => organization?.rootDirectoryId ?? backendModule.DirectoryId(''),
|
||||
[organization]
|
||||
)
|
||||
const isCloud = backend.type === backendModule.BackendType.remote
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @file The container that launches the IDE. */
|
||||
import * as React from 'react'
|
||||
|
||||
import * as hooks from '#/hooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import * as backendModule from '#/services/backend'
|
||||
import * as load from '#/utilities/load'
|
||||
|
||||
@ -39,7 +39,7 @@ export interface EditorProps {
|
||||
/** The container that launches the IDE. */
|
||||
export default function Editor(props: EditorProps) {
|
||||
const { hidden, supportsLocalBackend, projectStartupInfo, appRunner } = props
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const [initialized, setInitialized] = React.useState(supportsLocalBackend)
|
||||
|
||||
React.useEffect(() => {
|
||||
|
@ -31,8 +31,8 @@ export default function GlobalContextMenu(props: GlobalContextMenuProps) {
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const { setModal, unsetModal } = modalProvider.useSetModal()
|
||||
const rootDirectoryId = React.useMemo(
|
||||
() => backend.rootDirectoryId(organization),
|
||||
[backend, organization]
|
||||
() => organization?.rootDirectoryId ?? backendModule.DirectoryId(''),
|
||||
[organization]
|
||||
)
|
||||
const filesInputRef = React.useRef<HTMLInputElement>(null)
|
||||
const isCloud = backend.type === backendModule.BackendType.remote
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @file A modal to select labels for an asset. */
|
||||
import * as React from 'react'
|
||||
|
||||
import * as hooks from '#/hooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import * as authProvider from '#/providers/AuthProvider'
|
||||
import * as backendProvider from '#/providers/BackendProvider'
|
||||
import * as modalProvider from '#/providers/ModalProvider'
|
||||
@ -39,7 +39,7 @@ export default function ManageLabelsModal<
|
||||
const { organization } = authProvider.useNonPartialUserSession()
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const { unsetModal } = modalProvider.useSetModal()
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const [labels, setLabelsRaw] = React.useState(item.labels ?? [])
|
||||
const [query, setQuery] = React.useState('')
|
||||
const [color, setColor] = React.useState<backendModule.LChColor | null>(null)
|
||||
|
@ -4,7 +4,8 @@ import * as React from 'react'
|
||||
import * as toast from 'react-toastify'
|
||||
import isEmail from 'validator/es/lib/isEmail'
|
||||
|
||||
import * as hooks from '#/hooks'
|
||||
import * as asyncEffectHooks from '#/hooks/asyncEffectHooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import * as authProvider from '#/providers/AuthProvider'
|
||||
import * as backendProvider from '#/providers/BackendProvider'
|
||||
import * as modalProvider from '#/providers/ModalProvider'
|
||||
@ -53,7 +54,7 @@ export default function ManagePermissionsModal<
|
||||
const { organization } = authProvider.useNonPartialUserSession()
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const { unsetModal } = modalProvider.useSetModal()
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
const [permissions, setPermissions] = React.useState(item.permissions ?? [])
|
||||
const [users, setUsers] = React.useState<backendModule.SimpleUser[]>([])
|
||||
const [email, setEmail] = React.useState<string | null>(null)
|
||||
@ -103,7 +104,7 @@ export default function ManagePermissionsModal<
|
||||
// This MUST be an error, otherwise the hooks below are considered as conditionally called.
|
||||
throw new Error('Cannot share assets on the local backend.')
|
||||
} else {
|
||||
const listedUsers = hooks.useAsyncEffect([], () => backend.listUsers(), [])
|
||||
const listedUsers = asyncEffectHooks.useAsyncEffect([], () => backend.listUsers(), [])
|
||||
const allUsers = React.useMemo(
|
||||
() =>
|
||||
listedUsers.filter(
|
||||
|
@ -56,7 +56,9 @@ export default function TopBar(props: TopBarProps) {
|
||||
{supportsLocalBackend && page !== pageSwitcher.Page.editor && (
|
||||
<BackendSwitcher setBackendType={setBackendType} />
|
||||
)}
|
||||
{page !== pageSwitcher.Page.editor && (
|
||||
{page === pageSwitcher.Page.editor ? (
|
||||
<div className="flex-1" />
|
||||
) : (
|
||||
<div className="flex-1 flex flex-wrap justify-around">
|
||||
<AssetSearchBar
|
||||
query={query}
|
||||
|
@ -4,7 +4,8 @@ import * as React from 'react'
|
||||
import DefaultUserIcon from 'enso-assets/default_user.svg'
|
||||
|
||||
import * as appUtils from '#/appUtils'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as navigateHooks from '#/hooks/navigateHooks'
|
||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||
import ChangePasswordModal from '#/layouts/dashboard/ChangePasswordModal'
|
||||
import * as authProvider from '#/providers/AuthProvider'
|
||||
import * as modalProvider from '#/providers/ModalProvider'
|
||||
@ -27,11 +28,11 @@ export interface UserMenuProps {
|
||||
/** Handling the UserMenuItem click event logic and displaying its content. */
|
||||
export default function UserMenu(props: UserMenuProps) {
|
||||
const { supportsLocalBackend, onSignOut } = props
|
||||
const navigate = hooks.useNavigate()
|
||||
const navigate = navigateHooks.useNavigate()
|
||||
const { signOut } = authProvider.useAuth()
|
||||
const { accessToken, organization } = authProvider.useNonPartialUserSession()
|
||||
const { setModal, unsetModal } = modalProvider.useSetModal()
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||
|
||||
// The shape of the JWT payload is statically known.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
|
@ -1,169 +0,0 @@
|
||||
/** @file The icon and name of a {@link backendModule.SecretAsset}. */
|
||||
import * as React from 'react'
|
||||
|
||||
import ConnectorIcon from 'enso-assets/connector.svg'
|
||||
|
||||
import AssetEventType from '#/events/AssetEventType'
|
||||
import AssetListEventType from '#/events/AssetListEventType'
|
||||
import * as hooks from '#/hooks'
|
||||
import UpsertSecretModal from '#/layouts/dashboard/UpsertSecretModal'
|
||||
import * as backendProvider from '#/providers/BackendProvider'
|
||||
import * as modalProvider from '#/providers/ModalProvider'
|
||||
import * as shortcutsProvider from '#/providers/ShortcutsProvider'
|
||||
import * as backendModule from '#/services/backend'
|
||||
import * as assetTreeNode from '#/utilities/assetTreeNode'
|
||||
import * as eventModule from '#/utilities/event'
|
||||
import * as indent from '#/utilities/indent'
|
||||
import * as object from '#/utilities/object'
|
||||
import * as shortcutsModule from '#/utilities/shortcuts'
|
||||
import Visibility from '#/utilities/visibility'
|
||||
|
||||
import type * as column from '#/components/dashboard/column'
|
||||
import EditableSpan from '#/components/EditableSpan'
|
||||
|
||||
// =====================
|
||||
// === ConnectorName ===
|
||||
// =====================
|
||||
|
||||
/** Props for a {@link SecretNameColumn}. */
|
||||
export interface SecretNameColumnProps extends column.AssetColumnProps {}
|
||||
|
||||
/** The icon and name of a {@link backendModule.SecretAsset}.
|
||||
* @throws {Error} when the asset is not a {@link backendModule.SecretAsset}.
|
||||
* This should never happen. */
|
||||
export default function SecretNameColumn(props: SecretNameColumnProps) {
|
||||
const { item, setItem, selected, state, rowState, setRowState } = props
|
||||
const { assetEvents, dispatchAssetListEvent } = state
|
||||
const toastAndLog = hooks.useToastAndLog()
|
||||
const { setModal } = modalProvider.useSetModal()
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const { shortcuts } = shortcutsProvider.useShortcuts()
|
||||
const asset = item.item
|
||||
if (asset.type !== backendModule.AssetType.secret) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
throw new Error('`SecretNameColumn` can only display secrets.')
|
||||
}
|
||||
const setAsset = assetTreeNode.useSetAsset(asset, setItem)
|
||||
|
||||
// TODO[sb]: Wait for backend implementation. `editable` should also be re-enabled, and the
|
||||
// context menu entry should be re-added.
|
||||
// Backend implementation is tracked here: https://github.com/enso-org/cloud-v2/issues/505.
|
||||
const doRename = async () => {
|
||||
await Promise.resolve(null)
|
||||
}
|
||||
|
||||
hooks.useEventHandler(assetEvents, async event => {
|
||||
switch (event.type) {
|
||||
case AssetEventType.newProject:
|
||||
case AssetEventType.newFolder:
|
||||
case AssetEventType.uploadFiles:
|
||||
case AssetEventType.openProject:
|
||||
case AssetEventType.closeProject:
|
||||
case AssetEventType.cancelOpeningAllProjects:
|
||||
case AssetEventType.copy:
|
||||
case AssetEventType.cut:
|
||||
case AssetEventType.cancelCut:
|
||||
case AssetEventType.move:
|
||||
case AssetEventType.delete:
|
||||
case AssetEventType.restore:
|
||||
case AssetEventType.download:
|
||||
case AssetEventType.downloadSelected:
|
||||
case AssetEventType.removeSelf:
|
||||
case AssetEventType.temporarilyAddLabels:
|
||||
case AssetEventType.temporarilyRemoveLabels:
|
||||
case AssetEventType.addLabels:
|
||||
case AssetEventType.removeLabels:
|
||||
case AssetEventType.deleteLabel: {
|
||||
// Ignored. These events should all be unrelated to secrets.
|
||||
// `deleteMultiple`, `restoreMultiple`, `download`,
|
||||
// and `downloadSelected` are handled by `AssetRow`.
|
||||
break
|
||||
}
|
||||
case AssetEventType.newDataConnector: {
|
||||
if (item.key === event.placeholderId) {
|
||||
if (backend.type !== backendModule.BackendType.remote) {
|
||||
toastAndLog('Data connectors cannot be created on the local backend')
|
||||
} else {
|
||||
rowState.setVisibility(Visibility.faded)
|
||||
try {
|
||||
const id = await backend.createSecret({
|
||||
parentDirectoryId: asset.parentId,
|
||||
name: asset.title,
|
||||
value: event.value,
|
||||
})
|
||||
rowState.setVisibility(Visibility.visible)
|
||||
setAsset(object.merger({ id }))
|
||||
} catch (error) {
|
||||
dispatchAssetListEvent({
|
||||
type: AssetListEventType.delete,
|
||||
key: item.key,
|
||||
})
|
||||
toastAndLog('Error creating new data connector', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex text-left items-center whitespace-nowrap rounded-l-full gap-1 px-1.5 py-1 min-w-max ${indent.indentClass(
|
||||
item.depth
|
||||
)}`}
|
||||
onKeyDown={event => {
|
||||
if (rowState.isEditingName && event.key === 'Enter') {
|
||||
event.stopPropagation()
|
||||
}
|
||||
}}
|
||||
onClick={event => {
|
||||
if (
|
||||
eventModule.isSingleClick(event) &&
|
||||
(selected ||
|
||||
shortcuts.matchesMouseAction(shortcutsModule.MouseAction.editName, event))
|
||||
) {
|
||||
setRowState(object.merger({ isEditingName: true }))
|
||||
} else if (eventModule.isDoubleClick(event)) {
|
||||
event.stopPropagation()
|
||||
setModal(
|
||||
<UpsertSecretModal
|
||||
id={asset.id}
|
||||
name={asset.title}
|
||||
doCreate={async (_name, value) => {
|
||||
try {
|
||||
await backend.updateSecret(asset.id, { value }, asset.title)
|
||||
} catch (error) {
|
||||
toastAndLog(null, error)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<img src={ConnectorIcon} className="m-1" />
|
||||
<EditableSpan
|
||||
editable={false}
|
||||
onSubmit={async newTitle => {
|
||||
setRowState(object.merger({ isEditingName: false }))
|
||||
if (newTitle !== asset.title) {
|
||||
const oldTitle = asset.title
|
||||
setAsset(object.merger({ title: newTitle }))
|
||||
try {
|
||||
await doRename()
|
||||
} catch {
|
||||
setAsset(object.merger({ title: oldTitle }))
|
||||
}
|
||||
}
|
||||
}}
|
||||
onCancel={() => {
|
||||
setRowState(object.merger({ isEditingName: false }))
|
||||
}}
|
||||
className="bg-transparent grow leading-170 h-6 py-px"
|
||||
>
|
||||
{asset.title}
|
||||
</EditableSpan>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -2,10 +2,11 @@
|
||||
* email address. */
|
||||
import * as React from 'react'
|
||||
|
||||
import * as router from 'react-router-dom'
|
||||
import * as toastify from 'react-toastify'
|
||||
|
||||
import * as appUtils from '#/appUtils'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as navigateHooks from '#/hooks/navigateHooks'
|
||||
import * as authProvider from '#/providers/AuthProvider'
|
||||
import * as loggerProvider from '#/providers/LoggerProvider'
|
||||
|
||||
@ -27,8 +28,8 @@ const REGISTRATION_QUERY_PARAMS = {
|
||||
export default function ConfirmRegistration() {
|
||||
const logger = loggerProvider.useLogger()
|
||||
const auth = authProvider.useAuth()
|
||||
const location = hooks.useLocation()
|
||||
const navigate = hooks.useNavigate()
|
||||
const location = router.useLocation()
|
||||
const navigate = navigateHooks.useNavigate()
|
||||
|
||||
const { verificationCode, email, redirectUrl } = parseUrlSearchParams(location.search)
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import * as appUtils from '#/appUtils'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as navigateHooks from '#/hooks/navigateHooks'
|
||||
import * as authProvider from '#/providers/AuthProvider'
|
||||
|
||||
// ========================
|
||||
@ -12,7 +12,7 @@ import * as authProvider from '#/providers/AuthProvider'
|
||||
/** An empty component redirecting users based on the backend response to user registration. */
|
||||
export default function EnterOfflineMode() {
|
||||
const { goOffline } = authProvider.useAuth()
|
||||
const navigate = hooks.useNavigate()
|
||||
const navigate = navigateHooks.useNavigate()
|
||||
|
||||
React.useEffect(() => {
|
||||
void (async () => {
|
||||
|
@ -10,7 +10,7 @@ import GoBackIcon from 'enso-assets/go_back.svg'
|
||||
import LockIcon from 'enso-assets/lock.svg'
|
||||
|
||||
import * as appUtils from '#/appUtils'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as navigateHooks from '#/hooks/navigateHooks'
|
||||
import * as authProvider from '#/providers/AuthProvider'
|
||||
import * as string from '#/utilities/string'
|
||||
import * as validation from '#/utilities/validation'
|
||||
@ -36,7 +36,7 @@ const RESET_PASSWORD_QUERY_PARAMS = {
|
||||
export default function ResetPassword() {
|
||||
const { resetPassword } = authProvider.useAuth()
|
||||
const { search } = router.useLocation()
|
||||
const navigate = hooks.useNavigate()
|
||||
const navigate = navigateHooks.useNavigate()
|
||||
|
||||
const { verificationCode, email } = parseUrlSearchParams(search)
|
||||
|
||||
|
@ -6,7 +6,7 @@ import type * as assetEvent from '#/events/assetEvent'
|
||||
import AssetEventType from '#/events/AssetEventType'
|
||||
import type * as assetListEvent from '#/events/assetListEvent'
|
||||
import AssetListEventType from '#/events/AssetListEventType'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as eventHooks from '#/hooks/eventHooks'
|
||||
import type * as assetSearchBar from '#/layouts/dashboard/assetSearchBar'
|
||||
import type * as assetSettingsPanel from '#/layouts/dashboard/AssetSettingsPanel'
|
||||
import AssetSettingsPanel from '#/layouts/dashboard/AssetSettingsPanel'
|
||||
@ -78,8 +78,8 @@ export default function Dashboard(props: DashboardProps) {
|
||||
const [openProjectAbortController, setOpenProjectAbortController] =
|
||||
React.useState<AbortController | null>(null)
|
||||
const [assetListEvents, dispatchAssetListEvent] =
|
||||
hooks.useEvent<assetListEvent.AssetListEvent>()
|
||||
const [assetEvents, dispatchAssetEvent] = hooks.useEvent<assetEvent.AssetEvent>()
|
||||
eventHooks.useEvent<assetListEvent.AssetListEvent>()
|
||||
const [assetEvents, dispatchAssetEvent] = eventHooks.useEvent<assetEvent.AssetEvent>()
|
||||
const [assetSettingsPanelProps, setAssetSettingsPanelProps] =
|
||||
React.useState<assetSettingsPanel.AssetSettingsPanelRequiredProps | null>(null)
|
||||
const [isAssetSettingsPanelVisible, setIsAssetSettingsPanelVisible] = React.useState(
|
||||
@ -89,8 +89,8 @@ export default function Dashboard(props: DashboardProps) {
|
||||
)
|
||||
const [initialProjectName, setInitialProjectName] = React.useState(rawInitialProjectName)
|
||||
const rootDirectoryId = React.useMemo(
|
||||
() => backend.rootDirectoryId(session.organization),
|
||||
[backend, session.organization]
|
||||
() => session.organization?.rootDirectoryId ?? backendModule.DirectoryId(''),
|
||||
[session.organization]
|
||||
)
|
||||
|
||||
const isListingLocalDirectoryAndWillFail =
|
||||
@ -142,7 +142,7 @@ export default function Dashboard(props: DashboardProps) {
|
||||
if (
|
||||
currentBackend.type === backendModule.BackendType.remote &&
|
||||
savedProjectStartupInfo.projectAsset.parentId ===
|
||||
backend.rootDirectoryId(session.organization)
|
||||
session.organization.rootDirectoryId
|
||||
) {
|
||||
// `projectStartupInfo` is still `null`, so the `editor` page will be empty.
|
||||
setPage(pageSwitcher.Page.drive)
|
||||
@ -220,7 +220,7 @@ export default function Dashboard(props: DashboardProps) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
hooks.useEventHandler(assetEvents, event => {
|
||||
eventHooks.useEventHandler(assetEvents, event => {
|
||||
switch (event.type) {
|
||||
case AssetEventType.openProject: {
|
||||
openProjectAbortController?.abort()
|
||||
|
@ -14,7 +14,6 @@ import * as gtag from 'enso-common/src/gtag'
|
||||
import * as appUtils from '#/appUtils'
|
||||
import * as cognitoModule from '#/authentication/cognito'
|
||||
import type * as authServiceModule from '#/authentication/service'
|
||||
import * as hooks from '#/hooks'
|
||||
import LoadingScreen from '#/pages/authentication/LoadingScreen'
|
||||
import * as backendProvider from '#/providers/BackendProvider'
|
||||
import * as localStorageProvider from '#/providers/LocalStorageProvider'
|
||||
@ -692,7 +691,7 @@ export function GuestLayout() {
|
||||
/** A React context hook returning the user session
|
||||
* for a user that has not yet completed registration. */
|
||||
export function usePartialUserSession() {
|
||||
return hooks.useOutletContext<PartialUserSession>()
|
||||
return router.useOutletContext<PartialUserSession>()
|
||||
}
|
||||
|
||||
// ================================
|
||||
@ -701,5 +700,5 @@ export function usePartialUserSession() {
|
||||
|
||||
/** A React context hook returning the user session for a user that can perform actions. */
|
||||
export function useNonPartialUserSession() {
|
||||
return hooks.useOutletContext<Exclude<UserSession, PartialUserSession>>()
|
||||
return router.useOutletContext<Exclude<UserSession, PartialUserSession>>()
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ import * as React from 'react'
|
||||
|
||||
import type * as cognito from '#/authentication/cognito'
|
||||
import * as listen from '#/authentication/listen'
|
||||
import * as hooks from '#/hooks'
|
||||
import * as asyncEffectHooks from '#/hooks/asyncEffectHooks'
|
||||
import * as refreshHooks from '#/hooks/refreshHooks'
|
||||
import * as error from '#/utilities/error'
|
||||
|
||||
// ======================
|
||||
@ -51,7 +52,7 @@ export interface SessionProviderProps {
|
||||
export default function SessionProvider(props: SessionProviderProps) {
|
||||
const { mainPageUrl, children, userSession, registerAuthEventListener } = props
|
||||
|
||||
const [refresh, doRefresh] = hooks.useRefresh()
|
||||
const [refresh, doRefresh] = refreshHooks.useRefresh()
|
||||
|
||||
/** Flag used to avoid rendering child components until we've fetched the user's session at least
|
||||
* once. Avoids flash of the login screen when the user is already logged in. */
|
||||
@ -60,7 +61,7 @@ export default function SessionProvider(props: SessionProviderProps) {
|
||||
/** Register an async effect that will fetch the user's session whenever the `refresh` state is
|
||||
* set. This is useful when a user has just logged in (as their cached credentials are
|
||||
* out of date, so this will update them). */
|
||||
const session = hooks.useAsyncEffect(
|
||||
const session = asyncEffectHooks.useAsyncEffect(
|
||||
null,
|
||||
async () => {
|
||||
const innerSession = await userSession()
|
||||
|
@ -93,6 +93,7 @@ export interface UserOrOrganization {
|
||||
/** If `false`, this account is awaiting acceptance from an admin, and endpoints other than
|
||||
* `usersMe` will not work. */
|
||||
isEnabled: boolean
|
||||
rootDirectoryId: DirectoryId
|
||||
}
|
||||
|
||||
/** A `Directory` returned by `createDirectory`. */
|
||||
@ -835,8 +836,6 @@ export function stripProjectExtension(name: string) {
|
||||
export abstract class Backend {
|
||||
abstract readonly type: BackendType
|
||||
|
||||
/** Return the root directory id for the given user. */
|
||||
abstract rootDirectoryId(user: UserOrOrganization | null): DirectoryId
|
||||
/** Return a list of all users in the same organization. */
|
||||
abstract listUsers(): Promise<SimpleUser[]>
|
||||
/** Set the username of the current user. */
|
||||
|
@ -42,10 +42,6 @@ export class LocalBackend extends backend.Backend {
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the root directory id for the given user. */
|
||||
override rootDirectoryId(): backend.DirectoryId {
|
||||
return backend.DirectoryId('')
|
||||
}
|
||||
/** Return a list of assets in a directory.
|
||||
* @throws An error if the JSON-RPC call fails. */
|
||||
override async listDirectory(): Promise<backend.AnyAsset[]> {
|
||||
|
@ -155,20 +155,6 @@ export class RemoteBackend extends backendModule.Backend {
|
||||
throw new Error(message)
|
||||
}
|
||||
|
||||
/** Return the root directory id for the given user. */
|
||||
override rootDirectoryId(
|
||||
user: backendModule.UserOrOrganization | null
|
||||
): backendModule.DirectoryId {
|
||||
if (user != null && !user.id.startsWith('organization-')) {
|
||||
this.logger.error(`User ID '${user.id}' does not start with 'organization-'`)
|
||||
}
|
||||
return backendModule.DirectoryId(
|
||||
// `user` is only null when the user is offline, in which case the remote backend cannot
|
||||
// be accessed anyway.
|
||||
user?.id.replace(/^organization-/, `${backendModule.AssetType.directory}-`) ?? ''
|
||||
)
|
||||
}
|
||||
|
||||
/** Return a list of all users in the same organization. */
|
||||
override async listUsers(): Promise<backendModule.SimpleUser[]> {
|
||||
const path = remoteBackendPaths.LIST_USERS_PATH
|
||||
|
@ -37,12 +37,13 @@ export async function mockApi(page: test.Page) {
|
||||
const defaultEmail = 'email@example.com' as backend.EmailAddress
|
||||
const defaultUsername = 'user name'
|
||||
const defaultOrganizationId = backend.UserOrOrganizationId('organization-placeholder id')
|
||||
const defaultDirectoryId = backend.UserOrOrganizationId('directory-placeholder id')
|
||||
const defaultDirectoryId = backend.DirectoryId('directory-placeholder id')
|
||||
const defaultUser: backend.UserOrOrganization = {
|
||||
email: defaultEmail,
|
||||
name: defaultUsername,
|
||||
id: defaultOrganizationId,
|
||||
isEnabled: true,
|
||||
rootDirectoryId: defaultDirectoryId,
|
||||
}
|
||||
let currentUser: backend.UserOrOrganization | null = defaultUser
|
||||
const assetMap = new Map<backend.AssetId, backend.AnyAsset>()
|
||||
@ -297,11 +298,16 @@ export async function mockApi(page: test.Page) {
|
||||
// The type of the body sent by this app is statically known.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const body: backend.CreateUserRequestBody = await request.postDataJSON()
|
||||
const id = body.organizationId ?? defaultUser.id
|
||||
const rootDirectoryId = backend.DirectoryId(
|
||||
id.replace(/^organization-/, 'directory-')
|
||||
)
|
||||
currentUser = {
|
||||
email: body.userEmail,
|
||||
name: body.userName,
|
||||
id: body.organizationId ?? defaultUser.id,
|
||||
id,
|
||||
isEnabled: false,
|
||||
rootDirectoryId,
|
||||
}
|
||||
await route.fulfill({ json: currentUser })
|
||||
} else if (request.method() === 'GET') {
|
||||
|
Loading…
Reference in New Issue
Block a user