feat(Dock): add GetLayoutConfig instance method (#2221)

* feat: 增加 Reset 方法

* feat: 增加 Reset 方法

* refactor: 增加 Readonly 关键字

* LayoutConfig  and SaveLayoutCallback

* Update BootstrapBlazor.Shared.csproj

* 分开配置

* 更新复位功能

* GetLayout 获取面板的显示布局

* refactor: 增加销毁逻辑

* fix: 修复 layout 查找逻辑

* doc: 更新资源文件

* refactor: 更新脚本

* doc: 更新注释文档

* refactor: 移除 SaveLayoutCallback 回调方法

* refactor: 更改方法为 getLayoutConfig

* revert: 移除 Update 方法参数

* revert: 撤销布局逻辑

* feat: 移除 LayoutConfig

* refactor: 更改标题样式

* refactor: 精简代码

* feat: 增加 LayoutConfig 参数

* refactor: 支持服务器端配置

* refactor: 更新示例代码

* doc: 更新示例

---------

Co-authored-by: Argo-AscioTech <argo@live.ca>
This commit is contained in:
Alex chow
2023-10-05 18:27:37 +02:00
committed by GitHub
parent 227aba8418
commit 9869ee23dd
11 changed files with 185 additions and 21 deletions

View File

@@ -743,6 +743,11 @@ internal static class MenusLocalizerExtensions
{
Text = Localizer["DockViewLock"],
Url = "dock-view/lock"
},
new()
{
Text = Localizer["DockViewLayout"],
Url = "dock-view/layout"
}
};
AddBadge(item, count: 1);

View File

@@ -4503,6 +4503,7 @@
"DockViewComplex": "Complex",
"DockViewVisible": "Visible",
"DockViewLock": "Lock",
"DockViewLayout": "Custom",
"OtherComponents": "Others",
"MouseFollowerIntro": "MouseFollower",
"Live2DDisplayIntro": "Live2D Widget",

View File

@@ -4503,6 +4503,7 @@
"DockViewComplex": "组合布局",
"DockViewVisible": "可见性切换",
"DockViewLock": "布局锁定",
"DockViewLayout": "布局自定义",
"OtherComponents": "其他组件",
"MouseFollowerIntro": "鼠标跟随",
"Live2DDisplayIntro": "Live2D 插件",

View File

@@ -0,0 +1,94 @@
@page "/dock-view/layout"
@inherits BaseDockView
<h4>自定义布局</h4>
<p>通过设置 <code>DockView</code> 的属性 <code>LayoutConfig</code> 初始化控制面板的显示布局, 方法 <code>GetLayoutConfig</code> 获取面板的显示布局</p>
<GroupBox Title="布局切换">
<Button OnClick="OnToggleLayout1" Text="布局1"></Button>
<Button OnClick="OnToggleLayout2" Text="布局2"></Button>
<Button OnClick="OnToggleLayout3" Text="布局3"></Button>
<Button OnClick="GetLayout" Text="获取布局"></Button>
<Button OnClick="Reset" Text="复位"></Button>
<code class="config">@LayoutConfigSave</code>
</GroupBox>
<div class="dock-toggle-demo">
<DockView @ref="DockView" Name="DockViewLayout" EnableLocalStorage="true" LayoutConfig="@LayoutConfig">
<DockContent Type="DockContentType.Column">
<DockComponent Title="标签一">
<Table TItem="DynamicObject" DynamicContext="DataTableDynamicContext"
IsStriped="true" IsBordered="true" IsExcel="true" ShowRefresh="false"
ShowDefaultButtons="false">
<DetailRowTemplate>
<div class="p-2 w-100">
<Table TItem="DynamicObject" DynamicContext="GetDetailDataTableDynamicContext(context)" IsStriped="true" IsBordered="true" IsExcel="true">
</Table>
</div>
</DetailRowTemplate>
</Table>
</DockComponent>
<DockComponent Title="标签二">
<Table TItem="Foo" @bind-Items="Items"
IsStriped="true" IsBordered="true" IsMultipleSelect="true"
ShowToolbar="true" ShowExtendButtons="true" ShowSkeleton="true"
OnAddAsync="@OnAddAsync">
<TableColumns>
<TableColumn @bind-Field="@context.DateTime" Width="180" />
<TableColumn @bind-Field="@context.Name" />
<TableColumn @bind-Field="@context.Address" Width="180" TextEllipsis="true" ShowTips="true" />
<TableColumn @bind-Field="@context.Education" />
<TableColumn @bind-Field="@context.Count" />
<TableColumn @bind-Field="@context.Complete" />
</TableColumns>
</Table>
</DockComponent>
<DockComponent Title="标签三">
<FetchData></FetchData>
</DockComponent>
</DockContent>
</DockView>
</div>
@code {
[NotNull]
private DockView? DockView { get; set; }
private async Task GetLayout()
{
LayoutConfigSave = await DockView.GetLayoutConfig();
}
private Task Reset() => DockView.Reset();
private void OnToggleLayout1()
{
LayoutConfig = LayoutConfig1;
}
private void OnToggleLayout2()
{
LayoutConfig = LayoutConfig2;
}
private void OnToggleLayout3()
{
LayoutConfig = LayoutConfig3;
}
string? LayoutConfig;
string? LayoutConfigSave;
string LayoutConfig1 = """"
{"root":{"type":"row","content":[{"type":"column","content":[{"type":"stack","content":[{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"bb_45517422","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"标签三","componentType":"component","componentState":{"id":"bb_45517422","showClose":true,"class":null,"key":"标签三","lock":false}}],"width":50,"minWidth":0,"height":33.460076045627375,"minHeight":0,"id":"","isClosable":true,"maximised":false,"activeItemIndex":0},{"type":"stack","content":[{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"bb_42425232","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"标签二","componentType":"component","componentState":{"id":"bb_42425232","showClose":true,"class":null,"key":"标签二","lock":false}}],"width":50,"minWidth":0,"height":66.53992395437263,"minHeight":0,"id":"","isClosable":true,"maximised":false,"activeItemIndex":0}],"width":50,"minWidth":50,"height":50,"minHeight":50,"id":"","isClosable":true},{"type":"stack","content":[{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"bb_21184535","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"标签一","componentType":"component","componentState":{"id":"bb_21184535","showClose":true,"class":null,"key":"标签一","lock":false}}],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"bb_54183781","isClosable":true,"maximised":false,"activeItemIndex":0}],"width":50,"minWidth":50,"height":50,"minHeight":50,"id":"","isClosable":true},"openPopouts":[],"settings":{"constrainDragToContainer":true,"reorderEnabled":true,"popoutWholeStack":false,"blockedPopoutsThrowError":true,"closePopoutsOnUnload":true,"responsiveMode":"none","tabOverlapAllowance":0,"reorderOnTabMenuClick":true,"tabControlOffset":10,"popInOnClose":false},"dimensions":{"borderWidth":5,"borderGrabWidth":5,"minItemHeight":10,"minItemWidth":10,"headerHeight":25,"dragProxyWidth":300,"dragProxyHeight":200},"header":{"show":"top","popout":"lock/unlock","dock":"dock","close":"close","maximise":"maximise","minimise":"minimise","tabDropdown":"additional tabs"},"resolved":true}
"""";
string LayoutConfig2 = """"
{"root":{"type":"row","content":[{"type":"stack","content":[{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"bb_24636646","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"标签三","componentType":"component","componentState":{"id":"bb_24636646","showClose":true,"class":null,"key":"标签三","lock":false}}],"width":33.333333333333336,"minWidth":0,"height":50,"minHeight":0,"id":"","isClosable":true,"maximised":false,"activeItemIndex":0},{"type":"stack","content":[{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"bb_60600063","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"标签一","componentType":"component","componentState":{"id":"bb_60600063","showClose":true,"class":null,"key":"标签一","lock":false}}],"width":33.333333333333336,"minWidth":0,"height":50,"minHeight":0,"id":"bb_54183781","isClosable":true,"maximised":false,"activeItemIndex":0},{"type":"stack","content":[{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"bb_6744750","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"标签二","componentType":"component","componentState":{"id":"bb_6744750","showClose":true,"class":null,"key":"标签二","lock":false}}],"width":33.33333333333333,"minWidth":0,"height":50,"minHeight":0,"id":"bb_6744750","isClosable":true,"maximised":false,"activeItemIndex":0}],"width":50,"minWidth":50,"height":50,"minHeight":50,"id":"","isClosable":true},"openPopouts":[],"settings":{"constrainDragToContainer":true,"reorderEnabled":true,"popoutWholeStack":false,"blockedPopoutsThrowError":true,"closePopoutsOnUnload":true,"responsiveMode":"none","tabOverlapAllowance":0,"reorderOnTabMenuClick":true,"tabControlOffset":10,"popInOnClose":false},"dimensions":{"borderWidth":5,"borderGrabWidth":5,"minItemHeight":10,"minItemWidth":10,"headerHeight":25,"dragProxyWidth":300,"dragProxyHeight":200},"header":{"show":"top","popout":"lock/unlock","dock":"dock","close":"close","maximise":"maximise","minimise":"minimise","tabDropdown":"additional tabs"},"resolved":true}
"""";
string LayoutConfig3 = """"
{"root":{"type":"stack","content":[{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"bb_24636646","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"标签三","componentType":"component","componentState":{"id":"bb_24636646","showClose":true,"class":null,"key":"标签三","lock":false}},{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"bb_60600063","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"标签一","componentType":"component","componentState":{"id":"bb_60600063","showClose":true,"class":null,"key":"标签一","lock":false}},{"type":"component","content":[],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"bb_6744750","maximised":false,"isClosable":true,"reorderEnabled":true,"title":"标签二","componentType":"component","componentState":{"id":"bb_6744750","showClose":true,"class":null,"key":"标签二","lock":false}}],"width":50,"minWidth":0,"height":50,"minHeight":0,"id":"bb_60600063","isClosable":true,"maximised":false,"activeItemIndex":1},"openPopouts":[],"settings":{"constrainDragToContainer":true,"reorderEnabled":true,"popoutWholeStack":false,"blockedPopoutsThrowError":true,"closePopoutsOnUnload":true,"responsiveMode":"none","tabOverlapAllowance":0,"reorderOnTabMenuClick":true,"tabControlOffset":10,"popInOnClose":false},"dimensions":{"borderWidth":5,"borderGrabWidth":5,"minItemHeight":10,"minItemWidth":10,"headerHeight":25,"dragProxyWidth":300,"dragProxyHeight":200},"header":{"show":"top","popout":"lock/unlock","dock":"dock","close":"close","maximise":"maximise","minimise":"minimise","tabDropdown":"additional tabs"},"resolved":true}
"""";
}

View File

@@ -0,0 +1,14 @@
.config {
display: block;
margin-top: 1rem;
border: 1px solid var(--bs-secondary);
border-radius: var(--bs-border-radius);
padding: 0.5rem;
overflow: auto;
height: 88px;
}
.dock-toggle-demo {
height: calc(100vh - 424px);
margin-top: 1rem;
}

View File

@@ -1,16 +1,16 @@
@page "/dock-view/lock"
@inherits BaseDockView
<h4 class="mt-3">锁定面板</h4>
<h4>锁定面板</h4>
<p>通过设置 <code>DockView</code> 的属性 <code>IsLock</code>,控制所有面板是否能拖动</p>
<p>通过设置 <code>DockComponent</code> 的属性 <code>IsLock</code>,控制某个面板是否能拖动</p>
<Button OnClick="OnToggleLock" Text="@LockText"></Button>
<div class="dock-lock-demo">
<DockView Name="DockViewLock" EnableLocalStorage="false" OnLockChangedCallbackAsync="OnLockChangedCallbackAsync" IsLock="@IsLock">
<DockView Name="DockViewLock" EnableLocalStorage="true" OnLockChangedCallbackAsync="OnLockChangedCallbackAsync" IsLock="@IsLock">
<DockContent Type="DockContentType.Row">
<DockComponent Title="标签一" IsLock="true">
<DockComponent Title="标签一">
<Table TItem="DynamicObject" DynamicContext="DataTableDynamicContext"
IsStriped="true" IsBordered="true" IsExcel="true" ShowRefresh="true"
ShowDefaultButtons="false" IsFixedHeader="false">

View File

@@ -1,7 +1,7 @@
@page "/dock-view/visible"
@inherits BaseDockView
<h4 class="mt-3">可隐藏的面板</h4>
<h4>可隐藏的面板</h4>
<p>通过设置 <code>DockComponent</code> 的属性 <code>Visible</code> 控制面板的显示和隐藏</p>
<Button OnClick="OnToggleVisible" Text="切换标签一"></Button>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<Version>7.0.11</Version>
<Version>7.0.12</Version>
</PropertyGroup>
<PropertyGroup>

View File

@@ -88,6 +88,12 @@ public partial class DockView
[Parameter]
public string? LocalStoragePrefix { get; set; }
/// <summary>
/// 获得/设置 布局配置
/// </summary>
[Parameter]
public string? LayoutConfig { get; set; }
private DockViewConfig Config { get; } = new();
private DockContent Content { get; } = new();
@@ -150,6 +156,7 @@ public partial class DockView
EnableLocalStorage = EnableLocalStorage,
IsLock = IsLock,
Contents = Config.Contents,
LayoutConfig = LayoutConfig,
LocalStorageKeyPrefix = $"{LocalStoragePrefix}-{Name}",
VisibleChangedCallback = nameof(VisibleChangedCallbackAsync),
InitializedCallback = nameof(InitializedCallbackAsync),
@@ -203,11 +210,25 @@ public partial class DockView
}
}
/// <summary>
/// 获取布局配置
/// </summary>
/// <returns></returns>
public Task<string?> GetLayoutConfig() => InvokeAsync<string>("getLayoutConfig", Id);
/// <summary>
/// 重置为默认布局
/// </summary>
/// <returns></returns>
public Task Reset() => InvokeVoidAsync("reset", Id, GetOption(), Interop);
public Task Reset(string? layoutConfig = null)
{
var config = GetOption();
if (layoutConfig != null)
{
config.LayoutConfig = layoutConfig;
}
return InvokeVoidAsync("reset", Id, config);
}
/// <summary>
/// 标签页关闭回调方法 由 JavaScript 调用

View File

@@ -11,7 +11,7 @@ export async function init(id, option, invoke) {
await addLink("./_content/BootstrapBlazor.Dock/css/goldenlayout-bb.css")
const eventsData = new Map()
const dock = { el, eventsData, lock: option.lock }
const dock = { el, eventsData, invoke, lock: option.lock, layoutConfig: option.layoutConfig }
Data.set(id, dock)
option.invokeVisibleChangedCallback = (title, visible) => {
@@ -74,7 +74,10 @@ export function update(id, option) {
const dock = Data.get(id)
if (dock) {
if (dock.lock !== option.lock) {
if (dock.layoutConfig !== option.layoutConfig) {
reset(id, option)
}
else if (dock.lock !== option.lock) {
// 处理 Lock 逻辑
dock.lock = option.lock
lockDock(dock)
@@ -92,7 +95,17 @@ export function lock(id, lock) {
lockDock(dock)
}
export function reset(id, option, invoke) {
export function getLayoutConfig(id) {
let config = "";
const dock = Data.get(id)
if (dock) {
const layout = dock.layout
config = JSON.stringify(layout.saveLayout())
}
return config;
}
export function reset(id, option) {
const dock = Data.get(id)
if (dock) {
removeConfig(option);
@@ -108,7 +121,7 @@ export function reset(id, option, invoke) {
})
dispose(id)
init(id, option, invoke)
init(id, option, dock.invoke)
}
}
@@ -122,6 +135,13 @@ export function dispose(id) {
dock.eventsData.clear()
dock.layout.destroy()
if (goldenLayout.bb_docks !== void 0) {
const index = goldenLayout.bb_docks.indexOf(dock);
if (index > 0) {
goldenLayout.bb_docks.splice(index, 1);
}
}
}
const lockDock = dock => {
@@ -291,21 +311,24 @@ const closeItem = (el, component) => {
}
const getConfig = option => {
let config = null
option = {
enableLocalStorage: false,
layoutConfig: null,
name: 'default',
...option
}
if (option.enableLocalStorage) {
const localConfig = localStorage.getItem(getLocalStorageKey(option));
if (localConfig) {
// 当tab全部关闭时没有root节点
const configItem = JSON.parse(localConfig)
if (configItem.root) {
config = configItem
resetComponentId(config, option)
}
let config = null
let layoutConfig = option.layoutConfig;
if (layoutConfig === null && option.enableLocalStorage) {
layoutConfig = localStorage.getItem(getLocalStorageKey(option));
}
if (layoutConfig) {
// 当tab全部关闭时没有root节点
const configItem = JSON.parse(layoutConfig)
if (configItem.root) {
config = configItem
resetComponentId(config, option)
}
}
@@ -474,7 +497,7 @@ const hackGoldenLayout = dock => {
this._closeButton.onClick = function (ev) {
// find own dock
const dock = goldenLayout.bb_docks.find(i => i.layout === this.layoutManager);
const dock = goldenLayout.bb_docks.find(i => i.layout === this._header.layoutManager);
const eventsData = dock.eventsData
const tabs = this._header.tabs.map(tab => {

View File

@@ -67,4 +67,9 @@ class DockViewConfig
[JsonPropertyName("content")]
[JsonConverter(typeof(DockContentRootConverter))]
public List<DockContent> Contents { get; set; } = new();
/// <summary>
/// 获得/设置 布局配置 默认 null 未设置
/// </summary>
public string? LayoutConfig { get; set; }
}