Модуль: тема и палитра CRM — crm:theme в iframe (data-theme/data-pal), в окно звонка — параметрами ?theme=&pal= с проверкой
This commit is contained in:
@@ -5,6 +5,7 @@ import { CopyButton } from "./CopyButton";
|
||||
import { retryReady, useAuth } from "./crm";
|
||||
import { ConfirmDialog } from "./Dialogs";
|
||||
import { Icon } from "./Icon";
|
||||
import { withTheme } from "./theme";
|
||||
|
||||
const WS_KEY = "calls_ws_v1:";
|
||||
|
||||
@@ -82,8 +83,9 @@ function Room(props: { ws: string }) {
|
||||
setBusy(true);
|
||||
try {
|
||||
const r = await api.join(ws);
|
||||
if (w && !w.closed) w.location.href = r.url;
|
||||
else setFallback(r.url); // браузер заблокировал окно — ссылка вручную (код живёт 60 секунд)
|
||||
const url = withTheme(r.url);
|
||||
if (w && !w.closed) w.location.href = url;
|
||||
else setFallback(url); // браузер заблокировал окно — ссылка вручную (код живёт 60 секунд)
|
||||
} catch (e) {
|
||||
try { w?.close(); } catch { /* уже закрыто */ }
|
||||
setJoinErr((e as Error).message);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Мост с CRM: postMessage (crm:ready → crm:auth, crm:workspace, crm:refresh, crm:logout).
|
||||
// Мост с CRM: postMessage (crm:ready → crm:auth, crm:workspace, crm:theme, crm:refresh, crm:logout).
|
||||
// Токен живёт только в памяти, никуда не логируется и не выводится.
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { applyTheme, parseTheme } from "./theme";
|
||||
|
||||
export type User = { id: string; email: string | null; name: string | null };
|
||||
export type CrmWorkspace = { id?: string; kind?: string; title?: string };
|
||||
@@ -150,6 +151,9 @@ function handle(ev: MessageEvent) {
|
||||
else if (d.type === "crm:workspace") {
|
||||
lastWs = d.workspace ?? null;
|
||||
set({ crmWorkspace: wsKey(lastWs, state.user?.id) });
|
||||
} else if (d.type === "crm:theme") {
|
||||
const t = parseTheme(d.theme, d.palette);
|
||||
if (t) applyTheme(t);
|
||||
} else if (d.type === "crm:logout") {
|
||||
token = null; expiresAt = null;
|
||||
set({ status: "logout", user: undefined });
|
||||
|
||||
@@ -4,13 +4,14 @@ import App from "./App";
|
||||
import { CallPage } from "./Call";
|
||||
import { initCrm } from "./crm";
|
||||
import { GuestPage } from "./Guest";
|
||||
import { initCallTheme } from "./theme";
|
||||
import "./calls.css";
|
||||
|
||||
// Три страницы: / — модуль в iframe CRM, /call — окно звонка, /g/<токен> — гость.
|
||||
const path = location.pathname;
|
||||
const guest = /^\/g\/([A-Za-z0-9_-]{20,100})\/?$/.exec(path);
|
||||
let page;
|
||||
if (path === "/call" || path === "/call/") page = <CallPage />;
|
||||
if (path === "/call" || path === "/call/") { initCallTheme(); page = <CallPage />; }
|
||||
else if (guest) page = <GuestPage token={guest[1]} />;
|
||||
else { void initCrm(); page = <App />; }
|
||||
|
||||
|
||||
52
calls/frontend/src/theme.ts
Normal file
52
calls/frontend/src/theme.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
// Тема и палитра CRM: <html data-theme="light|dark" data-pal="…">, остальное делают стили tvorymo-ui.css.
|
||||
// В iframe их присылает CRM (crm:theme), в окно звонка — модуль параметрами ?theme=&pal= в ссылке.
|
||||
|
||||
const THEME_KEY = "calls_theme_v1";
|
||||
|
||||
export type Theme = { theme: "light" | "dark"; pal: string };
|
||||
|
||||
/** Проверка значений: тема только light|dark, палитра — короткое имя из латиницы (иначе classic). */
|
||||
export function parseTheme(theme: unknown, pal: unknown): Theme | null {
|
||||
if (theme !== "light" && theme !== "dark") return null;
|
||||
const p = typeof pal === "string" && /^[a-z][a-z0-9-]{0,31}$/.test(pal) ? pal : "classic";
|
||||
return { theme, pal: p };
|
||||
}
|
||||
|
||||
export function applyTheme(t: Theme) {
|
||||
const el = document.documentElement;
|
||||
el.dataset.theme = t.theme;
|
||||
el.dataset.pal = t.pal;
|
||||
}
|
||||
|
||||
/** Текущая тема модуля (если CRM её присылала) — для передачи в окно звонка. */
|
||||
export function currentTheme(): Theme | null {
|
||||
const el = document.documentElement;
|
||||
return parseTheme(el.dataset.theme, el.dataset.pal);
|
||||
}
|
||||
|
||||
/** Окно звонка: тема из ссылки, при перезагрузке — из sessionStorage окна. Без неё — системная. */
|
||||
export function initCallTheme() {
|
||||
const q = new URLSearchParams(location.search);
|
||||
let t = parseTheme(q.get("theme"), q.get("pal"));
|
||||
if (t) {
|
||||
try { sessionStorage.setItem(THEME_KEY, JSON.stringify(t)); } catch { /* хранилище недоступно */ }
|
||||
} else {
|
||||
try {
|
||||
const s = JSON.parse(sessionStorage.getItem(THEME_KEY) || "null");
|
||||
t = s && parseTheme(s.theme, s.pal);
|
||||
} catch { /* битое значение */ }
|
||||
}
|
||||
if (t) applyTheme(t);
|
||||
}
|
||||
|
||||
/** Ссылка на окно звонка с темой модуля (в окно crm:theme не приходит). */
|
||||
export function withTheme(url: string): string {
|
||||
const t = currentTheme();
|
||||
if (!t) return url;
|
||||
try {
|
||||
const u = new URL(url, location.href);
|
||||
u.searchParams.set("theme", t.theme);
|
||||
u.searchParams.set("pal", t.pal);
|
||||
return u.toString();
|
||||
} catch { return url; }
|
||||
}
|
||||
Reference in New Issue
Block a user