feat(DriverJs): add Highlight method (#3997)

* feat: 增加 drive 方法

* refactor: 精简代码

* refactor: 移动回调方法到 Config 中

* doc: 更新示例

* feat: 增加高亮功能

* doc: 更新文档

* doc: 更新高亮示例

* chore: bump version 8.0.1
This commit is contained in:
Argo Zhang 2024-08-07 19:46:33 +08:00 committed by GitHub
parent 9cf3d3558c
commit 183e9a7890
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 186 additions and 114 deletions

View File

@ -39,7 +39,7 @@
<PackageReference Include="BootstrapBlazor.CodeEditor" Version="8.0.2" />
<PackageReference Include="BootstrapBlazor.Dock" Version="8.1.7" />
<PackageReference Include="BootstrapBlazor.DockView" Version="8.0.9" />
<PackageReference Include="BootstrapBlazor.DriverJs" Version="8.0.0" />
<PackageReference Include="BootstrapBlazor.DriverJs" Version="8.0.1" />
<PackageReference Include="BootstrapBlazor.FileViewer" Version="8.0.3" />
<PackageReference Include="BootstrapBlazor.FontAwesome" Version="8.0.5" />
<PackageReference Include="BootstrapBlazor.Gantt" Version="8.0.1" />

View File

@ -165,7 +165,7 @@
</DriverJs>
</DemoBlock>
<DemoBlock Title="@Localizer["DriverJsDestroyTitle"]"
<DemoBlock Title="@Localizer["DriverJsDestroyTitle"]"
Introduction="@Localizer["DriverJsDestroyIntro"]"
Name="Popover">
<section ignore class="bb-guid4">
@ -182,7 +182,7 @@
</div>
</div>
</section>
<DriverJs @ref="_guideDestroy" Config="_config" OnBeforeDestroyAsync="OnBeforeDestroyAsync" OnDestroyedAsync="OnDestroyedAsync">
<DriverJs @ref="_guideDestroy" Config="_configDestroy">
<DriverJsStep Selector=".bb-title">
<DriverJsPopover Title="@Localizer["DriverJsPopoverTitleText"]" Description="@Localizer["DriverJsPopoverContentText2"]" Side="bottom" Align="center"></DriverJsPopover>
</DriverJsStep>
@ -198,3 +198,22 @@
</DriverJs>
<ConsoleLogger @ref="_logger"></ConsoleLogger>
</DemoBlock>
<DemoBlock Title="@Localizer["DriverJsHighlightTitle"]"
Introduction="@Localizer["DriverJsHighlightIntro"]"
Name="Highlight">
<section ignore class="bb-guid5">
<div class="row g-2">
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInput TValue="string"></BootstrapInput>
<Button Icon="fa-solid fa-magnifying-glass"></Button>
</BootstrapInputGroup>
</div>
<div class="col-12">
<Button OnClickWithoutRender="OnStartHighlight" Text="Start" class="btn-guide"></Button>
</div>
</div>
</section>
<DriverJs @ref="_guideHighlight"></DriverJs>
</DemoBlock>

View File

@ -13,9 +13,11 @@ public partial class DriverDotnetJs
private DriverJs _guidePopover = default!;
private DriverJs _guidePopoverStyle = default!;
private DriverJs _guideDestroy = default!;
private DriverJs _guideHighlight = default!;
private DriverJsConfig _config = default!;
private DriverJsConfig _configPopover = default!;
private DriverJsConfig _configPopoverStyle = default!;
private DriverJsConfig _configDestroy = default!;
private ConsoleLogger _logger = default!;
/// <summary>
@ -37,6 +39,11 @@ public partial class DriverDotnetJs
{
PopoverClass = "driverjs-theme"
};
_configDestroy = new()
{
OnDestroyStartedAsync = OnBeforeDestroyAsync,
OnDestroyedAsync = OnDestroyedAsync
};
}
private Task OnStart() => _guide.Start();
@ -64,4 +71,20 @@ public partial class DriverDotnetJs
_logger.Log("Trigger OnDestroyedAsync");
return Task.CompletedTask;
}
private async Task OnStartHighlight()
{
var config = new DriverJsConfig
{
StagePadding = 20f
};
var popover = new DriverJsHighlightPopover
{
Title = "Highlight Demo",
Description = "This is a highlight demo",
Align = "center",
Side = "bottom"
};
await _guideHighlight.Highlight(config, ".bb-guid5 .col-12", popover);
}
}

View File

@ -6587,7 +6587,9 @@
"DriverJsPopoverStyleIntro": "By setting <code>PopoverClass=\"driverjs-theme\"</code> custome the popover UI",
"DriverJsDestroyTitle": "Destroy callback",
"DriverJsDestroyIntro": "Callback method before destruction <code>OnBeforeDestroyAsync</code> or callback method for destruction <code>OnDestroyedAsync</code>",
"DriverJsDestroyDesc": "You can use the <code>OnBeforeDestroyAsync</code> callback to add some logic when the user tries to exit the tour. Prevent destruction when the callback method <code>OnBeforeDestroyAsync</code> returns not <code>NULL</code> string. You can also prevent the user from exiting the tour using <code>AllowClose</code> option. This option is useful when you want to force the user to complete the tour before they can exit."
"DriverJsDestroyDesc": "You can use the <code>OnBeforeDestroyAsync</code> callback to add some logic when the user tries to exit the tour. Prevent destruction when the callback method <code>OnBeforeDestroyAsync</code> returns not <code>NULL</code> string. You can also prevent the user from exiting the tour using <code>AllowClose</code> option. This option is useful when you want to force the user to complete the tour before they can exit.",
"DriverJsHighlightTitle": "Highlight",
"DriverJsHighlightIntro": "By calling the <code>DriverJs</code> component instance method <code>Highlight</code>, the specified element is highlighted and focused."
},
"BootstrapBlazor.Server.Components.Samples.IntersectionObservers": {
"IntersectionObserverTitle": "IntersectionObserver",

View File

@ -6587,7 +6587,9 @@
"DriverJsPopoverStyleIntro": "通过设置 <code>PopoverClass=\"driverjs-theme\"</code> 通过自定义样式 <code>driverjs-theme</code> 设置弹窗 UI",
"DriverJsDestroyTitle": "销毁回调方法",
"DriverJsDestroyIntro": "销毁前回调方法 <code>OnBeforeDestroyAsync</code> 或者销毁回调方法 <code>OnDestroyedAsync</code>",
"DriverJsDestroyDesc": "当用户尝试退出游览时,可以使用 <code>OnBeforeDestroyAsync</code> 回调添加销毁前逻辑,返回 <b>非空字符串</b> 时客户端弹窗二次确认是否阻止销毁;可通过设置 <code>AllowClose</code> 阻止用户退出向导"
"DriverJsDestroyDesc": "当用户尝试退出游览时,可以使用 <code>OnBeforeDestroyAsync</code> 回调添加销毁前逻辑,返回 <b>非空字符串</b> 时客户端弹窗二次确认是否阻止销毁;可通过设置 <code>AllowClose</code> 阻止用户退出向导",
"DriverJsHighlightTitle": "高亮显示",
"DriverJsHighlightIntro": "通过调用 <code>DriverJs</code> 组件实例方法 <code>Highlight</code> 使指定元素高亮聚焦显示"
},
"BootstrapBlazor.Server.Components.Samples.IntersectionObservers": {
"IntersectionObserverTitle": "IntersectionObserver 交叉观察器",

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<Version>8.0.0</Version>
<Version>8.0.1</Version>
<RootNamespace>BootstrapBlazor.Components</RootNamespace>
</PropertyGroup>

View File

@ -21,18 +21,6 @@ public partial class DriverJs
[Parameter]
public DriverJsConfig? Config { get; set; }
/// <summary>
/// 获得/设置 组件销毁前回调方法 返回 false 时阻止销毁
/// </summary>
[Parameter]
public Func<DriverJsConfig, int, Task<string?>>? OnBeforeDestroyAsync { get; set; }
/// <summary>
/// 获得/设置 组件销毁回调方法
/// </summary>
[Parameter]
public Func<Task>? OnDestroyedAsync { get; set; }
/// <summary>
/// 获得/设置 子组件内容
/// </summary>
@ -60,15 +48,6 @@ public partial class DriverJs
Config.Steps = _steps;
Config.ProgressText ??= Localizer[nameof(Config.ProgressText)];
if (OnBeforeDestroyAsync != null)
{
Config.OnDestroyStartedAsync = nameof(OnBeforeDestroy);
}
if (OnBeforeDestroyAsync != null)
{
Config.OnDestroyedAsync = nameof(OnDestroyed);
}
await InvokeVoidAsync("start", Id, Config, new
{
AutoDrive,
@ -77,18 +56,17 @@ public partial class DriverJs
}
/// <summary>
/// 组件销毁前回调方法由 JavaScript 调用 返回 false 阻止销毁
/// 组件销毁前回调方法由 JavaScript 调用 返回 非空字符串时客户端 confirm 确认弹窗
/// </summary>
[JSInvokable]
public async Task<string?> OnBeforeDestroy(int index)
{
string? ret = null;
if (OnBeforeDestroyAsync != null)
{
// Config 不为空
ret = await OnBeforeDestroyAsync(Config!, index);
}
if (Config?.OnDestroyStartedAsync != null)
{
ret = await Config.OnDestroyStartedAsync(Config, index);
}
return ret;
}
@ -99,9 +77,9 @@ public partial class DriverJs
[JSInvokable]
public async Task OnDestroyed()
{
if (OnDestroyedAsync != null)
if (Config?.OnDestroyedAsync != null)
{
await OnDestroyedAsync();
await Config.OnDestroyedAsync();
}
}
@ -207,8 +185,17 @@ public partial class DriverJs
/// <summary>
/// Look at the DriveStep section of configuration for format of the step
/// </summary>
/// <param name="config"><see cref="DriverJsConfig"/> 实例</param>
/// <param name="selector">target selector</param>
/// <param name="popover"><see cref="DriverJsHighlightPopover"/> 实例</param>
/// <returns></returns>
public Task Highlight() => InvokeVoidAsync("highlight", Id);
public async Task Highlight(DriverJsConfig config, string? selector, DriverJsHighlightPopover popover)
{
config ??= new();
config.ProgressText ??= Localizer[nameof(Config.ProgressText)];
await InvokeVoidAsync("highlight", Id, config, new { element = selector, popover });
}
/// <summary>
/// 添加步骤方法

View File

@ -18,18 +18,20 @@ export function start(id, options, config) {
if (d) {
d.config = config;
const { autoDrive, index } = config;
const { onDestroyStartedAsync, onDestroyedAsync } = options;
if (onDestroyStartedAsync) {
const { hookDestroyStarted, hookDestroyed } = options;
if (hookDestroyStarted) {
delete options.hookDestroyStarted;
options.onDestroyStarted = async (el, step, { state }) => {
const content = await d.invoke.invokeMethodAsync(onDestroyStartedAsync, state.activeIndex);
const content = await d.invoke.invokeMethodAsync("OnBeforeDestroy", state.activeIndex);
if (content === null || confirm(content)) {
d.driver.destroy();
}
}
}
if (onDestroyedAsync) {
if (hookDestroyed) {
delete options.hookDestroyed;
options.onDestroyed = () => {
d.invoke.invokeMethodAsync(onDestroyedAsync);
d.invoke.invokeMethodAsync("OnDestroyed");
};
}
const driverObj = driver(options);
@ -50,6 +52,13 @@ export function dispose(id) {
}
}
export function drive(id, index = 0) {
const d = Data.get(id);
if (d) {
d.driver.drive(index);
}
}
export function moveNext(id) {
const d = Data.get(id);
if (d) {
@ -131,3 +140,8 @@ export function refresh(id) {
d.driver.refresh();
}
}
export function highlight(id, options, config) {
const driverObj = driver(options);
driverObj.highlight(config);
}

View File

@ -125,15 +125,21 @@ public class DriverJsConfig
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? DoneBtnText { get; set; }
/// <summary>
/// 获得/设置 组件销毁前回调方法名称
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull), JsonInclude]
internal string? OnDestroyStartedAsync { get; set; }
[JsonInclude]
private bool HookDestroyStarted => OnDestroyStartedAsync != null;
/// <summary>
/// 获得/设置 组件销毁前回调方法名称
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull), JsonInclude]
internal string? OnDestroyedAsync { get; set; }
[JsonIgnore]
public Func<DriverJsConfig, int, Task<string?>>? OnDestroyStartedAsync { get; set; }
[JsonInclude]
private bool HookDestroyed => OnDestroyedAsync != null;
/// <summary>
/// 获得/设置 组件销毁前回调方法名称
/// </summary>
[JsonIgnore]
public Func<Task>? OnDestroyedAsync { get; set; }
}

View File

@ -66,13 +66,13 @@ public class DriverJsStep : ComponentBase, IDisposable
/// <param name="builder"></param>
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
_popover ??= new InternalDriverJsPopover()
_popover ??= new DriverJsHighlightPopover()
{
Title = Title,
Description = Description,
PrevBtnText = Localizer[nameof(InternalDriverJsPopover.PrevBtnText)],
NextBtnText = Localizer[nameof(InternalDriverJsPopover.NextBtnText)],
DoneBtnText = Localizer[nameof(InternalDriverJsPopover.DoneBtnText)]
PrevBtnText = Localizer[nameof(DriverJsHighlightPopover.PrevBtnText)],
NextBtnText = Localizer[nameof(DriverJsHighlightPopover.NextBtnText)],
DoneBtnText = Localizer[nameof(DriverJsHighlightPopover.DoneBtnText)]
};
builder.OpenComponent<CascadingValue<DriverJsStep>>(0);
builder.AddAttribute(1, nameof(CascadingValue<DriverJsStep>.Value), this);
@ -106,45 +106,4 @@ public class DriverJsStep : ComponentBase, IDisposable
Dispose(true);
GC.SuppressFinalize(this);
}
///// <summary>
///// The popover configuration for this step.
///// </summary>
//public FocusGuidePopover? Popover { get; set; }
///// <summary>
///// Callback when the current step is deselected
///// </summary>
///// <param name="step"></param>
///// <param name="config"></param>
///// <param name="state"></param>
///// <returns></returns>
//public Task OnDeselected(FocusGuideStep step, FocusGuideConfig config, FocusGuideState state)
//{
// return Task.CompletedTask;
//}
///// <summary>
/////
///// </summary>
///// <param name="step"></param>
///// <param name="config"></param>
///// <param name="state"></param>
///// <returns></returns>
//public Task OnHighlightStarted(FocusGuideStep step, FocusGuideConfig config, FocusGuideState state)
//{
// return Task.CompletedTask;
//}
///// <summary>
/////
///// </summary>
///// <param name="step"></param>
///// <param name="config"></param>
///// <param name="state"></param>
///// <returns></returns>
//public Task OnHighlighted(FocusGuideStep step, FocusGuideConfig config, FocusGuideState state)
//{
// return Task.CompletedTask;
//}
}

View File

@ -1,21 +0,0 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
namespace BootstrapBlazor.Components;
internal class InternalDriverJsPopover : IDriverJsPopover
{
public string? Title { get; set; }
public string? Description { get; set; }
public string? Side { get; set; }
public string? Align { get; set; }
public List<string>? ShowButtons { get; set; }
public List<string>? DisableButtons { get; set; }
public string? NextBtnText { get; set; }
public string? PrevBtnText { get; set; }
public string? DoneBtnText { get; set; }
public bool? ShowProgress { get; set; }
public string? ProgressText { get; set; }
public string? PopoverClass { get; set; }
}

View File

@ -0,0 +1,81 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
namespace BootstrapBlazor.Components;
/// <summary>
/// DriverJsHighlightPopover 实例
/// </summary>
public class DriverJsHighlightPopover : IDriverJsPopover
{
/// <summary>
/// <inheritdoc/>
/// </summary>
public string? Title { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
public string? Description { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Side { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Align { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<string>? ShowButtons { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<string>? DisableButtons { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? NextBtnText { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? PrevBtnText { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? DoneBtnText { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? ShowProgress { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? ProgressText { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? PopoverClass { get; set; }
}