✨ feat(client): 插件页改为三组列表与键值状态,权限带状态点
This commit is contained in:
@@ -104,6 +104,7 @@ var zh = {
|
|||||||
"config.testAny": "测试提醒",
|
"config.testAny": "测试提醒",
|
||||||
"config.testSystem": "测试系统通知",
|
"config.testSystem": "测试系统通知",
|
||||||
"config.diag.window": "窗口状态",
|
"config.diag.window": "窗口状态",
|
||||||
|
"config.diag.watch": "会话监听",
|
||||||
"config.diag.inFront": "在前台(会走轻弹窗)",
|
"config.diag.inFront": "在前台(会走轻弹窗)",
|
||||||
"config.diag.away": "不在前台(会走系统通知)",
|
"config.diag.away": "不在前台(会走系统通知)",
|
||||||
"config.diag.last": "最近一次投递",
|
"config.diag.last": "最近一次投递",
|
||||||
@@ -114,8 +115,9 @@ var zh = {
|
|||||||
"config.diag.replayDelivery": "应用内轻弹窗(回到窗口时补发)",
|
"config.diag.replayDelivery": "应用内轻弹窗(回到窗口时补发)",
|
||||||
"config.diag.popup": "应用内轻弹窗",
|
"config.diag.popup": "应用内轻弹窗",
|
||||||
"config.diag.refused": "系统通知被拒绝,改用了轻弹窗",
|
"config.diag.refused": "系统通知被拒绝,改用了轻弹窗",
|
||||||
"config.diag.replay": "等你回到窗口时补发:{count} 条",
|
"config.diag.replay": "待补发提醒",
|
||||||
"config.diag.replayNone": "没有待补发的提醒"
|
"config.diag.replayValue": "{count} 条",
|
||||||
|
"config.diag.replayNone": "没有"
|
||||||
};
|
};
|
||||||
|
|
||||||
// src/client/i18n/en.js
|
// src/client/i18n/en.js
|
||||||
@@ -163,6 +165,7 @@ var en = {
|
|||||||
"config.testAny": "Test alert",
|
"config.testAny": "Test alert",
|
||||||
"config.testSystem": "Test system notification",
|
"config.testSystem": "Test system notification",
|
||||||
"config.diag.window": "Window",
|
"config.diag.window": "Window",
|
||||||
|
"config.diag.watch": "Session watch",
|
||||||
"config.diag.inFront": "in front (popup is used)",
|
"config.diag.inFront": "in front (popup is used)",
|
||||||
"config.diag.away": "not in front (system notification is used)",
|
"config.diag.away": "not in front (system notification is used)",
|
||||||
"config.diag.last": "Last delivery",
|
"config.diag.last": "Last delivery",
|
||||||
@@ -173,8 +176,9 @@ var en = {
|
|||||||
"config.diag.replayDelivery": "in-app popup (replayed when you came back)",
|
"config.diag.replayDelivery": "in-app popup (replayed when you came back)",
|
||||||
"config.diag.popup": "in-app popup",
|
"config.diag.popup": "in-app popup",
|
||||||
"config.diag.refused": "system channel refused, popup was used instead",
|
"config.diag.refused": "system channel refused, popup was used instead",
|
||||||
"config.diag.replay": "waiting for you to come back: {count}",
|
"config.diag.replay": "Replay",
|
||||||
"config.diag.replayNone": "nothing waiting to be replayed"
|
"config.diag.replayValue": "{count} waiting",
|
||||||
|
"config.diag.replayNone": "None"
|
||||||
};
|
};
|
||||||
|
|
||||||
// src/client/i18n/index.js
|
// src/client/i18n/index.js
|
||||||
@@ -331,6 +335,9 @@ function createCopy(t) {
|
|||||||
if (candidate.kind === "completion") {
|
if (candidate.kind === "completion") {
|
||||||
return { kind: "completion", title: t("notification.completion"), body: t("body.completion", { title }) };
|
return { kind: "completion", title: t("notification.completion"), body: t("body.completion", { title }) };
|
||||||
}
|
}
|
||||||
|
if (candidate.pendingKind === "plan-review") {
|
||||||
|
return { kind: "question", title: t("notification.question"), body: t("body.planReview", { title }) };
|
||||||
|
}
|
||||||
if (candidate.kind === "question") {
|
if (candidate.kind === "question") {
|
||||||
const question = candidate.detail;
|
const question = candidate.detail;
|
||||||
return {
|
return {
|
||||||
@@ -339,9 +346,6 @@ function createCopy(t) {
|
|||||||
body: question === "" ? t("body.question", { title }) : `${title} · ${question}`
|
body: question === "" ? t("body.question", { title }) : `${title} · ${question}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (candidate.pendingKind === "plan-review") {
|
|
||||||
return { kind: "question", title: t("notification.question"), body: t("body.planReview", { title }) };
|
|
||||||
}
|
|
||||||
const tool = candidate.detail;
|
const tool = candidate.detail;
|
||||||
return {
|
return {
|
||||||
kind: "approval",
|
kind: "approval",
|
||||||
@@ -349,13 +353,13 @@ function createCopy(t) {
|
|||||||
body: tool === "" ? t("body.approvalPlain", { title }) : t("body.approval", { title, tool })
|
body: tool === "" ? t("body.approvalPlain", { title }) : t("body.approval", { title, tool })
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
const describeDelivery = (last, tr) => {
|
const deliveryFacts = (last, tr) => {
|
||||||
if (last === null || last === void 0) return tr("config.diag.none");
|
if (last === null || last === void 0) return null;
|
||||||
const outcome = last.outcome.startsWith("system-refused") ? tr("config.diag.refused") : last.outcome === "system" ? last.shown === true ? tr("config.diag.systemShown") : last.shown === false ? tr("config.diag.systemFailed") : tr("config.diag.systemUnconfirmed") : last.outcome === "replay" ? tr("config.diag.replayDelivery") : tr("config.diag.popup");
|
const outcome = last.outcome.startsWith("system-refused") ? tr("config.diag.refused") : last.outcome === "system" ? last.shown === true ? tr("config.diag.systemShown") : last.shown === false ? tr("config.diag.systemFailed") : tr("config.diag.systemUnconfirmed") : last.outcome === "replay" ? tr("config.diag.replayDelivery") : tr("config.diag.popup");
|
||||||
return `${tr("config.diag.last")}: ${last.at} · ${outcome} · ${last.title}`;
|
return { at: last.at, outcome, title: last.title ?? "" };
|
||||||
};
|
};
|
||||||
const describeReplay = (count, tr) => count === 0 ? tr("config.diag.replayNone") : tr("config.diag.replay", { count });
|
const replayValue = (count, tr) => count === 0 ? tr("config.diag.replayNone") : tr("config.diag.replayValue", { count });
|
||||||
return { copyFor, describeDelivery, describeReplay };
|
return { copyFor, deliveryFacts, replayValue };
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/client/core/toasts.js
|
// src/client/core/toasts.js
|
||||||
@@ -548,13 +552,18 @@ function isOnScreen(sessionId, list) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function stillWorth(candidate, live) {
|
function stillOwed(candidate, live) {
|
||||||
|
if (candidate.test === true) return true;
|
||||||
|
if (candidate.pendingKind !== "") {
|
||||||
|
const sessionStatus = live.status?.get?.(candidate.sessionId);
|
||||||
|
if (sessionStatus === void 0) return true;
|
||||||
|
return servedPendingKind(sessionStatus.pendingInteraction) === candidate.pendingKind;
|
||||||
|
}
|
||||||
const list = live.list;
|
const list = live.list;
|
||||||
if (candidate.test === true || list === void 0) return true;
|
if (list === void 0) return true;
|
||||||
const summary = list.byId?.[candidate.sessionId];
|
const summary = list.byId?.[candidate.sessionId];
|
||||||
if (summary === void 0) return true;
|
if (summary === void 0) return true;
|
||||||
const status = live.status;
|
const running = live.status?.get?.(candidate.sessionId)?.running ?? summary.running;
|
||||||
const running = status?.get?.(candidate.sessionId)?.running ?? summary.running;
|
|
||||||
return running !== true;
|
return running !== true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,28 +591,32 @@ function createDelivery({ store, copy, live, toasts, system, replay }) {
|
|||||||
const deliver = (candidate, ignoreThrottle) => {
|
const deliver = (candidate, ignoreThrottle) => {
|
||||||
if (ignoreThrottle !== true) {
|
if (ignoreThrottle !== true) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (now - lastDelivery < THROTTLE_MS) return;
|
if (now - lastDelivery < THROTTLE_MS) return false;
|
||||||
lastDelivery = now;
|
lastDelivery = now;
|
||||||
}
|
}
|
||||||
if (!windowIsAway()) {
|
if (!windowIsAway()) {
|
||||||
if (isOnScreen(candidate.sessionId, live.list)) return;
|
if (isOnScreen(candidate.sessionId, live.list)) return true;
|
||||||
toasts.showToast(candidate);
|
toasts.showToast(candidate);
|
||||||
recordDelivery("popup", candidate);
|
recordDelivery("popup", candidate);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
const result = system.notifySystem(candidate, candidate.test === true);
|
const result = system.notifySystem(candidate, candidate.test === true);
|
||||||
if (result.outcome === "raised") {
|
if (result.outcome === "raised") {
|
||||||
recordDelivery("system", candidate, result);
|
recordDelivery("system", candidate, result);
|
||||||
if (candidate.test !== true) replay.holdForReplay(candidate, result);
|
if (candidate.test !== true) replay.holdForReplay(candidate, result);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
toasts.showToast(candidate);
|
toasts.showToast(candidate);
|
||||||
recordDelivery(`system-refused-${result.outcome}`, candidate);
|
recordDelivery(`system-refused-${result.outcome}`, candidate);
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
const flushSettle = () => {
|
const flushSettle = () => {
|
||||||
settleTimer = 0;
|
settleTimer = 0;
|
||||||
const candidate = pending.shift();
|
const candidate = pending.shift();
|
||||||
if (candidate !== void 0 && stillWorth(candidate, live)) deliver(candidate, false);
|
if (candidate !== void 0 && stillOwed(candidate, live)) {
|
||||||
|
const delivered = deliver(candidate, false);
|
||||||
|
if (delivered === false && candidate.pendingKind !== "") pending.push(candidate);
|
||||||
|
}
|
||||||
if (pending.length > 0) settleTimer = setTimeout(() => flushSettle(), SETTLE_MS);
|
if (pending.length > 0) settleTimer = setTimeout(() => flushSettle(), SETTLE_MS);
|
||||||
};
|
};
|
||||||
const queue = (candidate) => {
|
const queue = (candidate) => {
|
||||||
@@ -894,17 +907,29 @@ var CSS = `
|
|||||||
.dsn-toast-close{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-toast-label);opacity:.8;cursor:pointer}
|
.dsn-toast-close{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-toast-label);opacity:.8;cursor:pointer}
|
||||||
.dsn-toast-close:hover{background:var(--dsw-alias-interactive-bg-hover);opacity:1}
|
.dsn-toast-close:hover{background:var(--dsw-alias-interactive-bg-hover);opacity:1}
|
||||||
.dsn-toast-close:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
.dsn-toast-close:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
||||||
.dsn-sections{display:flex;flex-direction:column;gap:24px}
|
.dsn-page{display:flex;flex-direction:column;gap:28px;max-width:560px}
|
||||||
.dsn-section{display:flex;flex-direction:column;gap:10px}
|
.dsn-lede{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
||||||
.dsn-section-title{margin:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
|
.dsn-group{display:flex;flex-direction:column;gap:2px}
|
||||||
.dsn-section-desc{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
.dsn-group-title{margin:0 0 8px;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:20px}
|
||||||
.dsn-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:.5px solid var(--dsw-alias-border-l2)}
|
.dsn-group-note{margin:8px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
||||||
.dsn-row:last-child{border-bottom:0}
|
.dsn-list{display:flex;flex-direction:column}
|
||||||
|
.dsn-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:10px 0}
|
||||||
|
.dsn-row+.dsn-row{border-top:.5px solid var(--dsw-alias-border-l2)}
|
||||||
|
.dsn-row.is-action{justify-content:flex-end;padding:12px 0 0}
|
||||||
.dsn-row-text{display:flex;flex-direction:column;gap:2px;min-width:0}
|
.dsn-row-text{display:flex;flex-direction:column;gap:2px;min-width:0}
|
||||||
.dsn-row-title{color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
|
.dsn-row-title{color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
|
||||||
.dsn-row-desc{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
.dsn-row-sub{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
||||||
.dsn-status{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
.dsn-state{display:inline-flex;align-items:center;gap:8px;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
|
||||||
.dsn-status-error{color:var(--dsw-alias-state-error-primary)}
|
.dsn-dot{flex:none;width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-state-success-primary)}
|
||||||
|
.dsn-dot.is-warn{background:var(--dsw-alias-state-warn-label)}
|
||||||
|
.dsn-dot.is-error{background:var(--dsw-alias-state-error-primary)}
|
||||||
|
.dsn-dot.is-muted{background:var(--dsw-alias-border-l3)}
|
||||||
|
.dsn-facts{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px 14px;margin:0;min-width:0}
|
||||||
|
.dsn-fact-key{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;white-space:nowrap}
|
||||||
|
.dsn-fact{display:flex;flex-direction:column;gap:2px;margin:0;min-width:0}
|
||||||
|
.dsn-fact-value{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;overflow-wrap:anywhere}
|
||||||
|
.dsn-fact-value.is-error{color:var(--dsw-alias-state-error-primary)}
|
||||||
|
.dsn-fact-title{margin:0;color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;overflow-wrap:anywhere}
|
||||||
.dsn-switch{box-sizing:border-box;position:relative;flex:0 0 auto;width:36px;height:20px;padding:2px;border:0;border-radius:999px;background:var(--dsw-alias-border-l3);cursor:pointer}
|
.dsn-switch{box-sizing:border-box;position:relative;flex:0 0 auto;width:36px;height:20px;padding:2px;border:0;border-radius:999px;background:var(--dsw-alias-border-l3);cursor:pointer}
|
||||||
.dsn-switch[aria-checked='true']{background:var(--dsw-alias-brand-primary)}
|
.dsn-switch[aria-checked='true']{background:var(--dsw-alias-brand-primary)}
|
||||||
.dsn-switch:disabled{cursor:default;opacity:.5}
|
.dsn-switch:disabled{cursor:default;opacity:.5}
|
||||||
@@ -912,10 +937,12 @@ var CSS = `
|
|||||||
.dsn-thumb{display:block;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform 120ms ease}
|
.dsn-thumb{display:block;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform 120ms ease}
|
||||||
.dsn-switch[aria-checked='false'] .dsn-thumb{background:var(--dsw-alias-switch-thumb)}
|
.dsn-switch[aria-checked='false'] .dsn-thumb{background:var(--dsw-alias-switch-thumb)}
|
||||||
.dsn-switch[aria-checked='true'] .dsn-thumb{transform:translateX(16px)}
|
.dsn-switch[aria-checked='true'] .dsn-thumb{transform:translateX(16px)}
|
||||||
.dsn-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:36px;padding:0 14px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md);cursor:pointer;font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary);background:transparent;flex:none}
|
.dsn-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:32px;padding:0 12px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md);cursor:pointer;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);background:transparent;flex:none}
|
||||||
.dsn-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
.dsn-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
||||||
.dsn-button:disabled{cursor:not-allowed;opacity:.4}
|
.dsn-button:disabled{cursor:not-allowed;opacity:.4}
|
||||||
.dsn-button:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
.dsn-button:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
||||||
|
.dsn-button.is-primary{border-color:transparent;background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary-foreground)}
|
||||||
|
.dsn-button.is-primary:hover:not(:disabled){background:var(--dsw-alias-brand-primary);opacity:.88}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// src/client/ui/ToastLayer.js
|
// src/client/ui/ToastLayer.js
|
||||||
@@ -1022,85 +1049,125 @@ function ConfigSection(props) {
|
|||||||
const supported = permission !== "unsupported";
|
const supported = permission !== "unsupported";
|
||||||
const failed = permission === "denied" || snapshot.promptError !== "";
|
const failed = permission === "denied" || snapshot.promptError !== "";
|
||||||
const statusKey = permission === "granted" ? "settings.permission.granted" : permission === "denied" ? "settings.permission.denied" : supported ? "settings.permission.default" : "settings.permission.unsupported";
|
const statusKey = permission === "granted" ? "settings.permission.granted" : permission === "denied" ? "settings.permission.denied" : supported ? "settings.permission.default" : "settings.permission.unsupported";
|
||||||
const permissionText = snapshot.promptError === "" ? `${tr(statusKey)}${permission === "denied" || !supported ? ` · ${tr(`settings.permission.hint.${props.platform}`)}` : ""}` : snapshot.promptError;
|
const promptFailed = snapshot.promptError !== "";
|
||||||
|
const permissionText = promptFailed ? snapshot.promptError : tr(statusKey);
|
||||||
|
const dotClass = permission === "granted" ? "" : failed ? " is-error" : supported ? " is-warn" : " is-muted";
|
||||||
|
const permissionNote = promptFailed ? "" : permission === "denied" || !supported ? tr(`settings.permission.hint.${props.platform}`) : permission === "granted" ? tr("config.testHint") : tr("settings.permission.hint.plain");
|
||||||
const health = snapshot.health;
|
const health = snapshot.health;
|
||||||
const healthText = health.state === "watching" ? tr("settings.health.watching") : health.state === "noHooks" ? tr("settings.health.noHooks") : health.state === "error" ? tr("settings.health.error", { message: health.message }) : tr("settings.health.starting");
|
const healthText = health.state === "watching" ? tr("settings.health.watching") : health.state === "noHooks" ? tr("settings.health.noHooks") : health.state === "error" ? tr("settings.health.error", { message: health.message }) : tr("settings.health.starting");
|
||||||
const healthFailed = health.state !== "watching" && health.state !== "starting";
|
const healthFailed = health.state !== "watching" && health.state !== "starting";
|
||||||
|
const delivery = props.copy.deliveryFacts(snapshot.lastDelivery, tr);
|
||||||
return h4(
|
return h4(
|
||||||
React5.Fragment,
|
React5.Fragment,
|
||||||
null,
|
null,
|
||||||
h4("style", null, CSS),
|
h4("style", null, CSS),
|
||||||
h4(
|
h4(
|
||||||
"div",
|
"div",
|
||||||
{ className: "dsn-sections" },
|
{ className: "dsn-page" },
|
||||||
// The page header already shows this bundle's title and description, so
|
// The page header already shows this bundle's title and description, so this
|
||||||
// this section opens with what the header cannot say: how alerts are
|
// lede says what the header cannot: how an alert travels, and that the
|
||||||
// carried, and what this form changes.
|
// channel is chosen at delivery time.
|
||||||
|
h4("p", { className: "dsn-lede" }, tr("config.intro")),
|
||||||
h4(
|
h4(
|
||||||
"section",
|
"section",
|
||||||
{ className: "dsn-section" },
|
{ className: "dsn-group" },
|
||||||
h4("p", { className: "dsn-section-desc" }, tr("config.intro"))
|
h4("h3", { className: "dsn-group-title" }, tr("config.section.kinds")),
|
||||||
),
|
|
||||||
h4(
|
|
||||||
"section",
|
|
||||||
{ className: "dsn-section" },
|
|
||||||
h4("h3", { className: "dsn-section-title" }, tr("config.section.kinds")),
|
|
||||||
KINDS.map((kind) => h4(KindRow, {
|
|
||||||
key: kind,
|
|
||||||
label: tr(`settings.kind.${kind}`),
|
|
||||||
checked: snapshot.kinds[kind] === true,
|
|
||||||
disabled: false,
|
|
||||||
onToggle: () => props.setKind(kind, snapshot.kinds[kind] !== true)
|
|
||||||
})),
|
|
||||||
h4("p", { className: "dsn-section-desc" }, tr("config.saveNote"))
|
|
||||||
),
|
|
||||||
h4(
|
|
||||||
"section",
|
|
||||||
{ className: "dsn-section" },
|
|
||||||
h4("h3", { className: "dsn-section-title" }, tr("config.section.permission")),
|
|
||||||
h4(
|
h4(
|
||||||
"div",
|
"div",
|
||||||
{ className: "dsn-row" },
|
{ className: "dsn-list" },
|
||||||
h4(
|
KINDS.map((kind) => h4(KindRow, {
|
||||||
"div",
|
key: kind,
|
||||||
{ className: "dsn-row-text" },
|
label: tr(`settings.kind.${kind}`),
|
||||||
h4("p", {
|
checked: snapshot.kinds[kind] === true,
|
||||||
className: `dsn-status${failed ? " dsn-status-error" : ""}`,
|
disabled: false,
|
||||||
role: failed ? "alert" : "status"
|
onToggle: () => props.setKind(kind, snapshot.kinds[kind] !== true)
|
||||||
}, permissionText)
|
}))
|
||||||
),
|
|
||||||
h4("button", {
|
|
||||||
type: "button",
|
|
||||||
className: "dsn-button",
|
|
||||||
disabled: !supported,
|
|
||||||
onClick: permission === "granted" ? props.testSystem : props.ask
|
|
||||||
}, tr(!supported ? "settings.action.unsupported" : permission === "granted" ? "settings.action.testSystem" : "settings.action.allow"))
|
|
||||||
),
|
),
|
||||||
h4("p", { className: "dsn-section-desc" }, tr("config.testHint"))
|
h4("p", { className: "dsn-group-note" }, tr("config.saveNote"))
|
||||||
),
|
),
|
||||||
h4(
|
h4(
|
||||||
"section",
|
"section",
|
||||||
{ className: "dsn-section" },
|
{ className: "dsn-group" },
|
||||||
h4("h3", { className: "dsn-section-title" }, tr("config.section.status")),
|
h4("h3", { className: "dsn-group-title" }, tr("config.section.permission")),
|
||||||
h4(
|
h4(
|
||||||
"div",
|
"div",
|
||||||
{ className: "dsn-row" },
|
{ className: "dsn-list" },
|
||||||
h4(
|
h4(
|
||||||
"div",
|
"div",
|
||||||
{ className: "dsn-row-text" },
|
{ className: "dsn-row" },
|
||||||
h4("p", { className: "dsn-status" }, `${tr("config.diag.window")}: ${tr(windowIsAway() ? "config.diag.away" : "config.diag.inFront")}`),
|
h4(
|
||||||
h4("p", {
|
"div",
|
||||||
className: `dsn-status${healthFailed ? " dsn-status-error" : ""}`,
|
{ className: "dsn-row-text" },
|
||||||
role: healthFailed ? "alert" : "status"
|
h4(
|
||||||
}, healthText),
|
"span",
|
||||||
h4("p", { className: "dsn-status" }, props.copy.describeDelivery(snapshot.lastDelivery, tr)),
|
{
|
||||||
h4("p", { className: "dsn-status" }, props.copy.describeReplay(props.replayCount(), tr))
|
className: "dsn-state",
|
||||||
|
role: failed ? "alert" : "status"
|
||||||
|
},
|
||||||
|
h4("span", { className: `dsn-dot${dotClass}` }),
|
||||||
|
permissionText
|
||||||
|
),
|
||||||
|
h4("p", { className: "dsn-row-sub" }, permissionNote)
|
||||||
|
),
|
||||||
|
h4("button", {
|
||||||
|
type: "button",
|
||||||
|
className: `dsn-button${supported && permission !== "granted" ? " is-primary" : ""}`,
|
||||||
|
disabled: !supported,
|
||||||
|
onClick: permission === "granted" ? props.testSystem : props.ask
|
||||||
|
}, tr(!supported ? "settings.action.unsupported" : permission === "granted" ? "settings.action.testSystem" : "settings.action.allow"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
h4(
|
||||||
|
"section",
|
||||||
|
{ className: "dsn-group" },
|
||||||
|
h4("h3", { className: "dsn-group-title" }, tr("config.section.status")),
|
||||||
|
h4(
|
||||||
|
"dl",
|
||||||
|
{ className: "dsn-facts" },
|
||||||
|
h4("dt", { className: "dsn-fact-key" }, tr("config.diag.window")),
|
||||||
|
h4(
|
||||||
|
"dd",
|
||||||
|
{ className: "dsn-fact" },
|
||||||
|
h4("p", { className: "dsn-fact-value" }, tr(windowIsAway() ? "config.diag.away" : "config.diag.inFront"))
|
||||||
),
|
),
|
||||||
h4("button", {
|
h4("dt", { className: "dsn-fact-key" }, tr("config.diag.watch")),
|
||||||
type: "button",
|
h4(
|
||||||
className: "dsn-button",
|
"dd",
|
||||||
onClick: props.testAny
|
{ className: "dsn-fact" },
|
||||||
}, tr("settings.action.testAny"))
|
h4("p", {
|
||||||
|
className: `dsn-fact-value${healthFailed ? " is-error" : ""}`,
|
||||||
|
role: healthFailed ? "alert" : "status"
|
||||||
|
}, healthText)
|
||||||
|
),
|
||||||
|
h4("dt", { className: "dsn-fact-key" }, tr("config.diag.last")),
|
||||||
|
h4(
|
||||||
|
"dd",
|
||||||
|
{ className: "dsn-fact" },
|
||||||
|
delivery === null ? h4("p", { className: "dsn-fact-value" }, tr("config.diag.none")) : [
|
||||||
|
h4("p", { key: "line", className: "dsn-fact-value" }, `${delivery.at} · ${delivery.outcome}`),
|
||||||
|
delivery.title === "" ? null : h4("p", { key: "title", className: "dsn-fact-title" }, delivery.title)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
h4("dt", { className: "dsn-fact-key" }, tr("config.diag.replay")),
|
||||||
|
h4(
|
||||||
|
"dd",
|
||||||
|
{ className: "dsn-fact" },
|
||||||
|
h4("p", { className: "dsn-fact-value" }, props.copy.replayValue(props.replayCount(), tr))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
h4(
|
||||||
|
"div",
|
||||||
|
{ className: "dsn-list" },
|
||||||
|
h4(
|
||||||
|
"div",
|
||||||
|
{ className: "dsn-row is-action" },
|
||||||
|
h4("button", {
|
||||||
|
type: "button",
|
||||||
|
className: "dsn-button",
|
||||||
|
onClick: props.testAny
|
||||||
|
}, tr("settings.action.testAny"))
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export const en = {
|
|||||||
'config.testAny': 'Test alert',
|
'config.testAny': 'Test alert',
|
||||||
'config.testSystem': 'Test system notification',
|
'config.testSystem': 'Test system notification',
|
||||||
'config.diag.window': 'Window',
|
'config.diag.window': 'Window',
|
||||||
|
'config.diag.watch': 'Session watch',
|
||||||
'config.diag.inFront': 'in front (popup is used)',
|
'config.diag.inFront': 'in front (popup is used)',
|
||||||
'config.diag.away': 'not in front (system notification is used)',
|
'config.diag.away': 'not in front (system notification is used)',
|
||||||
'config.diag.last': 'Last delivery',
|
'config.diag.last': 'Last delivery',
|
||||||
@@ -62,6 +63,7 @@ export const en = {
|
|||||||
'config.diag.replayDelivery': 'in-app popup (replayed when you came back)',
|
'config.diag.replayDelivery': 'in-app popup (replayed when you came back)',
|
||||||
'config.diag.popup': 'in-app popup',
|
'config.diag.popup': 'in-app popup',
|
||||||
'config.diag.refused': 'system channel refused, popup was used instead',
|
'config.diag.refused': 'system channel refused, popup was used instead',
|
||||||
'config.diag.replay': 'waiting for you to come back: {count}',
|
'config.diag.replay': 'Replay',
|
||||||
'config.diag.replayNone': 'nothing waiting to be replayed',
|
'config.diag.replayValue': '{count} waiting',
|
||||||
|
'config.diag.replayNone': 'None',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export const zh = {
|
|||||||
'config.testAny': '测试提醒',
|
'config.testAny': '测试提醒',
|
||||||
'config.testSystem': '测试系统通知',
|
'config.testSystem': '测试系统通知',
|
||||||
'config.diag.window': '窗口状态',
|
'config.diag.window': '窗口状态',
|
||||||
|
'config.diag.watch': '会话监听',
|
||||||
'config.diag.inFront': '在前台(会走轻弹窗)',
|
'config.diag.inFront': '在前台(会走轻弹窗)',
|
||||||
'config.diag.away': '不在前台(会走系统通知)',
|
'config.diag.away': '不在前台(会走系统通知)',
|
||||||
'config.diag.last': '最近一次投递',
|
'config.diag.last': '最近一次投递',
|
||||||
@@ -62,6 +63,7 @@ export const zh = {
|
|||||||
'config.diag.replayDelivery': '应用内轻弹窗(回到窗口时补发)',
|
'config.diag.replayDelivery': '应用内轻弹窗(回到窗口时补发)',
|
||||||
'config.diag.popup': '应用内轻弹窗',
|
'config.diag.popup': '应用内轻弹窗',
|
||||||
'config.diag.refused': '系统通知被拒绝,改用了轻弹窗',
|
'config.diag.refused': '系统通知被拒绝,改用了轻弹窗',
|
||||||
'config.diag.replay': '等你回到窗口时补发:{count} 条',
|
'config.diag.replay': '待补发提醒',
|
||||||
'config.diag.replayNone': '没有待补发的提醒',
|
'config.diag.replayValue': '{count} 条',
|
||||||
|
'config.diag.replayNone': '没有',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,12 @@
|
|||||||
* row's description) and `page` (the whole form). Everything visible here
|
* row's description) and `page` (the whole form). Everything visible here
|
||||||
* belongs to this package, including the platform-specific permission hint.
|
* belongs to this package, including the platform-specific permission hint.
|
||||||
*
|
*
|
||||||
|
* The page is laid out as three groups — what to notify about, the system
|
||||||
|
* channel's own state, and what the plugin is doing right now. Each group keeps
|
||||||
|
* its controls in one place: a list whose rows carry the subject on the left and
|
||||||
|
* the control on the right, and a fact list for the readings, so the status
|
||||||
|
* block is scannable instead of being four sentences in one cell.
|
||||||
|
*
|
||||||
* @module @dsh-plugin/session-notify/client/ui/ConfigSection
|
* @module @dsh-plugin/session-notify/client/ui/ConfigSection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -22,6 +28,7 @@ export function ConfigSection(props) {
|
|||||||
const snapshot = useOwnStore(props.store)
|
const snapshot = useOwnStore(props.store)
|
||||||
const tr = seatTranslator(props, props.fallbackT)
|
const tr = seatTranslator(props, props.fallbackT)
|
||||||
if (props.view === 'summary') return h(React.Fragment, null, tr('settings.description'))
|
if (props.view === 'summary') return h(React.Fragment, null, tr('settings.description'))
|
||||||
|
|
||||||
const permission = snapshot.permission
|
const permission = snapshot.permission
|
||||||
const supported = permission !== 'unsupported'
|
const supported = permission !== 'unsupported'
|
||||||
const failed = permission === 'denied' || snapshot.promptError !== ''
|
const failed = permission === 'denied' || snapshot.promptError !== ''
|
||||||
@@ -32,9 +39,19 @@ export function ConfigSection(props) {
|
|||||||
: supported
|
: supported
|
||||||
? 'settings.permission.default'
|
? 'settings.permission.default'
|
||||||
: 'settings.permission.unsupported'
|
: 'settings.permission.unsupported'
|
||||||
const permissionText = snapshot.promptError === ''
|
const promptFailed = snapshot.promptError !== ''
|
||||||
? `${tr(statusKey)}${permission === 'denied' || !supported ? ` · ${tr(`settings.permission.hint.${props.platform}`)}` : ''}`
|
const permissionText = promptFailed ? snapshot.promptError : tr(statusKey)
|
||||||
: snapshot.promptError
|
const dotClass = permission === 'granted'
|
||||||
|
? ''
|
||||||
|
: failed ? ' is-error' : supported ? ' is-warn' : ' is-muted'
|
||||||
|
const permissionNote = promptFailed
|
||||||
|
? ''
|
||||||
|
: permission === 'denied' || !supported
|
||||||
|
? tr(`settings.permission.hint.${props.platform}`)
|
||||||
|
: permission === 'granted'
|
||||||
|
? tr('config.testHint')
|
||||||
|
: tr('settings.permission.hint.plain')
|
||||||
|
|
||||||
const health = snapshot.health
|
const health = snapshot.health
|
||||||
const healthText = health.state === 'watching'
|
const healthText = health.state === 'watching'
|
||||||
? tr('settings.health.watching')
|
? tr('settings.health.watching')
|
||||||
@@ -44,55 +61,80 @@ export function ConfigSection(props) {
|
|||||||
? tr('settings.health.error', { message: health.message })
|
? tr('settings.health.error', { message: health.message })
|
||||||
: tr('settings.health.starting')
|
: tr('settings.health.starting')
|
||||||
const healthFailed = health.state !== 'watching' && health.state !== 'starting'
|
const healthFailed = health.state !== 'watching' && health.state !== 'starting'
|
||||||
|
|
||||||
|
const delivery = props.copy.deliveryFacts(snapshot.lastDelivery, tr)
|
||||||
|
|
||||||
return h(React.Fragment, null,
|
return h(React.Fragment, null,
|
||||||
h('style', null, CSS),
|
h('style', null, CSS),
|
||||||
h('div', { className: 'dsn-sections' },
|
h('div', { className: 'dsn-page' },
|
||||||
// The page header already shows this bundle's title and description, so
|
// The page header already shows this bundle's title and description, so this
|
||||||
// this section opens with what the header cannot say: how alerts are
|
// lede says what the header cannot: how an alert travels, and that the
|
||||||
// carried, and what this form changes.
|
// channel is chosen at delivery time.
|
||||||
h('section', { className: 'dsn-section' },
|
h('p', { className: 'dsn-lede' }, tr('config.intro')),
|
||||||
h('p', { className: 'dsn-section-desc' }, tr('config.intro'))),
|
|
||||||
h('section', { className: 'dsn-section' },
|
h('section', { className: 'dsn-group' },
|
||||||
h('h3', { className: 'dsn-section-title' }, tr('config.section.kinds')),
|
h('h3', { className: 'dsn-group-title' }, tr('config.section.kinds')),
|
||||||
KINDS.map((kind) => h(KindRow, {
|
h('div', { className: 'dsn-list' },
|
||||||
key: kind,
|
KINDS.map((kind) => h(KindRow, {
|
||||||
label: tr(`settings.kind.${kind}`),
|
key: kind,
|
||||||
checked: snapshot.kinds[kind] === true,
|
label: tr(`settings.kind.${kind}`),
|
||||||
disabled: false,
|
checked: snapshot.kinds[kind] === true,
|
||||||
onToggle: () => props.setKind(kind, snapshot.kinds[kind] !== true),
|
disabled: false,
|
||||||
})),
|
onToggle: () => props.setKind(kind, snapshot.kinds[kind] !== true),
|
||||||
h('p', { className: 'dsn-section-desc' }, tr('config.saveNote'))),
|
}))),
|
||||||
h('section', { className: 'dsn-section' },
|
h('p', { className: 'dsn-group-note' }, tr('config.saveNote'))),
|
||||||
h('h3', { className: 'dsn-section-title' }, tr('config.section.permission')),
|
|
||||||
h('div', { className: 'dsn-row' },
|
h('section', { className: 'dsn-group' },
|
||||||
h('div', { className: 'dsn-row-text' },
|
h('h3', { className: 'dsn-group-title' }, tr('config.section.permission')),
|
||||||
|
h('div', { className: 'dsn-list' },
|
||||||
|
h('div', { className: 'dsn-row' },
|
||||||
|
h('div', { className: 'dsn-row-text' },
|
||||||
|
h('span', {
|
||||||
|
className: 'dsn-state',
|
||||||
|
role: failed ? 'alert' : 'status',
|
||||||
|
},
|
||||||
|
h('span', { className: `dsn-dot${dotClass}` }),
|
||||||
|
permissionText),
|
||||||
|
h('p', { className: 'dsn-row-sub' }, permissionNote)),
|
||||||
|
h('button', {
|
||||||
|
type: 'button',
|
||||||
|
className: `dsn-button${supported && permission !== 'granted' ? ' is-primary' : ''}`,
|
||||||
|
disabled: !supported,
|
||||||
|
onClick: permission === 'granted' ? props.testSystem : props.ask,
|
||||||
|
}, tr(!supported
|
||||||
|
? 'settings.action.unsupported'
|
||||||
|
: permission === 'granted' ? 'settings.action.testSystem' : 'settings.action.allow'))))),
|
||||||
|
|
||||||
|
h('section', { className: 'dsn-group' },
|
||||||
|
h('h3', { className: 'dsn-group-title' }, tr('config.section.status')),
|
||||||
|
h('dl', { className: 'dsn-facts' },
|
||||||
|
h('dt', { className: 'dsn-fact-key' }, tr('config.diag.window')),
|
||||||
|
h('dd', { className: 'dsn-fact' },
|
||||||
|
h('p', { className: 'dsn-fact-value' }, tr(windowIsAway() ? 'config.diag.away' : 'config.diag.inFront'))),
|
||||||
|
h('dt', { className: 'dsn-fact-key' }, tr('config.diag.watch')),
|
||||||
|
h('dd', { className: 'dsn-fact' },
|
||||||
h('p', {
|
h('p', {
|
||||||
className: `dsn-status${failed ? ' dsn-status-error' : ''}`,
|
className: `dsn-fact-value${healthFailed ? ' is-error' : ''}`,
|
||||||
role: failed ? 'alert' : 'status',
|
|
||||||
}, permissionText)),
|
|
||||||
h('button', {
|
|
||||||
type: 'button',
|
|
||||||
className: 'dsn-button',
|
|
||||||
disabled: !supported,
|
|
||||||
onClick: permission === 'granted' ? props.testSystem : props.ask,
|
|
||||||
}, tr(!supported
|
|
||||||
? 'settings.action.unsupported'
|
|
||||||
: permission === 'granted' ? 'settings.action.testSystem' : 'settings.action.allow'))),
|
|
||||||
h('p', { className: 'dsn-section-desc' }, tr('config.testHint'))),
|
|
||||||
h('section', { className: 'dsn-section' },
|
|
||||||
h('h3', { className: 'dsn-section-title' }, tr('config.section.status')),
|
|
||||||
h('div', { className: 'dsn-row' },
|
|
||||||
h('div', { className: 'dsn-row-text' },
|
|
||||||
h('p', { className: 'dsn-status' }, `${tr('config.diag.window')}: ${tr(windowIsAway() ? 'config.diag.away' : 'config.diag.inFront')}`),
|
|
||||||
h('p', {
|
|
||||||
className: `dsn-status${healthFailed ? ' dsn-status-error' : ''}`,
|
|
||||||
role: healthFailed ? 'alert' : 'status',
|
role: healthFailed ? 'alert' : 'status',
|
||||||
}, healthText),
|
}, healthText)),
|
||||||
h('p', { className: 'dsn-status' }, props.copy.describeDelivery(snapshot.lastDelivery, tr)),
|
h('dt', { className: 'dsn-fact-key' }, tr('config.diag.last')),
|
||||||
h('p', { className: 'dsn-status' }, props.copy.describeReplay(props.replayCount(), tr))),
|
h('dd', { className: 'dsn-fact' },
|
||||||
h('button', {
|
delivery === null
|
||||||
type: 'button',
|
? h('p', { className: 'dsn-fact-value' }, tr('config.diag.none'))
|
||||||
className: 'dsn-button',
|
: [
|
||||||
onClick: props.testAny,
|
h('p', { key: 'line', className: 'dsn-fact-value' }, `${delivery.at} · ${delivery.outcome}`),
|
||||||
}, tr('settings.action.testAny'))))))
|
delivery.title === ''
|
||||||
|
? null
|
||||||
|
: h('p', { key: 'title', className: 'dsn-fact-title' }, delivery.title),
|
||||||
|
]),
|
||||||
|
h('dt', { className: 'dsn-fact-key' }, tr('config.diag.replay')),
|
||||||
|
h('dd', { className: 'dsn-fact' },
|
||||||
|
h('p', { className: 'dsn-fact-value' }, props.copy.replayValue(props.replayCount(), tr)))),
|
||||||
|
h('div', { className: 'dsn-list' },
|
||||||
|
h('div', { className: 'dsn-row is-action' },
|
||||||
|
h('button', {
|
||||||
|
type: 'button',
|
||||||
|
className: 'dsn-button',
|
||||||
|
onClick: props.testAny,
|
||||||
|
}, tr('settings.action.testAny')))))))
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-15
@@ -3,11 +3,15 @@
|
|||||||
*
|
*
|
||||||
* The popup copies the shipped toast surface (a fixed entry in the frame-wide
|
* The popup copies the shipped toast surface (a fixed entry in the frame-wide
|
||||||
* overlay, the `--dsw-alias-toast-bg` / `--dsw-alias-toast-label` pair,
|
* overlay, the `--dsw-alias-toast-bg` / `--dsw-alias-toast-label` pair,
|
||||||
* `--dsw-radius-lg`, `--dsw-shadow-lv3`) and adds a title line above the text;
|
* `--dsw-radius-lg`, `--dsw-shadow-lv3`) and adds a title line above the text.
|
||||||
* the switch copies `Switch.module.css` behavior and proportion; the
|
*
|
||||||
* configuration sections follow the Plugin Manager page's own section rhythm
|
* The settings page is a reading order, not a form dump: a lede that says how
|
||||||
* (a column, 32px apart). Class names are renamed under `dsn-`; only the popup
|
* alerts travel, then one group per concern. Inside a group a title introduces a
|
||||||
* glyphs carry their own artwork colors.
|
* list whose rows put the subject on the left and the single control on the
|
||||||
|
* right, with hairlines only between rows; the status group is a two-column fact
|
||||||
|
* list, because four sentences stacked in one cell are not scannable. Class
|
||||||
|
* names are renamed under `dsn-`; only the popup glyphs carry their own artwork
|
||||||
|
* colors.
|
||||||
*
|
*
|
||||||
* A plugin must not import a Harness Client package, so the declarations are
|
* A plugin must not import a Harness Client package, so the declarations are
|
||||||
* this package's own text and reference theme tokens only.
|
* this package's own text and reference theme tokens only.
|
||||||
@@ -34,17 +38,29 @@ export const CSS = `
|
|||||||
.dsn-toast-close{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-toast-label);opacity:.8;cursor:pointer}
|
.dsn-toast-close{flex:none;display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:0;border-radius:var(--dsw-radius-sm);background:transparent;color:var(--dsw-alias-toast-label);opacity:.8;cursor:pointer}
|
||||||
.dsn-toast-close:hover{background:var(--dsw-alias-interactive-bg-hover);opacity:1}
|
.dsn-toast-close:hover{background:var(--dsw-alias-interactive-bg-hover);opacity:1}
|
||||||
.dsn-toast-close:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
.dsn-toast-close:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
||||||
.dsn-sections{display:flex;flex-direction:column;gap:24px}
|
.dsn-page{display:flex;flex-direction:column;gap:28px;max-width:560px}
|
||||||
.dsn-section{display:flex;flex-direction:column;gap:10px}
|
.dsn-lede{margin:0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
||||||
.dsn-section-title{margin:0;color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}
|
.dsn-group{display:flex;flex-direction:column;gap:2px}
|
||||||
.dsn-section-desc{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
.dsn-group-title{margin:0 0 8px;color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;line-height:20px}
|
||||||
.dsn-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:.5px solid var(--dsw-alias-border-l2)}
|
.dsn-group-note{margin:8px 0 0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
||||||
.dsn-row:last-child{border-bottom:0}
|
.dsn-list{display:flex;flex-direction:column}
|
||||||
|
.dsn-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:10px 0}
|
||||||
|
.dsn-row+.dsn-row{border-top:.5px solid var(--dsw-alias-border-l2)}
|
||||||
|
.dsn-row.is-action{justify-content:flex-end;padding:12px 0 0}
|
||||||
.dsn-row-text{display:flex;flex-direction:column;gap:2px;min-width:0}
|
.dsn-row-text{display:flex;flex-direction:column;gap:2px;min-width:0}
|
||||||
.dsn-row-title{color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
|
.dsn-row-title{color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
|
||||||
.dsn-row-desc{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
.dsn-row-sub{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
||||||
.dsn-status{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
|
.dsn-state{display:inline-flex;align-items:center;gap:8px;color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}
|
||||||
.dsn-status-error{color:var(--dsw-alias-state-error-primary)}
|
.dsn-dot{flex:none;width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-state-success-primary)}
|
||||||
|
.dsn-dot.is-warn{background:var(--dsw-alias-state-warn-label)}
|
||||||
|
.dsn-dot.is-error{background:var(--dsw-alias-state-error-primary)}
|
||||||
|
.dsn-dot.is-muted{background:var(--dsw-alias-border-l3)}
|
||||||
|
.dsn-facts{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px 14px;margin:0;min-width:0}
|
||||||
|
.dsn-fact-key{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;white-space:nowrap}
|
||||||
|
.dsn-fact{display:flex;flex-direction:column;gap:2px;margin:0;min-width:0}
|
||||||
|
.dsn-fact-value{margin:0;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;overflow-wrap:anywhere}
|
||||||
|
.dsn-fact-value.is-error{color:var(--dsw-alias-state-error-primary)}
|
||||||
|
.dsn-fact-title{margin:0;color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;overflow-wrap:anywhere}
|
||||||
.dsn-switch{box-sizing:border-box;position:relative;flex:0 0 auto;width:36px;height:20px;padding:2px;border:0;border-radius:999px;background:var(--dsw-alias-border-l3);cursor:pointer}
|
.dsn-switch{box-sizing:border-box;position:relative;flex:0 0 auto;width:36px;height:20px;padding:2px;border:0;border-radius:999px;background:var(--dsw-alias-border-l3);cursor:pointer}
|
||||||
.dsn-switch[aria-checked='true']{background:var(--dsw-alias-brand-primary)}
|
.dsn-switch[aria-checked='true']{background:var(--dsw-alias-brand-primary)}
|
||||||
.dsn-switch:disabled{cursor:default;opacity:.5}
|
.dsn-switch:disabled{cursor:default;opacity:.5}
|
||||||
@@ -52,8 +68,10 @@ export const CSS = `
|
|||||||
.dsn-thumb{display:block;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform 120ms ease}
|
.dsn-thumb{display:block;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary-foreground);transition:transform 120ms ease}
|
||||||
.dsn-switch[aria-checked='false'] .dsn-thumb{background:var(--dsw-alias-switch-thumb)}
|
.dsn-switch[aria-checked='false'] .dsn-thumb{background:var(--dsw-alias-switch-thumb)}
|
||||||
.dsn-switch[aria-checked='true'] .dsn-thumb{transform:translateX(16px)}
|
.dsn-switch[aria-checked='true'] .dsn-thumb{transform:translateX(16px)}
|
||||||
.dsn-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:36px;padding:0 14px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md);cursor:pointer;font-size:14px;line-height:22px;color:var(--dsw-alias-label-primary);background:transparent;flex:none}
|
.dsn-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:32px;padding:0 12px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md);cursor:pointer;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);background:transparent;flex:none}
|
||||||
.dsn-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
.dsn-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
|
||||||
.dsn-button:disabled{cursor:not-allowed;opacity:.4}
|
.dsn-button:disabled{cursor:not-allowed;opacity:.4}
|
||||||
.dsn-button:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
.dsn-button:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:2px}
|
||||||
|
.dsn-button.is-primary{border-color:transparent;background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-label-primary-foreground)}
|
||||||
|
.dsn-button.is-primary:hover:not(:disabled){background:var(--dsw-alias-brand-primary);opacity:.88}
|
||||||
`
|
`
|
||||||
|
|||||||
Reference in New Issue
Block a user