Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: try remove some SSR-related workarounds (24_2) #27645

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
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
setLicenseCheckSkipCondition,
validateLicense,
} from './license_validation';
import * as trialPanel from './trial_panel';
import * as trialPanel from './trial_panel.client';

jest.mock('./key', () => ({
PUBLIC_KEY: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import { INTERNAL_USAGE_ID, PUBLIC_KEY } from './key';
import { pad } from './pkcs1';
import { compareSignatures } from './rsa_bigint';
import { sha1 } from './sha1';
import type { CustomTrialPanelStyles } from './trial_panel';
import { registerCustomComponents, renderTrialPanel } from './trial_panel';
import { showTrialPanel } from './trial_panel';
import type {
License,
LicenseCheckParams,
Expand Down Expand Up @@ -158,22 +157,6 @@ function getLicenseCheckParams({
}
}

export function showTrialPanel(
buyNowUrl: string,
version: string,
customStyles?: CustomTrialPanelStyles,
): void {
if (typeof customElements !== 'undefined') {
renderTrialPanel(buyNowUrl, version, customStyles);
}
}

export function registerTrialPanelComponents(customStyles?: CustomTrialPanelStyles): void {
if (typeof customElements !== 'undefined') {
registerCustomComponents(customStyles);
}
}

export function validateLicense(licenseKey: string, versionStr: string = fullVersion): void {
if (validationPerformed) {
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { CustomTrialPanelStyles } from './trial_panel';
import type { Token } from './types';

// @ts-expect-error - only for internal usage
export function parseLicenseKey(encodedKey: string | undefined): Token {}

export function validateLicense(licenseKey: string, version?: string): void {}

export function showTrialPanel(
buyNowUrl: string,
version: string,
customStyles?: CustomTrialPanelStyles,
): void {}

export function registerTrialPanelComponents(customStyles?: CustomTrialPanelStyles): void {}

// @ts-expect-error - only for internal usage
export function peekValidationPerformed(): boolean {}

Expand Down
291 changes: 291 additions & 0 deletions packages/devextreme/js/__internal/core/license/trial_panel.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
/* eslint-disable max-classes-per-file */
/* eslint no-restricted-imports: ["error", { "patterns": ["*"] }] */
export const BASE_Z_INDEX = 1500;

export interface StylesMap {
[key: string]: string;
}

export interface CustomTrialPanelStyles {
containerStyles?: StylesMap;
textStyles?: StylesMap;
linkStyles?: StylesMap;
contentStyles?: StylesMap;
buttonStyles?: StylesMap;
}

export const isClient = (): boolean => typeof HTMLElement !== 'undefined';

const SafeHTMLElement = isClient()
? HTMLElement
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-extraneous-class
: class {} as any as typeof HTMLElement;

const DATA_PERMANENT_ATTRIBUTE = 'data-permanent';
const componentNames = {
trigger: 'dx-license-trigger',
panel: 'dx-license',
};
const attributeNames = {
buyNow: 'buy-now',
version: 'version',
};
const commonStyles = {
opacity: '1',
visibility: 'visible',
'clip-path': 'none',
filter: 'none',
};
const contentStyles = {
...commonStyles,
width: '100%',
height: 'auto',
'line-height': 'normal',
display: 'block',
'z-index': `${BASE_Z_INDEX}`,
position: 'static',
transform: 'translate(0px, 0px)',
'background-color': '#FF7200',
border: 'none',
margin: 'auto',
'box-sizing': 'border-box',
'text-align': 'center',
};
const containerStyles = {
...contentStyles,
display: 'flex',
'align-items': 'center',
'flex-direction': 'row',
position: 'relative',
top: '0px',
left: '0px',
padding: '0.5rem',
};
const buttonStyles = {
width: '1rem',
cursor: 'pointer',
height: '1rem',
};

const textStyles = {
...commonStyles,
display: 'inline',
position: 'static',
padding: '0px',
margin: '0px',
color: 'white',
'font-family': '\'Segoe UI\',\'Open Sans Condensed\',-apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica neue,helvetica,Cantarell,Ubuntu,roboto,noto,arial,sans-serif',
'font-size': '0.875rem',
'font-wight': '600',
};

function createImportantStyles(defaultStyles: StylesMap, customStyles?: StylesMap): string {
const styles = customStyles ? { ...defaultStyles, ...customStyles } : defaultStyles;

return Object.keys(styles)
.reduce(
(cssString, currentKey) => `${cssString}${[currentKey, `${styles[currentKey]} !important;`].join(': ')}`,
'',
);
}

class DxLicense extends SafeHTMLElement {
public static customStyles: CustomTrialPanelStyles | undefined = undefined;

private _observer: MutationObserver | null = null;

private _inReassign = false;

private _hidden = false;

private readonly _spanStyles: string;

private readonly _linkStyles: string;

private readonly _containerStyles: string;

private readonly _contentStyles: string;

private readonly _buttonStyles: string;

constructor() {
super();

this._spanStyles = createImportantStyles(textStyles, DxLicense.customStyles?.textStyles);
this._linkStyles = createImportantStyles(textStyles, DxLicense.customStyles?.linkStyles);

this._containerStyles = createImportantStyles(
containerStyles,
DxLicense.customStyles?.containerStyles,
);

this._contentStyles = createImportantStyles(
contentStyles,
DxLicense.customStyles?.contentStyles,
);

this._buttonStyles = createImportantStyles(
buttonStyles,
DxLicense.customStyles?.contentStyles,
);
}

private _createSpan(text: string): HTMLSpanElement {
const span = document.createElement('span');
span.innerText = text;
span.style.cssText = this._spanStyles;
return span;
}

private _createLink(text: string, href: string): HTMLAnchorElement {
const link = document.createElement('a');
link.innerText = text;
link.style.cssText = this._linkStyles;
link.href = href;
link.target = '_blank';
return link;
}

private _createButton(): HTMLDivElement {
const button = document.createElement('div');
button.style.cssText = this._buttonStyles;

const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');

polygon.setAttribute('points', '13.4 12.7 8.7 8 13.4 3.4 12.6 2.6 8 7.3 3.4 2.6 2.6 3.4 7.3 8 2.6 12.6 3.4 13.4 8 8.7 12.7 13.4 13.4 12.7');
polygon.style.cssText = createImportantStyles({
fill: '#fff',
opacity: '.5',
'stroke-width': '0px',
});

svg.setAttribute('id', 'Layer_1');
svg.setAttribute('data-name', 'Layer 1');
svg.setAttribute('version', '1.1');
svg.setAttribute('viewBox', '0 0 16 16');
svg.style.cssText = createImportantStyles({
'vertical-align': 'baseline',
});

svg.appendChild(polygon);
button.appendChild(svg);

button.onclick = (): void => {
this._hidden = true;
this.style.cssText = createImportantStyles({
display: 'none',
});
};
return button;
}

private _createContentContainer(): HTMLElement {
const contentContainer = document.createElement('div');
contentContainer.style.cssText = this._contentStyles;
contentContainer.append(
this._createSpan('For evaluation purposes only. Redistribution not authorized. Please '),
this._createLink('purchase a license', this.getAttribute(attributeNames.buyNow) as string),
this._createSpan(` to continue use of DevExpress product libraries (v${this.getAttribute(attributeNames.version)}).`),
);
return contentContainer;
}

private _reassignComponent(): void {
this.innerHTML = '';
this.style.cssText = this._containerStyles;
this.append(
this._createContentContainer(),
this._createButton(),
);
}

public connectedCallback(): void {
this._reassignComponent();
if (!this._observer) {
this._observer = new MutationObserver(() => {
if (this._hidden) {
this._observer?.disconnect();
return;
}
if (this._inReassign) {
this._inReassign = false;
} else {
this._inReassign = true;
this._reassignComponent();
}
});
this._observer.observe(this, {
childList: true,
attributes: true,
// eslint-disable-next-line spellcheck/spell-checker
subtree: true,
});
}
}

public disconnectedCallback(): void {
setTimeout(() => {
const licensePanel = document.getElementsByTagName(componentNames.panel);
if (!licensePanel.length) {
document.body.prepend(this);
}
}, 100);
}
}
class DxLicenseTrigger extends SafeHTMLElement {
public connectedCallback(): void {
this.style.cssText = createImportantStyles({
display: 'none',
});

const licensePanel = document.getElementsByTagName(componentNames.panel);
if (!licensePanel.length) {
const license = document.createElement(componentNames.panel);

license.setAttribute(
attributeNames.version,
this.getAttribute(attributeNames.version) as string,
);

license.setAttribute(
attributeNames.buyNow,
this.getAttribute(attributeNames.buyNow) as string,
);

license.setAttribute(DATA_PERMANENT_ATTRIBUTE, 'true');

document.body.prepend(license);
}
}
}

export function registerCustomComponents(customStyles?: CustomTrialPanelStyles): void {
if (!customElements.get(componentNames.trigger)) {
DxLicense.customStyles = customStyles;
customElements.define(
componentNames.trigger,
DxLicenseTrigger,
);
customElements.define(
componentNames.panel,
DxLicense,
);
}
}

export function renderTrialPanel(
buyNowUrl: string,
version: string,
customStyles?: CustomTrialPanelStyles,
): void {
registerCustomComponents(customStyles);

const trialPanelTrigger = document.createElement(componentNames.trigger);

trialPanelTrigger.setAttribute(attributeNames.buyNow, buyNowUrl);
trialPanelTrigger.setAttribute(attributeNames.version, version);

document.body.appendChild(trialPanelTrigger);
}
Loading
Loading