@huaqiu/dsh-auth 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +767 -34
- package/lib/client.js.map +1 -1
- package/package.json +2 -2
- package/src/client/client.ts +63 -0
- package/src/client/i18n.ts +37 -0
- package/src/client/index.tsx +15 -0
- package/src/client/lib.ts +111 -0
- package/src/client/ui/sidebar-action.tsx +477 -20
- package/src/client/ui/subscription-dialog.ts +183 -0
package/lib/client.js
CHANGED
|
@@ -107,6 +107,89 @@ window.__ModuleLoader__.load({
|
|
|
107
107
|
/**「Go to profile」destination: the eda.cn account page. */
|
|
108
108
|
const PROFILE_URL = "https://www.eda.cn/account/profile";
|
|
109
109
|
/**
|
|
110
|
+
* Subscription landing page — the same page `hq-eda-ai` serves at
|
|
111
|
+
* `gen.eda.cn/subscription` (token taken from the URL query; see its
|
|
112
|
+
* `UserInfoContext`). The preferred window size for BOTH surfaces (KiCad
|
|
113
|
+
* native dialog via DialogService.OpenUrl and the standalone iframe dialog)
|
|
114
|
+
* is 1400×740.
|
|
115
|
+
*/
|
|
116
|
+
const SUBSCRIPTION_ORIGIN = "https://gen.eda.cn";
|
|
117
|
+
const SUBSCRIPTION_PATH = "/subscription";
|
|
118
|
+
const SUBSCRIPTION_PREFERRED_SIZE = {
|
|
119
|
+
width: 1400,
|
|
120
|
+
height: 740
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Build the subscription landing URL. Mirrors `buildProfileUrl`: the token is
|
|
124
|
+
* always attached — `gen.eda.cn/subscription` consumes it to establish the
|
|
125
|
+
* session and hides it itself. `encodeURIComponent` is required (tokens are
|
|
126
|
+
* base64-ish and may contain `+`, `/` or `=`).
|
|
127
|
+
*/
|
|
128
|
+
function buildSubscriptionUrl(options) {
|
|
129
|
+
return `${SUBSCRIPTION_ORIGIN}${SUBSCRIPTION_PATH}?token=${encodeURIComponent(options.token)}`;
|
|
130
|
+
}
|
|
131
|
+
const SUB_API_PREFIX = "/sub-api";
|
|
132
|
+
const SUB_API_SUCCESS_CODES = [200, 2e5];
|
|
133
|
+
function unwrapSubApiPayload(payload) {
|
|
134
|
+
if (payload && typeof payload === "object" && "code" in payload) {
|
|
135
|
+
const p = payload;
|
|
136
|
+
if (!SUB_API_SUCCESS_CODES.includes(p.code)) return null;
|
|
137
|
+
if (p.result !== void 0) return p.result;
|
|
138
|
+
if (p.data !== void 0) return p.data;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
return payload;
|
|
142
|
+
}
|
|
143
|
+
function quotaOf(subscription) {
|
|
144
|
+
if (!subscription || typeof subscription === "object" && Object.keys(subscription).length === 0) return {
|
|
145
|
+
hasSubscription: false,
|
|
146
|
+
packageName: "",
|
|
147
|
+
currentQuota: 0,
|
|
148
|
+
totalQuota: 0
|
|
149
|
+
};
|
|
150
|
+
const currentQuota = Number(subscription.currentQuota) || 0;
|
|
151
|
+
return {
|
|
152
|
+
hasSubscription: currentQuota > 0,
|
|
153
|
+
packageName: typeof subscription.packageName === "string" ? subscription.packageName : "",
|
|
154
|
+
currentQuota,
|
|
155
|
+
totalQuota: Number(subscription.totalQuota) || 0
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Fetch the active subscription quota DIRECTLY from eda.cn
|
|
160
|
+
* (`GET {baseUrl}/sub-api/subscriptions/active?userId&productCode` with
|
|
161
|
+
* `x-token` + `X-User-Id` headers) — the same API `hq-eda-ai`'s
|
|
162
|
+
* `getActiveSubscription` and `hq-edge`'s ERC quota proxy call, so displaying
|
|
163
|
+
* the available quota never depends on an hq-edge hop.
|
|
164
|
+
*
|
|
165
|
+
* Returns null when the fetch/unwrap fails (callers decide fail-open vs
|
|
166
|
+
* fail-closed); a well-formed response always yields a `SubscriptionQuota`
|
|
167
|
+
* (possibly with `hasSubscription: false`).
|
|
168
|
+
*/
|
|
169
|
+
async function fetchSubscriptionQuota(options) {
|
|
170
|
+
const { userId, token, productCode, baseUrl = "https://www.eda.cn" } = options;
|
|
171
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
172
|
+
const url = `${baseUrl}${SUB_API_PREFIX}/subscriptions/active?${new URLSearchParams({
|
|
173
|
+
userId: String(userId),
|
|
174
|
+
productCode
|
|
175
|
+
}).toString()}`;
|
|
176
|
+
try {
|
|
177
|
+
const res = await fetchImpl(url, {
|
|
178
|
+
method: "GET",
|
|
179
|
+
headers: {
|
|
180
|
+
Accept: "application/json, text/plain, */*",
|
|
181
|
+
"Content-Type": "application/json",
|
|
182
|
+
"x-token": token,
|
|
183
|
+
"X-User-Id": String(userId)
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
if (!res || !res.ok) return null;
|
|
187
|
+
return quotaOf(unwrapSubApiPayload(await res.json()));
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
110
193
|
* Build the「Go to profile」URL: the eda.cn account page WITH the access token
|
|
111
194
|
* in the query, mirroring `hq-eda-ai`'s `UserMenu`
|
|
112
195
|
* (`/account/profile?token=…&phone=…`).
|
|
@@ -484,6 +567,22 @@ window.__ModuleLoader__.load({
|
|
|
484
567
|
"sidebar.account": "华秋EDA AI · 已登录",
|
|
485
568
|
"menu.profile": "个人中心",
|
|
486
569
|
"menu.logout": "退出登录",
|
|
570
|
+
"subscription.title": "订阅",
|
|
571
|
+
"subscription.dialogTitle": "华秋 EDA 订阅",
|
|
572
|
+
"subscription.gen": "系统设计(GEN)",
|
|
573
|
+
"subscription.erc": "电路检查(ERC)",
|
|
574
|
+
"subscription.package": "套餐:{name}",
|
|
575
|
+
"subscription.quota": "可用次数:{count}",
|
|
576
|
+
"subscription.quotaCount": "{used} / {total}次",
|
|
577
|
+
"subscription.noQuota": "无可用次数",
|
|
578
|
+
"subscription.unsubscribed": "未订阅",
|
|
579
|
+
"subscription.subscribe": "订阅 / 升级",
|
|
580
|
+
"subscription.subscribing": "正在打开订阅…",
|
|
581
|
+
"subscription.desc": "当前账号没有可用的 GEN 生成次数,请订阅或升级套餐后重试。",
|
|
582
|
+
"subscription.hostUnavailable": "无法打开订阅页面(宿主未提供对话框服务)。",
|
|
583
|
+
"subscription.quotaHeader": "套餐与使用额度",
|
|
584
|
+
"subscription.upgrade": "订阅 / 升级套餐",
|
|
585
|
+
"subscription.online": "在线",
|
|
487
586
|
"card.title": "华秋 EDA AI(eda.cn)登录",
|
|
488
587
|
"card.desc": "工具「{tool}」需要登录华秋 EDA AI 账号才能继续。请在下方的登录框完成登录(或点击左侧「华秋EDA AI登录」按钮);登录完成后,回复助手「已登录,请重试」,助手会自动重新调用该工具。",
|
|
489
588
|
"card.loggedIn": "✓ 已登录{nickname} —— 现在可以回复助手「已登录,请重试」,助手会重新调用工具。",
|
|
@@ -502,6 +601,22 @@ window.__ModuleLoader__.load({
|
|
|
502
601
|
"sidebar.account": "Huaqiu EDA AI · signed in",
|
|
503
602
|
"menu.profile": "Go to profile",
|
|
504
603
|
"menu.logout": "Log out",
|
|
604
|
+
"subscription.title": "Subscription",
|
|
605
|
+
"subscription.dialogTitle": "Huaqiu EDA subscription",
|
|
606
|
+
"subscription.gen": "System design (GEN)",
|
|
607
|
+
"subscription.erc": "Circuit check (ERC)",
|
|
608
|
+
"subscription.package": "Plan: {name}",
|
|
609
|
+
"subscription.quota": "Available: {count}",
|
|
610
|
+
"subscription.quotaCount": "{used} / {total}",
|
|
611
|
+
"subscription.noQuota": "No quota left",
|
|
612
|
+
"subscription.unsubscribed": "Not subscribed",
|
|
613
|
+
"subscription.subscribe": "Subscribe / Upgrade",
|
|
614
|
+
"subscription.subscribing": "Opening subscription…",
|
|
615
|
+
"subscription.desc": "This account has no GEN generation quota left. Subscribe or upgrade to continue.",
|
|
616
|
+
"subscription.hostUnavailable": "Cannot open the subscription page (the host provides no dialog service).",
|
|
617
|
+
"subscription.quotaHeader": "Plan & usage quota",
|
|
618
|
+
"subscription.upgrade": "Subscribe / Upgrade plan",
|
|
619
|
+
"subscription.online": "Online",
|
|
505
620
|
"card.title": "Huaqiu EDA AI (eda.cn) login",
|
|
506
621
|
"card.desc": "Tool \"{tool}\" needs a Huaqiu EDA AI account. Complete the login below (or use the Huaqiu EDA AI button in the sidebar), then reply \"I have logged in, please retry\" so the assistant can retry the tool.",
|
|
507
622
|
"card.loggedIn": "✓ Logged in{nickname} — reply \"I have logged in, please retry\" and the assistant will retry the tool.",
|
|
@@ -583,8 +698,8 @@ window.__ModuleLoader__.load({
|
|
|
583
698
|
*/
|
|
584
699
|
const IFRAME_HEIGHT = 440;
|
|
585
700
|
const CARD_MAX_WIDTH = 768;
|
|
586
|
-
let container = null;
|
|
587
|
-
let unsubscribe$
|
|
701
|
+
let container$1 = null;
|
|
702
|
+
let unsubscribe$2 = null;
|
|
588
703
|
let onCloseRequested = null;
|
|
589
704
|
/**
|
|
590
705
|
* Open the login dialog. Idempotent: a second call while open is a no-op
|
|
@@ -594,7 +709,7 @@ window.__ModuleLoader__.load({
|
|
|
594
709
|
* unblock its own `isOpen` state.
|
|
595
710
|
*/
|
|
596
711
|
function openLoginDialog(options = {}, onClose) {
|
|
597
|
-
if (container) return;
|
|
712
|
+
if (container$1) return;
|
|
598
713
|
syncUiEnv();
|
|
599
714
|
const locale = options.lang ?? getCurrentLocale();
|
|
600
715
|
const root = document.createElement("div");
|
|
@@ -672,6 +787,163 @@ window.__ModuleLoader__.load({
|
|
|
672
787
|
card.appendChild(iframe);
|
|
673
788
|
root.appendChild(card);
|
|
674
789
|
document.body.appendChild(root);
|
|
790
|
+
root.addEventListener("mousedown", backdropMouseDown$1);
|
|
791
|
+
card.addEventListener("mousedown", stopPropagation$1);
|
|
792
|
+
document.addEventListener("keydown", onKeyDown$1);
|
|
793
|
+
unsubscribe$2 = subscribeUiEnv(() => {
|
|
794
|
+
if (!container$1) return;
|
|
795
|
+
applyCardColors();
|
|
796
|
+
iframe.style.background = getCurrentSurfaceColor();
|
|
797
|
+
closeButton.title = translate(getCurrentLocale(), "dialog.close");
|
|
798
|
+
closeButton.setAttribute("aria-label", closeButton.title);
|
|
799
|
+
});
|
|
800
|
+
container$1 = root;
|
|
801
|
+
onCloseRequested = onClose ?? null;
|
|
802
|
+
}
|
|
803
|
+
/** Programmatic close (used by the auth client after a successful login). */
|
|
804
|
+
function closeLoginDialog() {
|
|
805
|
+
if (!container$1) return;
|
|
806
|
+
container$1.remove();
|
|
807
|
+
container$1 = null;
|
|
808
|
+
document.removeEventListener("keydown", onKeyDown$1);
|
|
809
|
+
unsubscribe$2?.();
|
|
810
|
+
unsubscribe$2 = null;
|
|
811
|
+
const cb = onCloseRequested;
|
|
812
|
+
onCloseRequested = null;
|
|
813
|
+
cb?.();
|
|
814
|
+
}
|
|
815
|
+
/** True while the dialog is mounted. */
|
|
816
|
+
function isLoginDialogOpen() {
|
|
817
|
+
return container$1 !== null;
|
|
818
|
+
}
|
|
819
|
+
function backdropMouseDown$1(event) {
|
|
820
|
+
if (event.target === container$1) closeLoginDialog();
|
|
821
|
+
}
|
|
822
|
+
function stopPropagation$1(event) {
|
|
823
|
+
event.stopPropagation();
|
|
824
|
+
}
|
|
825
|
+
function onKeyDown$1(event) {
|
|
826
|
+
if (event.key === "Escape") {
|
|
827
|
+
event.stopPropagation();
|
|
828
|
+
closeLoginDialog();
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
//#endregion
|
|
832
|
+
//#region src/client/ui/subscription-dialog.ts
|
|
833
|
+
/**
|
|
834
|
+
* The subscription dialog — a real modal (backdrop + centered card + iframe)
|
|
835
|
+
* for `gen.eda.cn/subscription`, mirroring the login dialog's DOM pattern
|
|
836
|
+
* (`ui/login-dialog.ts`): a fixed full-viewport semi-transparent backdrop +
|
|
837
|
+
* centered host-surface card + the subscription iframe.
|
|
838
|
+
*
|
|
839
|
+
* WHY A CARD AND NOT A FULL-VIEWPORT IFRAME
|
|
840
|
+
*
|
|
841
|
+
* Same reason as the login dialog: the iframe element sits on top of the
|
|
842
|
+
* host page, and Blink paints a WHITE base canvas behind any transparent
|
|
843
|
+
* region of the embedded doc. Putting the iframe inside a host-painted card
|
|
844
|
+
* masks that white in both light and dark themes (the card surface shows
|
|
845
|
+
* through wherever the embedded doc is transparent).
|
|
846
|
+
*
|
|
847
|
+
* SIZING: the subscription page prefers 1400×740 (`SUBSCRIPTION_PREFERRED_SIZE`,
|
|
848
|
+
* shared with the host-side native dialog via DialogService.OpenUrl). The card
|
|
849
|
+
* clamps to the viewport (`min(96vw, width)` × `min(92vh, height)`) so it can
|
|
850
|
+
* never overflow a small host window; the iframe fills the card.
|
|
851
|
+
*
|
|
852
|
+
* Close paths: backdrop click, the × button, and Escape. Unlike the login
|
|
853
|
+
* embed there is no postMessage close contract to honor — the page is
|
|
854
|
+
* `gen.eda.cn/subscription`, not `auth.eda.cn`.
|
|
855
|
+
*/
|
|
856
|
+
/** Aria / data attribute names — stable so tests and CSS can target them. */
|
|
857
|
+
const SUB_DIALOG_OVERLAY_ATTR = "data-hq-sub-dialog";
|
|
858
|
+
const SUB_DIALOG_CARD_ATTR = "data-hq-sub-dialog-card";
|
|
859
|
+
const SUB_DIALOG_IFRAME_ATTR = "data-hq-sub-dialog-iframe";
|
|
860
|
+
const SUB_DIALOG_CLOSE_ATTR = "data-hq-sub-dialog-close";
|
|
861
|
+
let container = null;
|
|
862
|
+
let unsubscribe$1 = null;
|
|
863
|
+
/**
|
|
864
|
+
* Open the subscription dialog. Idempotent: a second call while open is a
|
|
865
|
+
* no-op. `size` overrides the default 1400×740 preferred size (clamped to the
|
|
866
|
+
* viewport).
|
|
867
|
+
*/
|
|
868
|
+
function openSubscriptionDialog(url, options = {}, onClose) {
|
|
869
|
+
if (container) return;
|
|
870
|
+
syncUiEnv();
|
|
871
|
+
const locale = getCurrentLocale();
|
|
872
|
+
const width = Math.max(320, options.size?.width ?? SUBSCRIPTION_PREFERRED_SIZE.width);
|
|
873
|
+
const height = Math.max(240, options.size?.height ?? SUBSCRIPTION_PREFERRED_SIZE.height);
|
|
874
|
+
const root = document.createElement("div");
|
|
875
|
+
root.setAttribute(SUB_DIALOG_OVERLAY_ATTR, "");
|
|
876
|
+
root.style.cssText = [
|
|
877
|
+
"position:fixed",
|
|
878
|
+
"inset:0",
|
|
879
|
+
"width:100vw",
|
|
880
|
+
"height:100vh",
|
|
881
|
+
"border:0",
|
|
882
|
+
"z-index:2147483646",
|
|
883
|
+
"background:rgba(0, 0, 0, 0.55)",
|
|
884
|
+
"display:flex",
|
|
885
|
+
"align-items:center",
|
|
886
|
+
"justify-content:center",
|
|
887
|
+
"box-sizing:border-box"
|
|
888
|
+
].join(";");
|
|
889
|
+
const card = document.createElement("div");
|
|
890
|
+
card.setAttribute(SUB_DIALOG_CARD_ATTR, "");
|
|
891
|
+
const applyCardColors = () => {
|
|
892
|
+
card.style.cssText = [
|
|
893
|
+
`width:min(96vw, ${width}px)`,
|
|
894
|
+
`height:min(92vh, ${height}px)`,
|
|
895
|
+
"border-radius:12px",
|
|
896
|
+
"box-shadow:0 24px 48px rgba(0, 0, 0, 0.32)",
|
|
897
|
+
"position:relative",
|
|
898
|
+
"box-sizing:border-box",
|
|
899
|
+
`background:${getCurrentSurfaceColor()}`,
|
|
900
|
+
"display:flex",
|
|
901
|
+
"flex-direction:column",
|
|
902
|
+
"overflow:hidden"
|
|
903
|
+
].join(";");
|
|
904
|
+
};
|
|
905
|
+
applyCardColors();
|
|
906
|
+
const closeButton = document.createElement("button");
|
|
907
|
+
closeButton.setAttribute(SUB_DIALOG_CLOSE_ATTR, "");
|
|
908
|
+
closeButton.type = "button";
|
|
909
|
+
closeButton.setAttribute("aria-label", translate(locale, "dialog.close"));
|
|
910
|
+
closeButton.title = translate(locale, "dialog.close");
|
|
911
|
+
closeButton.textContent = "×";
|
|
912
|
+
closeButton.style.cssText = [
|
|
913
|
+
"position:absolute",
|
|
914
|
+
"top:6px",
|
|
915
|
+
"right:10px",
|
|
916
|
+
"width:28px",
|
|
917
|
+
"height:28px",
|
|
918
|
+
"border:0",
|
|
919
|
+
"background:transparent",
|
|
920
|
+
"color:var(--dsw-alias-label-secondary, #5b6472)",
|
|
921
|
+
"font-size:22px",
|
|
922
|
+
"line-height:1",
|
|
923
|
+
"cursor:pointer",
|
|
924
|
+
"border-radius:6px",
|
|
925
|
+
"padding:0",
|
|
926
|
+
"z-index:1"
|
|
927
|
+
].join(";");
|
|
928
|
+
closeButton.addEventListener("click", closeSubscriptionDialog);
|
|
929
|
+
const iframe = document.createElement("iframe");
|
|
930
|
+
iframe.setAttribute(SUB_DIALOG_IFRAME_ATTR, "");
|
|
931
|
+
iframe.src = url;
|
|
932
|
+
iframe.title = translate(locale, "subscription.dialogTitle");
|
|
933
|
+
iframe.allow = "clipboard-write";
|
|
934
|
+
iframe.style.cssText = [
|
|
935
|
+
"width:100%",
|
|
936
|
+
"height:100%",
|
|
937
|
+
"border:0",
|
|
938
|
+
"border-radius:8px",
|
|
939
|
+
`background:${getCurrentSurfaceColor()}`,
|
|
940
|
+
"display:block",
|
|
941
|
+
"flex:1 1 auto"
|
|
942
|
+
].join(";");
|
|
943
|
+
card.appendChild(closeButton);
|
|
944
|
+
card.appendChild(iframe);
|
|
945
|
+
root.appendChild(card);
|
|
946
|
+
document.body.appendChild(root);
|
|
675
947
|
root.addEventListener("mousedown", backdropMouseDown);
|
|
676
948
|
card.addEventListener("mousedown", stopPropagation);
|
|
677
949
|
document.addEventListener("keydown", onKeyDown);
|
|
@@ -683,26 +955,27 @@ window.__ModuleLoader__.load({
|
|
|
683
955
|
closeButton.setAttribute("aria-label", closeButton.title);
|
|
684
956
|
});
|
|
685
957
|
container = root;
|
|
686
|
-
|
|
958
|
+
onCloseCallback = onClose ?? null;
|
|
687
959
|
}
|
|
688
|
-
/** Programmatic close (used by the auth client
|
|
689
|
-
function
|
|
960
|
+
/** Programmatic close (used by the auth client / tests). */
|
|
961
|
+
function closeSubscriptionDialog() {
|
|
690
962
|
if (!container) return;
|
|
691
963
|
container.remove();
|
|
692
964
|
container = null;
|
|
693
965
|
document.removeEventListener("keydown", onKeyDown);
|
|
694
966
|
unsubscribe$1?.();
|
|
695
967
|
unsubscribe$1 = null;
|
|
696
|
-
const cb =
|
|
697
|
-
|
|
968
|
+
const cb = onCloseCallback;
|
|
969
|
+
onCloseCallback = null;
|
|
698
970
|
cb?.();
|
|
699
971
|
}
|
|
700
|
-
/** True while the dialog is mounted. */
|
|
701
|
-
function
|
|
972
|
+
/** True while the subscription dialog is mounted. */
|
|
973
|
+
function isSubscriptionDialogOpen() {
|
|
702
974
|
return container !== null;
|
|
703
975
|
}
|
|
976
|
+
let onCloseCallback = null;
|
|
704
977
|
function backdropMouseDown(event) {
|
|
705
|
-
if (event.target === container)
|
|
978
|
+
if (event.target === container) closeSubscriptionDialog();
|
|
706
979
|
}
|
|
707
980
|
function stopPropagation(event) {
|
|
708
981
|
event.stopPropagation();
|
|
@@ -710,7 +983,7 @@ window.__ModuleLoader__.load({
|
|
|
710
983
|
function onKeyDown(event) {
|
|
711
984
|
if (event.key === "Escape") {
|
|
712
985
|
event.stopPropagation();
|
|
713
|
-
|
|
986
|
+
closeSubscriptionDialog();
|
|
714
987
|
}
|
|
715
988
|
}
|
|
716
989
|
//#endregion
|
|
@@ -810,6 +1083,37 @@ window.__ModuleLoader__.load({
|
|
|
810
1083
|
emit(null);
|
|
811
1084
|
closeIframe();
|
|
812
1085
|
},
|
|
1086
|
+
getSubscriptionQuota: async (productCode) => {
|
|
1087
|
+
if (hostMode && !hostSessionLoaded) await resolveHost();
|
|
1088
|
+
const token = await auth.getAccessToken();
|
|
1089
|
+
const id = hostMode ? hostSession?.id ?? null : hostSession?.id ?? storage.get()?.id ?? null;
|
|
1090
|
+
if (!token || !id) return null;
|
|
1091
|
+
return fetchSubscriptionQuota({
|
|
1092
|
+
userId: id,
|
|
1093
|
+
token,
|
|
1094
|
+
productCode
|
|
1095
|
+
});
|
|
1096
|
+
},
|
|
1097
|
+
openSubscriptionDialog: async () => {
|
|
1098
|
+
if (hostMode && !hostSessionLoaded) await resolveHost();
|
|
1099
|
+
const token = await auth.getAccessToken();
|
|
1100
|
+
if (!token) return;
|
|
1101
|
+
const url = buildSubscriptionUrl({ token });
|
|
1102
|
+
if (hostMode) {
|
|
1103
|
+
if (!deps.openHostDialog) return;
|
|
1104
|
+
await deps.openHostDialog(url, {
|
|
1105
|
+
title: "Huaqiu EDA subscription",
|
|
1106
|
+
size: {
|
|
1107
|
+
width: SUBSCRIPTION_PREFERRED_SIZE.width,
|
|
1108
|
+
height: SUBSCRIPTION_PREFERRED_SIZE.height
|
|
1109
|
+
},
|
|
1110
|
+
key: "hq-subscription-dialog"
|
|
1111
|
+
});
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
if (isSubscriptionDialogOpen()) return;
|
|
1115
|
+
openSubscriptionDialog(url, { size: SUBSCRIPTION_PREFERRED_SIZE });
|
|
1116
|
+
},
|
|
813
1117
|
onAuthStateChanged: (listener) => {
|
|
814
1118
|
listeners.add(listener);
|
|
815
1119
|
return () => listeners.delete(listener);
|
|
@@ -892,6 +1196,7 @@ window.__ModuleLoader__.load({
|
|
|
892
1196
|
dispose() {
|
|
893
1197
|
deps.windowLike.removeEventListener("message", onWindowMessage);
|
|
894
1198
|
closeIframe();
|
|
1199
|
+
if (isSubscriptionDialogOpen()) closeSubscriptionDialog();
|
|
895
1200
|
hostMode = false;
|
|
896
1201
|
hostSession = null;
|
|
897
1202
|
hostSessionLoaded = false;
|
|
@@ -1184,18 +1489,40 @@ window.__ModuleLoader__.load({
|
|
|
1184
1489
|
danger: "var(--dsw-alias-state-error-primary, #d4380d)",
|
|
1185
1490
|
dangerHover: "rgba(216, 56, 13, 0.08)",
|
|
1186
1491
|
avatarBg: "var(--dsw-alias-bg-layer-2, #eef2f7)",
|
|
1187
|
-
shadow: "0
|
|
1492
|
+
shadow: "0 20px 40px -15px rgba(0, 0, 0, 0.12), 0 0 1px 1px rgba(0, 0, 0, 0.05)",
|
|
1493
|
+
card: "#f7f8fa",
|
|
1494
|
+
cardBorder: "#e8ebf0",
|
|
1495
|
+
metric: "#f7f8fa",
|
|
1496
|
+
metricHover: "#eef1f5",
|
|
1497
|
+
primary: "#e62e2d",
|
|
1498
|
+
primaryHover: "#cc2423",
|
|
1499
|
+
primarySoft: "rgba(230, 46, 45, 0.08)",
|
|
1500
|
+
track: "#e4e7ec",
|
|
1501
|
+
genGradient: "linear-gradient(90deg, #3b82f6, #6366f1)",
|
|
1502
|
+
ercGradient: "linear-gradient(90deg, #a855f7, #ec4899)",
|
|
1503
|
+
online: "#10b981"
|
|
1188
1504
|
};
|
|
1189
1505
|
const DARK_PALETTE = {
|
|
1190
|
-
surface: "var(--dsw-alias-bg-overlay, #
|
|
1191
|
-
border: "var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.
|
|
1506
|
+
surface: "var(--dsw-alias-bg-overlay, #1e1b24)",
|
|
1507
|
+
border: "var(--dsw-alias-border-l1, rgba(255, 255, 255, 0.10))",
|
|
1192
1508
|
text: "var(--dsw-alias-label-primary, #e6eaf0)",
|
|
1193
1509
|
muted: "var(--dsw-alias-label-secondary, #8b95a5)",
|
|
1194
1510
|
hover: "var(--dsw-alias-interactive-bg-hover, rgba(255, 255, 255, 0.08))",
|
|
1195
1511
|
danger: "var(--dsw-alias-state-error-primary, #ff7875)",
|
|
1196
1512
|
dangerHover: "rgba(255, 120, 117, 0.14)",
|
|
1197
1513
|
avatarBg: "var(--dsw-alias-bg-layer-2, rgba(255, 255, 255, 0.10))",
|
|
1198
|
-
shadow: "0
|
|
1514
|
+
shadow: "0 20px 40px -15px rgba(0, 0, 0, 0.7), 0 0 1px 1px rgba(255, 255, 255, 0.1)",
|
|
1515
|
+
card: "rgba(255, 255, 255, 0.03)",
|
|
1516
|
+
cardBorder: "rgba(255, 255, 255, 0.08)",
|
|
1517
|
+
metric: "rgba(0, 0, 0, 0.20)",
|
|
1518
|
+
metricHover: "rgba(0, 0, 0, 0.30)",
|
|
1519
|
+
primary: "#e62e2d",
|
|
1520
|
+
primaryHover: "#cc2423",
|
|
1521
|
+
primarySoft: "rgba(230, 46, 45, 0.10)",
|
|
1522
|
+
track: "rgba(255, 255, 255, 0.10)",
|
|
1523
|
+
genGradient: "linear-gradient(90deg, #3b82f6, #6366f1)",
|
|
1524
|
+
ercGradient: "linear-gradient(90deg, #a855f7, #ec4899)",
|
|
1525
|
+
online: "#10b981"
|
|
1199
1526
|
};
|
|
1200
1527
|
const TRIGGER_BASE = {
|
|
1201
1528
|
width: "100%",
|
|
@@ -1216,11 +1543,11 @@ window.__ModuleLoader__.load({
|
|
|
1216
1543
|
const MENU_BASE = {
|
|
1217
1544
|
position: "fixed",
|
|
1218
1545
|
zIndex: 2147483e3,
|
|
1219
|
-
|
|
1220
|
-
padding:
|
|
1546
|
+
width: 320,
|
|
1547
|
+
padding: 12,
|
|
1221
1548
|
borderWidth: 1,
|
|
1222
1549
|
borderStyle: "solid",
|
|
1223
|
-
borderRadius:
|
|
1550
|
+
borderRadius: 16,
|
|
1224
1551
|
fontFamily: "inherit",
|
|
1225
1552
|
fontSize: 13
|
|
1226
1553
|
};
|
|
@@ -1247,22 +1574,47 @@ window.__ModuleLoader__.load({
|
|
|
1247
1574
|
};
|
|
1248
1575
|
/**
|
|
1249
1576
|
* One menu row. Hover is tracked in state: the client bundle ships no CSS
|
|
1250
|
-
* file, so inline styles cannot express `:hover`.
|
|
1577
|
+
* file, so inline styles cannot express `:hover`. `trailing` renders a
|
|
1578
|
+
* chevron on the right (prototype nav rows), `danger` switches to the danger
|
|
1579
|
+
* color scheme (prototype logout).
|
|
1251
1580
|
*/
|
|
1252
|
-
function MenuItem({ label, icon, danger, palette, onSelect }) {
|
|
1581
|
+
function MenuItem({ label, icon, trailing, danger, palette, onSelect }) {
|
|
1253
1582
|
const [hovered, setHovered] = (0, react.useState)(false);
|
|
1254
1583
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1255
1584
|
type: "button",
|
|
1256
1585
|
role: "menuitem",
|
|
1257
1586
|
style: {
|
|
1258
1587
|
...MENU_ITEM_BASE,
|
|
1588
|
+
justifyContent: "space-between",
|
|
1259
1589
|
color: danger ? palette.danger : palette.text,
|
|
1260
1590
|
background: hovered ? danger ? palette.dangerHover : palette.hover : "transparent"
|
|
1261
1591
|
},
|
|
1262
1592
|
onMouseEnter: () => setHovered(true),
|
|
1263
1593
|
onMouseLeave: () => setHovered(false),
|
|
1264
1594
|
onClick: onSelect,
|
|
1265
|
-
children: [
|
|
1595
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1596
|
+
style: {
|
|
1597
|
+
display: "flex",
|
|
1598
|
+
alignItems: "center",
|
|
1599
|
+
gap: 10,
|
|
1600
|
+
minWidth: 0
|
|
1601
|
+
},
|
|
1602
|
+
children: [icon, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1603
|
+
style: {
|
|
1604
|
+
overflow: "hidden",
|
|
1605
|
+
textOverflow: "ellipsis",
|
|
1606
|
+
whiteSpace: "nowrap"
|
|
1607
|
+
},
|
|
1608
|
+
children: label
|
|
1609
|
+
})]
|
|
1610
|
+
}), trailing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1611
|
+
style: {
|
|
1612
|
+
display: "flex",
|
|
1613
|
+
flex: "0 0 auto",
|
|
1614
|
+
color: hovered ? danger ? palette.danger : palette.text : palette.muted
|
|
1615
|
+
},
|
|
1616
|
+
children: trailing
|
|
1617
|
+
}) : null]
|
|
1266
1618
|
});
|
|
1267
1619
|
}
|
|
1268
1620
|
function UserIcon() {
|
|
@@ -1310,6 +1662,286 @@ window.__ModuleLoader__.load({
|
|
|
1310
1662
|
]
|
|
1311
1663
|
});
|
|
1312
1664
|
}
|
|
1665
|
+
/** GEN metric icon (lucide `cpu`, blue like the prototype). */
|
|
1666
|
+
function CpuIcon() {
|
|
1667
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1668
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1669
|
+
width: 13,
|
|
1670
|
+
height: 13,
|
|
1671
|
+
viewBox: "0 0 24 24",
|
|
1672
|
+
fill: "none",
|
|
1673
|
+
stroke: "#3b82f6",
|
|
1674
|
+
strokeWidth: 2,
|
|
1675
|
+
strokeLinecap: "round",
|
|
1676
|
+
strokeLinejoin: "round",
|
|
1677
|
+
"aria-hidden": true,
|
|
1678
|
+
style: { flex: "0 0 auto" },
|
|
1679
|
+
children: [
|
|
1680
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
1681
|
+
x: "4",
|
|
1682
|
+
y: "4",
|
|
1683
|
+
width: "16",
|
|
1684
|
+
height: "16",
|
|
1685
|
+
rx: "2"
|
|
1686
|
+
}),
|
|
1687
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
1688
|
+
x: "9",
|
|
1689
|
+
y: "9",
|
|
1690
|
+
width: "6",
|
|
1691
|
+
height: "6"
|
|
1692
|
+
}),
|
|
1693
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M9 2v2M15 2v2M9 20v2M15 20v2M2 9h2M2 15h2M20 9h2M20 15h2" })
|
|
1694
|
+
]
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1697
|
+
/** ERC metric icon (lucide `waves`, purple like the prototype). */
|
|
1698
|
+
function WaveIcon() {
|
|
1699
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1700
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1701
|
+
width: 13,
|
|
1702
|
+
height: 13,
|
|
1703
|
+
viewBox: "0 0 24 24",
|
|
1704
|
+
fill: "none",
|
|
1705
|
+
stroke: "#a855f7",
|
|
1706
|
+
strokeWidth: 2,
|
|
1707
|
+
strokeLinecap: "round",
|
|
1708
|
+
strokeLinejoin: "round",
|
|
1709
|
+
"aria-hidden": true,
|
|
1710
|
+
style: { flex: "0 0 auto" },
|
|
1711
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 12c1.5-3 3-3 4.5 0s3 3 4.5 0 3-3 4.5 0 3 3 4.5 0" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 17c1.5-3 3-3 4.5 0s3 3 4.5 0 3-3 4.5 0 3 3 4.5 0" })]
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
/** Upgrade-button crown (solid amber like the prototype). */
|
|
1715
|
+
function CrownIcon() {
|
|
1716
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1717
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1718
|
+
width: 14,
|
|
1719
|
+
height: 14,
|
|
1720
|
+
viewBox: "0 0 24 24",
|
|
1721
|
+
fill: "#fcd34d",
|
|
1722
|
+
"aria-hidden": true,
|
|
1723
|
+
style: { flex: "0 0 auto" },
|
|
1724
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2 18h20l-1.5-9-5.5 4L12 5.5 9 13l-5.5-4L2 18z" })
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
/** Trailing chevron for nav rows (lucide `chevron-right`). */
|
|
1728
|
+
function ChevronRightIcon() {
|
|
1729
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1730
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1731
|
+
width: 12,
|
|
1732
|
+
height: 12,
|
|
1733
|
+
viewBox: "0 0 24 24",
|
|
1734
|
+
fill: "none",
|
|
1735
|
+
stroke: "currentColor",
|
|
1736
|
+
strokeWidth: 2,
|
|
1737
|
+
strokeLinecap: "round",
|
|
1738
|
+
strokeLinejoin: "round",
|
|
1739
|
+
"aria-hidden": true,
|
|
1740
|
+
style: { flex: "0 0 auto" },
|
|
1741
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "9 18 15 12 9 6" })
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
/** Quota metric card: icon + label, mono count and a gradient progress bar. */
|
|
1745
|
+
function QuotaMetric({ label, icon, quota, gradient, palette }) {
|
|
1746
|
+
const t = useT();
|
|
1747
|
+
const [hovered, setHovered] = (0, react.useState)(false);
|
|
1748
|
+
const used = quota?.currentQuota;
|
|
1749
|
+
const total = quota?.totalQuota;
|
|
1750
|
+
const percent = quota?.hasSubscription && typeof total === "number" && total > 0 ? Math.max(0, Math.min(100, Math.round((used ?? 0) / total * 100))) : 0;
|
|
1751
|
+
const value = quota ? quota.hasSubscription ? typeof total === "number" ? t("subscription.quotaCount", {
|
|
1752
|
+
used: used ?? 0,
|
|
1753
|
+
total
|
|
1754
|
+
}) : String(used ?? 0) : t("subscription.unsubscribed") : "—";
|
|
1755
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1756
|
+
role: "presentation",
|
|
1757
|
+
style: {
|
|
1758
|
+
background: hovered ? palette.metricHover : palette.metric,
|
|
1759
|
+
borderRadius: 12,
|
|
1760
|
+
border: `1px solid ${palette.cardBorder}`,
|
|
1761
|
+
padding: 10,
|
|
1762
|
+
boxSizing: "border-box",
|
|
1763
|
+
transition: "background 0.15s ease"
|
|
1764
|
+
},
|
|
1765
|
+
onMouseEnter: () => setHovered(true),
|
|
1766
|
+
onMouseLeave: () => setHovered(false),
|
|
1767
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1768
|
+
style: {
|
|
1769
|
+
display: "flex",
|
|
1770
|
+
justifyContent: "space-between",
|
|
1771
|
+
alignItems: "center",
|
|
1772
|
+
gap: 8,
|
|
1773
|
+
fontSize: 12,
|
|
1774
|
+
marginBottom: 6
|
|
1775
|
+
},
|
|
1776
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1777
|
+
style: {
|
|
1778
|
+
display: "flex",
|
|
1779
|
+
alignItems: "center",
|
|
1780
|
+
gap: 6,
|
|
1781
|
+
fontWeight: 500,
|
|
1782
|
+
color: palette.text,
|
|
1783
|
+
minWidth: 0
|
|
1784
|
+
},
|
|
1785
|
+
children: [icon, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1786
|
+
style: {
|
|
1787
|
+
overflow: "hidden",
|
|
1788
|
+
textOverflow: "ellipsis",
|
|
1789
|
+
whiteSpace: "nowrap"
|
|
1790
|
+
},
|
|
1791
|
+
children: label
|
|
1792
|
+
})]
|
|
1793
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1794
|
+
style: {
|
|
1795
|
+
fontWeight: 700,
|
|
1796
|
+
color: quota?.hasSubscription ? palette.text : palette.danger,
|
|
1797
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
1798
|
+
fontSize: 12,
|
|
1799
|
+
whiteSpace: "nowrap",
|
|
1800
|
+
flexShrink: 0
|
|
1801
|
+
},
|
|
1802
|
+
title: value,
|
|
1803
|
+
children: value
|
|
1804
|
+
})]
|
|
1805
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1806
|
+
style: {
|
|
1807
|
+
width: "100%",
|
|
1808
|
+
height: 6,
|
|
1809
|
+
borderRadius: 999,
|
|
1810
|
+
background: palette.track,
|
|
1811
|
+
overflow: "hidden"
|
|
1812
|
+
},
|
|
1813
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
|
|
1814
|
+
width: `${percent}%`,
|
|
1815
|
+
height: "100%",
|
|
1816
|
+
borderRadius: 999,
|
|
1817
|
+
background: gradient
|
|
1818
|
+
} })
|
|
1819
|
+
})]
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
/**
|
|
1823
|
+
* Profile header card (prototype): name + plan badge + phone line on the
|
|
1824
|
+
* left, an "online" pill on the right. No redundant avatar — the trigger
|
|
1825
|
+
* already shows it.
|
|
1826
|
+
*/
|
|
1827
|
+
function ProfileHeaderCard({ displayName, packageName, phone, palette }) {
|
|
1828
|
+
const t = useT();
|
|
1829
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1830
|
+
role: "presentation",
|
|
1831
|
+
style: {
|
|
1832
|
+
padding: 10,
|
|
1833
|
+
borderRadius: 12,
|
|
1834
|
+
background: palette.card,
|
|
1835
|
+
border: `1px solid ${palette.cardBorder}`,
|
|
1836
|
+
display: "flex",
|
|
1837
|
+
alignItems: "center",
|
|
1838
|
+
justifyContent: "space-between",
|
|
1839
|
+
gap: 8,
|
|
1840
|
+
marginBottom: 12,
|
|
1841
|
+
boxSizing: "border-box"
|
|
1842
|
+
},
|
|
1843
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1844
|
+
style: {
|
|
1845
|
+
minWidth: 0,
|
|
1846
|
+
flex: "1 1 auto"
|
|
1847
|
+
},
|
|
1848
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1849
|
+
style: {
|
|
1850
|
+
display: "flex",
|
|
1851
|
+
alignItems: "center",
|
|
1852
|
+
gap: 8,
|
|
1853
|
+
minWidth: 0
|
|
1854
|
+
},
|
|
1855
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1856
|
+
style: {
|
|
1857
|
+
fontSize: 14,
|
|
1858
|
+
fontWeight: 700,
|
|
1859
|
+
color: palette.text,
|
|
1860
|
+
overflow: "hidden",
|
|
1861
|
+
textOverflow: "ellipsis",
|
|
1862
|
+
whiteSpace: "nowrap"
|
|
1863
|
+
},
|
|
1864
|
+
children: displayName
|
|
1865
|
+
}), packageName ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1866
|
+
style: {
|
|
1867
|
+
fontSize: 10,
|
|
1868
|
+
background: palette.primarySoft,
|
|
1869
|
+
color: palette.primary,
|
|
1870
|
+
padding: "2px 6px",
|
|
1871
|
+
borderRadius: 4,
|
|
1872
|
+
fontWeight: 500,
|
|
1873
|
+
border: `1px solid ${palette.primarySoft}`,
|
|
1874
|
+
whiteSpace: "nowrap",
|
|
1875
|
+
flexShrink: 0
|
|
1876
|
+
},
|
|
1877
|
+
children: packageName
|
|
1878
|
+
}) : null]
|
|
1879
|
+
}), phone ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1880
|
+
style: {
|
|
1881
|
+
margin: 0,
|
|
1882
|
+
marginTop: 2,
|
|
1883
|
+
fontSize: 12,
|
|
1884
|
+
color: palette.muted,
|
|
1885
|
+
overflow: "hidden",
|
|
1886
|
+
textOverflow: "ellipsis",
|
|
1887
|
+
whiteSpace: "nowrap"
|
|
1888
|
+
},
|
|
1889
|
+
children: phone
|
|
1890
|
+
}) : null]
|
|
1891
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1892
|
+
style: {
|
|
1893
|
+
fontSize: 10,
|
|
1894
|
+
background: "rgba(16, 185, 129, 0.10)",
|
|
1895
|
+
color: palette.online,
|
|
1896
|
+
padding: "2px 8px",
|
|
1897
|
+
borderRadius: 999,
|
|
1898
|
+
fontWeight: 500,
|
|
1899
|
+
border: "1px solid rgba(16, 185, 129, 0.20)",
|
|
1900
|
+
display: "flex",
|
|
1901
|
+
alignItems: "center",
|
|
1902
|
+
gap: 5,
|
|
1903
|
+
flexShrink: 0
|
|
1904
|
+
},
|
|
1905
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
|
|
1906
|
+
width: 6,
|
|
1907
|
+
height: 6,
|
|
1908
|
+
borderRadius: 6,
|
|
1909
|
+
background: palette.online
|
|
1910
|
+
} }), t("subscription.online")]
|
|
1911
|
+
})]
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
/** Full-width red-gradient upgrade button (prototype CTA). */
|
|
1915
|
+
function UpgradeButton({ subscribing, palette, onClick }) {
|
|
1916
|
+
const t = useT();
|
|
1917
|
+
const [hovered, setHovered] = (0, react.useState)(false);
|
|
1918
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1919
|
+
type: "button",
|
|
1920
|
+
style: {
|
|
1921
|
+
width: "100%",
|
|
1922
|
+
marginTop: 10,
|
|
1923
|
+
padding: "9px 12px",
|
|
1924
|
+
borderRadius: 12,
|
|
1925
|
+
border: "none",
|
|
1926
|
+
background: subscribing ? `linear-gradient(90deg, ${palette.primary}, #e11d48)` : hovered ? `linear-gradient(90deg, ${palette.primaryHover}, #be123c)` : `linear-gradient(90deg, ${palette.primary}, #e11d48)`,
|
|
1927
|
+
color: "#ffffff",
|
|
1928
|
+
fontSize: 12,
|
|
1929
|
+
fontWeight: 600,
|
|
1930
|
+
display: "flex",
|
|
1931
|
+
alignItems: "center",
|
|
1932
|
+
justifyContent: "center",
|
|
1933
|
+
gap: 8,
|
|
1934
|
+
cursor: subscribing ? "default" : "pointer",
|
|
1935
|
+
opacity: subscribing ? .72 : 1,
|
|
1936
|
+
boxShadow: "0 8px 20px rgba(230, 46, 45, 0.22)",
|
|
1937
|
+
transition: "background 0.15s ease, opacity 0.15s ease"
|
|
1938
|
+
},
|
|
1939
|
+
onMouseEnter: () => setHovered(true),
|
|
1940
|
+
onMouseLeave: () => setHovered(false),
|
|
1941
|
+
onClick,
|
|
1942
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CrownIcon, {}), subscribing ? t("subscription.subscribing") : t("subscription.upgrade")]
|
|
1943
|
+
});
|
|
1944
|
+
}
|
|
1313
1945
|
const HuaqiuAuthSidebarAction = (0, react.memo)(function HuaqiuAuthSidebarAction({ wide }) {
|
|
1314
1946
|
const authState = (0, react.useSyncExternalStore)(subscribeAuth, getAuthState);
|
|
1315
1947
|
const auth = getAuth();
|
|
@@ -1320,6 +1952,8 @@ window.__ModuleLoader__.load({
|
|
|
1320
1952
|
const [menuStyle, setMenuStyle] = (0, react.useState)(null);
|
|
1321
1953
|
const [avatarBroken, setAvatarBroken] = (0, react.useState)(false);
|
|
1322
1954
|
const [hovered, setHovered] = (0, react.useState)(false);
|
|
1955
|
+
const [quota, setQuota] = (0, react.useState)(null);
|
|
1956
|
+
const [subscribing, setSubscribing] = (0, react.useState)(false);
|
|
1323
1957
|
const triggerRef = (0, react.useRef)(null);
|
|
1324
1958
|
const menuRef = (0, react.useRef)(null);
|
|
1325
1959
|
const palette = dark ? DARK_PALETTE : LIGHT_PALETTE;
|
|
@@ -1357,6 +1991,22 @@ window.__ModuleLoader__.load({
|
|
|
1357
1991
|
(0, react.useEffect)(() => {
|
|
1358
1992
|
if (!authenticated) setMenuOpen(false);
|
|
1359
1993
|
}, [authenticated]);
|
|
1994
|
+
(0, react.useEffect)(() => {
|
|
1995
|
+
if (!menuOpen || !authenticated) {
|
|
1996
|
+
if (!menuOpen) setQuota(null);
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
let cancelled = false;
|
|
2000
|
+
Promise.all([auth?.getSubscriptionQuota?.("auto-design").catch(() => null) ?? Promise.resolve(null), auth?.getSubscriptionQuota?.("erc").catch(() => null) ?? Promise.resolve(null)]).then(([gen, erc]) => {
|
|
2001
|
+
if (!cancelled) setQuota({
|
|
2002
|
+
gen,
|
|
2003
|
+
erc
|
|
2004
|
+
});
|
|
2005
|
+
});
|
|
2006
|
+
return () => {
|
|
2007
|
+
cancelled = true;
|
|
2008
|
+
};
|
|
2009
|
+
}, [menuOpen, authenticated]);
|
|
1360
2010
|
(0, react.useLayoutEffect)(() => {
|
|
1361
2011
|
if (!menuOpen || !triggerRef.current) return;
|
|
1362
2012
|
const rect = triggerRef.current.getBoundingClientRect();
|
|
@@ -1367,12 +2017,10 @@ window.__ModuleLoader__.load({
|
|
|
1367
2017
|
color: palette.text,
|
|
1368
2018
|
boxShadow: palette.shadow,
|
|
1369
2019
|
left: Math.max(8, Math.round(rect.left)),
|
|
1370
|
-
bottom: Math.max(8, Math.round(window.innerHeight - rect.top + 8))
|
|
1371
|
-
...wide ? { width: Math.round(rect.width) } : {}
|
|
2020
|
+
bottom: Math.max(8, Math.round(window.innerHeight - rect.top + 8))
|
|
1372
2021
|
});
|
|
1373
2022
|
}, [
|
|
1374
2023
|
menuOpen,
|
|
1375
|
-
wide,
|
|
1376
2024
|
avatar,
|
|
1377
2025
|
palette
|
|
1378
2026
|
]);
|
|
@@ -1423,6 +2071,16 @@ window.__ModuleLoader__.load({
|
|
|
1423
2071
|
const label = authenticated ? displayName ?? t("sidebar.account") : t("sidebar.login");
|
|
1424
2072
|
const title = authenticated ? t("sidebar.accountTitle") : t("sidebar.loginTitle");
|
|
1425
2073
|
const triggerBackground = menuOpen || hovered ? palette.hover : "transparent";
|
|
2074
|
+
/**
|
|
2075
|
+
* Open the subscription page. Host mode → the EDA host's native dialog
|
|
2076
|
+
* (DialogService.OpenUrl, preferred size 1400×740); standalone DSH → the
|
|
2077
|
+
* in-app iframe dialog at the same preferred size. No credential → no-op.
|
|
2078
|
+
*/
|
|
2079
|
+
const openSubscription = () => {
|
|
2080
|
+
if (subscribing) return;
|
|
2081
|
+
setSubscribing(true);
|
|
2082
|
+
auth.openSubscriptionDialog().catch(() => {}).finally(() => setSubscribing(false));
|
|
2083
|
+
};
|
|
1426
2084
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1427
2085
|
style: {
|
|
1428
2086
|
position: "relative",
|
|
@@ -1487,19 +2145,85 @@ window.__ModuleLoader__.load({
|
|
|
1487
2145
|
role: "menu",
|
|
1488
2146
|
style: menuStyle,
|
|
1489
2147
|
children: [
|
|
1490
|
-
|
|
2148
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProfileHeaderCard, {
|
|
2149
|
+
displayName: displayName ?? t("sidebar.account"),
|
|
2150
|
+
packageName: quota?.gen?.packageName ?? quota?.erc?.packageName ?? null,
|
|
2151
|
+
phone: authState.phone ?? null,
|
|
2152
|
+
palette
|
|
2153
|
+
}),
|
|
2154
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2155
|
+
role: "presentation",
|
|
1491
2156
|
style: {
|
|
1492
2157
|
...MENU_HEADER_BASE,
|
|
2158
|
+
paddingTop: 0,
|
|
2159
|
+
paddingBottom: 6,
|
|
2160
|
+
fontSize: 11,
|
|
2161
|
+
fontWeight: 600,
|
|
2162
|
+
letterSpacing: .4,
|
|
2163
|
+
textTransform: "uppercase",
|
|
1493
2164
|
color: palette.muted
|
|
1494
2165
|
},
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
2166
|
+
children: t("subscription.quotaHeader")
|
|
2167
|
+
}),
|
|
2168
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2169
|
+
role: "presentation",
|
|
2170
|
+
style: {
|
|
2171
|
+
display: "flex",
|
|
2172
|
+
flexDirection: "column",
|
|
2173
|
+
gap: 8
|
|
2174
|
+
},
|
|
2175
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaMetric, {
|
|
2176
|
+
label: t("subscription.gen"),
|
|
2177
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CpuIcon, {}),
|
|
2178
|
+
quota: quota?.gen ?? null,
|
|
2179
|
+
gradient: palette.genGradient,
|
|
2180
|
+
palette
|
|
2181
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaMetric, {
|
|
2182
|
+
label: t("subscription.erc"),
|
|
2183
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WaveIcon, {}),
|
|
2184
|
+
quota: quota?.erc ?? null,
|
|
2185
|
+
gradient: palette.ercGradient,
|
|
2186
|
+
palette
|
|
2187
|
+
})]
|
|
2188
|
+
}),
|
|
2189
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UpgradeButton, {
|
|
2190
|
+
subscribing,
|
|
1501
2191
|
palette,
|
|
1502
|
-
|
|
2192
|
+
onClick: () => {
|
|
2193
|
+
if (subscribing) return;
|
|
2194
|
+
openSubscription();
|
|
2195
|
+
}
|
|
2196
|
+
}),
|
|
2197
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2198
|
+
role: "presentation",
|
|
2199
|
+
style: {
|
|
2200
|
+
height: 1,
|
|
2201
|
+
margin: "12px 2px",
|
|
2202
|
+
background: palette.border
|
|
2203
|
+
}
|
|
2204
|
+
}),
|
|
2205
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2206
|
+
role: "presentation",
|
|
2207
|
+
style: {
|
|
2208
|
+
display: "flex",
|
|
2209
|
+
flexDirection: "column",
|
|
2210
|
+
gap: 2
|
|
2211
|
+
},
|
|
2212
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuItem, {
|
|
2213
|
+
label: t("menu.profile"),
|
|
2214
|
+
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UserIcon, {}),
|
|
2215
|
+
trailing: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronRightIcon, {}),
|
|
2216
|
+
palette,
|
|
2217
|
+
onSelect: openProfile
|
|
2218
|
+
})
|
|
2219
|
+
}),
|
|
2220
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2221
|
+
role: "presentation",
|
|
2222
|
+
style: {
|
|
2223
|
+
height: 1,
|
|
2224
|
+
margin: "12px 2px",
|
|
2225
|
+
background: palette.border
|
|
2226
|
+
}
|
|
1503
2227
|
}),
|
|
1504
2228
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuItem, {
|
|
1505
2229
|
label: t("menu.logout"),
|
|
@@ -1570,7 +2294,16 @@ window.__ModuleLoader__.load({
|
|
|
1570
2294
|
storage: createAuthStorage(localStorage),
|
|
1571
2295
|
transport: createWebServerAuthTransport(),
|
|
1572
2296
|
windowLike: window,
|
|
1573
|
-
documentLike: document
|
|
2297
|
+
documentLike: document,
|
|
2298
|
+
openHostDialog: async (url, options) => {
|
|
2299
|
+
const hqEdge = ctx.get?.("hqEdge");
|
|
2300
|
+
if (!hqEdge || typeof hqEdge.openUrl !== "function") return;
|
|
2301
|
+
await hqEdge.openUrl(url, {
|
|
2302
|
+
...options.title ? { title: options.title } : {},
|
|
2303
|
+
...options.size ? { size: options.size } : {},
|
|
2304
|
+
...options.key ? { key: options.key } : {}
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
1574
2307
|
});
|
|
1575
2308
|
registerHqEdgeGetter(() => ctx.get?.("hqEdge"));
|
|
1576
2309
|
const disposers = [];
|