- 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
55 lines
2.6 KiB
TypeScript
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>
|
|
);
|
|
}
|