ویرایشگر دوبعدی نسل سوم
This content is not available in your language yet.
فاز ۴ پلن. بوم روبهروی دیوار در
apps/web/src/components/WallCanvas.tsxوwall-canvas/؛ منطق تعامل درapps/web/src/editor/.
پوسته و چیدمان (فاز ۶ ِ نسل پنجم)
Section titled “پوسته و چیدمان (فاز ۶ ِ نسل پنجم)”components/layout/WorkspaceLayout.tsx— کتابخانه (۱۶۸–۳۲۰، جمعشدنی با Enter روی جداکننده) · مرکز (کمینه ۴۸۰) · بازرس (۲۶۸–۴۸۰، آغاز ۳۲۰) رویSplitViewِ کیت. کمینه و بیشینه پیکسلی، سهمِ کشیدهشده درصدی.components/layout/CenterStage.tsx— سربرگِ مرکز (تبهای دیوار، کلیدِ نما باToggleButtonGroup؛ گروهِ «روی هم / کنار هم» و «جابهجا» فقط در «هر دو») و بدنهای که نماها را میچیند. بوم وScene3DباcreatePortalدر میزبانِ ثابت رندر میشوند و خودِ میزبان به جای دیداری منتقل میشود؛ تعویضِ آرایش، ترتیب یا «سهبعدی ↔ هر دو» صحنه را از نو نمیسازد (test/center-stage.dom.test.tsx). فرمانها:arrangementCommandsدرapp/display-commands.ts(منوی نما و پالت؛\روی هم/کنار هم،Shift+\جابهجا —matchesChordنشانهٔ Shiftدار را با کلیدِ بی Shift ِ همان جا میسنجد). «کنار هم» وقتی صحنه از2 × 240 + 8پیکسل تنگتر است «روی هم» چیده میشود (ResizeObserverروی صحنه)؛ انتخابِ کاربر درuseUiنمیماند که عوض شود و گزینهٔ «کنار هم» تا جا باز شود خاموش است. پیشفرضِ آرایش (defaultLayoutِstate/ui-layout.ts، یافتهٔ U9 ِ فاز ۵) محورِ بلندِ صحنه را نصف میکند: بومی که از بلندیِ صحنه پهنتر است کنار هم، وگرنه روی هم — نمای دیوار بلندتر از پهن است و نصف کردنِ ارتفاع در پنجرهٔ ۱۴۰۰×۹۰۰ دو نمای ۸۲۴×۳۷۰ میداد که نقشه در آن یکپنجمِ پنل بود.- وضعیت:
state/ui.ts+state/ui-layout.ts—darzsaz.uiنسخهٔ ۲:{ version: 2, layouts: { phone|tablet|desktop: { view, arrangement, swapped, center?, workspace? } }, rulers, grid, dims, tourSeen }. مهاجرتِ نسخهٔ ۱:splitِ پیکسلی ÷ ارتفاعِ مرکزِ همان پنجره ← سهمِ درصدی (۲۰ تا ۸۰)، فقط برای اندازهٔ جاری (test/ui-layout.test.ts). اندازهٔ صفحه باmatchMedia(lib/useScreenClass.ts؛ ≤۷۶۸ گوشی، ≤۱۲۷۹ تبلت). - گوشی: یک نما در هر لحظه («هر دو» به دوبعدی برمیگردد)؛
BottomSheetبا سه ایست (۱۲۰ پیکسل، ۵۰٪، ۹۲٪) که همان کتابخانه و همان بازرس را در تبهای کیت میزبانی میکند؛ سربرگش دکمه است (فشردن = ایستِ بعدی)، کشیدن به نزدیکترین ایست مینشیند. - دوربین (۶.۹):
components/useFitFrame.ts— اندازهٔ اول ازResizeObserver(نه ۸۰۰×۳۰۰ ِ حدسی)، قاب فقط در اولین اندازه و «قاب»؛ تغییرِ اندازهٔ پنل و ویرایشِ پروژه چرخشِ کاربر را نگه میدارند. - پنجرهها: همه روی
Dialogِ کیت (۵.۵)؛Overlay.tsxو قاعدههای.sheetOverlay/.photoModalبرداشته شدند. پنجرهٔ کاریِ بلند (عکس، رندر، چاپ) کلاسِtallDialogِlayout.module.cssرا میگیرد. - e2e:
e2e/layout.spec.ts(پروژهٔapp) — کشیدن و بارگذاری دوباره، Enter روی جداکننده، آرایش با یک بومِ WebGL، برگهٔ گوشی در ۳۹۰×۸۴۴.
ماشین حالت تعامل — editor/interaction.ts
Section titled “ماشین حالت تعامل — editor/interaction.ts”خالص و آزمونشده (test/interaction.test.ts): transition(mode, event) → { mode, effects }.
- حالتها:
idle،pan،marquee(کادر انتخاب)،moveUnit،resizeBoundary،moveObstacle،resizeObstacle(چهار لبه، کمینهٔ ۲۰)،placeObstacle،dragFromLibrary،editNumber،moveRunHeight. - رویدادها:
down(با هدف: زمینه، یونیت، مرز، مانع، لبهٔ مانع، خط ارتفاع ردیف، عدد عرض)،move(با Alt = چفت خاموش)،up،escape،pickObstacle،libraryDrag،dblclick. - اثرها (
Effect) دادهاند، نه فراخوانی:select،selectObstacle،beginDrag،liveBoundary،liveObstacle،liveObstacleBox،liveRunHeight،dropPreview،libraryPreview،commitMove،commitDrag،placeObstacle،dropLibrary،marquee،openNumber،startPan. جدولHANDLERSدرwall-canvas/effects.tsهر اثر را روی فروشگاه اعمال میکند (آزمون:test/canvas-effects.dom.test.ts). کامپوننت فقط رویداد میفرستد (useCanvasInteraction). - کلیک بیحرکت روی یونیت انتخاب است؛ جابهجایی از
DRAG_THRESHOLD_PX(۴ پیکسلِ صفحه، ۷.۱) به بعد شروع میشود — رویدادِdownمقیاسِ همان لحظه (pxPerMmازgetScreenCTM) را میآورد و آستانه درArmed.slopمیلیمتر میشود. پیشتر ۱۲ میلیمترِ دیوار بود: ≈ ۱ پیکسل در «جا کردن» ِ دیوارِ ۱۲ متری و ≈ ۳۵ در بیشینهٔ بزرگنمایی. مختصات همه در میلیمتر دیوارند، y از کف. - کشیدنهای زنده (مرز، مانع، لبهٔ مانع، خط ارتفاع؛
editor/live-drag.ts) هم تا آستانه فقط کلیکاند:beginDragبا گذشتن از آستانه یک بار و پیش از اولین اثرِ زنده، وcommitDragفقط اگر شروع شده باشد — کلیکِ بیکشیدنpast.lengthرا ثابت میگذارد (فاز ۷، ۷.۶). - یک مالک برای اشارهگر (۷.۲):
downپرچمِspaceدارد؛ Space ِ نگهداشته یا دکمهٔ میانی/راست روی هر هدف فقطpan+startPanاست.useCanvasInteractionرویدادِ ماوس را روی بوم نگه میدارد (stopPropagation) و اشارهگر را برای کادر هم میگیرد، پس قابِuseViewportدوبارهstartPanنمیزند و خروج از SVG چیزی ثبت نمیکند (pointercancel= رها کردنِ کار). لمس روی زمینه هنوز به قاب میرسد (بزرگنماییِ دو انگشتی). - Shift + کادر (
marqueeباadditive) انتخاب را پاک نمیکند و یونیتهای کادر را در رها کردن (commit) به انتخاب اضافه میکند؛ کادرِ ساده مثل قبل زنده جایگزین میکند.
چفت و راهنما — editor/snap.ts
Section titled “چفت و راهنما — editor/snap.ts”snapBoundary: عرض استاندارد (catalog.defaults.standardWidths)، همراستایی با مرز یونیتهای ردیف دیگر (دیواری روی زمینی)، لبهٔ مانع با فاصلهٔ ایمن ۵۰؛ برد ۱۵ میلیمتر (SNAP_TOLERANCE). هر چفت یکGuideدارد که بوم به شکل خط کهربایی نقطهچین و برچسب میکشد؛ عدد زنده در حباب کنار نشانگر. برچسب (GUIDEدرsnap.ts) و حباب (BUBBLEدرeffects.ts) پیام با مقدارِ تیپدارِmm()اند وwall-canvas/Guides.tsxدر روکشِ فضای صفحه باrender(i18n, …)مینویسدشان — زبان و رقمِ لحظه؛ تا موجِ ۳ متنِ فارسیِ سختکد با رقمِ لاتین بود و در رابطِ انگلیسی فارسی میماند.snapHobUnderHood: مرکز یونیت گازدار به مرکز دودکش (یا یونیتِ «جای هود»)، برد ۳۰. هنگامِ جابهجاییِ یونیتِ گاز (wall-canvas/move.ts): مرکزِ یونیتِ کشیدهشده همان فاصلهٔ لحظهٔ گرفتن را با اشارهگر دارد؛ اگر در برد است، جایی از ترتیب که مرکز آنجا به دودکش نزدیکترین است انتخاب میشود وcentreUnitهمسایهٔ چپ (یا شروعِ ردیف) و راست را به یک اندازه جابهجا میکند تا گاز پهنایش را نگه دارد. همسایه بیرون از حدِ عرضش (widthLimitsِ هسته؛ قفل هیچ) کشیده نمیشود؛ اگر جا نیست راهنما هم نیست و جابهجایی ساده است. e2e:e2e/hob-snap.spec.ts.- حدِ عرض و جای x یک منبع دارند (۷٫۱۴): کشیدنِ مرز (
resize.ts)، Alt+جهتنما، عددِ درجا، بازرس، کشو و سازنده عرض را ازwidthLimits/clampWidthِ هسته میگیرند — ۱۵۰ تا ۱۲۰۰، قفل فقط عرضِ خودش، و یونیتی که از پیش بیرون از بازه است (الگوی دیوارِ بلند، پرکنِ ۱۰۰) تا عرضِ خودش، تا ویرایش هرگز برخلافِ جهتش نپرد. جای درج، مرزِ دستگیره، چفت، عددِ زنده، کادرِ انتخاب و بندانگشتی جای x را ازlayoutRun/runEdgesمیخوانند.test/math-sources.test.tsنسخهٔ تازه را در همهٔ بستهها میگیرد. snapRunHeight: ارتفاع نصب ردیف دیواری به ۶۰۰/۶۵۰/۷۰۰ میلیمتر بالای صفحه.- Alt چفت را خاموش میکند (
enabled: false).
روکشها
Section titled “روکشها”خطکش (هر ۱۰۰ میلیمتر) با کلید R، شبکه با G، اندازهگذاری
(زنجیرهٔ عرض کنارِ هر ردیف — زیرِ ردیفِ زمینی، بالای ردیفِ دیواری —، ارتفاع ردیف کنار، فاصلهٔ مانع از
گوشهٔ چپ) با D. وضعیت
در state/ui.ts و localStorage؛ از منوی «نما» هم.
روکشِ فضای صفحه (۷٫۱) — wall-canvas/ScreenOverlay.tsx یک SVG ِ جدا روی بوم: عددهای خطکش و
اندازه و دستگیرههای مرزِ یونیتِ انتخابشده. جا از toScreen (همان ریاضیِ viewBox)، اندازه به
پیکسل از screen-layout.ts: قلم ۱۱، دستگیرهٔ دیدنی ۸، ضربه ۲۴. گامِ عددِ خطکش با بزرگنمایی
(۱۰۰ تا ۱۰٬۰۰۰، فاصلهٔ کمینه ۵۶ پیکسل)؛ زنجیرهٔ اندازه اگر یک عدد جا نشود به جمعِ ردیف جمع میشود
(data-collapsed). خطها در لایهٔ دیوار با طرح بزرگ میشوند. لایه pointer-events: none است جز
دستگیرهها؛ direction="ltr" چون textAnchor در راستبهچپ وارونه میشود. خطِ راهنمای چفت (۲ پیکسل؛
راهنمای قائم سراسرِ بلندیِ قاب، راهنمای ارتفاع به طولِ دیوار) و حبابِ عددِ زنده هم اینجایند (data-guides، data-guide-label، data-bubble)؛ متنِ
فارسی با unicodeBidi="plaintext". نشانِ مسئله (ScreenMarks.tsx → IssueBadges: دایرهٔ ۱۶، ضربهٔ
۲۴، یک قطر بیرونِ لبهٔ یونیت و در سویی که زنجیرهٔ اندازهٔ آن ردیف نیست — ردیفِ دیواری زیر، زمینی بالا؛
یافتهٔ U9 ِ فاز ۵: روی گوشهٔ بالا-راست، نشان روی عددِ زنجیره مینشست)، نامِ مانع (ObstacleLabels: قلمِ ۱۱ و
هالهٔ کاغذی؛ پیشتر در drawing.module.css به میلیمترِ نقشه بود و در نمای جاشدهٔ دیوارِ ۱۲ متری ≈ ۸ پیکسل
میشد) و دستگیرههای لبهٔ مانعِ انتخابشده (ObstacleHandles: ضربهٔ ۲۴،
دیدنیِ ۸؛ درونِ نوار هرگز بیش از یکچهارمِ مانع، تا پریزِ ۹ پیکسلی زیرِ نوارها گم نشود) هم اینجایند.
حرکت و رها کردن روی قابِ بوم شنیده میشوند، نه SVG ِ دیوار: اشارهگرِ گرفتهشده روی عنصرِ روکش
به SVG ِ دیوار (خواهرش) نمیرسید.
نوارهای روی بوم بیرون حساب (۷.۶) — «افزودن مانع» (wall-canvas/ObstacleBar.tsx: یک دکمهٔ منوی کیت
با دو بخشِ «مانع» و «بازشو»؛ هنگامِ گذاشتن راهنما و «لغو») و کنترلِ نما در پایینِ قاب شناورند. سهمِ هر نوار
از محورِ خودش در fit به یکپنجم بسته است (U9 ِ فاز ۵): نوارِ ۵۶ پیکسلی از قابِ ۳۷۰ پیکسلیِ «هر دو»
یکهفتم میگرفت، و حاشیهٔ درصدیِ fit کفِ ۱۲ پیکسل در هر سو دارد چون عددهای خطکش بیرونِ جعبهٔ محتوا
کشیده میشوند.
useBarInsets بلندیِ آنها را از چیدمانِ واقعی میخوانَد ([data-view-controls]، [data-obstacle-bar]،
ResizeObserver) و fit(content, frame, margin, insets) محتوا را در قابِ منهای نوارها جا میکند؛
settleView همان را میگیرد. پیشتر ردیفِ هشتدکمهایِ نوارِ مانع در «هر دو» (بومِ ۸۲۴×۳۷۰ در
۱۴۰۰×۹۰۰) ۵۱۱×۷۴ پیکسل بود و روی یونیتهای زمینی (پایینِ ۴۰۶ > بالای نوار ۳۸۸) مینشست و کلیکشان را
میگرفت؛ حالا دکمه ۱۱۶×۳۲ و پایینِ یونیتها ۳۶۸ < بالای نوارها ۴۳۰. بزرگنمایی و جابهجاییِ دستی هنوز
میتوانند محتوا را زیرِ نوار ببرند.
نمای یونیت (UnitFace) خانهها را از facadeCells ِ هندسه میکشد، یعنی layoutFront ِ هسته —
با درز و درزِ لبه، همان که برشکار میبُرد (۷٫۱۴). آنچه روی نما دیده میشود از facadeMarks ِ همان بسته
میآید و front.opening ِ یونیت را میخوانَد (نقشهٔ فاز ۲): میلهٔ دستگیره (ریلی: نوارِ لبهٔ بالا)، گشادیِ
پروفیلِ دستگیرهٔ مخفی بالای ردیفِ اول و میانِ ردیفها، و نوارِ فرزِ J یا شیارِ انگشتی — و برای نمای فشاری
هیچ. پیشتر هر درب و کشو دستگیره داشت، هرچه بازش میکرد. پیشنمای سازنده همان نشانها را میکشد
(rect[data-mark]، test/facade.dom.test.tsx).
صحنهٔ تنبل (components/scene/SceneChunk.tsx، پیام در SceneStopped.tsx): شکستِ import() ِ تکهٔ سهبعدی ← «نمای سهبعدی بار نشد» با
«دوباره» (یک lazy ِ تازه) و «برو به دوبعدی» (۷٫۱۰).
رجیستریِ فرمان و کلیدها (۷.۵) — app/registry.ts
Section titled “رجیستریِ فرمان و کلیدها (۷.۵) — app/registry.ts”یک فهرست برای همهٔ فرمانها: REGISTRY با scope (app پنجره، canvas بومِ متمرکز، unit
یونیتِ متمرکز، scene برچسبِ نوارهای سهبعدی و F ِ «رفتن روی انتخاب» با تمرکز درونِ صحنه —
components/scene/scene-keys.ts)، group ِ منو، برچسبِ /*i18n*/ و shortcut (+ keys ِ همارزِ نانوشته)؛
فرمانهای منوی «نما» در app/registry-view.ts اند و درونِ همان REGISTRY پخش میشوند. همه از آن میخوانند: منوی بالا و پالت (commandOf در useCommands)،
keymap(i18n, scopes) برای صفحهٔ راهنما (سه جدول به لایه) و «راهنمای بوم» (app/CanvasHelp.tsx، دکمهٔ
سربرگِ بوم به جای متنِ دائمی)، منوی راستکلیک و نوارِ شناورِ یونیت (برچسب + کلید)، canvas-keys.ts و
unit-keys.ts (تطبیق با chordsOf + matchesChord). useShortcuts فقط لایهٔ app را روی پنجره
میبندد؛ D/G/R در منوی «نما» دیده میشوند ولی مالِ بوماند. آکوردِ Arrows یعنی هر چهار جهتنما؛
Shift برای حرف، رقم و کلیدِ نامدار سنجیده میشود، نه برای نشانه («?»، «+»).
| لایه | کلید |
|---|---|
| پنجره | Ctrl+O/S/P/K/Z/Shift+Z/D، Delete (Backspace)، Escape، ۱–۴ نما، \ روی هم/کنار هم، Shift+\ جابهجا، ? |
| بوم | + / − بزرگنمایی، Shift+1 جا کردن، Shift+0 اندازهٔ واقعی (تصمیمِ ۴)، F انتخاب، D / G / R |
| یونیت | جهتنما (Shift افزودن)، Ctrl+جهتنما جابهجایی، Alt+جهتنما عرض ±۱۰ (Alt+Shift ±۵۰)، L، Enter |
| صحنه | F رفتن روی انتخاب (تمرکز درونِ نمای سهبعدی) |
آزمون (test/command-registry.dom.test.tsx): نقشه از رجیستری، هیچ آکوردی دو فرمان ندارد در لایهٔ خودش
و لایههای بیرونی، هر کلیدِ بوم کاری دارد، راهنما هر کلیدِ پنجره، بوم و یونیت را فهرست میکند. «۱» ِ تنها روی بوم دیگر
preventDefault نمیکند، پس نما را عوض میکند.
جابهجایی و بزرگنمایی (۷.۳، lib/viewport.ts): Ctrl/⌘ + چرخ و پینچِ صفحهٔ لمسی بزرگنمایی
(wheelIntent، ضریب پیوسته با سقفِ یک پله)؛ چرخِ تنها و دو انگشت روی صفحهٔ لمسی جابهجایی؛ روی
نمایشگرِ لمسی یک انگشت جابهجایی و دو انگشت بزرگنمایی + جابهجایی (touchStep). یک انگشت روی زمینهٔ
بوم مثلِ Space است (کادرِ انتخاب نمیکشد). شنوندهٔ بومیِ غیرفعالنشده فقط پیشفرضِ مرورگر را میگیرد.
بزرگنماییِ کاربر با تغییرِ اندازهٔ دیوار یا قاب میماند مگر محتوا از قاب بیرون برود (settleView، ۷.۷).
پالت رقمِ فارسی و لاتین را یکی میبیند (paletteFilter).
جهتنما جهتِ روی صفحه است (V5): نقشهٔ دیوار در هیچ زبانی قرینه نمیشود، پس «راست» یونیتِ
index+1 است، در فارسی هم.
میانبرهای سراسری (lib/useShortcuts.ts): Delete (یا Backspace) برای هر انتخاب — همهٔ یونیتهای selection،
یا مانع/بازشوی selectedObstacleId — یک قدمِ واگرد (deleteSelected، hasDeletable برای فرمانِ
«حذف انتخاب»)؛ «دوبار کلیک = حذف» ِ مانع برداشته شد. Escape فقط بالاترین لایه: انتخاب را وقتی پاک
میکند که هدف درونِ dialog/alertdialog/menu/listbox نباشد و هنوز در صفحه باشد. کلیدِ بی
Ctrl درونِ نقشهای listbox، menu، menubar، slider، spinbutton، combobox، textbox،
grid، tree (بر اساسِ نقش، نه تگ) و Alt ِ تنها هیچ میانبری نمیزنند. آزمون:
test/canvas-input.dom.test.tsx.
انتخاب چندتایی
Section titled “انتخاب چندتایی”selection (فهرست) و selectedUnitId (اصلی، آخرین) در فروشگاه؛ select(id, additive)
برای Shift+کلیک، selectMany برای کادر انتخاب. بازرس اصلی را نشان میدهد؛
با بیش از یک یونیت properties/GroupEditor.tsx بالای بخشهایش میآید («ویرایش گروهی»، پایینتر).
ارتفاع ردیف دیواری
Section titled “ارتفاع ردیف دیواری”خط آبی نقطهچین در baseHeight ردیف دیواری (RunHeightHandle) با کشیدن قائم
عوض میشود؛ چفت به ۶۰۰/۶۵۰/۷۰۰ میلیمتر بالای صفحه، عدد زنده «۶۵۰ بالای صفحه».
کشیدن از کتابخانه (E4)
Section titled “کشیدن از کتابخانه (E4)”کارت کشو با ۶ پیکسل حرکت (startDrag در components/UnitDrawer.tsx) به «در دست» میرود
(state/drag-source.ts) و سایهاش دنبال اشارهگر میآید؛ بوم (wall-canvas/library-drop.ts) روی پنجره گوش
میدهد: داخل بوم ماشین حالت به dragFromLibrary میرود و libraryPreview خط درج را در ردیفِ همان y نشان
میدهد (ردیفی که y در بلندیاش میافتد، وگرنه نزدیکترین)؛ رها کردن dropLibrary است (همان
lib/add-unit.ts که کلیک روی کارت هم استفاده میکند) و فقط اگر نوعِ آن ردیف با نوعِ یونیت یکی باشد درج
میکند. کلیک بیحرکت مثل قبل میافزاید.
نوار شناور، منوی راستکلیک، عدد در جا (E6)
Section titled “نوار شناور، منوی راستکلیک، عدد در جا (E6)”- نوار شناور بالای یونیت اصلی (
FloatingToolbar): تکثیر، قفل، آینه کردن درب، یادداشت (تمرکز روی کادر یادداشت بازرس)، حذف. جایش از ریاضی نما (toScreen) حساب میشود نه از DOM. - منوی راستکلیک (
ContextMenuدر@darzsaz/ui): همان فرمانها (wall-canvas/unit-menu.ts، برچسب و کلید از رجیستری). - منوی دیوار (
app/WallMenu.tsx، ⋯ کنارِ زبانههای دیوار): «پر کردنِ دیوار…» همیشه؛ دیوارِ پُر پیش از پنجرهٔ پیشنهادهاConfirmDialogمیخواهد (۶.۸). - نوارِ سهبعدی (
components/scene/SceneBar.tsx) رویOverlayBarِ کیت: قاب، نزدیک/دور، چهار حالت، رندر — به ترتیبِ اولویت و سرریز به ⋯. «نمایش» منوی کیت با گروهِ چندانتخابی در نوارِ پایینِ صحنه (SceneTools) کنارِ برش، اندازه، روکش و لغزندهٔ بازشدن (۶.۴) — یک ردیفِ پیمایشپذیر، بیflex-wrap. رنگگزینِ روکش فقط با رویدادِ بومیِchangeثبت میکند، نه در هر گامِ کشیدن (۷.۸). زیرِ ۳۰۰ پیکسل ارتفاعِ صحنه،SceneChromeتنها یک نوار میگذارد: ابزارها یک گزینهٔ همان نوارِ بالا میشوند و درSheetِ پایینی ستونی باز میشوند (U12 ِ فاز ۵ — دو نوارِ شناور یکپنجمِ پنلِ کوتاه را میگرفتند و نوارِ پایین روی کابینتها مینشست). - «درها باز» و کلیک روی یک درب (نسل ششم، فاز ۳): در این حالت کلیک روی یک نما همان یکی را باز و بارِ دوم بسته میکند و انتخاب دست نمیخورد — در بقیهٔ حالتها کلیک مثل همیشه یونیت را انتخاب میکند، وگرنه هر انتخابی یک درب را هم میچرخاند. لغزندهٔ «میزان باز شدن درها» مقدارِ همه است (scene.md، «حالتِ باز»).
- دوبار کلیک روی عدد عرض: کادر عددی روی بوم (
InlineNumber،foreignObject)؛ Enter/blur ثبت، Escape رها.
ویرایش گروهی (E5)
Section titled “ویرایش گروهی (E5)”properties/GroupEditor.tsx وقتی بیش از یک یونیت انتخاب است: جنس بدنه، جنس
نما، ارتفاع، عمق، قفل — روی همه با یک قدم واگرد؛ مقدار متفاوت «—».
مانع و بازشو (E7)
Section titled “مانع و بازشو (E7)”- کلیک روی مانع انتخابش میکند (
selectedObstacleId) و بازرس مانع (properties/ObstacleInspector.tsx) میآید: نوع (ازOBSTACLE_KINDS)، برچسب، x/y/w/h، عمق، «باید دسترسی داشته باشد»، حذف. چهار دستگیرهٔ لبه اندازه را میکشند. - پنجره و در بازشواند (
wall.openings)، نه مانع: از بخشِ «بازشو» ِ منوی «افزودن مانع» (wall-canvas/ObstacleBar.tsx) انتخاب و با کلیک روی دیوار گذاشته میشوند (lib/openings.ts: در همیشه از کف)،OpeningLayerنشان میدهد، همان بازرس ویرایش میکند (فقط x/y/w/h — بی نوع، برچسب، عمق و دسترسی). قاعدهٔ هستهopening-blockedیونیتی را که روی بازشو بنشیند خطا میگیرد.
بازرسِ بخشبندیشده (E9، پلن ۶.۳)
Section titled “بازرسِ بخشبندیشده (E9، پلن ۶.۳)”components/Properties.tsx سه شاخه دارد: بازرس مانع/بازشو، راهنمای خالی، و UnitInspector. هر سه در
یک پوسته (inspector/InspectorShell.tsx): سربرگِ چسبان (نام + زیرنویسِ «ردیف · یونیت n · شمارهٔ چاپی»)
و منوی ⋯ ِ کیت — تکثیر، آینه کردن دربها، قفل عرض (یا باز کردن قفل عرض)، یادداشت…، حذف یونیت (همان توابعِ
نوارِ شناور؛ مانع فقط حذف).
بدنه بخشهای بازشو است، نه تب (inspector/InspectorSection.tsx روی Disclosure/DisclosureGroup ِ
کیت). بخشِ بسته خلاصهٔ یکخطی دارد (inspector/summaries.ts)؛ باز/بسته در localStorage با کلیدِ
darzsaz.inspector میماند (inspector/sections.ts؛ جدا از darzsaz.ui، پیشفرض فقط «اندازه»). پنلِ
بسته در DOM هست (hidden="until-found")؛ جستوجوی مرورگر بازش میکند.
| بخش | چه چیزی | خلاصهٔ بسته |
|---|---|---|
| اندازه | نام، شمارهٔ چاپی، عرض/ارتفاع/عمق، قفل عرض، یادداشت | عرض × ارتفاع × عمق |
| نما | سبک، قاب کلاسیک، درزها، باز کردنِ نما (شش راه، OpeningFields)، چیدمان سریع، پیشنما، ردیفها، و هر خانه: عرض، لولا، تقسیم/ادغام |
سبک · ردیف · خانه |
| داخل | طبقهها (نمای جانبی، پلهٔ ۳۲)، تیغهها، بیرونکشها | طبقه · تیغه |
| ساختار | سبک جعبه، کف، سقف/کلاف، پشتبند (جنس و شیار)، پاخور، اتصال، فارسیبر | سقف · پشتبند |
| نوار لبه | چهار لبه × نوار (بدنه، طبقه، نما، پهلوی نمایان) | نوارِ لبهٔ جلوی بدنه |
| یراق | لولا، ریل، پایه، جک (دستگیره در «نما» است) | لولا |
| وسایل | گاز/سینک/هود از نقشِ کاتالوگ (appliancesOf)، دستگاه توکار |
شمارِ وسیله |
| مسئلهها | همان کارتهای دستیار برای همین یونیت، با راهحل | نشانِ شمار |
| قطعات این یونیت | همهٔ قطعات؛ هر ردیف دکمه است — تمرکز، شناور و لمس همان قطعه را در سهبعدی روشن میکند (useUi.hoverPart) |
— |
Enter روی بوم اولین کنترلِ اولین بخشِ باز را تمرکز میدهد؛ «یادداشت…» بخشِ «اندازه» را اول باز
میکند. راهش کنشِ تیپدار است، نه رویدادِ document (۷.۱۴، V28): focusInspector('first' | 'width' | 'notes') ِ state/ports.ts درگاهی را صدا میزند که InspectorShell با ref (inspectorRef) گذاشته؛
همین فروشگاه openIssues (نشانِ بوم ← پنجرهٔ دستیار) و saveToFileRequested (اعلانِ ذخیرهگاه) را
به گیرندههای EditorPage (useEditorPorts) میرساند. بی گیرنده، کنش کاری نمیکند. چیدمانِ کنترلها در inspector/fields.module.css است
(موجِ ۳؛ پیشتر .propBody/.row3/.rowEdit/.cellEdit ِ سراسری در styles.css): ردیفِ سهکنترلی (row3) در
ظرفِ زیرِ ۳۰۰ پیکسل دوستونه میشود (@container)؛ ردیفِ نما (rowEdit) ستونِ حذفِ auto دارد، نه 30px که
دکمهٔ کیت با --hit ِ کارگاه (۴۸) از آن بیرون میزد؛ هر خانه کارتِ دوستونه است (cell: سربرگِ نام و تقسیم/ادغام،
زیرش عرض، لولا، دستگیره) با role="group" به نامِ «خانهٔ ۱-۲». هر کنترل data-field="<مسیر مدل>" دارد؛
test/inspector-tabs.dom.test.tsx فهرستِ فیلدها را میگردد و e2e/inspector.spec.ts در ۲۶۸/۳۲۰/۴۸۰ × سه پوسته
× دو زبان پیمایشِ افقی، تقاطع، در دید بودن و جا شدنِ هر خانهٔ ردیفِ نما (data-row-edit) را میسنجد. با انتخاب
چندتایی، GroupEditor بالای بخشها میآید.
دربِ شیشهای (inspector/GlassFields.tsx، نقشهٔ فاز ۲): خانهٔ glassDoor در ویرایشگرِ ردیفها و در
شبکهٔ سازنده انتخاب میشود؛ تا یونیت یکی داشته باشد، دو فهرستِ «قابِ دربِ شیشهای» و «شیشه» زیرِ بخشِ نما
میآیند و هر دو شناسه را با هم مینویسند (یونیتِ بیانتخاب، شیشه و قابِ کاتالوگ را میگیرد — glassOf).
بوم و پیشنما شیشه را تهرنگ میکشند (facadeMarks ِ glass) و صحنه شفاف (PartBox، DOOR_GLASS).
باز کردنِ نما (inspector/OpeningFields.tsx، نقشهٔ فاز ۲): شش راه در یک ToggleButtonGroup ِ قائم با
آیکون — همه با هم دیده میشوند، چون این انتخاب نما و سوراخکاری و فهرستِ خرید را با هم عوض میکند — و زیرش
فقط فیلدهای همان راه (مدلِ دستگیره، جکِ فشاری، پروفیلِ L و C). راهی که هسته یونیت را برایش رد میکند اصلاً
پیشنهاد نمیشود (kindsFor: دستگیرهٔ مخفی فقط زمینی، فرز فقط روی نمای صاف)، مگر همان که پرونده آورده.
test/inspector-tabs.dom.test.tsx میسنجد که هر فیلدِ front.opening کنترل دارد، و e2e/opening.spec.ts
مسیرِ کامل را: انتخابِ «فشاری، بیدستگیره» ← برگهٔ سوراخکاری بی سوراخِ دستگیره.
سازندهٔ یونیت v2 (E10)
Section titled “سازندهٔ یونیت v2 (E10)”پیشنمای نما (unit-builder/Preview.tsx، در سازنده و بخشِ «نما» ِ بازرس) خانهها را از facadeCells
میکشد — همان layoutFront ِ هسته که بوم و برگهٔ برش میبینند (۷.۱۴، V27)؛ جعبهها به میلیمتر و
viewBox اندازهٔ یونیت است (test/facade.dom.test.tsx، ویژگیمحور).
unit-builder/GridEditor.tsx: هر ردیف یک نوار از خانهها؛ کلیک نوع خانه را
میچرخاند، آیکونِ تقسیم و ادغام (lucide، جای «⫶» و «⊔»). خانه و دکمهٔ ریز Button ِ کیتاند و نوار با قاعدههای
تککلاسیِ grid-editor.module.css به هم میچسبد — لایهٔ app ِ CSS در هر ترتیبِ بار بر kit میچربد (۶.۱۰)؛
.wrap .cell ِ دوکلاسیِ پیشین برداشته شد. RowDraft.cells نوع هر خانه را جدا نگه میدارد
(rows.ts: splitCell، mergeCell، cycleCell)؛ فهرست ردیفها همان چیز را از
زاویهٔ دیگر ویرایش میکند و تغییر نوع/تعداد از آن، خانهها را از نو میسازد.
دستیار درونبومی (E11)
Section titled “دستیار درونبومی (E11)”- روی هر یونیتی که مسئله دارد، نشان شمارنده (رنگ = بدترین شدت) گوشهٔ بالا-راست
(
ScreenMarks→IssueBadges، به پیکسلِ صفحه)؛ کلیک، دستیار را با فیلتر همان یونیت باز میکند (openIssuesِstate/ports.ts). IssuesPanel: فیلتر شدت، گروهبندی به یونیت (بییونیتها زیر «کل دیوار»)، «چرا؟» (issues/RuleWhy.tsx، همان در تبِ مسئلههای بازرس) در زبانهٔ تازه به ردیفِ همان قاعده در «قاعدهها» ِ راهنما (#/help/rules/<id>،app/HelpRules.tsx، تنبل و پیشانباشته، پس آفلاین و درvite devهم): لنگرِrule-<id>تمرکز میگیرد، «چرا» از کاتالوگ به زبانِ رابط؛ فهرستِ سبکِ قاعدهها (lib/rule-index.ts، بی کدِcheck) راscripts/rules-doc.mjsازRULESمیسازد. همان صفحه از «کمک ▸ قاعدههای دستیار» و دکمهٔ راهنما؛ «در مستندات» ِ هر ردیف سایتِ مستندات است (docs/[en/]reference/rules/#<id>،lib/rule-doc.ts؛ لنگرِ<a id>ِ هر ردیف ازscripts/rules-doc.mjs، نه GitHub ِ خصوصی؛ سرویسورکر ناوبریِdocs/را به برنامه برنمیگرداند)، «پیامد؟» یک چیدمان سریع (۱۲۰ تلاش) روی پروژهٔ کنونی و اصلاحشده میزند و «+۱ ورق · −۱ مسئله» میگوید، «بیصدا برای این یونیت» (یا «برای این پروژه» برای مسئلهٔ بییونیت) با دلیل →project.settings.mutedRules(هسته درvalidateProjectفیلتر میکند)؛ پایینِ پنل فهرستِ بیصداشدهها با «برگرداندن». «بررسیِ X اجرا نشد» بیصدا نمیشود و «رونوشتِ گزارش» دارد (نسخه، قاعده، خطا). شناسهٔ قاعده فقط در حالت توسعه.- خروجی/چاپ با خطای ایمنی حلنشده تأیید میخواهد:
useCommandsپرسش را بهSafetyConfirm(ConfirmDialogِ کیت، «با این حال خروجی بگیر») میدهد، نهwindow.confirm.
دلتای زنده و ناحیهٔ زنده (E12، پلن ۶.۷)
Section titled “دلتای زنده و ناحیهٔ زنده (E12، پلن ۶.۷)”state/derived.ts خلاصهٔ قبلی را نگه میدارد؛ summaryDelta ورق و هزینه را مقایسه میکند و StatusBar
«این تغییر: …» را با رنگ میآورد. هزینه از Derived.bom.known (null اگر هیچ قلمی قیمت ندارد).
نوار زنده نیست. دو ناحیهٔ پنهان فقط: شمارِ مسئله (aria-live="polite") و دلتا (role="status")، هر دو
با درنگِ ANNOUNCE_MS (۷۰۰) و خالی تا نخستین تغییر — یک محاسبهٔ چیدمان صفر اعلان است
(test/status-live.dom.test.tsx).
تور (پلن ۶.۸)
Section titled “تور (پلن ۶.۸)”components/Tour.tsx روی Popover ِ کیت با triggerRef به عنصرِ data-tour: جای پاپاور از اندازهٔ
واقعیِ هدف، تمرکز روی «بعدی»، Escape و کلیکِ بیرون «رد کردن». گامی که هدفش دیده نمیشود (گوشی) کنار
میرود و با resize دوباره سنجیده میشود.
پلان (E13)
Section titled “پلان (E13)”نمای «پلان» (کلید ۴): components/PlanCanvas.tsx روی wallSegments/roomRect
(همان هندسهٔ اتاق سهبعدی). دیوارها، یونیتها به عمق (دیواری کمعمقتر و
نقطهچین)، بازشوها، موانع؛ کلیک دیوار انتخاب، پنل کنار: طول، زاویه، مبدأ x/z،
«+ دیوار تازه (گوشه)». جزیره در مدل داده نیست.
کارایی (E14)
Section titled “کارایی (E14)”e2e/perf.spec.ts: ۴۰ یونیت روی دیوار ۱۲ متری با window.__darzsazLoad
(قلاب ابزار، همان مسیر loadProject، شناسهٔ پروژهٔ باز حفظ میشود)، کشیدن یک
مرز با ۴۰ گام و ثبت فاصلهٔ فریمها با rAF؛ p50 و p95 چاپ میشوند (قلاب فقط زیرِ navigator.webdriver؛
پروژهٔ perf، تنها). آستانهها: کشیدنِ دوبعدی p50 < 34 ms؛ کشیدن در «هر دو» p50 < 26 و p95 < 64 ms؛
چرخاندنِ صحنه با ۴۰ یونیت p50 < 2200 و p95 < 5200 ms. سنجشِ فاز ۴ روی مک با WebGL نرمافزاری (فقط
کشیدنِ دوبعدی): p50 ≈ ۱۷ ms (~۵۷ فریم)، p95 ≈ ۷۷ ms.
پنجرهٔ دوم (فاز ۸ ِ نسل پنجم)
Section titled “پنجرهٔ دوم (فاز ۸ ِ نسل پنجم)”نمای سهبعدی (یا دوبعدی، یا پلان) در پنجرهای جدا، معمولاً روی مانیتورِ دوم؛ ویرایش همچنان در ویرایشگر است و پنجرهٔ پیرو فقط میبیند.
کاربر با کدام کلیک: سربرگِ مرکز ▸ دکمهٔ پنجره کنارِ هر نمای دیدنی («دوبعدی / سهبعدی / پلان در پنجرهٔ جدا»)، یا منوی نما ▸ «سهبعدی در پنجرهٔ جدا». تا پنجره باز است همان نما از چیدمان برداشته میشود و نشانِ «سهبعدی در پنجرهٔ دوم · برگرداندن» در سربرگ میآید؛ فشردنش پنجره را میبندد.
| پرونده | کار |
|---|---|
components/layout/detached.ts |
نماهای بیرون (۸.۴): WindowProxy ِ window.open هر WATCH_MS (۵۰۰) برای closed پاییده میشود؛ stageView(view, detached) نمای روزِ مرکز («هر دو» منهای سهبعدی = دوبعدی)؛ انتخابِ useUi دست نمیخورد و آرایش برمیگردد |
components/layout/DetachControls |
دکمهٔ هر نما در سربرگ (نه گوشهٔ پنل: OverlayBar ِ صحنه هر دو لبه را اندازه میگیرد) و نشانِ «برگرداندن» |
lib/second-window.ts |
باز کردن: #/p/:id/view?pane=3d|2d|plan؛ مانیتورِ دیگر با getScreenDetails، وگرنه ۱۲۸۰×۸۰۰؛ WindowHandle یا null |
lib/workspace-channel.ts |
پیامهای zod، WorkspaceChannel (وصله ≤ ۱ در فریم، ضربانِ ۲ ثانیه)، WriterWatch (بسته پس از ۳ ثانیه) |
lib/workspace-sync.ts |
نویسنده (serveViewers، با useWorkspaceWriter در ProjectSession تا قفل اینجاست) و پیرو (followWriter، useViewerLink) |
app/ViewerPage.tsx |
پنجرهٔ پیرو: نوارِ نام، «تمامصفحه»، «برگرداندن به ویرایشگر»؛ «در انتظار» و «ویرایشگر بسته شد» |
- پیامها:
hello،snapshot(پروژه بی رندرهای ذخیرهشده، دیوار، انتخاب، نمایش، پوسته، زبان)،patch(هر زیرمجموعهٔ ناتهی از همانها، حداکثر یکی در هر فریم با پشتوانهٔ زمانسنجِ ۱۰۰ms برای پنجرهٔ پنهان یا پوشیده)،select،hover،saveView،bye،heartbeat. هر پیام پاکتِ{ v, projectId, from, role }دارد و هر چه با اسکیما نخواند — نسخهٔ دیگرِ پروتکل، پروژهٔ بدشکل — به شنونده نمیرسد. - نویسنده فقط دارندهٔ قفلِ پروژه است و با گرفتنِ قفل «سلام» میکند؛ پیرو به نویسندهای که عکسش
را ندارد «سلام» میگوید و عکسِ کامل میگیرد — پس پس از «همینجا ادامه بده» ِ زبانهٔ دیگر هم همگام
میماند. بی پیروِ زنده هیچ وصلهای نمیفرستد (
postMessageپروژه را حتی بی شنونده کپی میکند و عکسِ دیوار درونِ پروژه است)؛ پیرویی که ۳ ثانیه خاموش ماند و برگشت، عکسِ کامل میگیرد. - پیرو ویرایش نمیکند: فروشگاهش
readOnlyاست، انتخاب و اشاره و «ذخیرهٔ نما» درخواست به نویسندهاند (ذخیرهٔ نما یک قدمِ واگردِ ویرایشگر میشود؛Scene3DِonSaveView). تا عکس نرسیده «در انتظار ویرایشگر…» است، نه صحنهٔ پروژهٔ خالی؛ با رفتن از صفحه فروشگاه خالی میشود تا ویرایشگری که بعد در همان پنجره باز شود پروژه را از پایگاه بخواند. - بسته شدن: «خداحافظ» ِ
pagehideهمان لحظه، و سکوتِ ۳ ثانیه — مگر قفلِ پروژه هنوز گرفته باشد (کروم زمانسنجِ زبانهٔ پنهان را پس از ۵ دقیقه به دقیقهای یک بار میرساند). «باز کردن ویرایشگر» همان پنجره را ویرایشگر میکند. - آزمون:
test/workspace-channel.test.ts،test/second-window.test.ts،test/viewer-page.dom.test.tsx؛e2e/second-window.spec.ts(دو صفحه: انتخاب و ویرایش در پیرو، صفر تراکنشِ نوشتنیِ IndexedDB، «بسته شد» در ≤ ۳ ثانیه؛ دکمهٔ پنل با یک زمینهٔ WebGL در دو پنجره و آرایشی که پس از بستن برمیگردد)؛e2e/second-window.perf.spec.ts(درنگِ ویرایش تا پیرو، p95 ≤ ۱۰۰ms، در پروژهٔperf؛ عددهای سنجیده درdocs/journal/30-second-window.md)؛test/center-stage.dom.test.tsx(پنلِ بیرون، «برگرداندن»، پنجرهٔ مسدود، «کنار هم» ِ تنگ).