Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/polite-tigers-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@projectstorm/react-workspaces-dropzone-plugin-tray': patch
---

Disable vertical split drop targets for trays while retaining tray transform targets.

Fix moving a collapsed-tray panel into another collection so the tray releases its floating-window ownership first.
7 changes: 7 additions & 0 deletions .changeset/soft-bugs-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@projectstorm/react-workspaces-core': patch
'@projectstorm/react-workspaces-model-floating-window': patch
'@projectstorm/react-workspaces-model-tray': patch
---

Move the content of floating tray windows, rather than the window shell, when dropping them into another collection. Release tray ownership first to prevent stale layout state during tray-to-tab drops.
33 changes: 33 additions & 0 deletions demo/stories/RootTabSplit.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import { useState } from 'react';
import 'typeface-open-sans';
import { ExpandNodeModel } from '@projectstorm/react-workspaces-core';
import { DefaultWorkspacePanelModel } from '@projectstorm/react-workspaces-defaults';
import { WorkspaceTabModel } from '@projectstorm/react-workspaces-model-tabs';
import { CompInternal, SharedArgs, StoryArgs, useEngine } from './helpers/tools';

export const RootTabSplit = Object.assign(
function RootTabSplit(args: StoryArgs) {
const engine = useEngine(args);
const [model] = useState(() => {
const tabs = new WorkspaceTabModel();
tabs.addModel(new DefaultWorkspacePanelModel('Tab 1'));
tabs.addModel(new DefaultWorkspacePanelModel('Tab 2'));

return new ExpandNodeModel()
.setHorizontal(true)
.addModel(new DefaultWorkspacePanelModel('Left panel'))
.addModel(tabs);
});

return <CompInternal model={model} engine={engine} />;
},
{ args: SharedArgs }
);

export default {
title: 'Workspace',
parameters: {
layout: 'fullscreen'
}
};
8 changes: 1 addition & 7 deletions demo/stories/helpers/tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
} from '@projectstorm/react-workspaces-behavior-panel-dropzone';
import { draggingItemDividerBehavior } from '@projectstorm/react-workspaces-behavior-divider-dropzone';
import { WorkspaceTabFactory } from '@projectstorm/react-workspaces-model-tabs';
import { WorkspaceTrayModel } from '@projectstorm/react-workspaces-model-tray';
import { resizingBehavior } from '@projectstorm/react-workspaces-behavior-resize';
import { RootWorkspaceModel } from '@projectstorm/react-workspaces-model-floating-window';
import { ConvertToTabZone, getDirectiveForTabModel } from '@projectstorm/react-workspaces-dropzone-plugin-tabs';
Expand Down Expand Up @@ -112,12 +111,7 @@ export const useEngine = (args: StoryArgs = SharedArgs) => {
engine: e,
getDropZoneForModel: (model) => {
return (
getDirectiveForTrayModel(
model,
[],
() => new ExpandNodeModel(),
!(e.rootModel.flatten().find((candidate) => candidate.id === e.draggingID) instanceof WorkspaceTrayModel)
) ||
getDirectiveForTrayModel(model) ||
getDirectiveForWorkspaceNode({
node: model,
transformZones: [ConvertToTabZone(tabFactory), ConvertToTrayZone(trayFactory)],
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/core-models/WorkspaceCollectionModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,24 @@ export class WorkspaceCollectionModel<
this.children.splice(position - 1, 0, model);
}
} else {
// A model can be rendered by an intermediary (for example a tray's floating
// window) while still being owned by a collection elsewhere in the tree.
// Detach that owner before re-parenting so it cannot continue to render the
// same model with stale layout dimensions.
if (
model.parent &&
(!(model.parent instanceof WorkspaceCollectionModel) || !model.parent.children.includes(model))
) {
const owner = model
.getRootModel()
.flatten()
.find(
(candidate) =>
candidate instanceof WorkspaceCollectionModel && candidate !== this && candidate.children.includes(model)
) as WorkspaceCollectionModel | undefined;
owner?.removeModel(model);
}

if (model.parent) {
model.delete();
}
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/core-models/WorkspaceModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,15 @@ export class WorkspaceModel<
return [this.r_dimensions];
}

/**
* Returns the model that should be inserted when this model is dropped into
* a workspace collection. Models that merely present another model can
* override this to move their content instead of their presentation shell.
*/
getDropModel(): WorkspaceModel {
return this;
}

invalidateDimensions() {
this.iterateListeners((cb) => cb.dimensionsInvalidated?.());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ export const useDroppableModel = (props: UseDroppableModelOptions) => {
draggingNode = found || draggingNode;
}

draggingNode = draggingNode.getDropModel();

log(`workspace model dropped`, draggingNode);
props.onDrop(draggingNode);
},
Expand Down
31 changes: 3 additions & 28 deletions packages/dropzone-plugin-tray/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import * as React from 'react';
import {
Alignment,
WorkspaceCollectionModel,
WorkspaceModel,
WorkspaceNodeModel
} from '@projectstorm/react-workspaces-core';
import { WorkspaceCollectionModel, WorkspaceModel } from '@projectstorm/react-workspaces-core';
import {
DropZoneLayerButtonWidget,
DropZonePanelDirective,
Expand Down Expand Up @@ -45,32 +40,12 @@ export const ConvertToTrayZone = (trayFactory: WorkspaceTrayFactory): TransformZ

export const getDirectiveForTrayModel = (
node: WorkspaceModel,
transformZones: TransformZone[] = [],
generateParentNode: () => WorkspaceNodeModel = () => new WorkspaceNodeModel(),
allowSplit: boolean = true
transformZones: TransformZone[] = []
): DropZonePanelDirective | null => {
if (!(node instanceof WorkspaceCollectionModel) && node.parent instanceof WorkspaceTrayModel) {
const tray = node.parent;
const parent = tray.parent;
const splitZones =
allowSplit && parent instanceof WorkspaceNodeModel && parent === tray.getRootModel()
? [Alignment.TOP, Alignment.BOTTOM].map((alignment) => ({
alignment,
handleDrop: (model: WorkspaceModel, engine) => {
const split = generateParentNode()
.setVertical(true)
.setExpand(tray.expandHorizontal, tray.expandVertical);
parent.replaceModel(tray, split);
split.addModel(tray);
split.addModel(model, alignment === Alignment.TOP ? 0 : null);
engine.normalize();
}
}))
: [];

return {
transformZones: [AppendToTrayZone, ...transformZones],
splitZones
splitZones: []
};
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export class FloatingWindowModel extends WorkspaceModel<SerializedFloatingWindow
this.iterateListeners((cb) => cb.draggableUpdated?.());
}

getDropModel(): WorkspaceModel {
return this.child || this;
}

fromArray(payload: SerializedFloatingWindowModel, engine: WorkspaceEngineInterface) {
super.fromArray(payload, engine);
this.dimension.update(payload.dimensions);
Expand Down
6 changes: 6 additions & 0 deletions packages/model-tray/src/WorkspaceTrayModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,12 @@ export class WorkspaceTrayModel extends WorkspaceCollectionModel<
}

removeModel(model: WorkspaceModel): this {
// In collapsed mode the selected child is temporarily parented by the
// floating window. Remove that window before handing the child to another
// collection, otherwise the tray retains a second stale renderer for it.
if (this.floatingWindow.child === model) {
this.setFloatingModel(null);
}
super.removeModel(model);
if (this.selectedModel && this.selectedModel === model) {
this.selectedModel = null;
Expand Down
Loading