makersclaw 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/package.json +1 -1
- package/r/activity-log.json +52 -0
- package/r/agent-changes.json +51 -0
- package/r/agent-chat.json +53 -0
- package/r/agent-column.json +85 -0
- package/r/agent-dock.json +57 -0
- package/r/agent-kit.json +49 -0
- package/r/agent-marks.json +49 -0
- package/r/agent-pane.json +93 -0
- package/r/ai-action.json +56 -0
- package/r/annotate.json +4 -4
- package/r/approval-request.json +2 -2
- package/r/approval.json +54 -0
- package/r/ask-bar.json +50 -0
- package/r/audio-player.json +2 -2
- package/r/auth-form.json +2 -2
- package/r/automation-editor.json +66 -0
- package/r/automation-list.json +57 -0
- package/r/automation-model.json +118 -0
- package/r/automations-screen.json +71 -0
- package/r/board.json +2 -2
- package/r/calendar-view.json +3 -3
- package/r/cart.json +2 -2
- package/r/chart.json +5 -5
- package/r/chat-pane.json +84 -0
- package/r/chat.json +3 -3
- package/r/code-editor.json +2 -2
- package/r/comment-box.json +3 -3
- package/r/connect-card.json +51 -0
- package/r/contact-section.json +2 -2
- package/r/core.json +18 -6
- package/r/cost.json +51 -0
- package/r/csv-import.json +3 -3
- package/r/data-picker.json +60 -0
- package/r/data-table.json +2 -2
- package/r/diagram.json +2 -2
- package/r/doc-editor.json +2 -2
- package/r/doc-page.json +3 -3
- package/r/email-render.json +2 -2
- package/r/faq.json +2 -2
- package/r/file-upload.json +2 -2
- package/r/first-run.json +63 -0
- package/r/fix-note.json +57 -0
- package/r/flow-canvas.json +3 -3
- package/r/flow-editor.json +79 -0
- package/r/footer.json +3 -3
- package/r/gantt.json +3 -3
- package/r/job-panel.json +2 -2
- package/r/json-editor.json +3 -3
- package/r/library-shelf.json +56 -0
- package/r/map.json +2 -2
- package/r/margin-notes.json +67 -0
- package/r/menubar.json +2 -2
- package/r/mind-map.json +4 -4
- package/r/navigation-menu.json +2 -2
- package/r/needs-you.json +55 -0
- package/r/order-detail.json +2 -2
- package/r/org-chart.json +4 -4
- package/r/pace-bar.json +69 -0
- package/r/page-builder.json +3 -3
- package/r/payment-methods.json +2 -2
- package/r/pdf-document.json +2 -2
- package/r/pdf-viewer.json +3 -3
- package/r/product-detail.json +2 -2
- package/r/product-grid.json +3 -3
- package/r/record-form.json +2 -2
- package/r/record-grid.json +52 -0
- package/r/record-notes.json +4 -4
- package/r/registry.json +4089 -1510
- package/r/run-history.json +52 -0
- package/r/run-view.json +80 -0
- package/r/schedule-card.json +54 -0
- package/r/schedule-engine.json +79 -0
- package/r/schedule-gantt.json +83 -0
- package/r/schedule-ruler.json +64 -0
- package/r/search-dialog.json +2 -2
- package/r/settings-list.json +2 -2
- package/r/setup-checklist.json +2 -2
- package/r/share-dialog.json +2 -2
- package/r/sidebar.json +3 -3
- package/r/signature-pad.json +2 -2
- package/r/sketch-canvas.json +3 -3
- package/r/source-receipt.json +52 -0
- package/r/spreadsheet.json +3 -3
- package/r/stat-cards.json +3 -3
- package/r/step-picker.json +53 -0
- package/r/step-settings.json +72 -0
- package/r/suggestion.json +58 -0
- package/r/sum-bar.json +64 -0
- package/r/task-list.json +3 -3
- package/r/task-model.json +2 -2
- package/r/testimonials.json +2 -2
- package/r/tier-map.json +67 -0
- package/r/time-grid.json +73 -0
- package/r/time-strip.json +64 -0
- package/r/top-bar.json +66 -0
- package/r/transaction-list.json +2 -2
- package/r/trigger-picker.json +61 -0
- package/r/whiteboard.json +4 -4
- package/r/workflow-view.json +55 -0
- package/registry/blocks/activity-log/README.md +112 -0
- package/registry/blocks/activity-log/activity-log.jsx +351 -0
- package/registry/blocks/activity-log/block.json +33 -0
- package/registry/blocks/activity-log/demo.jsx +48 -0
- package/registry/blocks/activity-log/index.js +4 -0
- package/registry/blocks/agent-changes/README.md +94 -0
- package/registry/blocks/agent-changes/agent-changes.jsx +118 -0
- package/registry/blocks/agent-changes/block.json +28 -0
- package/registry/blocks/agent-changes/demo.jsx +39 -0
- package/registry/blocks/agent-changes/index.js +3 -0
- package/registry/blocks/agent-chat/README.md +145 -0
- package/registry/blocks/agent-chat/agent-chat.jsx +415 -0
- package/registry/blocks/agent-chat/block.json +30 -0
- package/registry/blocks/agent-chat/demo.jsx +165 -0
- package/registry/blocks/agent-chat/index.js +3 -0
- package/registry/blocks/agent-column/README.md +197 -0
- package/registry/blocks/agent-column/agent-column.jsx +492 -0
- package/registry/blocks/agent-column/block.json +67 -0
- package/registry/blocks/agent-column/column-picker.jsx +163 -0
- package/registry/blocks/agent-column/demo.jsx +50 -0
- package/registry/blocks/agent-column/index.js +7 -0
- package/registry/blocks/agent-column/model.js +164 -0
- package/registry/blocks/agent-column/receipt.jsx +483 -0
- package/registry/blocks/agent-column/spreadsheet-type.jsx +24 -0
- package/registry/blocks/agent-dock/README.md +210 -0
- package/registry/blocks/agent-dock/agent-dock.jsx +356 -0
- package/registry/blocks/agent-dock/block.json +47 -0
- package/registry/blocks/agent-dock/demo.jsx +153 -0
- package/registry/blocks/agent-dock/dock-model.js +118 -0
- package/registry/blocks/agent-dock/index.js +5 -0
- package/registry/blocks/agent-kit/README.md +73 -0
- package/registry/blocks/agent-kit/agent-kit.jsx +86 -0
- package/registry/blocks/agent-kit/block.json +10 -0
- package/registry/blocks/agent-kit/demo.jsx +35 -0
- package/registry/blocks/agent-kit/index.js +3 -0
- package/registry/blocks/agent-marks/README.md +80 -0
- package/registry/blocks/agent-marks/agent-marks.jsx +133 -0
- package/registry/blocks/agent-marks/block.json +27 -0
- package/registry/blocks/agent-marks/demo.jsx +49 -0
- package/registry/blocks/agent-marks/index.js +3 -0
- package/registry/blocks/agent-pane/README.md +225 -0
- package/registry/blocks/agent-pane/agent-pane.jsx +244 -0
- package/registry/blocks/agent-pane/block.json +41 -0
- package/registry/blocks/agent-pane/composer.jsx +6 -0
- package/registry/blocks/agent-pane/demo.jsx +233 -0
- package/registry/blocks/agent-pane/dock-context.js +10 -0
- package/registry/blocks/agent-pane/index.js +4 -0
- package/registry/blocks/agent-pane/prose.jsx +84 -0
- package/registry/blocks/agent-pane/questions.jsx +134 -0
- package/registry/blocks/agent-pane/transcript.jsx +268 -0
- package/registry/blocks/agent-pane/work.jsx +368 -0
- package/registry/blocks/ai-action/README.md +94 -0
- package/registry/blocks/ai-action/ai-action.jsx +188 -0
- package/registry/blocks/ai-action/block.json +34 -0
- package/registry/blocks/ai-action/demo.jsx +93 -0
- package/registry/blocks/ai-action/index.js +3 -0
- package/registry/blocks/annotate/README.md +14 -11
- package/registry/blocks/approval/README.md +145 -0
- package/registry/blocks/approval/approval.jsx +163 -0
- package/registry/blocks/approval/block.json +31 -0
- package/registry/blocks/approval/demo.jsx +121 -0
- package/registry/blocks/approval/index.js +3 -0
- package/registry/blocks/ask-bar/README.md +105 -0
- package/registry/blocks/ask-bar/ask-bar.jsx +166 -0
- package/registry/blocks/ask-bar/block.json +26 -0
- package/registry/blocks/ask-bar/demo.jsx +78 -0
- package/registry/blocks/ask-bar/index.js +3 -0
- package/registry/blocks/automation-editor/README.md +291 -0
- package/registry/blocks/automation-editor/automation-editor.jsx +656 -0
- package/registry/blocks/automation-editor/block.json +39 -0
- package/registry/blocks/automation-editor/demo.jsx +284 -0
- package/registry/blocks/automation-editor/index.js +4 -0
- package/registry/blocks/automation-editor/run-inputs.jsx +82 -0
- package/registry/blocks/automation-list/README.md +129 -0
- package/registry/blocks/automation-list/automation-list.jsx +476 -0
- package/registry/blocks/automation-list/block.json +35 -0
- package/registry/blocks/automation-list/demo.jsx +67 -0
- package/registry/blocks/automation-list/index.js +3 -0
- package/registry/blocks/automation-model/README.md +109 -0
- package/registry/blocks/automation-model/block.json +135 -0
- package/registry/blocks/automation-model/catalog.js +256 -0
- package/registry/blocks/automation-model/check.js +161 -0
- package/registry/blocks/automation-model/demo.jsx +42 -0
- package/registry/blocks/automation-model/edit.js +262 -0
- package/registry/blocks/automation-model/fixes.js +287 -0
- package/registry/blocks/automation-model/glyph.jsx +13 -0
- package/registry/blocks/automation-model/graph.js +410 -0
- package/registry/blocks/automation-model/index.js +33 -0
- package/registry/blocks/automation-model/overlay.js +175 -0
- package/registry/blocks/automation-model/parts.jsx +94 -0
- package/registry/blocks/automation-model/schedule.js +53 -0
- package/registry/blocks/automation-model/templates.js +128 -0
- package/registry/blocks/automation-model/values.js +29 -0
- package/registry/blocks/automation-model/words.js +439 -0
- package/registry/blocks/automations-screen/README.md +207 -0
- package/registry/blocks/automations-screen/automations-screen.jsx +452 -0
- package/registry/blocks/automations-screen/block.json +47 -0
- package/registry/blocks/automations-screen/demo.jsx +364 -0
- package/registry/blocks/automations-screen/index.js +4 -0
- package/registry/blocks/automations-screen/screen-model.js +129 -0
- package/registry/blocks/chart/cartesian.jsx +1 -1
- package/registry/blocks/chart/core.jsx +33 -23
- package/registry/blocks/chart/pie.jsx +1 -1
- package/registry/blocks/chat/README.md +4 -1
- package/registry/blocks/chat/demo.jsx +2 -2
- package/registry/blocks/chat-pane/README.md +143 -0
- package/registry/blocks/chat-pane/block.json +48 -0
- package/registry/blocks/chat-pane/chat-attach.js +166 -0
- package/registry/blocks/chat-pane/chat-composer.jsx +195 -0
- package/registry/blocks/chat-pane/chat-context.js +17 -0
- package/registry/blocks/chat-pane/chat-model.js +43 -0
- package/registry/blocks/chat-pane/chat-pane.jsx +398 -0
- package/registry/blocks/chat-pane/chats-list.jsx +52 -0
- package/registry/blocks/chat-pane/demo.jsx +103 -0
- package/registry/blocks/chat-pane/index.js +9 -0
- package/registry/blocks/connect-card/README.md +99 -0
- package/registry/blocks/connect-card/block.json +10 -0
- package/registry/blocks/connect-card/connect-card.jsx +103 -0
- package/registry/blocks/connect-card/demo.jsx +49 -0
- package/registry/blocks/connect-card/index.js +3 -0
- package/registry/blocks/cost/README.md +115 -0
- package/registry/blocks/cost/block.json +10 -0
- package/registry/blocks/cost/cost.jsx +176 -0
- package/registry/blocks/cost/demo.jsx +66 -0
- package/registry/blocks/cost/index.js +3 -0
- package/registry/blocks/data-picker/README.md +81 -0
- package/registry/blocks/data-picker/block.json +38 -0
- package/registry/blocks/data-picker/data-picker.jsx +210 -0
- package/registry/blocks/data-picker/demo.jsx +34 -0
- package/registry/blocks/data-picker/fields.js +89 -0
- package/registry/blocks/data-picker/index.js +4 -0
- package/registry/blocks/data-table/demo.jsx +1 -1
- package/registry/blocks/first-run/README.md +118 -0
- package/registry/blocks/first-run/block.json +11 -0
- package/registry/blocks/first-run/demo.jsx +88 -0
- package/registry/blocks/first-run/first-run-model.js +51 -0
- package/registry/blocks/first-run/first-run.jsx +156 -0
- package/registry/blocks/first-run/index.js +5 -0
- package/registry/blocks/first-run/probe-rules.js +26 -0
- package/registry/blocks/fix-note/README.md +79 -0
- package/registry/blocks/fix-note/block.json +35 -0
- package/registry/blocks/fix-note/demo.jsx +30 -0
- package/registry/blocks/fix-note/fix-note.jsx +235 -0
- package/registry/blocks/fix-note/index.js +3 -0
- package/registry/blocks/flow-canvas/README.md +15 -8
- package/registry/blocks/flow-editor/README.md +108 -0
- package/registry/blocks/flow-editor/block.json +44 -0
- package/registry/blocks/flow-editor/demo.jsx +115 -0
- package/registry/blocks/flow-editor/flow-canvas.jsx +568 -0
- package/registry/blocks/flow-editor/flow-editor.jsx +154 -0
- package/registry/blocks/flow-editor/index.js +6 -0
- package/registry/blocks/flow-editor/minimap.jsx +78 -0
- package/registry/blocks/flow-editor/outline.jsx +134 -0
- package/registry/blocks/footer/README.md +3 -2
- package/registry/blocks/library-shelf/README.md +117 -0
- package/registry/blocks/library-shelf/block.json +28 -0
- package/registry/blocks/library-shelf/demo.jsx +68 -0
- package/registry/blocks/library-shelf/index.js +3 -0
- package/registry/blocks/library-shelf/library-shelf.jsx +211 -0
- package/registry/blocks/library-shelf/probe-rules.js +15 -0
- package/registry/blocks/margin-notes/README.md +222 -0
- package/registry/blocks/margin-notes/block.json +10 -0
- package/registry/blocks/margin-notes/demo.jsx +144 -0
- package/registry/blocks/margin-notes/index.js +20 -0
- package/registry/blocks/margin-notes/margin-model.js +224 -0
- package/registry/blocks/margin-notes/margin-notes.jsx +465 -0
- package/registry/blocks/mind-map/README.md +4 -2
- package/registry/blocks/needs-you/README.md +177 -0
- package/registry/blocks/needs-you/block.json +34 -0
- package/registry/blocks/needs-you/demo.jsx +143 -0
- package/registry/blocks/needs-you/index.js +3 -0
- package/registry/blocks/needs-you/needs-you.jsx +441 -0
- package/registry/blocks/org-chart/README.md +4 -2
- package/registry/blocks/pace-bar/README.md +230 -0
- package/registry/blocks/pace-bar/block.json +10 -0
- package/registry/blocks/pace-bar/demo.jsx +255 -0
- package/registry/blocks/pace-bar/index.js +22 -0
- package/registry/blocks/pace-bar/pace-bar.jsx +461 -0
- package/registry/blocks/pace-bar/pace-model.js +288 -0
- package/registry/blocks/page-builder/README.md +1 -1
- package/registry/blocks/page-builder/demo.jsx +3 -3
- package/registry/blocks/pdf-viewer/README.md +4 -1
- package/registry/blocks/record-grid/README.md +100 -0
- package/registry/blocks/record-grid/block.json +28 -0
- package/registry/blocks/record-grid/demo.jsx +43 -0
- package/registry/blocks/record-grid/index.js +3 -0
- package/registry/blocks/record-grid/record-grid.jsx +251 -0
- package/registry/blocks/run-history/README.md +138 -0
- package/registry/blocks/run-history/block.json +10 -0
- package/registry/blocks/run-history/demo.jsx +74 -0
- package/registry/blocks/run-history/index.js +3 -0
- package/registry/blocks/run-history/run-history.jsx +218 -0
- package/registry/blocks/run-view/README.md +174 -0
- package/registry/blocks/run-view/block.json +50 -0
- package/registry/blocks/run-view/demo.jsx +286 -0
- package/registry/blocks/run-view/index.js +5 -0
- package/registry/blocks/run-view/run-controls.jsx +266 -0
- package/registry/blocks/run-view/run-view.jsx +236 -0
- package/registry/blocks/run-view/runs-list.jsx +99 -0
- package/registry/blocks/schedule-card/README.md +121 -0
- package/registry/blocks/schedule-card/block.json +10 -0
- package/registry/blocks/schedule-card/demo.jsx +82 -0
- package/registry/blocks/schedule-card/index.js +3 -0
- package/registry/blocks/schedule-card/schedule-card.jsx +136 -0
- package/registry/blocks/schedule-engine/README.md +137 -0
- package/registry/blocks/schedule-engine/block.json +10 -0
- package/registry/blocks/schedule-engine/demo.jsx +82 -0
- package/registry/blocks/schedule-engine/engine.js +769 -0
- package/registry/blocks/schedule-engine/index.js +9 -0
- package/registry/blocks/schedule-engine/ops.js +71 -0
- package/registry/blocks/schedule-engine/place.js +294 -0
- package/registry/blocks/schedule-engine/tree.js +79 -0
- package/registry/blocks/schedule-engine/waits.js +80 -0
- package/registry/blocks/schedule-gantt/README.md +159 -0
- package/registry/blocks/schedule-gantt/block.json +10 -0
- package/registry/blocks/schedule-gantt/demo.jsx +223 -0
- package/registry/blocks/schedule-gantt/gantt-model.js +219 -0
- package/registry/blocks/schedule-gantt/geometry.js +176 -0
- package/registry/blocks/schedule-gantt/index.js +26 -0
- package/registry/blocks/schedule-gantt/schedule-gantt.jsx +882 -0
- package/registry/blocks/schedule-gantt/sheet.jsx +372 -0
- package/registry/blocks/schedule-gantt/track.jsx +190 -0
- package/registry/blocks/schedule-ruler/README.md +148 -0
- package/registry/blocks/schedule-ruler/block.json +10 -0
- package/registry/blocks/schedule-ruler/demo.jsx +181 -0
- package/registry/blocks/schedule-ruler/index.js +21 -0
- package/registry/blocks/schedule-ruler/ruler-model.js +239 -0
- package/registry/blocks/schedule-ruler/schedule-ruler.jsx +548 -0
- package/registry/blocks/sidebar/README.md +23 -13
- package/registry/blocks/sketch-canvas/README.md +15 -9
- package/registry/blocks/source-receipt/README.md +97 -0
- package/registry/blocks/source-receipt/block.json +30 -0
- package/registry/blocks/source-receipt/demo.jsx +71 -0
- package/registry/blocks/source-receipt/index.js +3 -0
- package/registry/blocks/source-receipt/source-receipt.jsx +107 -0
- package/registry/blocks/spreadsheet/spreadsheet.jsx +15 -1
- package/registry/blocks/step-picker/README.md +78 -0
- package/registry/blocks/step-picker/block.json +31 -0
- package/registry/blocks/step-picker/demo.jsx +23 -0
- package/registry/blocks/step-picker/index.js +3 -0
- package/registry/blocks/step-picker/step-picker.jsx +144 -0
- package/registry/blocks/step-settings/README.md +100 -0
- package/registry/blocks/step-settings/block.json +44 -0
- package/registry/blocks/step-settings/demo.jsx +93 -0
- package/registry/blocks/step-settings/fields.jsx +58 -0
- package/registry/blocks/step-settings/flow-settings.jsx +115 -0
- package/registry/blocks/step-settings/index.js +5 -0
- package/registry/blocks/step-settings/step-settings.jsx +290 -0
- package/registry/blocks/suggestion/README.md +171 -0
- package/registry/blocks/suggestion/block.json +40 -0
- package/registry/blocks/suggestion/demo.jsx +91 -0
- package/registry/blocks/suggestion/index.js +3 -0
- package/registry/blocks/suggestion/suggestion.jsx +436 -0
- package/registry/blocks/sum-bar/README.md +122 -0
- package/registry/blocks/sum-bar/block.json +10 -0
- package/registry/blocks/sum-bar/demo.jsx +110 -0
- package/registry/blocks/sum-bar/index.js +5 -0
- package/registry/blocks/sum-bar/sum-bar.jsx +209 -0
- package/registry/blocks/sum-bar/sum-model.js +126 -0
- package/registry/blocks/tier-map/README.md +217 -0
- package/registry/blocks/tier-map/block.json +10 -0
- package/registry/blocks/tier-map/demo.jsx +222 -0
- package/registry/blocks/tier-map/index.js +24 -0
- package/registry/blocks/tier-map/tier-map.jsx +574 -0
- package/registry/blocks/tier-map/tier-model.js +318 -0
- package/registry/blocks/time-grid/README.md +305 -0
- package/registry/blocks/time-grid/block.json +10 -0
- package/registry/blocks/time-grid/demo.jsx +279 -0
- package/registry/blocks/time-grid/grid-model.js +322 -0
- package/registry/blocks/time-grid/index.js +25 -0
- package/registry/blocks/time-grid/time-grid.jsx +1325 -0
- package/registry/blocks/time-strip/README.md +141 -0
- package/registry/blocks/time-strip/block.json +10 -0
- package/registry/blocks/time-strip/demo.jsx +129 -0
- package/registry/blocks/time-strip/index.js +19 -0
- package/registry/blocks/time-strip/strip-model.js +178 -0
- package/registry/blocks/time-strip/time-strip.jsx +260 -0
- package/registry/blocks/top-bar/README.md +149 -0
- package/registry/blocks/top-bar/block.json +35 -0
- package/registry/blocks/top-bar/demo.jsx +78 -0
- package/registry/blocks/top-bar/index.js +5 -0
- package/registry/blocks/top-bar/probe-rules.js +74 -0
- package/registry/blocks/top-bar/top-bar-model.js +152 -0
- package/registry/blocks/top-bar/top-bar.jsx +244 -0
- package/registry/blocks/trigger-picker/README.md +98 -0
- package/registry/blocks/trigger-picker/block.json +38 -0
- package/registry/blocks/trigger-picker/demo.jsx +59 -0
- package/registry/blocks/trigger-picker/index.js +3 -0
- package/registry/blocks/trigger-picker/trigger-picker.jsx +374 -0
- package/registry/blocks/whiteboard/README.md +20 -14
- package/registry/blocks/workflow-view/README.md +123 -0
- package/registry/blocks/workflow-view/block.json +10 -0
- package/registry/blocks/workflow-view/demo.jsx +90 -0
- package/registry/blocks/workflow-view/index.js +3 -0
- package/registry/blocks/workflow-view/workflow-view.jsx +203 -0
- package/src/cli.mjs +2 -2
- package/src/install.mjs +100 -22
- package/src/registry.mjs +37 -3
- package/templates/next/browser/lib/data.js +6 -1
- package/templates/next/files/README.md +909 -26
- package/templates/next/files/app/shell.jsx +54 -37
- package/templates/next/files/check/automation-format.ts +2103 -0
- package/templates/next/files/check/automation-package.ts +142 -0
- package/templates/next/files/check/run.mjs +1043 -12
- package/templates/next/files/components/ui/field.jsx +17 -4
- package/templates/next/files/components/ui/icon.jsx +13 -0
- package/templates/next/files/components/ui/index.js +3 -1
- package/templates/next/files/components/ui/layout.jsx +382 -0
- package/templates/next/files/components/ui/notice.jsx +70 -0
- package/templates/next/files/components/ui/page.jsx +124 -0
- package/templates/next/files/components/ui/stat.jsx +22 -7
- package/templates/next/files/components/ui/toast.jsx +6 -3
- package/templates/next/files/lib/automations.js +660 -0
- package/templates/next/files/lib/claw.js +244 -5
- package/templates/next/files/lib/door.js +92 -0
- package/templates/next/files/lib/hooks.js +1694 -6
- package/templates/next/files/lib/platform.js +611 -14
- package/templates/next/template.json +22 -3
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { Icon, Menu, cx } from "../../ui";
|
|
5
|
+
import { monthDay } from "./gantt-model";
|
|
6
|
+
|
|
7
|
+
// The task sheet on the chart's left, one row for one row of bars: the
|
|
8
|
+
// outline number, the task (a phase folds, a milestone is a diamond, its
|
|
9
|
+
// tags in words), and whichever columns the page asked for: start, finish,
|
|
10
|
+
// length in working days, how much is done, the owner, what it waits on,
|
|
11
|
+
// slack, and how far it sits from a baseline. A row's "…" holds every
|
|
12
|
+
// change; double-click a name, a length, a % or a "waits on" to type it.
|
|
13
|
+
// The last row adds a task in place.
|
|
14
|
+
|
|
15
|
+
export const ROW = 30;
|
|
16
|
+
|
|
17
|
+
const NUM = "tabular-nums";
|
|
18
|
+
|
|
19
|
+
function Chevron({ open }) {
|
|
20
|
+
return <Icon name={open ? "chevron-down" : "chevron-right"} size={14} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const TAG = {
|
|
24
|
+
late: "border border-warning text-warning",
|
|
25
|
+
crit: "border border-destructive text-destructive",
|
|
26
|
+
pole: "border border-destructive text-destructive",
|
|
27
|
+
unsaved: "border border-destructive text-destructive",
|
|
28
|
+
blue: "border border-info text-info",
|
|
29
|
+
ink: "bg-foreground text-background",
|
|
30
|
+
quiet: "text-muted-foreground",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** A tag in a row: words in a small outlined pill; the agent's is a filled badge with its reason on hover. */
|
|
34
|
+
export function TagChip({ tag }) {
|
|
35
|
+
if (tag.kind === "agent")
|
|
36
|
+
return (
|
|
37
|
+
<span title={tag.title ?? tag.label ?? "The agent did this"} className="inline-flex size-[14px] shrink-0 items-center justify-center rounded-[3px] bg-info text-background">
|
|
38
|
+
<Icon name="sparkles" size={9} label={tag.title ?? "The agent did this"} />
|
|
39
|
+
</span>
|
|
40
|
+
);
|
|
41
|
+
if (tag.kind === "dot") return <span title={tag.title ?? tag.label} aria-label={tag.title ?? tag.label} className="size-1.5 shrink-0 rounded-full bg-warning" />;
|
|
42
|
+
return <span className={cx("inline-flex h-4 shrink-0 items-center whitespace-nowrap rounded-[3px] px-1 text-[10px] font-semibold", tag.kind === "quiet" ? "font-normal" : "", TAG[tag.kind] ?? TAG.quiet)}>{tag.label}</span>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** A dense cell editor: a 30 px row needs its own. */
|
|
46
|
+
export function CellInput({ type = "text", value, onCommit, onCancel, label, align = "left", min, max, className, placeholder, quiet = false, autoFocus = true }) {
|
|
47
|
+
const ref = useRef(null);
|
|
48
|
+
const [v, setV] = useState(value ?? "");
|
|
49
|
+
const settled = useRef(false);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!autoFocus) return;
|
|
52
|
+
ref.current?.focus();
|
|
53
|
+
ref.current?.select?.();
|
|
54
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
55
|
+
const commit = () => {
|
|
56
|
+
if (settled.current) return;
|
|
57
|
+
settled.current = true;
|
|
58
|
+
onCommit(v);
|
|
59
|
+
};
|
|
60
|
+
return (
|
|
61
|
+
<input
|
|
62
|
+
ref={ref}
|
|
63
|
+
type={type}
|
|
64
|
+
aria-label={label}
|
|
65
|
+
value={v}
|
|
66
|
+
min={min}
|
|
67
|
+
max={max}
|
|
68
|
+
placeholder={placeholder}
|
|
69
|
+
onChange={(e) => setV(e.target.value)}
|
|
70
|
+
onBlur={commit}
|
|
71
|
+
onKeyDown={(e) => {
|
|
72
|
+
e.stopPropagation();
|
|
73
|
+
if (e.key === "Enter") {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
commit();
|
|
76
|
+
} else if (e.key === "Escape") {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
settled.current = true;
|
|
79
|
+
onCancel();
|
|
80
|
+
}
|
|
81
|
+
}}
|
|
82
|
+
className={cx("h-6 w-full min-w-0 rounded-[3px] bg-background px-1.5 text-[12px] text-foreground outline-none", quiet ? "border border-border focus-visible:border-ring" : "border border-ring ring-1 ring-ring", align === "right" && "text-right", type !== "text" && NUM, className)}
|
|
83
|
+
/>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function SheetHead({ cols }) {
|
|
88
|
+
return (
|
|
89
|
+
<div className="flex h-full items-end px-0 pb-1.5">
|
|
90
|
+
{cols.map((c) => (
|
|
91
|
+
<div key={c.key} className={cx("shrink-0 truncate px-1.5 text-[11px] font-semibold text-muted-foreground", c.key === "title" && "min-w-0 flex-1", c.right && "text-right")} style={c.key === "title" ? undefined : { width: c.w }}>
|
|
92
|
+
{c.label}
|
|
93
|
+
</div>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const tone = { late: "text-warning", early: "text-success", same: "text-muted-foreground", new: "text-info" };
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* One row of the sheet. `r` is the engine's row; `cells` carries what the
|
|
103
|
+
* page worked out (owner words, the waits-on text, the baseline delta).
|
|
104
|
+
* `editing` { field } when a cell is open.
|
|
105
|
+
*/
|
|
106
|
+
export function SheetRow({ e, t, r, cols, selected, closed, cells, tags, late, editing, rowH, critical, onSelect, onOpen, onToggle, onStartEdit, onCommit, onCancel, onOpenLink, menu, dim, onHover, foldedCount, moving }) {
|
|
107
|
+
const summary = r?.summary;
|
|
108
|
+
const done = r?.pinned;
|
|
109
|
+
const crit = critical && r?.critical && !done;
|
|
110
|
+
const editableLength = !summary && !r?.milestone && !done;
|
|
111
|
+
return (
|
|
112
|
+
<div
|
|
113
|
+
data-row={e.id}
|
|
114
|
+
onMouseEnter={onHover}
|
|
115
|
+
onClick={(ev) => onSelect?.(e.id, { additive: ev.metaKey || ev.ctrlKey || ev.shiftKey })}
|
|
116
|
+
onDoubleClick={() => onOpen?.(t)}
|
|
117
|
+
className={cx("group/row relative flex items-center border-b border-border text-[13px]", selected ? "bg-accent" : "hover:bg-muted/50", dim && "opacity-50")}
|
|
118
|
+
style={{ height: rowH }}
|
|
119
|
+
>
|
|
120
|
+
{cols.map((c) => {
|
|
121
|
+
if (c.key === "outline")
|
|
122
|
+
return (
|
|
123
|
+
<div key="outline" className={cx(NUM, "shrink-0 truncate px-1.5 text-[12px]", crit ? "text-destructive" : "text-muted-foreground")} style={{ width: c.w }}>
|
|
124
|
+
{e.outline}
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
if (c.key === "title") {
|
|
128
|
+
const isEditing = editing?.field === "title";
|
|
129
|
+
return (
|
|
130
|
+
<div key="title" className="flex min-w-0 flex-1 items-center gap-1 px-1.5" style={{ paddingLeft: 6 + e.depth * 14 }}>
|
|
131
|
+
{summary ? (
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
aria-label={closed ? `Open ${t.title}` : `Fold ${t.title}`}
|
|
135
|
+
aria-expanded={!closed}
|
|
136
|
+
onClick={(ev) => {
|
|
137
|
+
ev.stopPropagation();
|
|
138
|
+
onToggle?.(e.id);
|
|
139
|
+
}}
|
|
140
|
+
className="grid size-5 shrink-0 place-items-center rounded-[3px] text-muted-foreground outline-none hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
|
|
141
|
+
>
|
|
142
|
+
<Chevron open={!closed} />
|
|
143
|
+
</button>
|
|
144
|
+
) : r?.milestone ? (
|
|
145
|
+
<span className="grid size-5 shrink-0 place-items-center">
|
|
146
|
+
<span aria-hidden="true" className={cx("size-[9px] rotate-45 bg-foreground", crit && "bg-destructive")} />
|
|
147
|
+
</span>
|
|
148
|
+
) : (
|
|
149
|
+
<span className="size-5 shrink-0" aria-hidden="true" />
|
|
150
|
+
)}
|
|
151
|
+
{isEditing ? (
|
|
152
|
+
<CellInput value={t.title} label={`Name of ${t.title}`} onCancel={onCancel} onCommit={(v) => onCommit("title", v)} />
|
|
153
|
+
) : (
|
|
154
|
+
<span
|
|
155
|
+
onDoubleClick={(ev) => {
|
|
156
|
+
ev.stopPropagation();
|
|
157
|
+
onStartEdit?.("title");
|
|
158
|
+
}}
|
|
159
|
+
title={t.title}
|
|
160
|
+
className={cx("min-w-0 truncate", summary ? "font-semibold" : done ? "text-muted-foreground" : "text-foreground")}
|
|
161
|
+
>
|
|
162
|
+
{t.title}
|
|
163
|
+
</span>
|
|
164
|
+
)}
|
|
165
|
+
{closed && summary && foldedCount ? <span className="shrink-0 text-[11px] text-muted-foreground">{foldedCount}</span> : null}
|
|
166
|
+
{late ? <TagChip tag={{ label: "Late", kind: "late" }} /> : null}
|
|
167
|
+
{(tags ?? []).map((g, i) => (
|
|
168
|
+
<TagChip key={`${g.kind}${g.label ?? i}`} tag={g} />
|
|
169
|
+
))}
|
|
170
|
+
{menu ? (
|
|
171
|
+
<span className={cx("ml-auto shrink-0 opacity-0 group-hover/row:opacity-100 focus-within:opacity-100", selected && "opacity-100")} onClickCapture={() => !selected && onSelect?.(e.id)} onClick={(ev) => ev.stopPropagation()}>
|
|
172
|
+
<Menu label={`${t.title}: change it`} items={menu} />
|
|
173
|
+
</span>
|
|
174
|
+
) : null}
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (c.key === "start" || c.key === "due") {
|
|
179
|
+
const iso = c.key === "start" ? r?.start : r?.due;
|
|
180
|
+
const shown = c.key === "due" && r?.milestone ? "" : iso ? monthDay(iso) : null;
|
|
181
|
+
return (
|
|
182
|
+
<div key={c.key} className={cx(NUM, "shrink-0 truncate px-1.5 text-[12px]", moving?.has?.(e.id) && "text-ring")} style={{ width: c.w }}>
|
|
183
|
+
{shown === null ? <span className="text-muted-foreground">no date</span> : shown}
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (c.key === "who") {
|
|
188
|
+
const o = cells?.owner;
|
|
189
|
+
return (
|
|
190
|
+
<div key="who" className="shrink-0 truncate px-1.5 text-[12px]" style={{ width: c.w }} title={o?.title}>
|
|
191
|
+
{summary || r?.milestone ? null : o?.name ? (
|
|
192
|
+
<span className={o.left ? "text-muted-foreground" : undefined}>{o.name}{o.left ? " (left)" : ""}</span>
|
|
193
|
+
) : (
|
|
194
|
+
<span className="text-warning">no owner</span>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (c.key === "days") {
|
|
200
|
+
const undated = !r?.dated;
|
|
201
|
+
if (editing?.field === "days" || (undated && !summary && editableLength))
|
|
202
|
+
return (
|
|
203
|
+
<div key="days" className="shrink-0 px-1" style={{ width: c.w }} onClick={(ev) => ev.stopPropagation()}>
|
|
204
|
+
<CellInput
|
|
205
|
+
type="number"
|
|
206
|
+
min={1}
|
|
207
|
+
align="right"
|
|
208
|
+
placeholder="none"
|
|
209
|
+
value={cells?.lengthGuess != null ? String(cells.lengthGuess) : undated ? "" : String(r?.duration ?? "")}
|
|
210
|
+
label={`Length of ${t.title} in working days`}
|
|
211
|
+
onCancel={onCancel}
|
|
212
|
+
onCommit={(v) => (String(v) === String(r?.duration ?? "") || v === "" ? onCancel() : onCommit("days", v))}
|
|
213
|
+
quiet={undated && editing?.field !== "days"}
|
|
214
|
+
autoFocus={!undated || editing?.field === "days"}
|
|
215
|
+
/>
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
const text = summary ? (r?.dated && r.duration != null ? `${r.duration} wd` : "") : r?.milestone ? "0 wd" : r?.duration != null ? `${r.duration} wd${cells?.guessed ? "?" : ""}` : "";
|
|
219
|
+
return (
|
|
220
|
+
<div
|
|
221
|
+
key="days"
|
|
222
|
+
onDoubleClick={(ev) => {
|
|
223
|
+
if (!editableLength) return;
|
|
224
|
+
ev.stopPropagation();
|
|
225
|
+
onStartEdit?.("days");
|
|
226
|
+
}}
|
|
227
|
+
className={cx(NUM, "shrink-0 truncate px-1.5 text-right text-[12px]", cells?.guessed ? "text-info" : summary || r?.milestone ? "text-muted-foreground" : "text-foreground")}
|
|
228
|
+
style={{ width: c.w }}
|
|
229
|
+
title={cells?.guessed ? "A guess from the title" : undefined}
|
|
230
|
+
>
|
|
231
|
+
{text}
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (c.key === "done") {
|
|
236
|
+
if (editing?.field === "progress")
|
|
237
|
+
return (
|
|
238
|
+
<div key="done" className="shrink-0 px-1" style={{ width: c.w }} onClick={(ev) => ev.stopPropagation()}>
|
|
239
|
+
<CellInput type="number" min={0} max={100} align="right" value={String(r?.progress ?? 0)} label={`Done, in per cent, of ${t.title}`} onCancel={onCancel} onCommit={(v) => onCommit("progress", v)} />
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
return (
|
|
243
|
+
<div
|
|
244
|
+
key="done"
|
|
245
|
+
onDoubleClick={(ev) => {
|
|
246
|
+
if (summary || r?.milestone || done) return;
|
|
247
|
+
ev.stopPropagation();
|
|
248
|
+
onStartEdit?.("progress");
|
|
249
|
+
}}
|
|
250
|
+
className={cx(NUM, "flex shrink-0 justify-end px-1.5 text-[12px] text-muted-foreground")}
|
|
251
|
+
style={{ width: c.w }}
|
|
252
|
+
>
|
|
253
|
+
{done && !summary ? <Icon name="check" size={14} label="Done" /> : r?.milestone || !r ? "" : `${r.progress}%`}
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
if (c.key === "waits") {
|
|
258
|
+
const text = cells?.waits ?? "";
|
|
259
|
+
if (editing?.field === "waits")
|
|
260
|
+
return (
|
|
261
|
+
<div key="waits" className="shrink-0 px-1" style={{ width: c.w }} onClick={(ev) => ev.stopPropagation()}>
|
|
262
|
+
<CellInput value={text} label={`What ${t.title} waits on`} placeholder="2.2FS+2d" onCancel={onCancel} onCommit={(v) => (v === text ? onCancel() : onCommit("waits", v))} />
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
return (
|
|
266
|
+
<div
|
|
267
|
+
key="waits"
|
|
268
|
+
onDoubleClick={(ev) => {
|
|
269
|
+
ev.stopPropagation();
|
|
270
|
+
onStartEdit?.("waits");
|
|
271
|
+
}}
|
|
272
|
+
onClick={(ev) => {
|
|
273
|
+
if (cells?.waitsLinks?.length === 1 && onOpenLink && (ev.metaKey || ev.ctrlKey)) {
|
|
274
|
+
ev.stopPropagation();
|
|
275
|
+
onOpenLink(cells.waitsLinks[0]);
|
|
276
|
+
}
|
|
277
|
+
}}
|
|
278
|
+
title={text ? `${text} (double-click to change)` : "Double-click to say what it waits on"}
|
|
279
|
+
className={cx(NUM, "shrink-0 truncate px-1.5 text-[12px]", cells?.waitsBroken ? "text-destructive" : "text-foreground")}
|
|
280
|
+
style={{ width: c.w }}
|
|
281
|
+
>
|
|
282
|
+
{text}
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
if (c.key === "slack") {
|
|
287
|
+
const text = cells?.slack ?? "";
|
|
288
|
+
return (
|
|
289
|
+
<div key="slack" className={cx(NUM, "shrink-0 truncate px-1.5 text-right text-[12px]", crit ? "font-semibold text-destructive" : "text-muted-foreground")} style={{ width: c.w }}>
|
|
290
|
+
{text}
|
|
291
|
+
</div>
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
if (c.key === "vs") {
|
|
295
|
+
const d = cells?.delta;
|
|
296
|
+
return (
|
|
297
|
+
<div key="vs" className={cx(NUM, "shrink-0 truncate px-1.5 text-right text-[12px]", d?.tone ? tone[d.tone] : "text-muted-foreground")} style={{ width: c.w }}>
|
|
298
|
+
{d?.text ?? ""}
|
|
299
|
+
</div>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
})}
|
|
304
|
+
</div>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The row that adds in place: a title and a length. Enter adds a task,
|
|
310
|
+
* Shift+Enter a milestone; Tab makes it a sub-task of the row above; Esc
|
|
311
|
+
* cancels. Closed, it is one quiet line that says so.
|
|
312
|
+
*/
|
|
313
|
+
export function AddRow({ open, onOpen, draft, setDraft, onSubmit, busy, addRef, parentTitle, aboveTitle, onIndent, height = ROW }) {
|
|
314
|
+
if (!open)
|
|
315
|
+
return (
|
|
316
|
+
<button type="button" onClick={onOpen} className="flex w-full items-center gap-2 border-b border-border px-3 text-left text-[13px] text-muted-foreground outline-none hover:bg-muted/50 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring" style={{ height }}>
|
|
317
|
+
<Icon name="plus" size={14} />
|
|
318
|
+
<span>Add task</span>
|
|
319
|
+
<kbd className="rounded-[3px] border border-border px-1 text-[10px]">Enter</kbd>
|
|
320
|
+
<span>milestone</span>
|
|
321
|
+
<kbd className="rounded-[3px] border border-border px-1 text-[10px]">Shift+Enter</kbd>
|
|
322
|
+
</button>
|
|
323
|
+
);
|
|
324
|
+
return (
|
|
325
|
+
<div className="border-b border-border bg-accent">
|
|
326
|
+
<div className="flex items-center gap-2 px-3" style={{ height: height + 6 }}>
|
|
327
|
+
<span className="w-9 shrink-0">
|
|
328
|
+
<Icon name={draft.milestone ? "diamond" : "plus"} size={14} className="text-muted-foreground" />
|
|
329
|
+
</span>
|
|
330
|
+
<input
|
|
331
|
+
ref={addRef}
|
|
332
|
+
value={draft.title}
|
|
333
|
+
onChange={(ev) => setDraft((d) => ({ ...d, title: ev.target.value }))}
|
|
334
|
+
onKeyDown={(ev) => {
|
|
335
|
+
ev.stopPropagation();
|
|
336
|
+
if (ev.key === "Enter") {
|
|
337
|
+
ev.preventDefault();
|
|
338
|
+
onSubmit(ev.shiftKey);
|
|
339
|
+
} else if (ev.key === "Tab" && !ev.shiftKey && aboveTitle && !draft.milestone) {
|
|
340
|
+
ev.preventDefault();
|
|
341
|
+
onIndent?.();
|
|
342
|
+
} else if (ev.key === "Escape") {
|
|
343
|
+
setDraft({ title: "", days: "", milestone: false, parentId: null, afterId: null });
|
|
344
|
+
ev.currentTarget.blur();
|
|
345
|
+
}
|
|
346
|
+
}}
|
|
347
|
+
placeholder={parentTitle ? `A sub-task of ${parentTitle}` : draft.milestone ? "Name the milestone" : "Add a task"}
|
|
348
|
+
aria-label={parentTitle ? `Add a sub-task of ${parentTitle}` : draft.milestone ? "Milestone name" : "Task title"}
|
|
349
|
+
disabled={busy}
|
|
350
|
+
className="h-6 min-w-0 flex-1 rounded-[3px] border border-ring bg-background px-1.5 text-[13px] text-foreground outline-none ring-1 ring-ring placeholder:text-muted-foreground"
|
|
351
|
+
/>
|
|
352
|
+
{!draft.milestone ? (
|
|
353
|
+
<input
|
|
354
|
+
value={draft.days}
|
|
355
|
+
onChange={(ev) => setDraft((d) => ({ ...d, days: ev.target.value.replace(/[^0-9]/g, "").slice(0, 3) }))}
|
|
356
|
+
onKeyDown={(ev) => {
|
|
357
|
+
ev.stopPropagation();
|
|
358
|
+
if (ev.key === "Enter") {
|
|
359
|
+
ev.preventDefault();
|
|
360
|
+
onSubmit(ev.shiftKey);
|
|
361
|
+
}
|
|
362
|
+
}}
|
|
363
|
+
inputMode="numeric"
|
|
364
|
+
placeholder="5"
|
|
365
|
+
aria-label="Length in working days"
|
|
366
|
+
className={cx(NUM, "h-6 w-10 shrink-0 rounded-[3px] border border-ring bg-background px-1 text-right text-[13px] outline-none ring-1 ring-ring")}
|
|
367
|
+
/>
|
|
368
|
+
) : null}
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
);
|
|
372
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Fragment } from "react";
|
|
4
|
+
import { cx } from "../../ui";
|
|
5
|
+
import { monthDay } from "./gantt-model";
|
|
6
|
+
|
|
7
|
+
// The time scale's paper and lines: the header with its flags (today, the
|
|
8
|
+
// target and the finish, with the gap between them as a line you can read),
|
|
9
|
+
// weekends and the grid, the target as a dashed rule, the finish as a solid
|
|
10
|
+
// one, today in the accent, the link lines (elbows between the bars, the
|
|
11
|
+
// critical ones red when the page asks for them, a broken one dashed with a
|
|
12
|
+
// cross), ghosts of where bars were, pencil for where a suggestion would put
|
|
13
|
+
// them, and the lanes under the rows (who is on what, on the same scale).
|
|
14
|
+
|
|
15
|
+
const NUM = "tabular-nums";
|
|
16
|
+
export const HEAD = 56;
|
|
17
|
+
|
|
18
|
+
/** The header: the axis tiers, then a band with Today, Target and Finish. */
|
|
19
|
+
export function TrackHead({ axis, range, ppd, z, gridW, marks }) {
|
|
20
|
+
const late = marks.gap?.state === "late";
|
|
21
|
+
const early = marks.gap?.state === "early";
|
|
22
|
+
const finishText = marks.gap?.text ?? "";
|
|
23
|
+
const targetText = marks.target != null ? `Target ${monthDay(marks.targetIso)}` : "";
|
|
24
|
+
const same = marks.target != null && marks.finish != null && marks.target === marks.finish;
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
{axis.top.map((s) => (
|
|
28
|
+
<div key={`t${s.start}`} className="absolute top-0 h-[18px] border-l border-border" style={{ left: (s.start - range.from) * ppd, width: (s.end - s.start + 1) * ppd }}>
|
|
29
|
+
<span className="sticky block w-max px-1.5 pt-0.5 text-[11px] font-semibold text-foreground" style={{ left: gridW }}>
|
|
30
|
+
{s.label}
|
|
31
|
+
</span>
|
|
32
|
+
</div>
|
|
33
|
+
))}
|
|
34
|
+
{axis.bottom.map((s) => (
|
|
35
|
+
<div key={`b${s.start}`} className={cx(NUM, "absolute top-[18px] h-[18px] overflow-hidden whitespace-nowrap pt-0.5 text-[11px]", z === "day" ? "text-center" : "pl-1", s.dim ? "text-muted-foreground/70" : "text-muted-foreground")} style={{ left: (s.start - range.from) * ppd, width: (s.end - s.start + 1) * ppd }}>
|
|
36
|
+
{s.label}
|
|
37
|
+
</div>
|
|
38
|
+
))}
|
|
39
|
+
<div className="absolute inset-x-0 top-[36px] border-t border-border" />
|
|
40
|
+
{marks.target != null && marks.finish != null && !same ? (
|
|
41
|
+
<span aria-hidden="true" className={cx("absolute top-[45px] h-0 border-t-2", late ? "border-warning" : "border-success")} style={{ left: Math.min(marks.target, marks.finish), width: Math.abs(marks.finish - marks.target) }} />
|
|
42
|
+
) : null}
|
|
43
|
+
{marks.finishFrom != null && marks.finishFrom !== marks.finish ? <span aria-hidden="true" className="absolute top-[40px] h-3 w-0.5 bg-chart-3" style={{ left: marks.finishFrom }} /> : null}
|
|
44
|
+
{marks.today != null ? (
|
|
45
|
+
<span className="absolute top-[38px] z-10 h-4 -translate-x-1/2 whitespace-nowrap rounded-[3px] bg-primary px-1.5 text-[11px] font-semibold leading-4 text-primary-foreground" style={{ left: marks.today }}>
|
|
46
|
+
Today
|
|
47
|
+
</span>
|
|
48
|
+
) : null}
|
|
49
|
+
{marks.target != null && !same ? (
|
|
50
|
+
<span className={cx("absolute top-[38px] z-10 h-4 whitespace-nowrap rounded-[3px] border border-dashed border-foreground bg-background px-1.5 text-[11px] font-semibold leading-[14px] text-foreground", late ? "-translate-x-full -ml-px" : "ml-px")} style={{ left: marks.target }}>
|
|
51
|
+
{targetText}
|
|
52
|
+
</span>
|
|
53
|
+
) : null}
|
|
54
|
+
{marks.finish != null ? (
|
|
55
|
+
<span className={cx("absolute top-[38px] z-10 h-4 whitespace-nowrap rounded-[3px] px-1.5 text-[11px] font-semibold leading-4", marks.moving ? "bg-ring text-primary-foreground" : "bg-foreground text-background", early ? "-translate-x-full -ml-px" : "ml-px")} style={{ left: marks.finish }}>
|
|
56
|
+
{finishText}
|
|
57
|
+
</span>
|
|
58
|
+
) : null}
|
|
59
|
+
</>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Weekends, grid lines, the selected rows, target, finish and today, under everything. */
|
|
64
|
+
export function Paper({ axis, range, ppd, z, marks, rowTops, rowH, selectedRows }) {
|
|
65
|
+
return (
|
|
66
|
+
<div aria-hidden="true" className="pointer-events-none absolute inset-0">
|
|
67
|
+
{z === "month" || z === "fit" || ppd < 4 ? null : axis.shade.map((s) => <div key={`s${s.start}`} className="absolute inset-y-0 bg-muted" style={{ left: (s.start - range.from) * ppd, width: (s.end - s.start + 1) * ppd }} />)}
|
|
68
|
+
{axis.lines.map((l) => (
|
|
69
|
+
<div key={`l${l.day}${l.strong ? "s" : ""}`} className={cx("absolute inset-y-0 w-px", l.strong ? "bg-border" : "bg-border/50")} style={{ left: (l.day - range.from) * ppd }} />
|
|
70
|
+
))}
|
|
71
|
+
{rowTops.map((top, i) => (
|
|
72
|
+
<div key={i} className={cx("absolute inset-x-0 border-b border-border", selectedRows.has(i) && "bg-accent")} style={{ top, height: rowH }} />
|
|
73
|
+
))}
|
|
74
|
+
{marks.target != null ? <div className="absolute inset-y-0 border-l-[1.5px] border-dashed border-foreground" style={{ left: marks.target }} /> : null}
|
|
75
|
+
{marks.finish != null ? <div className={cx("absolute inset-y-0 w-0.5 -translate-x-px", marks.moving ? "bg-ring" : "bg-foreground")} style={{ left: marks.finish }} /> : null}
|
|
76
|
+
{marks.today != null ? <div className="absolute inset-y-0 w-0.5 -translate-x-px bg-primary" style={{ left: marks.today }} /> : null}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const HEAD_PATH = {
|
|
82
|
+
down: (x, y) => `${x - 3.5},${y - 5} ${x + 3.5},${y - 5} ${x},${y}`,
|
|
83
|
+
up: (x, y) => `${x - 3.5},${y + 5} ${x + 3.5},${y + 5} ${x},${y}`,
|
|
84
|
+
right: (x, y) => `${x - 5},${y - 3.5} ${x - 5},${y + 3.5} ${x},${y}`,
|
|
85
|
+
left: (x, y) => `${x + 5},${y - 3.5} ${x + 5},${y + 3.5} ${x},${y}`,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Link lines on one svg: hairline in muted ink, the critical ones red (when
|
|
90
|
+
* the page switches the critical path on), a broken one dashed with a
|
|
91
|
+
* cross, the ones that move with a drag in the ring colour, the lag beside
|
|
92
|
+
* the first leg. Each is pressable (double-click) when `onOpenLink` is given.
|
|
93
|
+
*/
|
|
94
|
+
export function Links({ lines, width, height, critical, onOpenLink }) {
|
|
95
|
+
return (
|
|
96
|
+
<svg aria-hidden="true" className="pointer-events-none absolute left-0 top-0 z-[5] overflow-visible" width={width} height={height}>
|
|
97
|
+
{lines.map((l) => {
|
|
98
|
+
const tone = l.link.violated ? "broken" : l.moving ? "moving" : critical && l.critical ? "crit" : l.touches ? "strong" : "quiet";
|
|
99
|
+
const stroke = { broken: "stroke-destructive", crit: "stroke-destructive", moving: "stroke-ring", strong: "stroke-foreground", quiet: "stroke-muted-foreground" }[tone];
|
|
100
|
+
const fill = { broken: "fill-destructive", crit: "fill-destructive", moving: "fill-ring", strong: "fill-foreground", quiet: "fill-muted-foreground" }[tone];
|
|
101
|
+
const w = tone === "crit" || tone === "broken" || tone === "moving" ? 1.5 : 1;
|
|
102
|
+
const segs = l.route.segments;
|
|
103
|
+
if (!segs.length) return null;
|
|
104
|
+
const d = `M${segs[0].x1 + 0.5} ${segs[0].y1 + 0.5}${segs.map((s) => ` L${s.x2 + 0.5} ${s.y2 + 0.5}`).join("")}`;
|
|
105
|
+
const first = segs.find((s) => s.x1 === s.x2);
|
|
106
|
+
const mid = segs[Math.floor(segs.length / 2)];
|
|
107
|
+
return (
|
|
108
|
+
<Fragment key={l.id}>
|
|
109
|
+
<path d={d} fill="none" className={stroke} strokeWidth={w} strokeDasharray={tone === "broken" ? "4 3" : undefined} />
|
|
110
|
+
{l.route.head ? <polygon points={HEAD_PATH[l.route.head.dir](l.route.head.x + 0.5, l.route.head.y + 0.5)} className={fill} /> : null}
|
|
111
|
+
{tone === "broken" && mid ? (
|
|
112
|
+
<path d={`M${(mid.x1 + mid.x2) / 2 - 4} ${(mid.y1 + mid.y2) / 2 - 4}l8 8M${(mid.x1 + mid.x2) / 2 + 4} ${(mid.y1 + mid.y2) / 2 - 4}l-8 8`} fill="none" className="stroke-destructive" strokeWidth={2} />
|
|
113
|
+
) : null}
|
|
114
|
+
{l.lag && first ? (
|
|
115
|
+
<text x={first.x1 + 4} y={(first.y1 + first.y2) / 2} dominantBaseline="middle" className={cx("text-[10px]", tone === "crit" ? "fill-destructive" : "fill-muted-foreground")}>
|
|
116
|
+
{l.lag > 0 ? `+${l.lag}` : l.lag}
|
|
117
|
+
</text>
|
|
118
|
+
) : null}
|
|
119
|
+
{onOpenLink ? (
|
|
120
|
+
<path d={d} fill="none" stroke="transparent" strokeWidth={10} className="pointer-events-auto cursor-pointer" onDoubleClick={() => onOpenLink(l.link)}>
|
|
121
|
+
<title>{`Double-click to change this link`}</title>
|
|
122
|
+
</path>
|
|
123
|
+
) : null}
|
|
124
|
+
</Fragment>
|
|
125
|
+
);
|
|
126
|
+
})}
|
|
127
|
+
</svg>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Where bars were (dashed grey) or where a suggestion would put them (dashed in the agent's colour). */
|
|
132
|
+
export function Ghosts({ ghosts, visible, X, ppd, rowH, rowTop, live, kind = "was", bar = 14 }) {
|
|
133
|
+
if (!ghosts?.size) return null;
|
|
134
|
+
const ring = kind === "pencil" ? "border-info" : "border-chart-3";
|
|
135
|
+
return (
|
|
136
|
+
<div aria-hidden="true" className="pointer-events-none absolute inset-0 z-[6]">
|
|
137
|
+
{visible.map((e, i) => {
|
|
138
|
+
const g = ghosts.get(e.id);
|
|
139
|
+
const r = live.rows.get(e.id);
|
|
140
|
+
if (!g || !r || r.summary) return null;
|
|
141
|
+
const y = (rowTop ? rowTop(i) : i * rowH) + rowH / 2;
|
|
142
|
+
if (r.milestone) {
|
|
143
|
+
const cx0 = X(g.due ?? g.start) + ppd / 2;
|
|
144
|
+
return <span key={e.id} className={cx("absolute size-3 -translate-x-1/2 -translate-y-1/2 rotate-45 border-[1.5px] border-dashed", ring)} style={{ left: cx0, top: y }} />;
|
|
145
|
+
}
|
|
146
|
+
const x0 = X(g.start);
|
|
147
|
+
const w = Math.max(ppd, X(g.due) + ppd - x0);
|
|
148
|
+
return <span key={e.id} className={cx("absolute -translate-y-1/2 rounded-[2px] border-[1.5px] border-dashed", ring)} style={{ left: x0, width: w, top: y, height: bar }} />;
|
|
149
|
+
})}
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Lanes under the rows, on the same scale: one thin row per lane (a person)
|
|
156
|
+
* with a bar for each of its items and a box where two or more overlap
|
|
157
|
+
* (the count in it). The label side is drawn by the sheet's twin
|
|
158
|
+
* (LaneLabels); both take the same lane list.
|
|
159
|
+
*/
|
|
160
|
+
export function LaneTrack({ lanes, X, ppd, laneH = 20, top }) {
|
|
161
|
+
return (
|
|
162
|
+
<div className="absolute inset-x-0" style={{ top }}>
|
|
163
|
+
{lanes.map((lane, i) => (
|
|
164
|
+
<div key={lane.id} className="absolute inset-x-0 border-b border-border" style={{ top: i * laneH, height: laneH }}>
|
|
165
|
+
{(lane.items ?? []).map((it, k) => (
|
|
166
|
+
<span key={`${it.id ?? k}`} title={it.title} className="absolute h-1.5 rounded-[2px] bg-chart-1" style={{ left: X(it.start), width: Math.max(ppd, X(it.due) + ppd - X(it.start)), top: 7 }} />
|
|
167
|
+
))}
|
|
168
|
+
{(lane.marks ?? []).map((m, k) => (
|
|
169
|
+
<span key={`m${k}`} title={m.title ?? `${m.count} at once`} className="absolute rounded-[2px] border-[1.5px] border-warning pl-0.5 text-[10px] font-bold leading-[11px] text-warning" style={{ left: X(m.from), width: Math.max(ppd, X(m.to) + ppd - X(m.from)), top: 3, height: 14 }}>
|
|
170
|
+
{m.count}
|
|
171
|
+
</span>
|
|
172
|
+
))}
|
|
173
|
+
</div>
|
|
174
|
+
))}
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function LaneLabels({ lanes, laneH = 20, width }) {
|
|
180
|
+
return (
|
|
181
|
+
<div style={{ width }}>
|
|
182
|
+
{lanes.map((lane) => (
|
|
183
|
+
<div key={lane.id} className="flex items-center gap-1.5 border-b border-border px-3 text-[12px]" style={{ height: laneH }}>
|
|
184
|
+
<span className="truncate">{lane.label}</span>
|
|
185
|
+
<span className="ml-auto shrink-0 text-muted-foreground">{lane.sub}</span>
|
|
186
|
+
</div>
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
}
|