Files
WebDrop/web/src/features/channel/members/SharePanel.tsx
T
justme 5489407241 refactor(web): restructure into api/domain/services/worker; capabilities, file domains, video covers
- dissolve core-bridge into api (wire contract), domain (models), services (what the UI
  calls) and worker (everything that runs in the Web Worker, grouped by account, crypto,
  space, files, shares, runtime); regroup features, lib and store by responsibility and
  delete unused components
- the UI only reads backend capabilities (domain/capabilities); one tokenRole picks the
  token, hasCode/isRetryable handle errors; pre-checks that decided permissions on the
  client are gone; tests fail if those rules are reimplemented
- files go straight to the file domain (create, upload, download, delete, commit, cancel);
  the host sees one registration per file; creating a channel allocates a file domain and
  works even when the file service is down
- file panel shows which channel the files belong to and its space usage; unlimited files
  preview in chat; videos get a stored cover; the file service status is shown
- fix: console flooded by Retry-After reads on every upload frame
2026-10-19 13:38:51 +08:00

55 lines
2.6 KiB
TypeScript

import { useState } from 'react';
import { useWorkspace } from '@/store/space/workspace';
import { Button } from '@/ui/Button';
import { CopyButton } from '@/ui/CopyButton';
import { QrCode } from '@/ui/QrCode';
import styles from './SharePanel.module.css';
/** 分享入口:空间地址与(持链接加入时的)成员链接。成员与邀请在右侧「成员」页管理,这里不列人 */
export function SharePanel({ qrOpen = false }: { qrOpen?: boolean }) {
const channel = useWorkspace((s) => s.channel);
const [showOwner, setShowOwner] = useState(false);
const [showQr, setShowQr] = useState(qrOpen);
const accountAddress = channel ? `${location.origin}/c/${channel.id}` : '';
return (
<aside className={styles.panel}>
<section>
<h2 className={styles.h}>分享</h2>
<div className={styles.link}>
<div className={styles.label}>账号空间地址</div>
<code className={styles.code}>{accountAddress}</code>
<CopyButton text={accountAddress} />
<p className={styles.note}>此地址不包含密钥。在「成员」页邀请账号后,对方登录即可自动领取密钥包。</p>
</div>
{channel?.joinPolicy === 'open' ? <>
<div className={styles.link}>
<div className={styles.label}>成员链接</div>
<code className={styles.code}>{channel.memberLink}</code>
<div className={styles.row}>
<CopyButton text={channel.memberLink} />
<Button variant="ghost" icon="scan" aria-expanded={showQr} onClick={() => setShowQr((v) => !v)}>
{showQr ? '收起二维码' : '二维码'}
</Button>
</div>
{showQr ? <QrCode text={channel.memberLink} label="成员链接二维码" /> : null}
</div>
{channel.ownerLink ? (
<div className={styles.link}>
<div className={styles.label}>所有者链接 <span className={styles.warn}>含管理权限</span></div>
<code className={styles.code}>{showOwner ? channel.ownerLink : '••••••••••••••••••••'}</code>
<div className={styles.row}>
<Button variant="ghost" icon="eye" onClick={() => setShowOwner((v) => !v)}>{showOwner ? '隐藏' : '显示'}</Button>
<CopyButton text={channel.ownerLink} />
</div>
</div>
) : null}
<p className={styles.note}>
持有链接并符合空间加入规则后,可读取空间历史内容。不隐藏 IP 与访问时间。
</p>
</> : null}
</section>
</aside>
);
}