Skip to content

Commit

Permalink
feat(Ssr): add SsrPageScript, SsrStateProvider and SsrThemeProvider c…
Browse files Browse the repository at this point in the history
…omponents (#517)

* 🆕 feat(Theme): support for SSR

* 🚧 feat: wip

* update

* 🚧 feat: wip

* 🚧 feat: wip

* 🚧 feat: wip

* complete

* nullable return

* simplify srr rollup config
  • Loading branch information
capdiem authored Dec 4, 2023
1 parent 148e381 commit b0fc4b5
Show file tree
Hide file tree
Showing 17 changed files with 273 additions and 17 deletions.
3 changes: 2 additions & 1 deletion src/Component/BlazorComponent.Web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"build:drawflow": "rollup --config rollup.config.drawflow.ts",
"build:swiper": "rollup --config rollup.config.swiper.ts",
"build:intersect": "rollup --config rollup.config.intersect.ts",
"build:resize": "rollup --config rollup.config.resize.ts"
"build:resize": "rollup --config rollup.config.resize.ts",
"build:ssr": "rollup --config rollup.config.ssr.ts"
},
"author": "",
"license": "MIT",
Expand Down
25 changes: 25 additions & 0 deletions src/Component/BlazorComponent.Web/rollup.config.ssr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineConfig } from "rollup";
import { terser } from "rollup-plugin-terser";

import typescript from "@rollup/plugin-typescript";

export default defineConfig([
{
input: "./src/ssr/ssr-state-restore.ts",
output: {
file: "../../../../MASA.Blazor/wwwroot/js/ssr-state-restore.js",
format: "iife",
sourcemap: true,
},
plugins: [typescript(), terser()],
},
{
input: "./src/ssr/page-script.ts",
output: {
file: "../../../../MASA.Blazor/wwwroot/js/ssr-page-script.js",
format: "esm",
sourcemap: true,
},
plugins: [typescript(), terser()],
},
]);
8 changes: 6 additions & 2 deletions src/Component/BlazorComponent.Web/src/interop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,8 +823,12 @@ export function getSize(selectors, sizeProp) {
return size;
}

export function getProp(selectors, name) {
var el = getDom(selectors);
export function getProp(elOrString, name) {
if (elOrString === 'window') {
return window[name];
}

var el = getDom(elOrString);
if (!el) {
return null;
}
Expand Down
2 changes: 2 additions & 0 deletions src/Component/BlazorComponent.Web/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as slider from "./components/slider";
import * as interop from "./interop";
import * as overlayable from "./mixins/overlayable";
import * as ssr from "./ssr";

declare global {
interface Window {
Expand All @@ -14,6 +15,7 @@ window.BlazorComponent = {
...interop,
...overlayable,
...slider,
ssr
},
};

Expand Down
123 changes: 123 additions & 0 deletions src/Component/BlazorComponent.Web/src/ssr/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
export type MasaBlazorApplication = {
bar?: number;
top: number;
right: number;
bottom: number;
left: number;
footer?: number;
insetFooter?: number;
};

export type MasaBlazorSsrPassiveState = {
application: MasaBlazorApplication;
};

export type MasaBlazorSsrState = {
culture?: string;
rtl?: boolean;
dark?: boolean;
passive: MasaBlazorSsrPassiveState;
};

export const MASA_BLAZOR_SSR_STATE = "masablazor@ssr-state";

export function setTheme(dark: boolean) {
const selector = `.${getThemeCss(!dark)}:not(.theme--independent)`
const elements = document.querySelectorAll(selector);
for (let i = 0; i < elements.length; i++) {
elements[i].classList.remove(getThemeCss(!dark));
elements[i].classList.add(getThemeCss(dark));
}

updateStorage({ dark });
}

export function setCulture(culture: string) {
const app = getApp();
if (!app) return;

updateStorage({ culture });
}

export function setRtl(rtl: boolean, updateCache: boolean = true) {
const app = getApp();
if (!app) return;

const rtlCss = "m-application--is-rtl";
if (!rtl) {
app.classList.remove(rtlCss);
} else if (!app.classList.contains(rtlCss)) {
app.classList.add(rtlCss);
}

if (updateCache) {
updateStorage({ rtl });
}
}

export function updateMain(application: MasaBlazorApplication) {
const main: HTMLElement = document.querySelector(".m-main");

const newApplication: MasaBlazorApplication = {
top: application.top ?? getPadding("Top"),
right: application.right ?? getPadding("Right"),
bottom: application.bottom ?? getPadding("Bottom"),
left: application.left ?? getPadding("Left"),
};

restoreMain(newApplication);

function getPadding(prop: "Top" | "Right" | "Bottom" | "Left") {
return Number(main.style[`padding${prop}`].match(/\d+/)[0]);
}
}

export function updatePassiveState(passive: MasaBlazorSsrPassiveState) {
const oldState = getState() ?? {};
const state: MasaBlazorSsrState = {
...oldState,
passive,
};
localStorage.setItem(MASA_BLAZOR_SSR_STATE, JSON.stringify(state));
}

export function getThemeCss(dark: boolean) {
return dark ? "theme--dark" : "theme--light";
}

function getApp() {
return document.querySelector(".m-application");
}

function updateStorage(obj: Partial<MasaBlazorSsrState>) {
const stateStr = localStorage.getItem(MASA_BLAZOR_SSR_STATE);
if (stateStr) {
const state = JSON.parse(stateStr);
localStorage.setItem(
MASA_BLAZOR_SSR_STATE,
JSON.stringify({
...state,
...obj,
})
);
}
}

export function restoreMain(application: MasaBlazorApplication) {
const main = document.querySelector(".m-main") as HTMLElement;
if (main && application) {
main.style.paddingTop = application.top + application.bar + "px";
main.style.paddingRight = application.right + "px";
main.style.paddingBottom =
application.bottom + application.insetFooter + application.bottom + "px";
main.style.paddingLeft = application.left + "px";
}
}

export function getState(): MasaBlazorSsrState {
const stateStr = localStorage.getItem(MASA_BLAZOR_SSR_STATE);
if (stateStr) {
return JSON.parse(stateStr);
}
return null;
}
45 changes: 45 additions & 0 deletions src/Component/BlazorComponent.Web/src/ssr/page-script.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { getThemeCss, MASA_BLAZOR_SSR_STATE, MasaBlazorSsrState, restoreMain, setRtl } from "./";

let firstUpdate = true;

// Called when the script first gets loaded on the page.
export function onLoad() {}

// Called when an enhanced page update occurs, plus once immediately after
// the initial load.
export function onUpdate() {
if (firstUpdate) {
firstUpdate = false;
return;
}

const stateStr = localStorage.getItem(MASA_BLAZOR_SSR_STATE);
if (!stateStr) return;

const state: MasaBlazorSsrState = JSON.parse(stateStr);
if (!state) return;

restoreMain(state.passive.application);
restoreTheme(state);
restoreRtl(state);
}

// Called when an enhanced page update removes the script from the page.
export function onDispose() {}

export function restoreTheme(state: MasaBlazorSsrState) {
if (typeof state.dark === "boolean") {
const selector = `.${getThemeCss(!state.dark)}:not(.theme--independent)`;
const elements = document.querySelectorAll(selector);
for (let i = 0; i < elements.length; i++) {
elements[i].classList.remove(getThemeCss(!state.dark));
elements[i].classList.add(getThemeCss(state.dark));
}
}
}

export function restoreRtl(state: MasaBlazorSsrState) {
if (typeof state.rtl === "boolean") {
setRtl(state.rtl, false);
}
}
10 changes: 10 additions & 0 deletions src/Component/BlazorComponent.Web/src/ssr/ssr-state-restore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { MASA_BLAZOR_SSR_STATE } from "./";
import { restoreRtl, restoreTheme } from "./page-script";

const stateStr = localStorage.getItem(MASA_BLAZOR_SSR_STATE);
if (stateStr) {
const state = JSON.parse(stateStr);

restoreTheme(state);
restoreRtl(state);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
public class ThemeCssBuilder
{
public string Build(ThemeOptions theme)
public string? Build(ThemeOptions theme)
{
var combinePrefix = theme.CombinePrefix;
combinePrefix ??= string.Empty;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
public abstract class BComponentBase : NextTickComponentBase, IHandleEvent
{
[Inject]
[NotNull]
public virtual IJSRuntime? Js { get; set; }
public virtual IJSRuntime Js { get; set; } = null!;

[CascadingParameter]
protected IDefaultsProvider? DefaultsProvider { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,4 @@ public async Task ToggleAsync()
Value = !Value;
await ValueChanged.InvokeAsync(Value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public abstract partial class BNavigationDrawer : BDomComponentBase, IDependent,

[Inject]
private OutsideClickJSModule? OutsideClickJsModule { get; set; }

[CascadingParameter]
public IDependent? CascadingDependent { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,36 @@
@inherits ComponentPartBase<TPagination>

<li>
<button class="@CssProvider.GetClass(IconClassName())" type="button" disabled="@Disabled" @onclick="HandleAsync" @onclick:preventDefault>
<BIcon @attributes="GetAttributes(typeof(BIcon))">
@GetIcon
</BIcon>
</button>
</li>
@CreatePaginationNavigation()
</li>

@code {

private RenderFragment CreatePaginationNavigation() => builder =>
{
builder.OpenElement(0, HrefFormat is null ? "button" : "a");
builder.AddAttribute(1, "class", CssProvider.GetClass(IconClassName()));
builder.AddAttribute(2, "onclick", HandleAsync);
builder.AddAttribute(3, "__internal_preventDefault_onclick", true);
if (HrefFormat is null)
{
builder.AddAttribute(4, "type", "button");
}
else if (!Disabled)
{
builder.AddAttribute(5, "href", string.Format(HrefFormat, ItemIndex == -1 ? Value - 1 : Value + 1));
}

builder.AddContent(6, (RenderFragment)(childBuilder =>
{
childBuilder.OpenComponent<BIcon>(0);
childBuilder.AddMultipleAttributes(1, GetAttributes(typeof(BIcon)));
// childBuilder.AddContent(2, _ => _.AddContent(0, GetIcon));
childBuilder.AddAttribute(2, "ChildContent", (RenderFragment)(childBuilder2 => { childBuilder2.AddContent(0, GetIcon); }));
childBuilder.CloseComponent();
}));

builder.CloseElement();
};

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ public partial class BPaginationIcon<TPagination> where TPagination : IPaginatio
[Parameter]
public int ItemIndex { get; set; }

public int Value => Component.Value;

public string? HrefFormat => Component.HrefFormat;

public string GetIcon => Component.GetIcon(ItemIndex);

public bool Disabled => ItemIndex == (int)PaginationIconTypes.First ? Component.PrevDisabled : Component.NextDisabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@
builder.OpenElement(0, HrefFormat is null ? "button" : "a");
builder.AddAttribute(1, "class", CssProvider.GetClass(itemClass));
builder.AddAttribute(2, "onclick", () => HandleItemClickAsync(item));

// TODO: hide this if the component is not interactive
// https://github.com/dotnet/aspnetcore/issues/49401
builder.AddAttribute(3, "__internal_preventDefault_onclick", true);

if (HrefFormat is null)
{
builder.AddAttribute(4, "type", "button");
Expand Down
14 changes: 13 additions & 1 deletion src/Component/BlazorComponent/JSInterop/JsInteropConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,25 @@ public static class JsInteropConstants
public static string ResizableDataTable => $"{JsInteropFuncNamePrefix}resizableDataTable";

public static string UpdateDataTableResizeHeight => $"{JsInteropFuncNamePrefix}updateDataTableResizeHeight";

public static string RegisterDragEvent => $"{JsInteropFuncNamePrefix}registerDragEvent";

public static string UnregisterDragEvent => $"{JsInteropFuncNamePrefix}unregisterDragEvent";

public static string RegisterSliderEvents => $"{JsInteropFuncNamePrefix}registerSliderEvents";

public static string UnregisterSliderEvents => $"{JsInteropFuncNamePrefix}unregisterSliderEvents";

public static string SsrSetTheme => $"{JsInteropFuncNamePrefix}ssr.setTheme";

public static string SsrSetRtl => $"{JsInteropFuncNamePrefix}ssr.setRtl";

public static string SsrSetCulture => $"{JsInteropFuncNamePrefix}ssr.setCulture";

public static string SsrUpdateMain => $"{JsInteropFuncNamePrefix}ssr.updateMain";

public static string SsrGetState => $"{JsInteropFuncNamePrefix}ssr.getState";

public static string SsrUpdatePassiveState => $"{JsInteropFuncNamePrefix}ssr.updatePassiveState";
}
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

0 comments on commit b0fc4b5

Please sign in to comment.