diff --git a/src/ui.tsx b/src/ui.tsx
index cb7c4ae..dc560ca 100644
--- a/src/ui.tsx
+++ b/src/ui.tsx
@@ -16,13 +16,13 @@ import { h, Fragment } from 'preact'
import { useState, useEffect, useRef } from 'preact/hooks'
const MIN_WINDOW_SIZE = { width: 320, height: 200 }
-// Статичного максимума нет: "развернуть" целится в весь viewport (maxViewport из main),
-// а верхняя граница drag-ресайза тоже приходит оттуда. useWindowResize ставим очень
-// большой max — реальный кламп делает main через clampWindowSize (bounds*zoom).
+// No static max: "maximize" targets the whole viewport (maxViewport from main),
+// and the upper bound of drag-resize also comes from there. useWindowResize max is set
+// very high — actual clamp is done by main via clampWindowSize (bounds*zoom).
-// Одна строка отладочного вывода: имя переменной из кода (как в main.ts),
-// опционально — формула расчёта, опционально — подстановка значений в формулу,
-// и само значение (моноширинное, по правому краю).
+// One debug output row: variable name from code (as in main.ts),
+// optional — calculation formula, optional — value substitution into the formula,
+// and the value itself (monospace, right-aligned).
function Field (props: { name: string; formula?: string; subst?: string; value: string | number }) {
return (
@@ -38,7 +38,7 @@ function Field (props: { name: string; formula?: string; subst?: string; value:
)
}
-// Секция-карточка: заголовок + список Field.
+// Section card: title + list of Fields.
function Section (props: { title: string; children: preact.ComponentChildren }) {
return (
@@ -51,10 +51,10 @@ function Section (props: { title: string; children: preact.ComponentChildren })
function Plugin () {
const [currentWindowSize, setCurrentWindowSize] = useState(MIN_WINDOW_SIZE)
const resizeRef = useRef({ active: false, dir: '', startX: 0, startY: 0, startW: 0, startH: 0, fixedAxis: 'none' as 'none' | 'x' | 'y' })
- // Текущая док-позиция (для детекта "окно в доке у края" в UI). Центр и углы НЕ пишем —
- // они не считаются доком (окно там просто лежит, не растянуто).
+ // Current dock position (for "window is edge-docked" detection in UI). Center and
+ // corners are NOT written here — they are not considered docked (window just lies there).
const dockedPosRef = useRef
(null)
- // Флаг: ресайз выводит окно из дока (на отпускании не ре-докать, очистить dock-состояние).
+ // Flag: resize pulls the window out of dock (on release, do not re-dock, clear dock state).
const exitingDockRef = useRef(false)
const [windowInfo, setWindowInfo] = useState<{
x: number; y: number; width: number; height: number
@@ -67,9 +67,9 @@ function Plugin () {
} | null>(null)
const [copied, setCopied] = useState(false)
- // Кастомный ресайз по ВСЕМУ периметру (8 невидимых ручек: 4 края + 4 угла).
- // Левый/верхний края двигают окно (main двигает через reposition), правый/нижний — только resize.
- // Live-обновление: RESIZE_WINDOW_DRAG во время drag'а, финальный RESIZE_WINDOW по pointerup.
+ // Custom resize over the WHOLE perimeter (8 handles: 4 edges + 4 corners).
+ // Left/top edges move the window (main moves via reposition), right/bottom — resize only.
+ // Live update: RESIZE_WINDOW_DRAG during drag, final RESIZE_WINDOW on pointerup.
function startResize (e: PointerEvent, dir: string, fixedAxis: 'none' | 'x' | 'y' = 'none') {
e.preventDefault()
e.stopPropagation()
@@ -80,8 +80,8 @@ function Plugin () {
resizeRef.current.startY = e.clientY
resizeRef.current.startW = window.innerWidth
resizeRef.current.startH = window.innerHeight
- // Сообщаем main зафиксировать якорь (старт-позиция + старт-размер) ДО первого движения.
- // Размер берём из window (UI-поток), т.к. в main-потоке window нет.
+ // Tell main to fix the anchor (start position + start size) BEFORE the first move.
+ // Size comes from window (UI thread) since main thread has no window.
emit('RESIZE_START', { dir: dir, startW: window.innerWidth, startH: window.innerHeight, fixedAxis: fixedAxis })
}
@@ -100,9 +100,9 @@ function Plugin () {
const h = window.innerHeight
setCurrentWindowSize({ width: w, height: h })
emit('RESIZE_WINDOW', { width: w, height: h })
- // Если ресайз выводил окно из дока — больше не держим док-состояние (чтобы
- // поллинг не переприжимал и RESIZE_WINDOW не ре-докал). Обычный (не из дока) ресайз
- // сам держит centerLocked/dock через applyDock в main.
+ // If resize pulled the window out of dock — stop holding dock state (so polling
+ // does not re-pin and RESIZE_WINDOW does not re-dock). A normal (non-dock) resize
+ // holds centerLocked/dock itself via applyDock in main.
if (exitingDockRef.current) {
exitingDockRef.current = false
dockedPosRef.current = null
@@ -111,15 +111,15 @@ function Plugin () {
refreshInfo()
}
- // Клик-или-драг на периметральной ручке:
- // - смещение курсора > CLICK_THRESHOLD px → ресайз (startResize + live drag через глобальный onPointerMove)
- // - иначе (чистый клик) → зависит от позиции:
- // * угол (top-left/right, bottom-left/right):
- // - окно НЕ в доке у края (dockedPosRef == null) → телепорт в угол (dockWindow)
- // - окно В ДОКЕ у края → старт single-axis live-ресайза сжатой оси
- // (окно "выезжает" в угол, выход из дока), dir = угол, fixedAxis = сжатая ось
- // * край (top/bottom-center, center-left/right) → телепорт в край (dockWindow)
- // Центр НЕ входит в периметр — только кнопкой.
+ // Click-or-drag on a perimeter handle:
+ // - cursor move > CLICK_THRESHOLD px → resize (startResize + live drag via global onPointerMove)
+ // - otherwise (clean click) → depends on position:
+ // * corner (top-left/right, bottom-left/right):
+ // - window NOT edge-docked (dockedPosRef == null) → teleport to corner (dockWindow)
+ // - window IS edge-docked → start single-axis live resize of the compressed axis
+ // (window "slides out" to corner, exits dock), dir = corner, fixedAxis = compressed axis
+ // * edge (top/bottom-center, center-left/right) → teleport to edge (dockWindow)
+ // Center is NOT in perimeter — button only.
const CLICK_THRESHOLD = 4
const isCornerPos = (p: DockPosition) =>
p === 'top-left' || p === 'top-right' || p === 'bottom-left' || p === 'bottom-right'
@@ -132,7 +132,7 @@ function Plugin () {
let started = false
const isCorner = isCornerPos(dockPos)
const docked = dockedPosRef.current !== null
- // Угол в доке: drag = single-axis ресайз сжатой оси (выход из дока). Обычный/не-док — просто ресайз.
+ // Corner in dock: drag = single-axis resize of compressed axis (exit dock). Normal/non-dock = plain resize.
const fixed = (isCorner && docked) ? (window.innerHeight <= 324 ? 'y' : 'x') : 'none'
function move (ev: PointerEvent) {
if (started) return
@@ -142,10 +142,10 @@ function Plugin () {
started = true
exitingDockRef.current = isCorner && docked
startResize(ev, dir, fixed)
- // Считаем ресайз от исходной точки (до порога), а не от текущей (>порога).
+ // Count resize from the original point (before threshold), not current (>threshold).
resizeRef.current.startX = startX
resizeRef.current.startY = startY
- // Дальше drag ведёт глобальный onPointerMove (resizeRef.active уже true).
+ // Drag continues via global onPointerMove (resizeRef.active already true).
}
function up () {
window.removeEventListener('pointermove', move)
@@ -153,15 +153,15 @@ function Plugin () {
if (started) {
onPointerUpResize()
} else {
- // Чистый клик.
+ // Clean click.
if (isCorner && docked) {
- // Угол в доке: свободная ось -> 50% viewport, прижим в угол (выход из дока).
- // Тот же механизм изменения размера, что и у центра (DOCK_HALF в main.ts).
+ // Corner in dock: free axis -> 50% viewport, pin to corner (exit dock).
+ // Same resize mechanism as center (DOCK_HALF in main.ts).
emit('DOCK_HALF', { position: dockPos, width: window.innerWidth, height: window.innerHeight })
dockedPosRef.current = null
requestAnimationFrame(function () { requestAnimationFrame(refreshInfo) })
} else {
- // Не-док угол / любой край: телепорт в позицию.
+ // Non-dock corner / any edge: teleport to position.
dockWindow(dockPos)
}
}
@@ -179,7 +179,7 @@ function Plugin () {
}
}, [])
- // Слушаем ответ от main с актуальными x/y (canvas-space) и размерами окна
+ // Listen for the response from main with actual x/y (canvas-space) and window sizes
useEffect(function () {
const off = on('WINDOW_INFO', function (info: {
x: number; y: number; width: number; height: number
@@ -195,9 +195,9 @@ function Plugin () {
return function () { off() }
}, [])
- // Запрашиваем свежие данные об окне у main (после любого действия/старта).
- // Передаём РЕАЛЬНЫЙ размер iframe (window.innerWidth/innerHeight) — это то, что
- // Figma реально отрисовала, а не запрошенный lastSize. Иначе gaps будут тавтологией.
+ // Request fresh window data from main (after any action / on start).
+ // Pass the REAL iframe size (window.innerWidth/innerHeight) — what Figma actually
+ // rendered, not the requested lastSize. Otherwise gaps would be tautological.
function refreshInfo () {
emit('GET_WINDOW_INFO', {
width: window.innerWidth,
@@ -205,9 +205,9 @@ function Plugin () {
})
}
- // Копирует все debug-значения (RAW + расчёт) в буфер обмена как текст.
- // Важно: в iframe плагина Figma navigator.clipboard часто недоступен (или его
- // промис тихо reject'ится), поэтому обязателен fallback через document.execCommand('copy').
+ // Copies all debug values (RAW + calc) to the clipboard as text.
+ // Important: in the Figma plugin iframe navigator.clipboard is often unavailable (or its
+ // promise silently rejects), so a fallback via document.execCommand('copy') is mandatory.
function copyInfo () {
if (windowInfo == null) return
const i = windowInfo
@@ -222,7 +222,7 @@ function Plugin () {
`relX = ${i.relX} (x − bounds.x)`,
`relY = ${i.relY} (y − bounds.y)`,
'',
- '== РАСЧЁТ · в main.ts ==',
+ '== CALC · in main.ts ==',
`gapL = ${i.gapL} (x − bounds.x)`,
`gapR = ${i.gapR} (bounds.x + bounds.width − (x + w))`,
`gapT = ${i.gapT} (y − bounds.y)`,
@@ -237,23 +237,23 @@ function Plugin () {
setTimeout(function () { setCopied(false) }, 1200)
}
- // Путь 1: современный async Clipboard API (если доступен и сработал)
+ // Path 1: modern async Clipboard API (if available and worked)
if (navigator.clipboard != null && navigator.clipboard.writeText != null) {
const apiResult = navigator.clipboard.writeText(text)
if (apiResult != null && typeof apiResult.then === 'function') {
apiResult.then(function () { markCopied() }).catch(function () {
- // тихо — переходим к fallback
+ // silent — fall back
fallbackCopy(text, markCopied)
})
return
}
}
- // Путь 2: синхронный fallback для iframe/небезопасных контекстов
+ // Path 2: synchronous fallback for iframe/non-secure contexts
fallbackCopy(text, markCopied)
}
- // Fallback-копирование через временный