|
1 | 1 | import { |
2 | 2 | Button, |
| 3 | + Dialog, |
| 4 | + DialogActions, |
| 5 | + DialogBody, |
| 6 | + DialogContent, |
| 7 | + DialogSurface, |
| 8 | + DialogTitle, |
| 9 | + DialogTrigger, |
3 | 10 | Field, |
4 | 11 | Input, |
5 | 12 | Spinner, |
6 | 13 | Text, |
| 14 | + Textarea, |
7 | 15 | tokens, |
8 | 16 | } from "@fluentui/react-components"; |
9 | 17 | import { useStyles } from "../useStyles"; |
10 | 18 | import { useI18n } from "../../tools/i18n"; |
11 | | -import { useCallback, useState } from "react"; |
| 19 | +import { useCallback, useEffect, useRef, useState } from "react"; |
12 | 20 | import { useGlobalDrop } from "../useGlobalDrop"; |
13 | 21 | import { CharacterCard } from "@lenml/char-card-reader"; |
14 | 22 | import { useGlobalPaste } from "../useGlobalPaste"; |
@@ -55,6 +63,8 @@ export const StartPanel = ({ |
55 | 63 | const [dragHighlight, setDragHighlight] = useState(false); |
56 | 64 | const [isLoading, setIsLoading] = useState(false); |
57 | 65 | const [loadFromUrl, setLoadFromUrl] = useState(""); |
| 66 | + const loadFromUrlRef = useRef(loadFromUrl); |
| 67 | + loadFromUrlRef.current = loadFromUrl; |
58 | 68 |
|
59 | 69 | const handleFileCard = useCallback( |
60 | 70 | async (getCard: () => Promise<CharacterCard>) => { |
@@ -160,11 +170,34 @@ export const StartPanel = ({ |
160 | 170 | const handleLoadFromUrl = useCallback(async () => { |
161 | 171 | // 下载文件 |
162 | 172 | await handleFileCard(async () => { |
163 | | - const response = await fetch(getRawImageUrl(loadFromUrl)); |
| 173 | + const response = await fetch(getRawImageUrl(loadFromUrlRef.current)); |
164 | 174 | const arrayBuffer = await response.arrayBuffer(); |
165 | 175 | return CharacterCard.from_file(arrayBuffer); |
166 | 176 | }); |
167 | | - }, [loadFromUrl]); |
| 177 | + }, []); |
| 178 | + |
| 179 | + useEffect(() => { |
| 180 | + // 从 ?load_url=xxx 读取 |
| 181 | + const urlParams = new URLSearchParams(window.location.search); |
| 182 | + const loadFromUrl = urlParams.get("load_url"); |
| 183 | + if (loadFromUrl) { |
| 184 | + setLoadFromUrl(decodeURIComponent(loadFromUrl)); |
| 185 | + loadFromUrlRef.current = decodeURIComponent(loadFromUrl); |
| 186 | + handleLoadFromUrl(); |
| 187 | + // 删除路径中的 url |
| 188 | + urlParams.delete("load_url"); |
| 189 | + history.replaceState( |
| 190 | + null, |
| 191 | + "", |
| 192 | + `${window.location.pathname}?${urlParams.toString()}` |
| 193 | + ); |
| 194 | + } |
| 195 | + }, [handleLoadFromUrl]); |
| 196 | + |
| 197 | + const [shareModal, updateShareModal] = useState({ |
| 198 | + open: false, |
| 199 | + url: "", |
| 200 | + }); |
168 | 201 |
|
169 | 202 | return ( |
170 | 203 | <div |
@@ -252,22 +285,87 @@ export const StartPanel = ({ |
252 | 285 | <div style={{ width: "40vw" }}> |
253 | 286 | <Field label={"3. " + t("Load image from url")}> |
254 | 287 | <Input |
| 288 | + type="url" |
255 | 289 | placeholder="https://example.com/avatar.png" |
256 | 290 | onChange={(e) => { |
257 | 291 | const avatarUrl = e.target.value; |
258 | 292 | setLoadFromUrl(avatarUrl); |
259 | 293 | }} |
| 294 | + onKeyDown={(e) => { |
| 295 | + if (e.key === "Enter") { |
| 296 | + handleLoadFromUrl(); |
| 297 | + } |
| 298 | + }} |
260 | 299 | contentAfter={ |
261 | | - <Button |
262 | | - appearance="transparent" |
263 | | - size="small" |
264 | | - onClick={handleLoadFromUrl} |
| 300 | + <span |
| 301 | + style={{ |
| 302 | + display: loadFromUrl ? "inline-block" : "none", |
| 303 | + }} |
265 | 304 | > |
266 | | - {t("Load")} |
267 | | - </Button> |
| 305 | + <Button |
| 306 | + style={{ minWidth: 0, padding: 0, paddingLeft: "0.5rem" }} |
| 307 | + appearance="transparent" |
| 308 | + size="small" |
| 309 | + onClick={() => { |
| 310 | + const urlObj = new URL(window.location.href); |
| 311 | + urlObj.searchParams.set("load_url", loadFromUrl); |
| 312 | + updateShareModal({ |
| 313 | + open: true, |
| 314 | + url: urlObj.href, |
| 315 | + }); |
| 316 | + }} |
| 317 | + > |
| 318 | + {t("Share")} |
| 319 | + </Button> |
| 320 | + <Button |
| 321 | + style={{ minWidth: 0, padding: 0, paddingLeft: "0.5rem" }} |
| 322 | + appearance="transparent" |
| 323 | + size="small" |
| 324 | + onClick={handleLoadFromUrl} |
| 325 | + > |
| 326 | + {t("Load")} |
| 327 | + </Button> |
| 328 | + </span> |
268 | 329 | } |
269 | 330 | /> |
270 | 331 | </Field> |
| 332 | + |
| 333 | + <Dialog |
| 334 | + open={shareModal.open} |
| 335 | + onOpenChange={(_: any, data: { open: any }) => |
| 336 | + updateShareModal((prev) => ({ ...prev, open: data.open })) |
| 337 | + } |
| 338 | + > |
| 339 | + <DialogSurface> |
| 340 | + <DialogBody> |
| 341 | + <DialogTitle>{t("Share Link")}</DialogTitle> |
| 342 | + <DialogContent> |
| 343 | + <p className="mb-3"> |
| 344 | + {t( |
| 345 | + // 你可以使用下面的链接分享你的卡片,你的朋友点击之后就可以到达此工具的编辑页面 |
| 346 | + "You can share the link to edit this card, your friend can click it to reach this tool's edit page" |
| 347 | + )} |
| 348 | + </p> |
| 349 | + <Textarea |
| 350 | + className="w-full" |
| 351 | + readOnly |
| 352 | + rows={10} |
| 353 | + value={shareModal.url} |
| 354 | + /> |
| 355 | + </DialogContent> |
| 356 | + <DialogActions> |
| 357 | + <DialogTrigger disableButtonEnhancement> |
| 358 | + <Button |
| 359 | + appearance="primary" |
| 360 | + onClick={() => updateShareModal({ open: false, url: "" })} |
| 361 | + > |
| 362 | + {t("Ok")} |
| 363 | + </Button> |
| 364 | + </DialogTrigger> |
| 365 | + </DialogActions> |
| 366 | + </DialogBody> |
| 367 | + </DialogSurface> |
| 368 | + </Dialog> |
271 | 369 | </div> |
272 | 370 |
|
273 | 371 | {/* Option 3: 复制粘贴文件 */} |
|
0 commit comments