2026-05-08 21:12:26 +07:00
|
|
|
|
/**
|
|
|
|
|
|
* PinPickerDialog
|
|
|
|
|
|
*
|
|
|
|
|
|
* Touch-friendly modal for picking a pin from a component or board. Shown
|
|
|
|
|
|
* primarily on mobile when the user can't reliably hit a 12px pin overlay.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Used in two flows:
|
|
|
|
|
|
* 1. Wire in progress + user taps a component or board body → this dialog
|
|
|
|
|
|
* lists every pin so they can finish the wire by tapping a name.
|
|
|
|
|
|
* 2. (Future) Long-press on a board → start a wire from a chosen pin.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Touch propagation: same trick as SelectionActionBar / WireModeBanner — bind
|
|
|
|
|
|
* native listeners to stop the canvas's own touch handler from running
|
|
|
|
|
|
* preventDefault and swallowing our synthetic clicks.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
feat(i18n): translate small editor remates + admin internals (Blocks 11+12)
Block 11 — small editor surfaces
- PinPickerDialog: close, filter pins, no-match, rotate / delete
action buttons.
- RaspberryPiWorkspace: connection status (Connected / Starting…
/ Offline), Start Pi button + tooltip, Connect / Disconnect
buttons + tooltips, Terminal tab label, file-tab close, the
full offline overlay (title / subtitle / start CTA / two-line
note about the staging area), terminal-loading + no-file-
selected fallbacks.
- GitHubStarBanner: aria-label, title, body copy, Star CTA,
dismiss button.
- ExampleLoaderPage: "Loading example…" status, "Example {{id}}
not found." 404 line, "Browse all examples" recovery link.
/editor and /examples links wrapped in localize().
Block 12 — admin-only internals (only admins ever see these)
- AdminBoardsTab: section headings ("By board family" / "By
exact FQBN"), full table column headers (Family / FQBN /
Compiles / Errors / Success rate / Runs / Distinct users /
Distinct projects), range selector label, no-data fallback,
load-failed error.
- AdminDashboardTab: 8 KPI cards (Total users / Total projects
/ Compiles / Runs / DAU / WAU / MAU / Public-Private), all
chart titles + subtitles (Activity over time, Compiles by
board family, Board diversity for pricing signal, Top FQBNs,
Top countries with Cloudflare disclaimer, Top users / Top
projects), table column headers, no-data fallbacks, load
errors. Pluralised "{{count}} board(s)" via i18next plurals
in the diversity pie chart.
- UserActivityModal: title with username interpolation, subtitle,
range selector label, table column headers (Date / Project /
Compiles / Errors / Runs / Saves), pluralised
"{{count}} project(s)" line, deleted-project / no-project
placeholder strings, no-activity empty state, load-failed.
All 8 non-English locales auto-translated via the
`scripts/translate-i18n.mjs` DeepSeek pipeline (one --force run,
~7 min). sameShape() validation passed on every output.
2026-05-10 02:22:16 +07:00
|
|
|
|
import { useTranslation } from 'react-i18next';
|
2026-05-08 21:12:26 +07:00
|
|
|
|
|
|
|
|
|
|
interface PinInfoLike {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
x: number;
|
|
|
|
|
|
y: number;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface PinPickerDialogProps {
|
|
|
|
|
|
/** Component or board ID — used by the parent to compute world-space pin coords. */
|
|
|
|
|
|
targetId: string;
|
|
|
|
|
|
/** Display label, e.g. "Arduino Uno" or "7 Segment". */
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
/** Subtitle hint, e.g. "Tap a pin to connect". */
|
|
|
|
|
|
subtitle?: string;
|
|
|
|
|
|
/** Pin list to render. */
|
|
|
|
|
|
pins: PinInfoLike[];
|
|
|
|
|
|
onPinSelect: (targetId: string, pinName: string) => void;
|
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
|
/** Optional rotate action — only meaningful for components. */
|
|
|
|
|
|
onRotate?: () => void;
|
|
|
|
|
|
/** Optional delete action — closes the dialog and removes the target. */
|
|
|
|
|
|
onDelete?: () => void;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const PinPickerDialog: React.FC<PinPickerDialogProps> = ({
|
|
|
|
|
|
targetId,
|
|
|
|
|
|
title,
|
|
|
|
|
|
subtitle,
|
|
|
|
|
|
pins,
|
|
|
|
|
|
onPinSelect,
|
|
|
|
|
|
onClose,
|
|
|
|
|
|
onRotate,
|
|
|
|
|
|
onDelete,
|
|
|
|
|
|
}) => {
|
feat(i18n): translate small editor remates + admin internals (Blocks 11+12)
Block 11 — small editor surfaces
- PinPickerDialog: close, filter pins, no-match, rotate / delete
action buttons.
- RaspberryPiWorkspace: connection status (Connected / Starting…
/ Offline), Start Pi button + tooltip, Connect / Disconnect
buttons + tooltips, Terminal tab label, file-tab close, the
full offline overlay (title / subtitle / start CTA / two-line
note about the staging area), terminal-loading + no-file-
selected fallbacks.
- GitHubStarBanner: aria-label, title, body copy, Star CTA,
dismiss button.
- ExampleLoaderPage: "Loading example…" status, "Example {{id}}
not found." 404 line, "Browse all examples" recovery link.
/editor and /examples links wrapped in localize().
Block 12 — admin-only internals (only admins ever see these)
- AdminBoardsTab: section headings ("By board family" / "By
exact FQBN"), full table column headers (Family / FQBN /
Compiles / Errors / Success rate / Runs / Distinct users /
Distinct projects), range selector label, no-data fallback,
load-failed error.
- AdminDashboardTab: 8 KPI cards (Total users / Total projects
/ Compiles / Runs / DAU / WAU / MAU / Public-Private), all
chart titles + subtitles (Activity over time, Compiles by
board family, Board diversity for pricing signal, Top FQBNs,
Top countries with Cloudflare disclaimer, Top users / Top
projects), table column headers, no-data fallbacks, load
errors. Pluralised "{{count}} board(s)" via i18next plurals
in the diversity pie chart.
- UserActivityModal: title with username interpolation, subtitle,
range selector label, table column headers (Date / Project /
Compiles / Errors / Runs / Saves), pluralised
"{{count}} project(s)" line, deleted-project / no-project
placeholder strings, no-activity empty state, load-failed.
All 8 non-English locales auto-translated via the
`scripts/translate-i18n.mjs` DeepSeek pipeline (one --force run,
~7 min). sameShape() validation passed on every output.
2026-05-10 02:22:16 +07:00
|
|
|
|
const { t } = useTranslation();
|
2026-05-08 21:12:26 +07:00
|
|
|
|
const overlayRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
|
|
const [filter, setFilter] = useState('');
|
|
|
|
|
|
|
|
|
|
|
|
// Stop native touch events from reaching the simulator canvas listener.
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const el = overlayRef.current;
|
|
|
|
|
|
if (!el) return;
|
|
|
|
|
|
const stop = (e: Event) => e.stopPropagation();
|
|
|
|
|
|
el.addEventListener('touchstart', stop, { passive: false });
|
|
|
|
|
|
el.addEventListener('touchmove', stop, { passive: false });
|
|
|
|
|
|
el.addEventListener('touchend', stop, { passive: false });
|
|
|
|
|
|
el.addEventListener('mousedown', stop);
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
el.removeEventListener('touchstart', stop);
|
|
|
|
|
|
el.removeEventListener('touchmove', stop);
|
|
|
|
|
|
el.removeEventListener('touchend', stop);
|
|
|
|
|
|
el.removeEventListener('mousedown', stop);
|
|
|
|
|
|
};
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
const filteredPins = useMemo(() => {
|
|
|
|
|
|
const q = filter.trim().toLowerCase();
|
|
|
|
|
|
if (!q) return pins;
|
|
|
|
|
|
return pins.filter(
|
|
|
|
|
|
(p) =>
|
|
|
|
|
|
p.name.toLowerCase().includes(q) ||
|
|
|
|
|
|
(p.description && p.description.toLowerCase().includes(q)),
|
|
|
|
|
|
);
|
|
|
|
|
|
}, [pins, filter]);
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
ref={overlayRef}
|
|
|
|
|
|
role="dialog"
|
|
|
|
|
|
aria-modal="true"
|
|
|
|
|
|
aria-label={`Pick a pin on ${title}`}
|
|
|
|
|
|
onClick={(e) => {
|
|
|
|
|
|
// Click on backdrop (not the panel itself) closes the dialog.
|
|
|
|
|
|
if (e.target === overlayRef.current) onClose();
|
|
|
|
|
|
}}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
|
inset: 0,
|
|
|
|
|
|
background: 'rgba(0, 0, 0, 0.5)',
|
|
|
|
|
|
zIndex: 1100,
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
alignItems: 'flex-end',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
padding: 8,
|
|
|
|
|
|
touchAction: 'none',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
background: '#2d2d2d',
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
border: '1px solid #555',
|
|
|
|
|
|
borderRadius: 12,
|
|
|
|
|
|
width: '100%',
|
|
|
|
|
|
maxWidth: 420,
|
|
|
|
|
|
maxHeight: '90%',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
|
boxShadow: '0 -8px 24px rgba(0,0,0,0.6)',
|
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
|
fontFamily:
|
|
|
|
|
|
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{/* Header */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
gap: 8,
|
|
|
|
|
|
padding: '12px 14px',
|
|
|
|
|
|
borderBottom: '1px solid #444',
|
|
|
|
|
|
flexShrink: 0,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0 }}>
|
|
|
|
|
|
<span style={{ fontSize: 14, fontWeight: 600 }}>{title}</span>
|
|
|
|
|
|
{subtitle && (
|
|
|
|
|
|
<span style={{ fontSize: 12, color: '#9aa', marginTop: 2 }}>{subtitle}</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={onClose}
|
feat(i18n): translate small editor remates + admin internals (Blocks 11+12)
Block 11 — small editor surfaces
- PinPickerDialog: close, filter pins, no-match, rotate / delete
action buttons.
- RaspberryPiWorkspace: connection status (Connected / Starting…
/ Offline), Start Pi button + tooltip, Connect / Disconnect
buttons + tooltips, Terminal tab label, file-tab close, the
full offline overlay (title / subtitle / start CTA / two-line
note about the staging area), terminal-loading + no-file-
selected fallbacks.
- GitHubStarBanner: aria-label, title, body copy, Star CTA,
dismiss button.
- ExampleLoaderPage: "Loading example…" status, "Example {{id}}
not found." 404 line, "Browse all examples" recovery link.
/editor and /examples links wrapped in localize().
Block 12 — admin-only internals (only admins ever see these)
- AdminBoardsTab: section headings ("By board family" / "By
exact FQBN"), full table column headers (Family / FQBN /
Compiles / Errors / Success rate / Runs / Distinct users /
Distinct projects), range selector label, no-data fallback,
load-failed error.
- AdminDashboardTab: 8 KPI cards (Total users / Total projects
/ Compiles / Runs / DAU / WAU / MAU / Public-Private), all
chart titles + subtitles (Activity over time, Compiles by
board family, Board diversity for pricing signal, Top FQBNs,
Top countries with Cloudflare disclaimer, Top users / Top
projects), table column headers, no-data fallbacks, load
errors. Pluralised "{{count}} board(s)" via i18next plurals
in the diversity pie chart.
- UserActivityModal: title with username interpolation, subtitle,
range selector label, table column headers (Date / Project /
Compiles / Errors / Runs / Saves), pluralised
"{{count}} project(s)" line, deleted-project / no-project
placeholder strings, no-activity empty state, load-failed.
All 8 non-English locales auto-translated via the
`scripts/translate-i18n.mjs` DeepSeek pipeline (one --force run,
~7 min). sameShape() validation passed on every output.
2026-05-10 02:22:16 +07:00
|
|
|
|
aria-label={t('editor.pinPicker.close')}
|
2026-05-08 21:12:26 +07:00
|
|
|
|
style={{
|
|
|
|
|
|
background: 'transparent',
|
|
|
|
|
|
border: 'none',
|
|
|
|
|
|
color: '#ccc',
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
fontSize: 22,
|
|
|
|
|
|
width: 36,
|
|
|
|
|
|
height: 36,
|
|
|
|
|
|
borderRadius: 6,
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
×
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Filter input — handy when boards have 30+ pins */}
|
|
|
|
|
|
{pins.length > 8 && (
|
|
|
|
|
|
<div style={{ padding: '8px 12px', flexShrink: 0 }}>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={filter}
|
|
|
|
|
|
onChange={(e) => setFilter(e.target.value)}
|
feat(i18n): translate small editor remates + admin internals (Blocks 11+12)
Block 11 — small editor surfaces
- PinPickerDialog: close, filter pins, no-match, rotate / delete
action buttons.
- RaspberryPiWorkspace: connection status (Connected / Starting…
/ Offline), Start Pi button + tooltip, Connect / Disconnect
buttons + tooltips, Terminal tab label, file-tab close, the
full offline overlay (title / subtitle / start CTA / two-line
note about the staging area), terminal-loading + no-file-
selected fallbacks.
- GitHubStarBanner: aria-label, title, body copy, Star CTA,
dismiss button.
- ExampleLoaderPage: "Loading example…" status, "Example {{id}}
not found." 404 line, "Browse all examples" recovery link.
/editor and /examples links wrapped in localize().
Block 12 — admin-only internals (only admins ever see these)
- AdminBoardsTab: section headings ("By board family" / "By
exact FQBN"), full table column headers (Family / FQBN /
Compiles / Errors / Success rate / Runs / Distinct users /
Distinct projects), range selector label, no-data fallback,
load-failed error.
- AdminDashboardTab: 8 KPI cards (Total users / Total projects
/ Compiles / Runs / DAU / WAU / MAU / Public-Private), all
chart titles + subtitles (Activity over time, Compiles by
board family, Board diversity for pricing signal, Top FQBNs,
Top countries with Cloudflare disclaimer, Top users / Top
projects), table column headers, no-data fallbacks, load
errors. Pluralised "{{count}} board(s)" via i18next plurals
in the diversity pie chart.
- UserActivityModal: title with username interpolation, subtitle,
range selector label, table column headers (Date / Project /
Compiles / Errors / Runs / Saves), pluralised
"{{count}} project(s)" line, deleted-project / no-project
placeholder strings, no-activity empty state, load-failed.
All 8 non-English locales auto-translated via the
`scripts/translate-i18n.mjs` DeepSeek pipeline (one --force run,
~7 min). sameShape() validation passed on every output.
2026-05-10 02:22:16 +07:00
|
|
|
|
placeholder={t('editor.pinPicker.filterPins')}
|
2026-05-08 21:12:26 +07:00
|
|
|
|
autoFocus={false}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
width: '100%',
|
|
|
|
|
|
boxSizing: 'border-box',
|
|
|
|
|
|
padding: '8px 10px',
|
|
|
|
|
|
background: '#1f1f1f',
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
border: '1px solid #555',
|
|
|
|
|
|
borderRadius: 6,
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Pin list */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
overflowY: 'auto',
|
|
|
|
|
|
WebkitOverflowScrolling: 'touch',
|
|
|
|
|
|
overscrollBehavior: 'contain',
|
|
|
|
|
|
padding: '4px 8px 8px',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{filteredPins.length === 0 ? (
|
feat(i18n): translate small editor remates + admin internals (Blocks 11+12)
Block 11 — small editor surfaces
- PinPickerDialog: close, filter pins, no-match, rotate / delete
action buttons.
- RaspberryPiWorkspace: connection status (Connected / Starting…
/ Offline), Start Pi button + tooltip, Connect / Disconnect
buttons + tooltips, Terminal tab label, file-tab close, the
full offline overlay (title / subtitle / start CTA / two-line
note about the staging area), terminal-loading + no-file-
selected fallbacks.
- GitHubStarBanner: aria-label, title, body copy, Star CTA,
dismiss button.
- ExampleLoaderPage: "Loading example…" status, "Example {{id}}
not found." 404 line, "Browse all examples" recovery link.
/editor and /examples links wrapped in localize().
Block 12 — admin-only internals (only admins ever see these)
- AdminBoardsTab: section headings ("By board family" / "By
exact FQBN"), full table column headers (Family / FQBN /
Compiles / Errors / Success rate / Runs / Distinct users /
Distinct projects), range selector label, no-data fallback,
load-failed error.
- AdminDashboardTab: 8 KPI cards (Total users / Total projects
/ Compiles / Runs / DAU / WAU / MAU / Public-Private), all
chart titles + subtitles (Activity over time, Compiles by
board family, Board diversity for pricing signal, Top FQBNs,
Top countries with Cloudflare disclaimer, Top users / Top
projects), table column headers, no-data fallbacks, load
errors. Pluralised "{{count}} board(s)" via i18next plurals
in the diversity pie chart.
- UserActivityModal: title with username interpolation, subtitle,
range selector label, table column headers (Date / Project /
Compiles / Errors / Runs / Saves), pluralised
"{{count}} project(s)" line, deleted-project / no-project
placeholder strings, no-activity empty state, load-failed.
All 8 non-English locales auto-translated via the
`scripts/translate-i18n.mjs` DeepSeek pipeline (one --force run,
~7 min). sameShape() validation passed on every output.
2026-05-10 02:22:16 +07:00
|
|
|
|
<div style={{ padding: 16, color: '#999', fontSize: 13 }}>{t('editor.pinPicker.noMatch')}</div>
|
2026-05-08 21:12:26 +07:00
|
|
|
|
) : (
|
|
|
|
|
|
filteredPins.map((pin) => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={pin.name}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => onPinSelect(targetId, pin.name)}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
width: '100%',
|
|
|
|
|
|
textAlign: 'left',
|
|
|
|
|
|
padding: '12px 12px',
|
|
|
|
|
|
margin: '2px 0',
|
|
|
|
|
|
background: '#3d3d3d',
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
border: '1px solid transparent',
|
|
|
|
|
|
borderRadius: 6,
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
minHeight: 44,
|
|
|
|
|
|
}}
|
|
|
|
|
|
onMouseEnter={(e) => {
|
|
|
|
|
|
e.currentTarget.style.background = '#4a4a4a';
|
|
|
|
|
|
e.currentTarget.style.borderColor = '#007acc';
|
|
|
|
|
|
}}
|
|
|
|
|
|
onMouseLeave={(e) => {
|
|
|
|
|
|
e.currentTarget.style.background = '#3d3d3d';
|
|
|
|
|
|
e.currentTarget.style.borderColor = 'transparent';
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span style={{ color: '#00d9ff', fontWeight: 600, marginRight: 10 }}>
|
|
|
|
|
|
{pin.name}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{pin.description && (
|
|
|
|
|
|
<span style={{ color: '#aaa', fontSize: 12, flex: 1, minWidth: 0 }}>
|
|
|
|
|
|
{pin.description}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<span
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
style={{ marginLeft: 'auto', color: '#007acc', fontSize: 16, fontWeight: 700 }}
|
|
|
|
|
|
>
|
|
|
|
|
|
→
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Action footer — Rotate / Delete pinned at the bottom of the
|
|
|
|
|
|
sheet so they're always reachable, mirroring the property
|
|
|
|
|
|
dialog. Only rendered when the parent passes the handlers. */}
|
|
|
|
|
|
{(onRotate || onDelete) && (
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
gap: 8,
|
|
|
|
|
|
padding: '10px 12px',
|
|
|
|
|
|
borderTop: '1px solid #444',
|
|
|
|
|
|
background: '#252525',
|
|
|
|
|
|
flexShrink: 0,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{onRotate && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={onRotate}
|
|
|
|
|
|
style={footerButtonStyle}
|
feat(i18n): translate small editor remates + admin internals (Blocks 11+12)
Block 11 — small editor surfaces
- PinPickerDialog: close, filter pins, no-match, rotate / delete
action buttons.
- RaspberryPiWorkspace: connection status (Connected / Starting…
/ Offline), Start Pi button + tooltip, Connect / Disconnect
buttons + tooltips, Terminal tab label, file-tab close, the
full offline overlay (title / subtitle / start CTA / two-line
note about the staging area), terminal-loading + no-file-
selected fallbacks.
- GitHubStarBanner: aria-label, title, body copy, Star CTA,
dismiss button.
- ExampleLoaderPage: "Loading example…" status, "Example {{id}}
not found." 404 line, "Browse all examples" recovery link.
/editor and /examples links wrapped in localize().
Block 12 — admin-only internals (only admins ever see these)
- AdminBoardsTab: section headings ("By board family" / "By
exact FQBN"), full table column headers (Family / FQBN /
Compiles / Errors / Success rate / Runs / Distinct users /
Distinct projects), range selector label, no-data fallback,
load-failed error.
- AdminDashboardTab: 8 KPI cards (Total users / Total projects
/ Compiles / Runs / DAU / WAU / MAU / Public-Private), all
chart titles + subtitles (Activity over time, Compiles by
board family, Board diversity for pricing signal, Top FQBNs,
Top countries with Cloudflare disclaimer, Top users / Top
projects), table column headers, no-data fallbacks, load
errors. Pluralised "{{count}} board(s)" via i18next plurals
in the diversity pie chart.
- UserActivityModal: title with username interpolation, subtitle,
range selector label, table column headers (Date / Project /
Compiles / Errors / Runs / Saves), pluralised
"{{count}} project(s)" line, deleted-project / no-project
placeholder strings, no-activity empty state, load-failed.
All 8 non-English locales auto-translated via the
`scripts/translate-i18n.mjs` DeepSeek pipeline (one --force run,
~7 min). sameShape() validation passed on every output.
2026-05-10 02:22:16 +07:00
|
|
|
|
aria-label={t('editor.pinPicker.rotate')}
|
2026-05-08 21:12:26 +07:00
|
|
|
|
>
|
feat(i18n): translate small editor remates + admin internals (Blocks 11+12)
Block 11 — small editor surfaces
- PinPickerDialog: close, filter pins, no-match, rotate / delete
action buttons.
- RaspberryPiWorkspace: connection status (Connected / Starting…
/ Offline), Start Pi button + tooltip, Connect / Disconnect
buttons + tooltips, Terminal tab label, file-tab close, the
full offline overlay (title / subtitle / start CTA / two-line
note about the staging area), terminal-loading + no-file-
selected fallbacks.
- GitHubStarBanner: aria-label, title, body copy, Star CTA,
dismiss button.
- ExampleLoaderPage: "Loading example…" status, "Example {{id}}
not found." 404 line, "Browse all examples" recovery link.
/editor and /examples links wrapped in localize().
Block 12 — admin-only internals (only admins ever see these)
- AdminBoardsTab: section headings ("By board family" / "By
exact FQBN"), full table column headers (Family / FQBN /
Compiles / Errors / Success rate / Runs / Distinct users /
Distinct projects), range selector label, no-data fallback,
load-failed error.
- AdminDashboardTab: 8 KPI cards (Total users / Total projects
/ Compiles / Runs / DAU / WAU / MAU / Public-Private), all
chart titles + subtitles (Activity over time, Compiles by
board family, Board diversity for pricing signal, Top FQBNs,
Top countries with Cloudflare disclaimer, Top users / Top
projects), table column headers, no-data fallbacks, load
errors. Pluralised "{{count}} board(s)" via i18next plurals
in the diversity pie chart.
- UserActivityModal: title with username interpolation, subtitle,
range selector label, table column headers (Date / Project /
Compiles / Errors / Runs / Saves), pluralised
"{{count}} project(s)" line, deleted-project / no-project
placeholder strings, no-activity empty state, load-failed.
All 8 non-English locales auto-translated via the
`scripts/translate-i18n.mjs` DeepSeek pipeline (one --force run,
~7 min). sameShape() validation passed on every output.
2026-05-10 02:22:16 +07:00
|
|
|
|
<RotateGlyph /> {t('editor.pinPicker.rotate')}
|
2026-05-08 21:12:26 +07:00
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{onDelete && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={onDelete}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
...footerButtonStyle,
|
|
|
|
|
|
marginLeft: onRotate ? 0 : 'auto',
|
|
|
|
|
|
background: '#3a2424',
|
|
|
|
|
|
borderColor: '#5a2c2c',
|
|
|
|
|
|
color: '#e06c75',
|
|
|
|
|
|
}}
|
feat(i18n): translate small editor remates + admin internals (Blocks 11+12)
Block 11 — small editor surfaces
- PinPickerDialog: close, filter pins, no-match, rotate / delete
action buttons.
- RaspberryPiWorkspace: connection status (Connected / Starting…
/ Offline), Start Pi button + tooltip, Connect / Disconnect
buttons + tooltips, Terminal tab label, file-tab close, the
full offline overlay (title / subtitle / start CTA / two-line
note about the staging area), terminal-loading + no-file-
selected fallbacks.
- GitHubStarBanner: aria-label, title, body copy, Star CTA,
dismiss button.
- ExampleLoaderPage: "Loading example…" status, "Example {{id}}
not found." 404 line, "Browse all examples" recovery link.
/editor and /examples links wrapped in localize().
Block 12 — admin-only internals (only admins ever see these)
- AdminBoardsTab: section headings ("By board family" / "By
exact FQBN"), full table column headers (Family / FQBN /
Compiles / Errors / Success rate / Runs / Distinct users /
Distinct projects), range selector label, no-data fallback,
load-failed error.
- AdminDashboardTab: 8 KPI cards (Total users / Total projects
/ Compiles / Runs / DAU / WAU / MAU / Public-Private), all
chart titles + subtitles (Activity over time, Compiles by
board family, Board diversity for pricing signal, Top FQBNs,
Top countries with Cloudflare disclaimer, Top users / Top
projects), table column headers, no-data fallbacks, load
errors. Pluralised "{{count}} board(s)" via i18next plurals
in the diversity pie chart.
- UserActivityModal: title with username interpolation, subtitle,
range selector label, table column headers (Date / Project /
Compiles / Errors / Runs / Saves), pluralised
"{{count}} project(s)" line, deleted-project / no-project
placeholder strings, no-activity empty state, load-failed.
All 8 non-English locales auto-translated via the
`scripts/translate-i18n.mjs` DeepSeek pipeline (one --force run,
~7 min). sameShape() validation passed on every output.
2026-05-10 02:22:16 +07:00
|
|
|
|
aria-label={t('editor.pinPicker.delete')}
|
2026-05-08 21:12:26 +07:00
|
|
|
|
>
|
feat(i18n): translate small editor remates + admin internals (Blocks 11+12)
Block 11 — small editor surfaces
- PinPickerDialog: close, filter pins, no-match, rotate / delete
action buttons.
- RaspberryPiWorkspace: connection status (Connected / Starting…
/ Offline), Start Pi button + tooltip, Connect / Disconnect
buttons + tooltips, Terminal tab label, file-tab close, the
full offline overlay (title / subtitle / start CTA / two-line
note about the staging area), terminal-loading + no-file-
selected fallbacks.
- GitHubStarBanner: aria-label, title, body copy, Star CTA,
dismiss button.
- ExampleLoaderPage: "Loading example…" status, "Example {{id}}
not found." 404 line, "Browse all examples" recovery link.
/editor and /examples links wrapped in localize().
Block 12 — admin-only internals (only admins ever see these)
- AdminBoardsTab: section headings ("By board family" / "By
exact FQBN"), full table column headers (Family / FQBN /
Compiles / Errors / Success rate / Runs / Distinct users /
Distinct projects), range selector label, no-data fallback,
load-failed error.
- AdminDashboardTab: 8 KPI cards (Total users / Total projects
/ Compiles / Runs / DAU / WAU / MAU / Public-Private), all
chart titles + subtitles (Activity over time, Compiles by
board family, Board diversity for pricing signal, Top FQBNs,
Top countries with Cloudflare disclaimer, Top users / Top
projects), table column headers, no-data fallbacks, load
errors. Pluralised "{{count}} board(s)" via i18next plurals
in the diversity pie chart.
- UserActivityModal: title with username interpolation, subtitle,
range selector label, table column headers (Date / Project /
Compiles / Errors / Runs / Saves), pluralised
"{{count}} project(s)" line, deleted-project / no-project
placeholder strings, no-activity empty state, load-failed.
All 8 non-English locales auto-translated via the
`scripts/translate-i18n.mjs` DeepSeek pipeline (one --force run,
~7 min). sameShape() validation passed on every output.
2026-05-10 02:22:16 +07:00
|
|
|
|
<TrashGlyph /> {t('editor.pinPicker.delete')}
|
2026-05-08 21:12:26 +07:00
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const footerButtonStyle: React.CSSProperties = {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
display: 'inline-flex',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
|
gap: 8,
|
|
|
|
|
|
padding: '10px 12px',
|
|
|
|
|
|
background: '#3d3d3d',
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
border: '1px solid #555',
|
|
|
|
|
|
borderRadius: 6,
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
minHeight: 44,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const RotateGlyph: React.FC = () => (
|
|
|
|
|
|
<svg
|
|
|
|
|
|
width={16}
|
|
|
|
|
|
height={16}
|
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
|
fill="none"
|
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
|
strokeWidth={2}
|
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
|
strokeLinejoin="round"
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
>
|
|
|
|
|
|
<polyline points="23 4 23 10 17 10" />
|
|
|
|
|
|
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
const TrashGlyph: React.FC = () => (
|
|
|
|
|
|
<svg
|
|
|
|
|
|
width={16}
|
|
|
|
|
|
height={16}
|
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
|
fill="none"
|
|
|
|
|
|
stroke="currentColor"
|
|
|
|
|
|
strokeWidth={2}
|
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
|
strokeLinejoin="round"
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
>
|
|
|
|
|
|
<polyline points="3 6 5 6 21 6" />
|
|
|
|
|
|
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
|
|
|
|
<line x1="10" y1="11" x2="10" y2="17" />
|
|
|
|
|
|
<line x1="14" y1="11" x2="14" y2="17" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
);
|