diff --git a/.github/workflows/cli-release.yml b/.github/workflows/cli-release.yml new file mode 100644 index 0000000..643731d --- /dev/null +++ b/.github/workflows/cli-release.yml @@ -0,0 +1,50 @@ +name: Release CLI + +on: + push: + tags: + - "v*" + +jobs: + build: + name: Build CLI (${{ matrix.target }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + - os: macos-latest + target: x86_64-apple-darwin + - os: macos-latest + target: aarch64-apple-darwin + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + + - name: Add target + if: matrix.target != 'x86_64-unknown-linux-gnu' + run: rustup target add ${{ matrix.target }} + + - name: Build + working-directory: cli + run: cargo build --release --target ${{ matrix.target }} + + - name: Package + working-directory: cli + run: | + VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/') + BIN_PATH="target/${{ matrix.target }}/release/pairlane" + ASSET="pairlane-${VERSION}-${{ matrix.target }}.tar.gz" + tar -czf "$ASSET" -C "$(dirname "$BIN_PATH")" pairlane + echo "ASSET=$ASSET" >> $GITHUB_ENV + + - name: Upload Release Asset + uses: softprops/action-gh-release@v2 + with: + files: cli/${{ env.ASSET }} diff --git a/README.ja.md b/README.ja.md index 9178fed..5413f09 100644 --- a/README.ja.md +++ b/README.ja.md @@ -1,8 +1,8 @@ -# Share Files +# Pairlane [English](./README.md) | [中文](./README.zh.md) -**デモサイト: https://share-files.karakuri-maker.com/** +**デモサイト: https://getpairlane.com/** WebRTCを使ったP2Pファイル共有ツール。サーバーを経由せずブラウザ間で直接ファイルを転送します。 @@ -41,6 +41,15 @@ WebRTCを使ったP2Pファイル共有ツール。サーバーを経由せず GitHub Actionsにより、`cli/` ディレクトリへのpush/PR時にビルドが自動テストされます。 +### npxで実行 + +```sh +npx pairlane send /path/to/file +npx pairlane receive --output-dir ./downloads +``` + +### ソースからビルド + ```sh cd cli cargo run --release -- send /path/to/file @@ -50,8 +59,8 @@ cargo run --release -- receive --output-dir ./downloads 暗号化は `send` のデフォルトです。`send` が出力する `#k=...` 付きのURLを、そのまま `receive` に渡せます。 ```sh -cargo run --release -- send /path/to/file -cargo run --release -- receive "https://share-files.karakuri-maker.com/r/ROOM#k=..." +npx pairlane send /path/to/file +npx pairlane receive "https://getpairlane.com/r/ROOM#k=..." ``` 暗号化を無効にする場合は `--no-encrypt` を指定してください。 @@ -59,18 +68,18 @@ cargo run --release -- receive "https://share-files.karakuri-maker.com/r/ROOM#k= 復号鍵を明示したい場合は `receive` に `--key`(base64url)を渡します。 ```sh -cargo run --release -- receive --key --output-dir ./downloads +npx pairlane receive --key --output-dir ./downloads ``` デフォルトでは、`send` と `receive` は転送成功後に終了します。継続して待ちたい場合は `--stay-open` を指定してください。 ※ `#k=...` を含むURLはシェルでクォートしてください。従来の `--file` / `--room-id` も引き続き利用できます。 -デフォルトではデモ環境へ接続します。`SHARE_FILES_ENDPOINT` 環境変数で上書きできます。 +デフォルトではデモ環境へ接続します。`PAIRLANE_ENDPOINT` 環境変数で上書きできます(旧 `SHARE_FILES_ENDPOINT` も利用可)。 ```sh -SHARE_FILES_ENDPOINT=https://share-files.karakuri-maker.com \ - cargo run --release -- send /path/to/file +PAIRLANE_ENDPOINT=https://getpairlane.com \ + npx pairlane send /path/to/file ``` 既存ルームに参加したい場合は `--room-id` を明示指定してください。 diff --git a/README.md b/README.md index 7a2d12d..be332bb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Share Files +# Pairlane [日本語](./README.ja.md) | [中文](./README.zh.md) -**Live Demo: https://share-files.karakuri-maker.com/** +**Live Demo: https://getpairlane.com/** A P2P file sharing tool using WebRTC. Transfer files directly between browsers without going through a server. @@ -41,6 +41,15 @@ The `cli/` directory contains a Rust-based CLI that can send or receive files us Builds are automatically tested via GitHub Actions on push/PR to the `cli/` directory. +### Quick Start (npx) + +```sh +npx pairlane send /path/to/file +npx pairlane receive --output-dir ./downloads +``` + +### Build from source + ```sh cd cli cargo run --release -- send /path/to/file @@ -50,8 +59,8 @@ cargo run --release -- receive --output-dir ./downloads Encrypted transfers are enabled by default for `send`. The command prints a room URL with `#k=...` that you can pass directly to `receive`: ```sh -cargo run --release -- send /path/to/file -cargo run --release -- receive "https://share-files.karakuri-maker.com/r/ROOM#k=..." +npx pairlane send /path/to/file +npx pairlane receive "https://getpairlane.com/r/ROOM#k=..." ``` To disable encryption for `send`, pass `--no-encrypt`. @@ -59,18 +68,18 @@ To disable encryption for `send`, pass `--no-encrypt`. If you want to provide the decryption key explicitly, pass `--key` (base64url) to `receive`: ```sh -cargo run --release -- receive --key --output-dir ./downloads +npx pairlane receive --key --output-dir ./downloads ``` By default, `send` and `receive` exit after a successful transfer. Use `--stay-open` to keep the process running for additional transfers. Note: URLs with `#k=...` should be quoted in the shell. Legacy flags `--file` and `--room-id` are still accepted. -By default it connects to the demo endpoint. Override it with the `SHARE_FILES_ENDPOINT` environment variable: +By default it connects to the demo endpoint. Override it with the `PAIRLANE_ENDPOINT` environment variable (legacy `SHARE_FILES_ENDPOINT` also supported): ```sh -SHARE_FILES_ENDPOINT=https://share-files.karakuri-maker.com \ - cargo run --release -- send /path/to/file +PAIRLANE_ENDPOINT=https://getpairlane.com \ + npx pairlane send /path/to/file ``` You can also provide `--room-id` explicitly if you want to join an existing room. diff --git a/README.zh.md b/README.zh.md index 7619262..e37d4e6 100644 --- a/README.zh.md +++ b/README.zh.md @@ -1,8 +1,8 @@ -# Share Files +# Pairlane [English](./README.md) | [日本語](./README.ja.md) -**在线演示: https://share-files.karakuri-maker.com/** +**在线演示: https://getpairlane.com/** 基于WebRTC的P2P文件共享工具。无需经过服务器,直接在浏览器之间传输文件。 @@ -41,6 +41,15 @@ 通过GitHub Actions,在向 `cli/` 目录push/PR时自动测试构建。 +### npx快速开始 + +```sh +npx pairlane send /path/to/file +npx pairlane receive --output-dir ./downloads +``` + +### 从源码构建 + ```sh cd cli cargo run --release -- send /path/to/file @@ -50,8 +59,8 @@ cargo run --release -- receive --output-dir ./downloads `send` 默认启用加密。`send` 输出的带有 `#k=...` 的URL可以直接传给 `receive`: ```sh -cargo run --release -- send /path/to/file -cargo run --release -- receive "https://share-files.karakuri-maker.com/r/ROOM#k=..." +npx pairlane send /path/to/file +npx pairlane receive "https://getpairlane.com/r/ROOM#k=..." ``` 要禁用加密,请使用 `--no-encrypt`。 @@ -59,18 +68,18 @@ cargo run --release -- receive "https://share-files.karakuri-maker.com/r/ROOM#k= 如需显式指定解密密钥,请向 `receive` 传递 `--key`(base64url编码): ```sh -cargo run --release -- receive --key --output-dir ./downloads +npx pairlane receive --key --output-dir ./downloads ``` 默认情况下,`send` 和 `receive` 在传输成功后会退出。如需保持运行以进行更多传输,请使用 `--stay-open`。 ※ 包含 `#k=...` 的URL在shell中需要用引号包裹。传统的 `--file` / `--room-id` 参数仍然可用。 -默认连接到演示环境。可通过 `SHARE_FILES_ENDPOINT` 环境变量覆盖: +默认连接到演示环境。可通过 `PAIRLANE_ENDPOINT` 环境变量覆盖(旧 `SHARE_FILES_ENDPOINT` 也可用): ```sh -SHARE_FILES_ENDPOINT=https://share-files.karakuri-maker.com \ - cargo run --release -- send /path/to/file +PAIRLANE_ENDPOINT=https://getpairlane.com \ + npx pairlane send /path/to/file ``` 如需加入现有房间,请显式指定 `--room-id`。 diff --git a/bun.lock b/bun.lock index a7bdb37..dad1bbd 100644 --- a/bun.lock +++ b/bun.lock @@ -3,7 +3,7 @@ "configVersion": 0, "workspaces": { "": { - "name": "share-files", + "name": "pairlane", "dependencies": { "@cloudflare/vite-plugin": "^1.20.0", "hono": "^4.11.3", diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 17e3f9c..f926d4b 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -1283,6 +1283,29 @@ dependencies = [ "sha2", ] +[[package]] +name = "pairlane-cli" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "anyhow", + "base64 0.22.1", + "bytes", + "chrono", + "clap", + "futures-util", + "getrandom 0.2.16", + "mime_guess", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite", + "url", + "uuid", + "webrtc", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1919,29 +1942,6 @@ dependencies = [ "digest", ] -[[package]] -name = "share-files-cli" -version = "0.1.0" -dependencies = [ - "aes-gcm", - "anyhow", - "base64 0.22.1", - "bytes", - "chrono", - "clap", - "futures-util", - "getrandom 0.2.16", - "mime_guess", - "reqwest", - "serde", - "serde_json", - "tokio", - "tokio-tungstenite", - "url", - "uuid", - "webrtc", -] - [[package]] name = "shlex" version = "1.3.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 7b02f64..93461a9 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,8 +1,12 @@ [package] -name = "share-files-cli" +name = "pairlane-cli" version = "0.1.0" edition = "2021" +[[bin]] +name = "pairlane" +path = "src/main.rs" + [dependencies] aes-gcm = "0.10" anyhow = "1.0" diff --git a/cli/npm/bin/pairlane b/cli/npm/bin/pairlane new file mode 100755 index 0000000..ee530fe --- /dev/null +++ b/cli/npm/bin/pairlane @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const { spawnSync } = require("node:child_process"); +const { existsSync } = require("node:fs"); +const { join } = require("node:path"); + +const binaryPath = join(__dirname, "..", "dist", "pairlane"); + +if (!existsSync(binaryPath)) { + console.error("pairlane: binary not found. Reinstall to download the CLI."); + process.exit(1); +} + +const result = spawnSync(binaryPath, process.argv.slice(2), { stdio: "inherit" }); +if (result.error) { + console.error("pairlane: failed to launch the CLI binary."); + process.exit(1); +} + +process.exit(result.status ?? 1); diff --git a/cli/npm/package.json b/cli/npm/package.json new file mode 100644 index 0000000..4c2505f --- /dev/null +++ b/cli/npm/package.json @@ -0,0 +1,31 @@ +{ + "name": "pairlane", + "version": "0.1.0", + "description": "P2P file transfer CLI for Pairlane", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/kiyo-e/p2p-share-files.git" + }, + "engines": { + "node": ">=18" + }, + "bin": { + "pairlane": "bin/pairlane" + }, + "files": [ + "bin/", + "scripts/" + ], + "os": [ + "darwin", + "linux" + ], + "cpu": [ + "x64", + "arm64" + ], + "scripts": { + "postinstall": "node scripts/postinstall.js" + } +} diff --git a/cli/npm/scripts/postinstall.js b/cli/npm/scripts/postinstall.js new file mode 100644 index 0000000..8ae1325 --- /dev/null +++ b/cli/npm/scripts/postinstall.js @@ -0,0 +1,66 @@ +const { execFileSync } = require("node:child_process"); +const { createWriteStream, mkdirSync, readFileSync, unlinkSync, chmodSync } = require("node:fs"); +const { join } = require("node:path"); +const https = require("node:https"); + +const repo = "kiyo-e/p2p-share-files"; +const pkgPath = join(__dirname, "..", "package.json"); +const pkg = JSON.parse(readFileSync(pkgPath, "utf8")); +const version = pkg.version; + +const target = resolveTarget(process.platform, process.arch); +const asset = `pairlane-${version}-${target}.tar.gz`; +const url = `https://github.com/${repo}/releases/download/v${version}/${asset}`; + +const distDir = join(__dirname, "..", "dist"); +mkdirSync(distDir, { recursive: true }); + +const archivePath = join(distDir, asset); + +main().catch((err) => { + console.error("pairlane: failed to download the CLI binary."); + console.error(err.message || err); + process.exit(1); +}); + +async function main() { + await download(url, archivePath); + execFileSync("tar", ["-xzf", archivePath, "-C", distDir]); + chmodSync(join(distDir, "pairlane"), 0o755); + unlinkSync(archivePath); +} + +function resolveTarget(platform, arch) { + if (platform === "linux" && arch === "x64") { + return "x86_64-unknown-linux-gnu"; + } + if (platform === "darwin" && arch === "x64") { + return "x86_64-apple-darwin"; + } + if (platform === "darwin" && arch === "arm64") { + return "aarch64-apple-darwin"; + } + throw new Error(`Unsupported platform: ${platform} ${arch}`); +} + +function download(url, dest) { + return new Promise((resolve, reject) => { + https + .get(url, (res) => { + if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { + res.resume(); + download(res.headers.location, dest).then(resolve, reject); + return; + } + if (res.statusCode !== 200) { + reject(new Error(`Download failed with status ${res.statusCode}`)); + res.resume(); + return; + } + const file = createWriteStream(dest); + res.pipe(file); + file.on("finish", () => file.close(resolve)); + }) + .on("error", (err) => reject(err)); + }); +} diff --git a/cli/src/main.rs b/cli/src/main.rs index a87a5dd..37cfe7c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -42,9 +42,10 @@ const AES_NONCE_LEN: usize = 12; const AES_TAG_LEN: usize = 16; const MAX_FRAME_SIZE: usize = 16 * 1024; +// Design: see README.md and docs/signaling-protocol.md; related to Command and transfer helpers below. #[derive(Parser, Debug)] -#[command(name = "share-files-cli")] -#[command(about = "P2P file transfer CLI for share-files")] +#[command(name = "pairlane")] +#[command(about = "P2P file transfer CLI for Pairlane")] struct Cli { #[command(subcommand)] command: Command, @@ -1030,8 +1031,10 @@ fn build_room_url_with_key(endpoint: Option<&str>, room_id: &str, key: Option<&[ } fn base_endpoint_url(endpoint: Option<&str>) -> Result { - let default_endpoint = "https://share-files.karakuri-maker.com"; - let env_endpoint = env::var("SHARE_FILES_ENDPOINT").ok(); + let default_endpoint = "https://getpairlane.com"; + let env_endpoint = env::var("PAIRLANE_ENDPOINT") + .ok() + .or_else(|| env::var("SHARE_FILES_ENDPOINT").ok()); let endpoint = endpoint .map(|value| value.to_string()) .or(env_endpoint) diff --git a/package.json b/package.json index b8bb58c..93313d6 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "share-files", + "name": "pairlane", "type": "module", "scripts": { "dev": "vite dev", diff --git a/public/style.css b/public/style.css index 75daeff..baa04fd 100644 --- a/public/style.css +++ b/public/style.css @@ -546,7 +546,7 @@ input[type="checkbox"] { .stepProgress { display: flex; gap: 4px; - margin-top: 16px; + margin-bottom: 12px; } .stepDot { @@ -731,10 +731,33 @@ input[type="checkbox"] { } @media (max-width: 600px) { + /* Compact site header on mobile */ .topbar { - flex-direction: column; - align-items: flex-start; - gap: 12px; + flex-direction: row; + align-items: center; + padding: 8px 16px; + gap: 8px; + } + + .topbar .brand { + font-size: 16px; + } + + .topbar .sub { + display: none; + } + + .topbar .langSwitcher { + margin-left: auto; + } + + .topbar .langBtn { + padding: 4px 8px; + font-size: 10px; + } + + .container { + padding: 16px; } .homeHero, @@ -763,3 +786,226 @@ input[type="checkbox"] { font-size: 28px; } } + +/* ===== Receiver Mode Styles ===== */ + +/* Compact header for receiver */ +.roomHeader.compact { + padding: 16px 24px; + border-bottom: var(--border); +} + +.roomIdCompact { + margin: 4px 0; +} + +/* Receiver layout: no sidebar column */ +.roomGrid.receiverLayout { + grid-template-columns: 1fr; +} + +/* Compact inline sidebar for receiver */ +.roomSideCompact { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 24px; + border-bottom: var(--border); + background: rgba(0, 0, 0, 0.02); +} + +.sideDot { + width: 4px; + height: 4px; + background: var(--gray); + border-radius: 50%; +} + +/* Receiver step guide: larger and more prominent */ +.stepGuide.receiverGuide { + padding: 32px; + text-align: center; +} + +.stepGuide.receiverGuide .stepProgress { + justify-content: center; + margin-bottom: 16px; +} + +.stepGuide.receiverGuide .stepDot { + width: 32px; + height: 6px; +} + +.stepGuide.receiverGuide .stepLabel { + margin-bottom: 12px; +} + +.stepGuide.receiverGuide .stepMain { + font-size: 36px; +} + +.stepGuide.receiverGuide .stepSub { + font-size: 14px; + margin-top: 12px; + max-width: 320px; + margin-left: auto; + margin-right: auto; +} + +/* Receiver pane centered */ +.receiverMode .roomMain { + padding: 24px; +} + +.receiverMode #receiverPane { + text-align: center; + padding: 24px 0; +} + +.receiverMode #receiverPane .muted { + font-size: 14px; +} + +.receiverMode #downloadArea { + flex-direction: column; + text-align: center; + padding: 32px; +} + +.receiverMode #downloadArea .btn { + width: 100%; + max-width: 280px; + justify-content: center; +} + +/* ===== Sender Mode: Mobile/Desktop Toggle ===== */ + +/* Desktop: show full layout, hide mobile elements */ +.senderHeaderMobile, +.senderSideMobile { + display: none; +} + +.senderHeaderDesktop, +.senderSideDesktop { + display: flex; +} + +.senderSideDesktop { + display: flex; + flex-direction: column; +} + +/* Sender mobile layout */ +.senderMode .roomGrid { + grid-template-columns: 280px 1fr; +} + +/* Small button style */ +.btnSmall { + padding: 8px 12px; + font-size: 10px; +} + +/* Mobile: sender compact layout */ +@media (max-width: 600px) { + /* Hide desktop elements, show mobile elements */ + .senderHeaderDesktop, + .senderSideDesktop { + display: none !important; + } + + .senderHeaderMobile, + .senderSideMobile { + display: flex !important; + } + + /* Sender uses single column on mobile */ + .senderMode .roomGrid { + grid-template-columns: 1fr; + } + + /* Sender step guide: compact on mobile */ + .senderMode .stepGuide { + padding: 16px; + margin-bottom: 16px; + } + + .senderMode .stepGuide .stepMain { + font-size: 24px; + } + + .senderMode .stepGuide .stepSub { + font-size: 11px; + margin-top: 4px; + } + + .senderMode .stepGuide .stepProgress { + margin-bottom: 8px; + } + + .senderMode .stepGuide .stepLabel { + margin-bottom: 4px; + } + + /* Drop zone: mobile optimized */ + .senderMode .drop { + padding: 24px 16px; + } + + .senderMode .dropTitle { + font-size: 18px; + text-align: center; + white-space: nowrap; + } + + .senderMode .drop .btn { + width: 100%; + justify-content: center; + padding: 16px; + } + + /* Room main padding */ + .senderMode .roomMain { + padding: 16px; + } + + /* Send button row */ + .senderMode .row.gap.wrap { + flex-direction: column; + } + + .senderMode .row.gap.wrap .btn.primary { + width: 100%; + justify-content: center; + } +} + +/* Mobile receiver: even more compact */ +@media (max-width: 600px) { + .roomHeader.compact { + padding: 12px 16px; + } + + .roomSideCompact { + padding: 10px 16px; + font-size: 11px; + } + + .stepGuide.receiverGuide { + padding: 24px 16px; + } + + .stepGuide.receiverGuide .stepMain { + font-size: 22px; + } + + .stepGuide.receiverGuide .stepSub { + font-size: 12px; + } + + .receiverMode .roomMain { + padding: 16px; + } +} diff --git a/src/client/home.tsx b/src/client/home.tsx index 29606d4..701b4e5 100644 --- a/src/client/home.tsx +++ b/src/client/home.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource hono/jsx/dom */ /** - * Home client app for share-files. + * Home client app for Pairlane. * See README.md; pairs with src/ui/top.tsx. */ @@ -162,7 +162,7 @@ function b64urlEncode(u8: Uint8Array) { } function getClientId() { - const key = "share-files-client-id"; + const key = "pairlane-client-id"; const stored = localStorage.getItem(key); if (stored) return stored; const id = crypto.randomUUID(); diff --git a/src/client/room.tsx b/src/client/room.tsx index e0ae5eb..8f4efe9 100644 --- a/src/client/room.tsx +++ b/src/client/room.tsx @@ -1,6 +1,6 @@ /** @jsxImportSource hono/jsx/dom */ /** - * Room client app for share-files. + * Room client app for Pairlane. * See README.md; pairs with src/ui/room.tsx. */ @@ -119,6 +119,12 @@ function RoomApp({ roomId, maxConcurrent }: RoomAppProps) { useEffect(() => { roleRef.current = role; + // Add body class for receiver mode styling (affects header etc.) + if (role === "answerer") { + document.body.classList.add("receiverMode"); + } else { + document.body.classList.remove("receiverMode"); + } }, [role]); useEffect(() => { @@ -768,38 +774,82 @@ function RoomApp({ roomId, maxConcurrent }: RoomAppProps) { .replace("{total}", "4"); return ( -
-
-
-
{t.room.eyebrow}
-

- {t.room.roomLabel} {roomId} -

-
{status}
+
+ {/* Desktop header for sender */} + {showSender && ( +
+
+
{t.room.eyebrow}
+

+ {t.room.roomLabel} {roomId} +

+
{status}
+
+
+ + +
-
- - + )} + {/* Mobile header for sender */} + {showSender && ( +
+
+
+ {t.room.roomLabel} {roomId} +
+
+
+ +
-
- -
-
-
-
{t.room.roleLabel}
-
{roleLabel}
-
{t.room.peersLabel}
-
{peersLabel}
+ )} + {/* Receiver header */} + {showReceiver && ( +
+
+
{t.room.eyebrow}
+
+ {t.room.roomLabel} {roomId} +
+
{status}
-
{t.room.encryptHint}
-
{maxConcurrentLabel}
+ )} + +
+ {/* Desktop sidebar for sender */} + {showSender && ( +
+
+
{t.room.roleLabel}
+
{roleLabel}
+
{t.room.peersLabel}
+
{peersLabel}
+
+
{t.room.encryptHint}
+
{maxConcurrentLabel}
+
+ )} + {/* Mobile compact sidebar for sender */} + {showSender && ( +
+ {roleLabel} + + {t.room.peersLabel} {peersLabel} +
+ )} + {/* Receiver sidebar */} + {showReceiver && ( +
+ {roleLabel} + + {t.room.peersLabel} {peersLabel} +
+ )}
-
-
{stepLabel}
-
{guideState.main}
-
{guideState.sub}
+
{[1, 2, 3, 4].map((n) => (
))}
+
{stepLabel}
+
{guideState.main}
+
{guideState.sub}
@@ -957,7 +1010,7 @@ function formatBytes(n: number) { } function getClientId() { - const key = "share-files-client-id"; + const key = "pairlane-client-id"; const stored = localStorage.getItem(key); if (stored) return stored; const id = crypto.randomUUID(); diff --git a/src/i18n/client.ts b/src/i18n/client.ts index dd28bae..3a35c1f 100644 --- a/src/i18n/client.ts +++ b/src/i18n/client.ts @@ -1,5 +1,5 @@ /** - * Client-side i18n helpers for share-files. + * Client-side i18n helpers for Pairlane. * Uses translations injected by the server via window.__TRANSLATIONS__ */ diff --git a/src/i18n/index.ts b/src/i18n/index.ts index a779a8c..4e7919d 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -1,5 +1,5 @@ /** - * Simple i18n module for share-files. + * Simple i18n module for Pairlane. * Supports: ja, en, zh */ diff --git a/src/locales/en.json b/src/locales/en.json index 917465f..60ec15f 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1,9 +1,9 @@ { "lang": "en", "langName": "English", - "title": "SHARE-FILES", + "title": "PAIRLANE", "header": { - "brand": "SHARE—FILES", + "brand": "PAIR—LANE", "sub": "P2P / 1:1 / WEBRTC" }, "home": { diff --git a/src/locales/ja.json b/src/locales/ja.json index 602c747..ebeb3dd 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1,9 +1,9 @@ { "lang": "ja", "langName": "日本語", - "title": "SHARE-FILES", + "title": "PAIRLANE", "header": { - "brand": "SHARE—FILES", + "brand": "PAIR—LANE", "sub": "P2P / 1:1 / WEBRTC" }, "home": { diff --git a/src/locales/zh.json b/src/locales/zh.json index 466952d..136b4af 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -1,9 +1,9 @@ { "lang": "zh", "langName": "中文", - "title": "SHARE-FILES", + "title": "PAIRLANE", "header": { - "brand": "SHARE—FILES", + "brand": "PAIR—LANE", "sub": "P2P / 1:1 / WEBRTC" }, "home": { diff --git a/src/ui/layout.tsx b/src/ui/layout.tsx index bb8e9f9..0971f15 100644 --- a/src/ui/layout.tsx +++ b/src/ui/layout.tsx @@ -1,5 +1,5 @@ /** - * Layout shell for share-files pages. + * Layout shell for Pairlane pages. * See README.md for the flow overview; used by TopPage and RoomPage. */ diff --git a/src/ui/room.tsx b/src/ui/room.tsx index 2f4b1bb..644c97c 100644 --- a/src/ui/room.tsx +++ b/src/ui/room.tsx @@ -1,5 +1,5 @@ /** - * Room page UI for share-files. + * Room page UI for Pairlane. * See README.md for the flow; pairs with the Room Durable Object and room script. */ @@ -52,15 +52,15 @@ export function RoomPage({ roomId, maxConcurrent, t, locale }: RoomPageProps) {
-
{initialStepLabel}
-
{t.guide.receiverConnecting}
-
{t.guide.receiverConnectingSub}
+
{initialStepLabel}
+
{t.guide.receiverConnecting}
+
{t.guide.receiverConnectingSub}