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
This commit is contained in:
1 parent
17638b6e20
commit
5489407241
260 files changed
+1503
-1246
No files matched your search
@@ -0,0 +1,54 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user