diff --git a/scopes/component/component-drawer/component-drawer.tsx b/scopes/component/component-drawer/component-drawer.tsx index f8768ac317bd..55687273460d 100644 --- a/scopes/component/component-drawer/component-drawer.tsx +++ b/scopes/component/component-drawer/component-drawer.tsx @@ -32,6 +32,7 @@ export type TransformTreeFn = (host?: WorkspaceModel | ScopeModel) => (rootNode: export type ComponentsDrawerProps = Omit & { useComponents: () => { components: ComponentModel[]; loading?: boolean }; + useLanes?: () => { lanesModel?: LanesModel; loading?: boolean }; emptyMessage?: ReactNode; plugins?: ComponentsDrawerPlugins; transformTree?: TransformTreeFn; @@ -54,6 +55,7 @@ export type ComponentsDrawerPlugins = { export class ComponentsDrawer implements DrawerType { readonly id: string; readonly useComponents: () => { components: ComponentModel[]; loading?: boolean }; + readonly useLanes: () => { lanesModel?: LanesModel; loading?: boolean }; name: ReactNode; tooltip?: string; order?: number; @@ -68,6 +70,7 @@ export class ComponentsDrawer implements DrawerType { constructor(props: ComponentsDrawerProps) { Object.assign(this, props); this.useComponents = props.useComponents; + this.useLanes = props.useLanes || useLanes; this.emptyMessage = props.emptyMessage; this.plugins = props.plugins || {}; this.setWidgets(props.plugins?.drawerWidgets); @@ -144,7 +147,7 @@ export class ComponentsDrawer implements DrawerType { render = () => { const { loading, components } = this.useComponents(); - const { lanesModel: lanes } = useLanes(); + const { lanesModel: lanes } = this.useLanes(); const componentFiltersContext = useContext(ComponentFilterContext); const filters = componentFiltersContext?.filters || []; diff --git a/scopes/lanes/hooks/use-lanes/lanes-provider.tsx b/scopes/lanes/hooks/use-lanes/lanes-provider.tsx index b090bae27fa2..6ebe57eba0d7 100644 --- a/scopes/lanes/hooks/use-lanes/lanes-provider.tsx +++ b/scopes/lanes/hooks/use-lanes/lanes-provider.tsx @@ -12,6 +12,7 @@ export type LanesProviderProps = { children: ReactNode; viewedLaneId?: LaneId; targetLanes?: LanesModel; + skipNetworkCall?: boolean; ignoreDerivingFromUrl?: IgnoreDerivingFromUrl[]; }; @@ -20,8 +21,10 @@ export function LanesProvider({ viewedLaneId: viewedIdFromProps, targetLanes, ignoreDerivingFromUrl: ignoreDerivingFromUrlFromProps, + skipNetworkCall, }: LanesProviderProps) { - const { lanesModel, loading } = useLanes(targetLanes); + const { lanesModel, loading } = useLanes(targetLanes, skipNetworkCall); + const [lanesState, setLanesState] = useState(lanesModel); const [viewedLaneId, setViewedLaneId] = useState(viewedIdFromProps); diff --git a/scopes/lanes/hooks/use-lanes/use-lanes.tsx b/scopes/lanes/hooks/use-lanes/use-lanes.tsx index a240f70b014c..3031c99725df 100644 --- a/scopes/lanes/hooks/use-lanes/use-lanes.tsx +++ b/scopes/lanes/hooks/use-lanes/use-lanes.tsx @@ -40,10 +40,11 @@ const GET_LANES = gql` `; export function useLanes( - targetLanes?: LanesModel + targetLanes?: LanesModel, + skip?: boolean ): LanesContextModel & Omit, 'data'> { const lanesContext = useLanesContext(); - const shouldSkip = !!targetLanes || !!lanesContext; + const shouldSkip = skip || !!targetLanes || !!lanesContext; const { data, loading, ...rest } = useDataQuery(GET_LANES, { skip: shouldSkip, diff --git a/scopes/scope/scope/get-scope-options.ts b/scopes/scope/scope/get-scope-options.ts index c0f85b441a8f..977bddf22e0e 100644 --- a/scopes/scope/scope/get-scope-options.ts +++ b/scopes/scope/scope/get-scope-options.ts @@ -1,3 +1,4 @@ +import { DrawerType } from '@teambit/ui-foundation.ui.tree.drawer'; import { ComponentType, ReactNode } from 'react'; import { ScopeModel } from '.'; @@ -8,4 +9,5 @@ export type GetScopeOptions = { scopeClassName?: string; TargetScopeOverview?: ComponentType; PaneWrapper?: ComponentType<{ children: ReactNode }>; + overrideDrawers?: DrawerType[]; }; diff --git a/scopes/scope/scope/scope.ui.drawer.tsx b/scopes/scope/scope/scope.ui.drawer.tsx index 38683c2492f3..322ed5cb1de6 100644 --- a/scopes/scope/scope/scope.ui.drawer.tsx +++ b/scopes/scope/scope/scope.ui.drawer.tsx @@ -8,11 +8,11 @@ import { ScopeTreeNode, } from '@teambit/ui-foundation.ui.side-bar'; import { TreeNode as TreeNodeType, TreeNodeProps } from '@teambit/design.ui.tree'; -import { useLanes } from '@teambit/lanes.hooks.use-lanes'; +import { useLanes as defaultUseLanesHook } from '@teambit/lanes.hooks.use-lanes'; import { useLaneComponents } from '@teambit/lanes.hooks.use-lane-components'; import { ComponentModel } from '@teambit/component'; import { useScope, ScopeContext } from '@teambit/scope.ui.hooks.scope-context'; -// import { WorkspaceModel } from '@teambit/workspace'; +import { LanesModel } from '@teambit/lanes.ui.models.lanes-model'; import { SidebarSlot } from './scope.ui.runtime'; export type ScopeDrawerProps = { @@ -21,6 +21,7 @@ export type ScopeDrawerProps = { drawerWidgetSlot: DrawerWidgetSlot; assumeScopeInUrl?: boolean; overrideUseComponents?: () => { components: ComponentModel[] }; + overrideUseLanes?: () => { lanesModel?: LanesModel; loading?: boolean }; }; export const scopeDrawer = ({ @@ -29,12 +30,15 @@ export const scopeDrawer = ({ drawerWidgetSlot, assumeScopeInUrl = false, overrideUseComponents, + overrideUseLanes: useLanesFromProps, }: ScopeDrawerProps) => { + const useLanes = useLanesFromProps || defaultUseLanesHook; + const customScopeTreeNodeRenderer = (treeNodeSlot, host?: any) => function TreeNode(props: TreeNodeProps) { const children = props.node.children; - if (!children) return ; + if (!children) return ; // skip over scope node and render only children if (props.node.payload instanceof ScopePayload) { @@ -76,6 +80,7 @@ export const scopeDrawer = ({ drawerWidgets: drawerWidgetSlot, }, useHost: () => useScope(), + useLanes, emptyMessage: 'Scope is empty', // TODO: create an interface for Component host. transformTree: (host?: any) => { diff --git a/scopes/scope/scope/scope.ui.runtime.tsx b/scopes/scope/scope/scope.ui.runtime.tsx index 2cc11509488a..7b50328234e5 100644 --- a/scopes/scope/scope/scope.ui.runtime.tsx +++ b/scopes/scope/scope/scope.ui.runtime.tsx @@ -14,6 +14,7 @@ import { MenuLinkItem } from '@teambit/design.ui.surfaces.menu.link-item'; import CommandBarAspect, { CommandBarUI, CommandHandler } from '@teambit/command-bar'; import { ScopeModel } from '@teambit/scope.models.scope-model'; import { DrawerType } from '@teambit/ui-foundation.ui.tree.drawer'; +import { LanesModel } from '@teambit/lanes.ui.models.lanes-model'; import { DrawerWidgetSlot, FilterWidget, @@ -131,7 +132,7 @@ export class ScopeUI { TargetCorner={options.Corner} routeSlot={this.routeSlot} menuSlot={this.menuSlot} - sidebar={} + sidebar={} scopeUi={this} userUseScopeQuery={options.useScope} badgeSlot={this.scopeBadgeSlot} @@ -286,16 +287,27 @@ export class ScopeUI { this.drawerWidgetSlot.register(widgets); }; - registerDefaultDrawers(assumeScopeInUrl = false, overrideUseComponents?: () => { components: ComponentModel[] }) { - this.sidebar.registerDrawer( - scopeDrawer({ - treeWidgets: this.sidebarSlot, - filtersSlot: this.drawerComponentsFiltersSlot, - drawerWidgetSlot: this.drawerWidgetSlot, - assumeScopeInUrl, - overrideUseComponents, - }) - ); + registerDefaultDrawers( + assumeScopeInUrl = false, + overrideUseComponents?: () => { components: ComponentModel[] }, + overrideUseLanes?: () => { lanesModel: LanesModel } + ) { + this.sidebar.registerDrawer(this.getDefaultDrawer(assumeScopeInUrl, overrideUseComponents, overrideUseLanes)); + } + + getDefaultDrawer( + assumeScopeInUrl = false, + overrideUseComponents?: () => { components: ComponentModel[] }, + overrideUseLanes?: () => { lanesModel: LanesModel } + ) { + return scopeDrawer({ + treeWidgets: this.sidebarSlot, + filtersSlot: this.drawerComponentsFiltersSlot, + drawerWidgetSlot: this.drawerWidgetSlot, + assumeScopeInUrl, + overrideUseComponents, + overrideUseLanes, + }); } uiRoot(): UIRoot { diff --git a/scopes/ui-foundation/sidebar/ui/side-bar/side-bar.tsx b/scopes/ui-foundation/sidebar/ui/side-bar/side-bar.tsx index 35e5d98a8f3b..c6ddd6576680 100644 --- a/scopes/ui-foundation/sidebar/ui/side-bar/side-bar.tsx +++ b/scopes/ui-foundation/sidebar/ui/side-bar/side-bar.tsx @@ -15,13 +15,17 @@ export type SideBarProps = { * slot of registered items to the main section at the top. */ items?: ComponentType[]; + /** + * override register drawers from the slot + */ + overrideDrawerSlot?: DrawerType[]; } & React.HTMLAttributes; /** * side bar component. */ -export function SideBar({ drawerSlot, items = [], ...rest }: SideBarProps) { - const drawers = flatten(drawerSlot.values()) +export function SideBar({ drawerSlot, items = [], overrideDrawerSlot, ...rest }: SideBarProps) { + const drawers = (overrideDrawerSlot || flatten(drawerSlot.values())) .filter((drawer) => !drawer?.isHidden?.()) .sort(sortFn); diff --git a/scopes/ui-foundation/uis/side-bar/component-tree/component-view/component-view.tsx b/scopes/ui-foundation/uis/side-bar/component-tree/component-view/component-view.tsx index 5291812d1bd1..fcecd2cedac2 100644 --- a/scopes/ui-foundation/uis/side-bar/component-tree/component-view/component-view.tsx +++ b/scopes/ui-foundation/uis/side-bar/component-tree/component-view/component-view.tsx @@ -11,12 +11,14 @@ import { TreeContext } from '@teambit/base-ui.graph.tree.tree-context'; import { indentClass } from '@teambit/base-ui.graph.tree.indent'; import { TreeNodeProps } from '@teambit/base-ui.graph.tree.recursive-tree'; import { useLanes } from '@teambit/lanes.hooks.use-lanes'; +import { LanesModel } from '@teambit/lanes.ui.models.lanes-model'; import { PayloadType } from '../payload-type'; import { getName } from '../utils/get-name'; import styles from './component-view.module.scss'; export type ComponentViewProps = { treeNodeSlot?: ComponentTreeSlot; + useLanes?: () => { lanesModel?: LanesModel }; } & TreeNodeProps; export function ComponentView(props: ComponentViewProps) { @@ -24,7 +26,8 @@ export function ComponentView(props: ComponentViewProps) { const component = node.payload; const { onSelect } = useContext(TreeContext); - const { lanesModel } = useLanes(); + const { lanesModel } = (props.useLanes || useLanes)(); + const handleClick = useCallback( (event: React.MouseEvent) => { onSelect && onSelect(node.id, event); diff --git a/scopes/workspace/workspace/workspace.ui.drawer.tsx b/scopes/workspace/workspace/workspace.ui.drawer.tsx index b80793602e3e..ee1ec0e6b1a2 100644 --- a/scopes/workspace/workspace/workspace.ui.drawer.tsx +++ b/scopes/workspace/workspace/workspace.ui.drawer.tsx @@ -7,20 +7,29 @@ import { ScopePayload, ScopeTreeNode, } from '@teambit/ui-foundation.ui.side-bar'; -import { useLanes } from '@teambit/lanes.hooks.use-lanes'; +import { useLanes as defaultUseLanesHook } from '@teambit/lanes.hooks.use-lanes'; import { useLaneComponents } from '@teambit/lanes.hooks.use-lane-components'; import { TreeNodeProps } from '@teambit/design.ui.tree'; import { ComponentModel } from '@teambit/component'; +import { LanesModel } from '@teambit/lanes.ui.models.lanes-model'; import { SidebarWidgetSlot } from './workspace.ui.runtime'; export type WorkspaceDrawerProps = { treeWidgets: SidebarWidgetSlot; filtersSlot: ComponentFiltersSlot; drawerWidgetSlot: DrawerWidgetSlot; + overrideUseLanes?: () => { lanesModel?: LanesModel; loading?: boolean }; }; -export const workspaceDrawer = ({ treeWidgets, filtersSlot, drawerWidgetSlot }: WorkspaceDrawerProps) => - new ComponentsDrawer({ +export const workspaceDrawer = ({ + treeWidgets, + filtersSlot, + drawerWidgetSlot, + overrideUseLanes: useLanesFromProps, +}: WorkspaceDrawerProps) => { + const useLanes = useLanesFromProps || defaultUseLanesHook; + + return new ComponentsDrawer({ order: 0, id: 'workspace-components-drawer', name: 'COMPONENTS', @@ -31,7 +40,7 @@ export const workspaceDrawer = ({ treeWidgets, filtersSlot, drawerWidgetSlot }: function TreeNode(props: TreeNodeProps) { const children = props.node.children; - if (!children) return ; // non collapse + if (!children) return ; // non collapse if (props.node.payload instanceof ScopePayload) return ; @@ -42,6 +51,7 @@ export const workspaceDrawer = ({ treeWidgets, filtersSlot, drawerWidgetSlot }: drawerWidgets: drawerWidgetSlot, }, emptyMessage: 'Workspace is empty', + useLanes, useComponents: () => { const { lanesModel, loading: lanesLoading } = useLanes(); const viewedLaneId = lanesModel?.viewedLane?.id; @@ -62,6 +72,7 @@ export const workspaceDrawer = ({ treeWidgets, filtersSlot, drawerWidgetSlot }: }; }, }); +}; function mergeComponents(mainComponents: ComponentModel[], laneComponents: ComponentModel[]): ComponentModel[] { const mainComponentsThatAreNotOnLane = mainComponents.filter((mainComponent) => {