diff --git a/src/components/BootstrapBlazor.DockView/BootstrapBlazor.DockView.csproj b/src/components/BootstrapBlazor.DockView/BootstrapBlazor.DockView.csproj index a1f17289..e7360a7f 100644 --- a/src/components/BootstrapBlazor.DockView/BootstrapBlazor.DockView.csproj +++ b/src/components/BootstrapBlazor.DockView/BootstrapBlazor.DockView.csproj @@ -1,7 +1,7 @@  - 10.0.23 + 10.0.24 diff --git a/src/components/BootstrapBlazor.DockView/Components/DockViewComponent.razor b/src/components/BootstrapBlazor.DockView/Components/DockViewComponent.razor index 573cc056..5b3c1443 100644 --- a/src/components/BootstrapBlazor.DockView/Components/DockViewComponent.razor +++ b/src/components/BootstrapBlazor.DockView/Components/DockViewComponent.razor @@ -1,18 +1,21 @@ @inherits DockViewComponentBase -
- @if (TitleTemplate != null) - { -
- @TitleTemplate -
- } - else if (ShowTitleBar) - { - - } - @if (IsRender()) - { - @ChildContent - } -
+@if (ChildContent != null) +{ +
+ @if (TitleTemplate != null) + { +
+ @TitleTemplate +
+ } + else if (ShowTitleBar) + { + + } + @if (IsRender()) + { + @ChildContent + } +
+} diff --git a/src/components/BootstrapBlazor.DockView/Components/DockViewComponentBase.cs b/src/components/BootstrapBlazor.DockView/Components/DockViewComponentBase.cs index c28150be..2f27e62f 100644 --- a/src/components/BootstrapBlazor.DockView/Components/DockViewComponentBase.cs +++ b/src/components/BootstrapBlazor.DockView/Components/DockViewComponentBase.cs @@ -42,6 +42,13 @@ public abstract class DockViewComponentBase : IdComponentBase, IDisposable [JsonIgnore] public RenderFragment? ChildContent { get; set; } + /// + /// 获得/设置 名称 + /// Gets or sets the name + /// + [Parameter] + public string? LayoutName { get; set; } + [CascadingParameter] private List? Parent { get; set; } diff --git a/src/components/BootstrapBlazor.DockView/Components/DockViewConfig.cs b/src/components/BootstrapBlazor.DockView/Components/DockViewConfig.cs index 5cef1269..de1f4926 100644 --- a/src/components/BootstrapBlazor.DockView/Components/DockViewConfig.cs +++ b/src/components/BootstrapBlazor.DockView/Components/DockViewConfig.cs @@ -129,4 +129,10 @@ class DockViewConfig /// Gets or sets the layout configuration. Default is null /// public string? LayoutConfig { get; set; } + + /// + /// 获得/设置 布局名称,默认为 null + /// Gets or sets the layout name. Default is null + /// + public string? LayoutName { get; set; } } diff --git a/src/components/BootstrapBlazor.DockView/Components/DockViewV2.razor.cs b/src/components/BootstrapBlazor.DockView/Components/DockViewV2.razor.cs index 9c9c9c50..9402395f 100644 --- a/src/components/BootstrapBlazor.DockView/Components/DockViewV2.razor.cs +++ b/src/components/BootstrapBlazor.DockView/Components/DockViewV2.razor.cs @@ -155,6 +155,13 @@ public partial class DockViewV2 [Parameter] public DockViewTheme Theme { get; set; } = DockViewTheme.Light; + /// + /// 获得/设置 布局名称,默认为 null + /// Gets or sets the layout name. Default is null + /// + [Parameter] + public string? LayoutName { get; set; } + /// /// 嵌套 DockView 时生效防止生成冗余的 DOM 结构 /// @@ -179,6 +186,7 @@ public partial class DockViewV2 private DockViewOptions? _options = null; private ConcurrentDictionary _componentStates = new(); private string? _layoutConfig; + private string? _layoutName; private bool _disposed; /// @@ -224,6 +232,12 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (firstRender) { _layoutConfig = LayoutConfig; + _layoutName = LayoutName; + } + else if (_layoutName != LayoutName) + { + _layoutName = LayoutName; + await InvokeVoidAsync("switchLayout", Id, GetDockViewConfig()); } else if (!_triggerLoadTabs) { @@ -283,14 +297,47 @@ private DockViewConfig GetDockViewConfig() LockChangedCallback = nameof(LockChangedCallbackAsync), SplitterCallback = nameof(SplitterCallbackAsync), SaveConfigCallback = nameof(SaveConfigCallbackAsync), - Contents = _components, - LoadTabs = nameof(LoadTabs) + Contents = GetLayoutContents(), + LoadTabs = nameof(LoadTabs), + LayoutName = LayoutName }; } + /// + /// 根据 在服务器端过滤出匹配的布局,避免将所有布局序列化到客户端 + /// Filters the matching layout on the server side by to avoid serializing all layouts to the client + /// + private List GetLayoutContents() + { + if (_components.Count == 0) + { + return []; + } + + var content = _components[0]; + if (string.IsNullOrEmpty(LayoutName)) + { + return [content]; + } + + return [_components.Find(item => item.LayoutName == LayoutName) ?? content]; + } + private bool IsEnableLocalStorage => EnableLocalStorage ?? _options.EnableLocalStorage ?? false; - private string? LocalStorageKey => IsEnableLocalStorage ? $"{GetPrefixKey()}-{Name}-{GetVersion()}" : null; + private string? LocalStorageKey + { + get + { + if (!IsEnableLocalStorage) + { + return null; + } + + var layoutSegment = string.IsNullOrEmpty(LayoutName) ? "" : $"-{LayoutName}"; + return $"{GetPrefixKey()}-{Name}{layoutSegment}-{GetVersion()}"; + } + } private string GetVersion() => Version ?? _options.Version ?? "v1"; diff --git a/src/components/BootstrapBlazor.DockView/Components/DockViewV2.razor.js b/src/components/BootstrapBlazor.DockView/Components/DockViewV2.razor.js index e1818586..fc30e41f 100644 --- a/src/components/BootstrapBlazor.DockView/Components/DockViewV2.razor.js +++ b/src/components/BootstrapBlazor.DockView/Components/DockViewV2.razor.js @@ -17,9 +17,6 @@ export async function init(id, invoke, options) { } } const dockview = cerateDockview(el, options); - dockview.params.invisiblePanels?.forEach(invisiblePanel => { - invoke.invokeMethodAsync(options.panelVisibleChangedCallback, invisiblePanel.params.key, false); - }) const updateTheme = e => dockview.switchTheme(e.theme); Data.set(id, { el, dockview, updateTheme }); diff --git a/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-config.js b/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-config.js index b81b9fd7..91dd7d2f 100644 --- a/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-config.js +++ b/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-config.js @@ -1,4 +1,4 @@ -import { getPanelsFromOptions } from "./dockview-panel.js" +import { getPanelsFromOptions, getRootContent } from "./dockview-panel.js" const initDockviewFromConfig = (dockview, options) => { const { layoutConfig, enableLocalStorage } = options; @@ -58,17 +58,39 @@ const initDockviewFromConfig = (dockview, options) => { else { dockview.fromJSON(getConfigFromContent(options)); dockview.params.invisiblePanels = []; + dockview.params.floatingGroups = []; } + syncLayoutToContainer(dockview); } +const syncLayoutToContainer = dockview => { + const el = dockview.gridview?.element?.parentElement ?? dockview.element; + if (!el) return; + const width = el.clientWidth; + const height = el.clientHeight; + if (!width || !height) return; + if (width === dockview.width && height === dockview.height) return; + dockview.layout(width, height, true); +} + + const getConfigFromContent = options => { const { width, height } = { width: 800, height: 600 }; const getGroupId = getGroupIdFunc() options = filterEmptyContent(options) - const panels = {}, rootType = options.content[0].type + const rootContent = getRootContent(options) + if (!rootContent) { + return { + activeGroup: '1', + grid: { width, height, orientation: 'HORIZONTAL', root: { type: 'branch', size: 0, data: [] } }, + panels: {} + } + } + const rootParent = { content: [rootContent] } + const panels = {}, rootType = rootContent.type const orientation = rootType === 'column' ? 'VERTICAL' : 'HORIZONTAL'; - const root = getTree(options.content[0], { width, height, orientation }, options, panels, getGroupId, options) + const root = getTree(rootContent, { width, height, orientation }, rootParent, panels, getGroupId, options) return { activeGroup: '1', grid: { width, height, orientation, root }, @@ -260,9 +282,6 @@ const saveConfig = dockview => { return; } - // Don't persist a collapsed/unmeasured layout: while collapsed toJSON() serializes size:100, which - // makes the even-split sticky across refreshes. Real layouts are measured (width>0), so this only - // drops a degenerate save the next change re-saves. if (!dockview.width || !dockview.height) { return; } @@ -271,10 +290,6 @@ const saveConfig = dockview => { panel.params.isActive = panel.api.isActive || panel.group.activePanel === panel }) - // While maximized, toJSON()'s serialize() toggles sibling visibility once; guard it with - // `maximizing` so the onlyWhenVisible handler doesn't move sibling content into the template - // (which would blank it after exit). finally resets the flag even on throw, otherwise a stuck - // `maximizing` would make every later saveConfig bail at the top guard. let gridJson; dockview.params.maximizing = true; try { @@ -341,6 +356,10 @@ const syncLayoutWithOptions = (layout, options, invisiblePanels = []) => { if (p.params?.key) localIdByKey.set(p.params.key, p.id); }); + if (optionPanels.length === 0) { + return layout; + } + const matchCount = [...optionPanelByKey.keys()].filter(key => localIdByKey.has(key)).length; if (matchCount === 0) { return getConfigFromContent(options); diff --git a/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-group.js b/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-group.js index 465db180..56fca224 100644 --- a/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-group.js +++ b/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-group.js @@ -463,7 +463,7 @@ const createDrawerBtn = (floatingGroup, isRight) => { const removeDrawerBtn = group => { const parentEle = group.api.accessor.element.parentElement.parentElement const btnList = parentEle?.querySelectorAll(`[groupid="${group.api.accessor.id}_${group.id}"]`) - btnList.forEach(btn => btn.remove()) + btnList?.forEach(btn => btn.remove()) } const setDrawerTitle = group => { diff --git a/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-panel.js b/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-panel.js index cc3a755a..c9eb0f63 100644 --- a/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-panel.js +++ b/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-panel.js @@ -120,8 +120,10 @@ const updateTitle = panel => { } } +export const getRootContent = options => options.content.find(c => c.layoutName === options.layoutName) || options.content[0] const getPanelsFromOptions = options => { - return getPanels(options.content[0], options) + const rootContent = getRootContent(options) + return rootContent ? getPanels(rootContent, options) : [] } const getPanels = (contentItem, options, parent = {}, panels = []) => { diff --git a/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-utils.js b/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-utils.js index de4bad1a..fb1ed4fc 100644 --- a/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-utils.js +++ b/src/components/BootstrapBlazor.DockView/wwwroot/js/dockview-utils.js @@ -62,10 +62,79 @@ const guardCollapsedSaveProportions = dockview => { }; } +const removeDrawerButtons = dockview => { + dockview.element?.querySelectorAll(':scope > .bb-dockview-aside').forEach(el => el.remove()); +} + +const postLayoutInit = (dockview, options) => { + if (dockview._isDisposed) { + return + } + const { floatingGroups } = dockview.params + dockview.floatingGroups.forEach(fg => { + const saved = floatingGroups.find(g => g.data.id == fg.group.id); + if (saved?.position) { + const { top, right, bottom, left } = saved.position; + + fg.group.element.parentElement.style.inset = [top, right, bottom, left] + .map(item => typeof item == 'number' ? (item + 'px') : 'auto').join(' ') + } + + observeOverlayChange(fg.overlay, fg.group) + const { floatType, direction } = fg.group.getParams(); + if (floatType == 'drawer') { + createDrawerHandle(fg.group, direction == 'right') + } + observeFloatingGroupLocationChange(fg.group) + }) + + dockview.groups.forEach(group => { + observeGroup(group) + }) + + if (!dockview.params.drawerHandlerBound) { + dockview.params.drawerHandlerBound = true; + dockview.element.querySelector('&>.dv-dockview>.dv-branch-node')?.addEventListener('click', function (e) { + this.parentElement.querySelectorAll('&>.dv-resize-container-drawer, &>.dv-render-overlay-float-drawer')?.forEach(item => { + item.classList.remove('active') + }) + this.closest('.bb-dockview').querySelectorAll('&>.bb-dockview-aside>.bb-dockview-aside-button')?.forEach(item => { + item.classList.remove('active') + }) + }) + } + + dockview.params.inited = true; + + queueMicrotask(() => { + if (dockview._isDisposed) { + return + } + dockview.params.invisiblePanels?.forEach(p => { + dockview._panelVisibleChanged?.fire({ key: p.params.key, status: false }); + }) + dockview.panels.forEach(panel => { + if (panel.params.visible) { + dockview._panelVisibleChanged?.fire({ key: panel.params.key, status: true }); + } + else { + panel.group.model.closePanel(panel) + } + }) + + if (options.renderer === 'onlyWhenVisible') { + const visiblePanels = dockview.groups.filter(g => g.isVisible).map(g => g.panels.find(p => p.params.isActive) || g.panels.find(p => p.api.isVisible)) + dockview._loadTabs?.fire(visiblePanels.filter(p => p.params.key).map(p => p.params.key)); + } + dockview._initialized?.fire(); + }) +} + const initDockview = (dockview, options, template) => { - dockview.params = { panels: [], options, template, observer: null }; + dockview.params = { panels: [], options, template, observer: null, layoutSeq: 0 }; dockview.init = function (options) { initDockviewFromConfig(this, options); + postLayoutInit(this, options); } dockview.switchTheme = theme => { @@ -88,7 +157,7 @@ const initDockview = (dockview, options, template) => { } if (options.layoutConfig) { - dockview.reset(options); + dockview.reset(dockview.params.options); } else { toggleComponent(dockview, options); @@ -98,8 +167,19 @@ const initDockview = (dockview, options, template) => { dockview.reset = options => { dockview.params.inited = false; dockview.params.reset = true; - dockview.init(options); - dockview.params.reset = false; + dockview.params.layoutSeq++; + removeDrawerButtons(dockview); + try { + dockview.init(options); + } + finally { + dockview.params.reset = false; + } + } + + dockview.switchLayout = options => { + dockview.params.options = { ...options, renderer: options.renderer || 'onlyWhenVisible' }; + dockview.reset(dockview.params.options); } dockview.onDidRemovePanel(onRemovePanel); @@ -126,58 +206,6 @@ const initDockview = (dockview, options, template) => { dockview.groups.forEach(group => { markFirstVisibleElement(group); }) - const handler = setTimeout(() => { - clearTimeout(handler); - if (dockview._isDisposed) { - dockview = null; - return; - } - const panels = dockview.panels; - const groups = dockview.groups; - - panels.forEach(panel => { - const visible = panel.params.visible - if (visible) { - dockview._panelVisibleChanged?.fire({ key: panel.params.key, status: true }); - } - else { - panel.group.model.closePanel(panel) - } - }) - - if (options.renderer === 'onlyWhenVisible') { - const visiblePanels = groups.filter(g => g.isVisible).map(g => g.panels.find(p => p.params.isActive) || g.panels.find(p => p.api.isVisible)) - dockview._loadTabs?.fire(visiblePanels.filter(p => p.params.key).map(p => p.params.key)); - } - const { floatingGroups } = dockview.params - dockview.floatingGroups.forEach(fg => { - const { top, right, bottom, left } = floatingGroups.find(g => g.data.id == fg.group.id).position - - fg.group.element.parentElement.style.inset = [top, right, bottom, left] - .map(item => typeof item == 'number' ? (item + 'px') : 'auto').join(' ') - - observeOverlayChange(fg.overlay, fg.group) - const { floatType, direction } = fg.group.getParams(); - if (floatType == 'drawer') { - createDrawerHandle(fg.group, direction == 'right') - } - observeFloatingGroupLocationChange(fg.group) - }) - - dockview.groups.forEach(group => { - observeGroup(group) - }) - dockview.element.querySelector('&>.dv-dockview>.dv-branch-node')?.addEventListener('click', function (e) { - this.parentElement.querySelectorAll('&>.dv-resize-container-drawer, &>.dv-render-overlay-float-drawer')?.forEach(item => { - item.classList.remove('active') - }) - this.closest('.bb-dockview').querySelectorAll('&>.bb-dockview-aside>.bb-dockview-aside-button')?.forEach(item => { - item.classList.remove('active') - }) - }) - dockview.params.inited = true; - dockview._initialized?.fire(); - }, 0); }) dockview.gridview.onDidChange(event => { diff --git a/src/components/BootstrapBlazor.Region/BootstrapBlazor.Region.csproj b/src/components/BootstrapBlazor.Region/BootstrapBlazor.Region.csproj index d4200eaf..5f59acfd 100644 --- a/src/components/BootstrapBlazor.Region/BootstrapBlazor.Region.csproj +++ b/src/components/BootstrapBlazor.Region/BootstrapBlazor.Region.csproj @@ -1,7 +1,7 @@  - 10.0.4 + 10.0.5