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,279 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// TimeGrid in the states a screen meets — the gallery's and the smoke
|
|
4
|
+
// test's surface. Not installed into apps: `makersclaw add time-grid` lays
|
|
5
|
+
// index.js, time-grid.jsx and grid-model.js only.
|
|
6
|
+
// Week a planner's week: goals above the days, meetings that are
|
|
7
|
+
// locked, focus blocks in pencil, Keep, Dismiss, Keep all
|
|
8
|
+
// (it says what it will do), a clash, a day's total
|
|
9
|
+
// Inbox proposals on their hour with a tag hanging off the
|
|
10
|
+
// selected one, a clash hatched, a moved meeting's "was"
|
|
11
|
+
// Narrow the week in a 320 px column: one day at a time
|
|
12
|
+
// Seven days a full week, kinds told apart by colour
|
|
13
|
+
// One, long one event; words with no break to wrap at
|
|
14
|
+
// Empty, loading, failed, read-only
|
|
15
|
+
import { useMemo, useState } from "react";
|
|
16
|
+
import { Switch } from "../components/ui";
|
|
17
|
+
import { GoalStrip, TimeGrid, hoursOf, loadOf, timeGridDragProps } from "../components/blocks/time-grid";
|
|
18
|
+
|
|
19
|
+
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
20
|
+
|
|
21
|
+
const DAYS = ["2026-09-21", "2026-09-22", "2026-09-23", "2026-09-24", "2026-09-25"];
|
|
22
|
+
const WEEK = [...DAYS, "2026-09-26", "2026-09-27"];
|
|
23
|
+
const TODAY = "2026-09-24";
|
|
24
|
+
|
|
25
|
+
const KINDS = {
|
|
26
|
+
focus: { label: "Focus", tone: "primary", solid: true },
|
|
27
|
+
meeting: { label: "Meeting", tone: "neutral", locked: true },
|
|
28
|
+
admin: { label: "Admin", tone: "success" },
|
|
29
|
+
break: { label: "Break", tone: "warning" },
|
|
30
|
+
personal: { label: "Personal", hue: 5 },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const MEETINGS = [
|
|
34
|
+
{ id: "m1", date: DAYS[0], start: "09:30", end: "10:00", title: "Standup", kind: "meeting" },
|
|
35
|
+
{ id: "m2", date: DAYS[1], start: "09:30", end: "10:00", title: "Standup", kind: "meeting" },
|
|
36
|
+
{ id: "m3", date: DAYS[1], start: "14:00", end: "15:00", title: "Pricing review with Tom", kind: "meeting" },
|
|
37
|
+
{ id: "m4", date: DAYS[2], start: "09:30", end: "10:00", title: "Standup", kind: "meeting" },
|
|
38
|
+
{ id: "m5", date: DAYS[3], start: "09:30", end: "10:00", title: "Standup", kind: "meeting" },
|
|
39
|
+
{ id: "m6", date: DAYS[3], start: "11:00", end: "12:00", title: "Candidate call", kind: "meeting" },
|
|
40
|
+
{ id: "m7", date: DAYS[4], start: "09:30", end: "10:00", title: "Standup", kind: "meeting" },
|
|
41
|
+
{ id: "f0", date: DAYS[2], start: "13:00", end: "14:30", title: "Support lead scorecard", kind: "focus", done: true },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const DRAFT = [
|
|
45
|
+
{ id: "p1", date: DAYS[0], start: "10:15", end: "12:15", title: "Pricing page copy", kind: "focus", note: "pencilled in Mon 06:00" },
|
|
46
|
+
{ id: "p2", date: DAYS[0], start: "14:00", end: "15:00", title: "Inbox and approvals", kind: "admin" },
|
|
47
|
+
{ id: "p3", date: DAYS[1], start: "10:30", end: "12:30", title: "Draft the offer letter template", kind: "focus", note: "due Thursday; the morning before standup" },
|
|
48
|
+
{ id: "p4", date: DAYS[2], start: "09:00", end: "11:00", title: "Screen support lead candidates", kind: "focus" },
|
|
49
|
+
{ id: "p5", date: DAYS[3], start: "10:30", end: "12:30", title: "Ship the pricing page", kind: "focus" },
|
|
50
|
+
{ id: "p6", date: DAYS[4], start: "13:00", end: "15:00", title: "Friday review", kind: "focus" },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const GOALS = [
|
|
54
|
+
{ id: "g1", title: "Pricing page live with the new tiers", state: "pencil", mark: "pencilled in" },
|
|
55
|
+
{ id: "g2", title: "Offer letter template approved", state: "pencil", mark: "pencilled in" },
|
|
56
|
+
{ id: "g3", title: "Support lead job post live", state: "ink", meta: "kept" },
|
|
57
|
+
{ id: "g4", title: "Quarterly numbers sent to the board", state: "done", meta: "done" },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
function useWeek() {
|
|
61
|
+
const [events, setEvents] = useState(MEETINGS);
|
|
62
|
+
const [proposals, setProposals] = useState(DRAFT);
|
|
63
|
+
const [log, setLog] = useState("");
|
|
64
|
+
const [selected, setSelected] = useState(null);
|
|
65
|
+
|
|
66
|
+
const keepOne = (id, state) => setProposals((all) => all.map((p) => (p.id === id ? { ...p, state } : p)));
|
|
67
|
+
const onAction = async (item, id) => {
|
|
68
|
+
await wait(300);
|
|
69
|
+
if (id === "dismiss") setProposals((all) => all.filter((p) => p.id !== item.id));
|
|
70
|
+
else keepOne(item.id, id === "keep" ? "kept" : "pencil");
|
|
71
|
+
setLog(`${id}: ${item.title}`);
|
|
72
|
+
return { ok: true };
|
|
73
|
+
};
|
|
74
|
+
const onKeepAll = async (plan) => {
|
|
75
|
+
await wait(400);
|
|
76
|
+
setProposals((all) => all.map((p) => (plan.keep.includes(p.id) ? { ...p, state: "kept" } : p)));
|
|
77
|
+
setLog(`Keep all: ${plan.keep.length} blocks`);
|
|
78
|
+
return { ok: true };
|
|
79
|
+
};
|
|
80
|
+
const onChange = async (item, change, meta) => {
|
|
81
|
+
await wait(250);
|
|
82
|
+
if (item.id === "p4") return { ok: false, error: "The calendar is not answering. Try again." };
|
|
83
|
+
const patch = (all) => all.map((x) => (x.id === item.id ? { ...x, date: change.date, start: change.start, end: change.end } : x));
|
|
84
|
+
if (meta.proposal) setProposals(patch);
|
|
85
|
+
else setEvents(patch);
|
|
86
|
+
setLog(`${change.mode}: ${item.title} → ${change.date} ${change.start}–${change.end}`);
|
|
87
|
+
return { ok: true };
|
|
88
|
+
};
|
|
89
|
+
const onCompose = ({ date, start, end }) => {
|
|
90
|
+
setEvents((all) => [...all, { id: `n${all.length}`, date, start, end, title: "New block", kind: "focus" }]);
|
|
91
|
+
setLog(`Made ${date} ${start}–${end}`);
|
|
92
|
+
};
|
|
93
|
+
const onDrop = async (item, slot) => {
|
|
94
|
+
await wait(300);
|
|
95
|
+
setEvents((all) => [...all, { id: `d${all.length}`, ...slot, title: item.title, kind: "admin" }]);
|
|
96
|
+
return { ok: true };
|
|
97
|
+
};
|
|
98
|
+
return { events, proposals, log, selected, setSelected, onAction, onKeepAll, onChange, onCompose, onDrop, reset: () => { setEvents(MEETINGS); setProposals(DRAFT); setLog(""); } };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const notesOf = (events, proposals) => {
|
|
102
|
+
const out = {};
|
|
103
|
+
for (const d of DAYS) {
|
|
104
|
+
const all = [...events, ...proposals.filter((p) => p.state === "kept")];
|
|
105
|
+
const mins = loadOf(all.map((i) => ({ ...i, s: Number(i.start.slice(0, 2)) * 60 + Number(i.start.slice(3)), e: Number(i.end.slice(0, 2)) * 60 + Number(i.end.slice(3)) })), d);
|
|
106
|
+
out[d] = { text: `${hoursOf(mins)} of 9 h`, tone: mins > 9 * 60 ? "danger" : "neutral" };
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const MAIL_EVENTS = [
|
|
112
|
+
{ id: "e1", date: DAYS[0], start: "09:30", end: "10:00", title: "Standup" },
|
|
113
|
+
{ id: "e2", date: DAYS[1], start: "11:00", end: "12:00", title: "Board prep" },
|
|
114
|
+
{ id: "e3", date: DAYS[3], start: "14:30", end: "15:15", title: "1:1 with Tom" },
|
|
115
|
+
{ id: "e4", date: DAYS[3], allDay: true, title: "Offsite, Lisbon" },
|
|
116
|
+
];
|
|
117
|
+
const MAIL_PROPOSALS = [
|
|
118
|
+
{ id: "x1", date: DAYS[3], start: "15:00", end: "15:30", title: "Pilot scope for Q4 with Priya", kind: "meeting", tag: { quote: "Thursday at 3pm your time", source: "Priya Raman · Re: Pilot scope for Q4 · 11:25" } },
|
|
119
|
+
{ id: "x2", date: DAYS[1], start: "16:00", end: "16:30", title: "Intro call with Dana", kind: "meeting", tag: { quote: "Tuesday at 4?", source: "Dana Whitfield · Re: Intro · 09:02" } },
|
|
120
|
+
{ id: "x3", date: DAYS[2], start: "10:00", end: "10:30", title: "Send the signed order form", kind: "todo", unsure: true },
|
|
121
|
+
{ id: "x4", date: DAYS[0], start: "15:00", end: "15:45", title: "Monday is gone too", kind: "meeting", state: "was" },
|
|
122
|
+
{ id: "x5", date: DAYS[4], start: "09:00", end: "09:30", title: "Review the vendor contract", kind: "meeting", state: "failed" },
|
|
123
|
+
];
|
|
124
|
+
const MAIL_KINDS = { meeting: { label: "Meeting", hue: 1 }, deadline: { label: "Deadline", hue: 2 }, todo: { label: "To-do", hue: 4 } };
|
|
125
|
+
|
|
126
|
+
const LONG = "Supercalifragilisticexpialidocious".repeat(4);
|
|
127
|
+
|
|
128
|
+
function Section({ title, children, note }) {
|
|
129
|
+
return (
|
|
130
|
+
<section className="grid gap-3">
|
|
131
|
+
<h3 className="m-0 text-base font-semibold">{title}</h3>
|
|
132
|
+
{children}
|
|
133
|
+
{note ? <p className="m-0 text-xs text-muted-foreground">{note}</p> : null}
|
|
134
|
+
</section>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export default function TimeGridDemo() {
|
|
139
|
+
const week = useWeek();
|
|
140
|
+
const [mailSel, setMailSel] = useState("x1");
|
|
141
|
+
const [mail, setMail] = useState(MAIL_PROPOSALS);
|
|
142
|
+
const [readOnly, setReadOnly] = useState(false);
|
|
143
|
+
const notes = useMemo(() => notesOf(week.events, week.proposals), [week.events, week.proposals]);
|
|
144
|
+
const kept = week.proposals.filter((p) => p.state === "kept").length;
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<div className="grid gap-10 p-6">
|
|
148
|
+
<Section title="A planner’s week" note={week.log ? `Last: ${week.log}` : "Press Keep on a block, Keep all, drag a block (the Screen support lead one fails once), or drag across free hours."}>
|
|
149
|
+
<div className="flex items-center gap-4">
|
|
150
|
+
<Switch id="tg-ro" label="Read-only" checked={readOnly} onChange={(e) => setReadOnly(e.target.checked)} />
|
|
151
|
+
<button type="button" className="justify-self-start text-xs underline" onClick={week.reset}>
|
|
152
|
+
Put it all back in pencil
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
<TimeGrid
|
|
156
|
+
label="Week 39"
|
|
157
|
+
days={DAYS}
|
|
158
|
+
today={TODAY}
|
|
159
|
+
now="10:20"
|
|
160
|
+
startHour={5}
|
|
161
|
+
endHour={21}
|
|
162
|
+
workStart="08:30"
|
|
163
|
+
workEnd="17:30"
|
|
164
|
+
scrollTo="08:00"
|
|
165
|
+
height="34rem"
|
|
166
|
+
kinds={KINDS}
|
|
167
|
+
events={week.events}
|
|
168
|
+
proposals={week.proposals}
|
|
169
|
+
dayNotes={notes}
|
|
170
|
+
selectedId={week.selected}
|
|
171
|
+
onSelect={week.setSelected}
|
|
172
|
+
keepAll={{ label: "Keep all" }}
|
|
173
|
+
onAction={week.onAction}
|
|
174
|
+
onKeepAll={week.onKeepAll}
|
|
175
|
+
onChange={week.onChange}
|
|
176
|
+
onCompose={week.onCompose}
|
|
177
|
+
onDrop={week.onDrop}
|
|
178
|
+
readOnly={readOnly}
|
|
179
|
+
explainBlock={(b) => `${b.title} is on your calendar. Change it there.`}
|
|
180
|
+
explainOffHours={({ before }) => (before ? "Before your working day. Nothing is pencilled in here." : "After your working day.")}
|
|
181
|
+
above={<GoalStrip items={GOALS} summary={{ label: "Focus", value: hoursOf(10 * 60 + kept * 120), of: 15, unit: "h", note: `${kept} kept` }} />}
|
|
182
|
+
/>
|
|
183
|
+
<ul className="m-0 grid list-none gap-1 p-0 text-xs">
|
|
184
|
+
{["Reply to Priya", "Book the offsite flights"].map((t, i) => (
|
|
185
|
+
<li key={t} {...timeGridDragProps({ id: `t${i}`, title: t, minutes: 45 })} className="w-fit cursor-grab rounded-md border border-border bg-card px-2 py-1">
|
|
186
|
+
{t} <span className="text-muted-foreground">drag onto the week</span>
|
|
187
|
+
</li>
|
|
188
|
+
))}
|
|
189
|
+
</ul>
|
|
190
|
+
</Section>
|
|
191
|
+
|
|
192
|
+
<Section title="Inbox to calendar: words pinned to the hour" note="The selected proposal’s tag quotes the sender. The clash is hatched before anything is added; “was” is the meeting it replaces; the last one did not go in.">
|
|
193
|
+
<TimeGrid
|
|
194
|
+
label="Week 39 inbox"
|
|
195
|
+
days={DAYS}
|
|
196
|
+
today={TODAY}
|
|
197
|
+
now="13:10"
|
|
198
|
+
startHour={8}
|
|
199
|
+
endHour={19}
|
|
200
|
+
hourHeight={52}
|
|
201
|
+
height="30rem"
|
|
202
|
+
corner="BST"
|
|
203
|
+
pencilColor="kind"
|
|
204
|
+
kinds={MAIL_KINDS}
|
|
205
|
+
events={MAIL_EVENTS}
|
|
206
|
+
proposals={mail}
|
|
207
|
+
selectedId={mailSel}
|
|
208
|
+
onSelect={setMailSel}
|
|
209
|
+
onAction={async (item, id) => {
|
|
210
|
+
await wait(250);
|
|
211
|
+
if (id === "dismiss") setMail((all) => all.filter((p) => p.id !== item.id));
|
|
212
|
+
else setMail((all) => all.map((p) => (p.id === item.id ? { ...p, state: id === "keep" ? "kept" : "pencil" } : p)));
|
|
213
|
+
return { ok: true };
|
|
214
|
+
}}
|
|
215
|
+
/>
|
|
216
|
+
</Section>
|
|
217
|
+
|
|
218
|
+
<Section title="Narrow (320): one day at a time">
|
|
219
|
+
<div className="w-80 max-w-full">
|
|
220
|
+
<TimeGrid label="Narrow" days={DAYS} today={TODAY} now="10:20" startHour={7} endHour={19} height="24rem" kinds={KINDS} events={MEETINGS} proposals={DRAFT} defaultSelectedId="p5" onAction={async () => ({ ok: true })} />
|
|
221
|
+
</div>
|
|
222
|
+
</Section>
|
|
223
|
+
|
|
224
|
+
<Section title="Seven days, five kinds">
|
|
225
|
+
<TimeGrid
|
|
226
|
+
label="Seven days"
|
|
227
|
+
days={WEEK}
|
|
228
|
+
today={TODAY}
|
|
229
|
+
now="10:20"
|
|
230
|
+
startHour={7}
|
|
231
|
+
endHour={20}
|
|
232
|
+
height="26rem"
|
|
233
|
+
kinds={KINDS}
|
|
234
|
+
events={[
|
|
235
|
+
...MEETINGS,
|
|
236
|
+
{ id: "b1", date: DAYS[0], start: "12:30", end: "13:15", title: "Lunch", kind: "break" },
|
|
237
|
+
{ id: "b2", date: WEEK[5], start: "09:00", end: "11:00", title: "Climbing", kind: "personal" },
|
|
238
|
+
{ id: "b3", date: WEEK[6], start: "10:00", end: "12:00", title: "Weekly shop and cook", kind: "personal" },
|
|
239
|
+
]}
|
|
240
|
+
/>
|
|
241
|
+
</Section>
|
|
242
|
+
|
|
243
|
+
<Section title="One event">
|
|
244
|
+
<TimeGrid label="One" days={[DAYS[0]]} startHour={8} endHour={14} height="16rem" kinds={KINDS} events={[MEETINGS[0]]} />
|
|
245
|
+
</Section>
|
|
246
|
+
|
|
247
|
+
<Section title="Very long words">
|
|
248
|
+
<TimeGrid
|
|
249
|
+
label="Long"
|
|
250
|
+
days={DAYS.slice(0, 3)}
|
|
251
|
+
startHour={8}
|
|
252
|
+
endHour={16}
|
|
253
|
+
height="18rem"
|
|
254
|
+
kinds={KINDS}
|
|
255
|
+
events={[{ id: "l1", date: DAYS[0], start: "09:00", end: "11:00", title: LONG, kind: "admin", caption: LONG }]}
|
|
256
|
+
proposals={[{ id: "l2", date: DAYS[1], start: "09:00", end: "11:00", title: LONG, kind: "focus", label: LONG, tag: { quote: LONG, source: LONG } }]}
|
|
257
|
+
defaultSelectedId="l2"
|
|
258
|
+
onAction={async () => ({ ok: true })}
|
|
259
|
+
/>
|
|
260
|
+
</Section>
|
|
261
|
+
|
|
262
|
+
<Section title="Empty">
|
|
263
|
+
<TimeGrid label="Empty" days={DAYS} today={TODAY} startHour={8} endHour={14} height="14rem" emptyText="No meetings yet. Connect a calendar and they appear here." />
|
|
264
|
+
</Section>
|
|
265
|
+
|
|
266
|
+
<Section title="Loading">
|
|
267
|
+
<TimeGrid label="Loading" days={DAYS} today={TODAY} startHour={8} endHour={16} height="16rem" status="loading" />
|
|
268
|
+
</Section>
|
|
269
|
+
|
|
270
|
+
<Section title="Failed">
|
|
271
|
+
<TimeGrid label="Failed" days={DAYS} status="failed" error="The calendar did not answer. Nothing was changed." onRetry={() => {}} />
|
|
272
|
+
</Section>
|
|
273
|
+
|
|
274
|
+
<Section title="Read-only">
|
|
275
|
+
<TimeGrid label="Read-only" days={DAYS} today={TODAY} startHour={8} endHour={17} height="18rem" kinds={KINDS} events={MEETINGS} proposals={DRAFT} readOnly />
|
|
276
|
+
</Section>
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// The time grid's arithmetic: times as minutes, days as 'YYYY-MM-DD', how
|
|
2
|
+
// overlapping blocks share a day's width, which pencilled blocks clash with
|
|
3
|
+
// what is already there, what "Keep all" will do (in words, before it does
|
|
4
|
+
// it), and where a dragged block lands. Pure: no React, no DOM, no clock.
|
|
5
|
+
// The component (time-grid.jsx) draws what this returns;
|
|
6
|
+
// test/time-grid.test.mjs pins it.
|
|
7
|
+
|
|
8
|
+
export const WEEKDAYS_EN = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
9
|
+
|
|
10
|
+
/** '09:30', '9:30', '09:30:00' or '2026-09-24T09:30' → 570; a number passes (rounded); else null. */
|
|
11
|
+
export function toMin(v) {
|
|
12
|
+
if (typeof v === "number") return Number.isFinite(v) ? Math.round(v) : null;
|
|
13
|
+
const m = /(?:^|T|\s)(\d{1,2}):(\d{2})/.exec(String(v ?? ""));
|
|
14
|
+
if (!m) return null;
|
|
15
|
+
const h = Number(m[1]);
|
|
16
|
+
const mi = Number(m[2]);
|
|
17
|
+
return h > 24 || mi > 59 ? null : h * 60 + mi;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 570 → '09:30' (what a save sends). */
|
|
21
|
+
export const toHHMM = (min) => `${String(Math.floor(min / 60)).padStart(2, "0")}:${String(min % 60).padStart(2, "0")}`;
|
|
22
|
+
|
|
23
|
+
/** 570 → '9:30', 1020 → '17:00': the grid's printed clock (24-hour). */
|
|
24
|
+
export const clock = (min) => `${Math.floor(min / 60) % 24}:${String(min % 60).padStart(2, "0")}`;
|
|
25
|
+
|
|
26
|
+
/** '9:00–11:30'. */
|
|
27
|
+
export const span = (s, e, fmt = clock) => `${fmt(s)}–${fmt(e)}`;
|
|
28
|
+
|
|
29
|
+
/** 90 → '1 h 30 min', 45 → '45 min', 120 → '2 h'. */
|
|
30
|
+
export function durationWords(min) {
|
|
31
|
+
const m = Math.max(0, Math.round(min));
|
|
32
|
+
const h = Math.floor(m / 60);
|
|
33
|
+
const r = m % 60;
|
|
34
|
+
if (!h) return `${r} min`;
|
|
35
|
+
return r ? `${h} h ${r} min` : `${h} h`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 320 → '5.3' (hours, one decimal, no trailing .0): for a day's total beside its header. */
|
|
39
|
+
export function hoursOf(min, digits = 1) {
|
|
40
|
+
const f = 10 ** digits;
|
|
41
|
+
const v = Math.round((min / 60) * f) / f;
|
|
42
|
+
return String(v);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 0 = Sunday … 6 = Saturday, from 'YYYY-MM-DD'; 0 for anything else. */
|
|
46
|
+
export function weekdayIndex(iso) {
|
|
47
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(iso ?? ""));
|
|
48
|
+
return m ? new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]))).getUTCDay() : 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The day of the month as a number ('2026-09-24' → 24). */
|
|
52
|
+
export const dateNum = (iso) => Number(String(iso ?? "").slice(8, 10)) || 0;
|
|
53
|
+
|
|
54
|
+
export const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
55
|
+
|
|
56
|
+
export const snapRound = (m, snap) => Math.round(m / snap) * snap;
|
|
57
|
+
export const snapFloor = (m, snap) => Math.floor(m / snap) * snap;
|
|
58
|
+
|
|
59
|
+
// ---- reading what the app hands in -------------------------------------
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* An item as the grid draws it: minutes for its times, a day it can be
|
|
63
|
+
* placed on, and nothing else invented. An item with no day in `days` is
|
|
64
|
+
* dropped; a timed item with no length gets one `snap` long; a timed item
|
|
65
|
+
* wholly outside [lo, hi] is dropped, and one that crosses an edge is cut
|
|
66
|
+
* to it (`cutStart` / `cutEnd` say so).
|
|
67
|
+
*/
|
|
68
|
+
export function readItem(raw, { days, lo, hi, snap = 15, pencil = false }) {
|
|
69
|
+
if (!raw || raw.id == null) return null;
|
|
70
|
+
if (!days.includes(raw.date)) return null;
|
|
71
|
+
const base = { ...raw, id: String(raw.id), pencil };
|
|
72
|
+
if (raw.allDay) return { ...base, allDay: true, s: null, e: null };
|
|
73
|
+
let s = toMin(raw.start);
|
|
74
|
+
let e = toMin(raw.end);
|
|
75
|
+
if (s == null) return { ...base, allDay: true, s: null, e: null };
|
|
76
|
+
if (e == null || e <= s) e = s + snap;
|
|
77
|
+
if (e <= lo || s >= hi) return null;
|
|
78
|
+
return { ...base, allDay: false, s: Math.max(s, lo), e: Math.min(e, hi), cutStart: s < lo, cutEnd: e > hi };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Overlapping blocks share a day's width, calendar-style: lane and lane count per id. */
|
|
82
|
+
export function gridLanes(items) {
|
|
83
|
+
const out = new Map();
|
|
84
|
+
const sorted = [...items].sort((a, b) => a.s - b.s || b.e - a.e);
|
|
85
|
+
let group = [];
|
|
86
|
+
let groupEnd = -1;
|
|
87
|
+
const flush = () => {
|
|
88
|
+
const ends = [];
|
|
89
|
+
for (const it of group) {
|
|
90
|
+
let lane = ends.findIndex((end) => end <= it.s);
|
|
91
|
+
if (lane < 0) {
|
|
92
|
+
lane = ends.length;
|
|
93
|
+
ends.push(it.e);
|
|
94
|
+
} else ends[lane] = it.e;
|
|
95
|
+
out.set(it.id, { lane, lanes: 1 });
|
|
96
|
+
}
|
|
97
|
+
for (const it of group) out.get(it.id).lanes = ends.length;
|
|
98
|
+
group = [];
|
|
99
|
+
groupEnd = -1;
|
|
100
|
+
};
|
|
101
|
+
for (const it of sorted) {
|
|
102
|
+
if (group.length && it.s >= groupEnd) flush();
|
|
103
|
+
group.push(it);
|
|
104
|
+
groupEnd = Math.max(groupEnd, it.e);
|
|
105
|
+
}
|
|
106
|
+
if (group.length) flush();
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---- pencil and ink ------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
/** What a proposal can be. `pencil` is the default. */
|
|
113
|
+
export const PENCIL_STATES = ["pencil", "kept", "failed", "replaced", "was"];
|
|
114
|
+
|
|
115
|
+
/** The look an item is drawn in: ink (an event, or a kept proposal), or one of the pencil looks. */
|
|
116
|
+
export function lookOf(item) {
|
|
117
|
+
if (!item.pencil) return "ink";
|
|
118
|
+
const st = PENCIL_STATES.includes(item.state) ? item.state : "pencil";
|
|
119
|
+
if (st === "kept") return "ink";
|
|
120
|
+
if (st === "pencil" && item.unsure) return "unsure";
|
|
121
|
+
return st;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Is this proposal still waiting for a person? (Failed and replaced ones are not asked again; "was" is only a marker.) */
|
|
125
|
+
export const isWaiting = (item) => item.pencil && lookOf(item) !== "ink" && lookOf(item) !== "failed" && lookOf(item) !== "replaced" && lookOf(item) !== "was";
|
|
126
|
+
|
|
127
|
+
/** The proposals that are waiting, in the order a person reads a week: by day, then by time. */
|
|
128
|
+
export function waitingOrder(items, days) {
|
|
129
|
+
return items
|
|
130
|
+
.filter(isWaiting)
|
|
131
|
+
.sort((a, b) => days.indexOf(a.date) - days.indexOf(b.date) || (a.s ?? -1) - (b.s ?? -1) || (a.id < b.id ? -1 : 1));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** J (1) and K (-1): the id of the next waiting proposal; none selected → the first (or the last going back). */
|
|
135
|
+
export function walkProposal(items, days, currentId, dir = 1) {
|
|
136
|
+
const list = waitingOrder(items, days);
|
|
137
|
+
if (!list.length) return null;
|
|
138
|
+
const at = list.findIndex((i) => i.id === String(currentId));
|
|
139
|
+
if (at < 0) return (dir >= 0 ? list[0] : list[list.length - 1]).id;
|
|
140
|
+
return list[(at + dir + list.length) % list.length].id;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The ids whose look changed from a pencil look to ink between two renders:
|
|
145
|
+
* the ones that were just kept (or committed). In the order they sit in
|
|
146
|
+
* the week, so they can ink in one by one. `before` is a Map id → look.
|
|
147
|
+
*/
|
|
148
|
+
export function newlyInked(before, items, days) {
|
|
149
|
+
const out = items.filter((i) => before.get(i.id) && before.get(i.id) !== "ink" && lookOf(i) === "ink");
|
|
150
|
+
return out
|
|
151
|
+
.sort((a, b) => days.indexOf(a.date) - days.indexOf(b.date) || (a.s ?? -1) - (b.s ?? -1))
|
|
152
|
+
.map((i) => i.id);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ---- clashes -------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Where each waiting proposal overlaps something that is already there
|
|
159
|
+
* (ink events, and kept proposals; never another waiting one unless
|
|
160
|
+
* `among` is true). Returns Map id → [{ withId, title, s, e, at }] where s..e
|
|
161
|
+
* is the overlap, in minutes and `at` is when the other one starts. All-day items never clash.
|
|
162
|
+
*/
|
|
163
|
+
export function clashesOf(items, { among = false } = {}) {
|
|
164
|
+
const out = new Map();
|
|
165
|
+
const timed = items.filter((i) => !i.allDay && i.s != null);
|
|
166
|
+
for (const p of timed) {
|
|
167
|
+
if (!isWaiting(p)) continue;
|
|
168
|
+
for (const o of timed) {
|
|
169
|
+
if (o.id === p.id || o.date !== p.date) continue;
|
|
170
|
+
const solid = lookOf(o) === "ink";
|
|
171
|
+
if (!solid && !(among && isWaiting(o))) continue;
|
|
172
|
+
const s = Math.max(p.s, o.s);
|
|
173
|
+
const e = Math.min(p.e, o.e);
|
|
174
|
+
if (e <= s) continue;
|
|
175
|
+
if (!out.has(p.id)) out.set(p.id, []);
|
|
176
|
+
out.get(p.id).push({ withId: o.id, title: o.title ?? "", s, e, at: o.s });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** "Standup 9:30", or "Standup 9:30 and Review 11:00" / "Standup 9:30 and 2 more". */
|
|
183
|
+
export function namesOf(list, fmt = clock, max = 2) {
|
|
184
|
+
const names = list.map((c) => {
|
|
185
|
+
const t = c.at ?? c.s;
|
|
186
|
+
return t != null ? `${c.title} ${fmt(t)}`.trim() : c.title;
|
|
187
|
+
});
|
|
188
|
+
if (names.length <= max) return names.length > 1 ? `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}` : (names[0] ?? "");
|
|
189
|
+
return `${names.slice(0, max).join(", ")} and ${names.length - max} more`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ---- Keep all: what it will do, said before it does it -------------------
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* What "Keep all" will do. `skip` names what stays in pencil: "clash" (it
|
|
196
|
+
* overlaps something) and/or "unsure" (the item says it is). The rest are
|
|
197
|
+
* kept. Returns
|
|
198
|
+
* { keep: [ids], leave: [{ id, title, reason }], counts: [{ kind, label, n }], total, words }
|
|
199
|
+
* `words` is the sentence the screen shows next to the button.
|
|
200
|
+
*/
|
|
201
|
+
export function planKeepAll(items, { clashes, kinds = {}, skip = ["clash"], fmt = clock } = {}) {
|
|
202
|
+
const waiting = items.filter(isWaiting);
|
|
203
|
+
const clash = clashes ?? clashesOf(items);
|
|
204
|
+
const keep = [];
|
|
205
|
+
const leave = [];
|
|
206
|
+
for (const it of waiting) {
|
|
207
|
+
if (skip.includes("clash") && clash.has(it.id)) leave.push({ id: it.id, title: it.title ?? "", reason: "clash", with: clash.get(it.id) });
|
|
208
|
+
else if (skip.includes("unsure") && it.unsure) leave.push({ id: it.id, title: it.title ?? "", reason: "unsure" });
|
|
209
|
+
else keep.push(it);
|
|
210
|
+
}
|
|
211
|
+
const byKind = new Map();
|
|
212
|
+
for (const it of keep) {
|
|
213
|
+
const k = it.kind ?? "";
|
|
214
|
+
byKind.set(k, (byKind.get(k) ?? 0) + 1);
|
|
215
|
+
}
|
|
216
|
+
const counts = [...byKind].map(([kind, n]) => ({ kind, n, label: kinds[kind]?.label ?? kind }));
|
|
217
|
+
const total = waiting.length;
|
|
218
|
+
let words;
|
|
219
|
+
if (!total) words = "Nothing is waiting in pencil.";
|
|
220
|
+
else {
|
|
221
|
+
const head = keep.length
|
|
222
|
+
? `Keeps ${keep.length === total ? `all ${total}` : `${keep.length} of ${total}`} in ink${counts.length > 1 || (counts[0] && counts[0].label) ? `: ${counts.map((c) => `${c.n} ${c.label}`.trim()).join(", ")}` : ""}.`
|
|
223
|
+
: "Nothing can be kept as it is.";
|
|
224
|
+
const clashed = leave.filter((l) => l.reason === "clash");
|
|
225
|
+
const unsure = leave.filter((l) => l.reason === "unsure");
|
|
226
|
+
const tails = [];
|
|
227
|
+
if (clashed.length)
|
|
228
|
+
tails.push(
|
|
229
|
+
clashed.length === 1
|
|
230
|
+
? `Leaves ${clashed[0].title ? `“${clashed[0].title}”` : "1"} in pencil: it clashes with ${namesOf(clashed[0].with, fmt, 1)}.`
|
|
231
|
+
: `Leaves ${clashed.length} in pencil, they clash: ${clashed
|
|
232
|
+
.slice(0, 2)
|
|
233
|
+
.map((l) => `${l.title ? `“${l.title}”` : "one"} with ${namesOf(l.with, fmt, 1)}`)
|
|
234
|
+
.join(", ")}${clashed.length > 2 ? ` and ${clashed.length - 2} more` : ""}.`,
|
|
235
|
+
);
|
|
236
|
+
if (unsure.length) tails.push(`Leaves ${unsure.length} in pencil: ${unsure.length === 1 ? "it is" : "they are"} not sure.`);
|
|
237
|
+
words = [head, ...tails].join(" ");
|
|
238
|
+
}
|
|
239
|
+
return { keep: keep.map((i) => i.id), leave, counts, total, words };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---- a day's load, for the number beside its header ----------------------
|
|
243
|
+
|
|
244
|
+
/** Minutes of timed items on `date`, whose look and kind pass `where`. */
|
|
245
|
+
export function loadOf(items, date, where = () => true) {
|
|
246
|
+
let sum = 0;
|
|
247
|
+
for (const i of items) if (i.date === date && !i.allDay && i.s != null && where(i)) sum += i.e - i.s;
|
|
248
|
+
return sum;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ---- where a gesture lands ----------------------------------------------
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* The block as it would land. `g` is the gesture ({ kind: "create" | "move"
|
|
255
|
+
* | "start" | "end", anchor?, offset?, s0?, e0?, date? }), `at` the pointer
|
|
256
|
+
* as { date, minute } (minute is fractional), `box` the limits
|
|
257
|
+
* { lo, hi, ws, we, snap }. null when the pointer is nowhere.
|
|
258
|
+
*/
|
|
259
|
+
export function previewOf(g, at, box) {
|
|
260
|
+
if (!at) return null;
|
|
261
|
+
const { lo, hi, ws, we, snap } = box;
|
|
262
|
+
if (g.kind === "create") {
|
|
263
|
+
const lower = ws ?? lo;
|
|
264
|
+
const upper = we ?? hi;
|
|
265
|
+
const cur = clamp(at.minute, lower, upper);
|
|
266
|
+
let s;
|
|
267
|
+
let e;
|
|
268
|
+
if (cur >= g.anchor) {
|
|
269
|
+
s = g.anchor;
|
|
270
|
+
e = Math.max(g.anchor + snap, Math.ceil(cur / snap) * snap);
|
|
271
|
+
} else {
|
|
272
|
+
s = snapFloor(cur, snap);
|
|
273
|
+
e = g.anchor + snap;
|
|
274
|
+
}
|
|
275
|
+
return { date: g.date, start: clamp(s, lower, upper - snap), end: clamp(e, lower + snap, upper), mode: "create" };
|
|
276
|
+
}
|
|
277
|
+
const dur = g.e0 - g.s0;
|
|
278
|
+
if (g.kind === "move") {
|
|
279
|
+
const s = clamp(snapRound(at.minute - g.offset, snap), lo, hi - dur);
|
|
280
|
+
return { id: g.id, date: at.date, start: s, end: s + dur, mode: "move" };
|
|
281
|
+
}
|
|
282
|
+
if (g.kind === "end") {
|
|
283
|
+
const e = clamp(snapRound(at.minute, snap), g.s0 + snap, hi);
|
|
284
|
+
return { id: g.id, date: g.date, start: g.s0, end: e, mode: "end" };
|
|
285
|
+
}
|
|
286
|
+
const s = clamp(snapRound(at.minute, snap), lo, g.e0 - snap);
|
|
287
|
+
return { id: g.id, date: g.date, start: s, end: g.e0, mode: "start" };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** An arrow key on a focused block, as the change it asks for (or null). Shift+↑/↓ changes the length. */
|
|
291
|
+
export function keyChange(key, shift, item, { days, lo, hi, snap }) {
|
|
292
|
+
const dur = item.e - item.s;
|
|
293
|
+
if (key === "ArrowUp" && shift) return { date: item.date, start: item.s, end: Math.max(item.s + snap, item.e - snap), mode: "end" };
|
|
294
|
+
if (key === "ArrowDown" && shift) return { date: item.date, start: item.s, end: Math.min(hi, item.e + snap), mode: "end" };
|
|
295
|
+
if (key === "ArrowUp") {
|
|
296
|
+
const s = Math.max(lo, item.s - snap);
|
|
297
|
+
return { date: item.date, start: s, end: s + dur, mode: "move" };
|
|
298
|
+
}
|
|
299
|
+
if (key === "ArrowDown") {
|
|
300
|
+
const s = Math.min(hi - dur, item.s + snap);
|
|
301
|
+
return { date: item.date, start: s, end: s + dur, mode: "move" };
|
|
302
|
+
}
|
|
303
|
+
if (key === "ArrowLeft" || key === "ArrowRight") {
|
|
304
|
+
const i = days.indexOf(item.date) + (key === "ArrowLeft" ? -1 : 1);
|
|
305
|
+
if (i < 0 || i >= days.length) return null;
|
|
306
|
+
return { date: days[i], start: item.s, end: item.e, mode: "move" };
|
|
307
|
+
}
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ---- the tag and where it hangs ------------------------------------------
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* A tag hangs off a block. Under it, left-aligned, unless the block is in
|
|
315
|
+
* the right half of the days (then right-aligned, so it stays inside the
|
|
316
|
+
* grid) or the room under it runs out (then it hangs above).
|
|
317
|
+
*/
|
|
318
|
+
export function tagPlacement({ dayIndex, dayCount, bottom, top, bodyHeight, tagHeight = 64 }) {
|
|
319
|
+
const side = dayCount > 1 && dayIndex >= Math.ceil(dayCount / 2) ? "right" : "left";
|
|
320
|
+
const above = bottom + 6 + tagHeight > bodyHeight && top - 6 - tagHeight >= 0;
|
|
321
|
+
return { side, above };
|
|
322
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// The TimeGrid block — `makersclaw add time-grid`. Fixed: compose it
|
|
2
|
+
// through its props (README.md beside this file). The model's helpers are
|
|
3
|
+
// plain functions; they work on the server too.
|
|
4
|
+
export { TimeGrid, GoalStrip, TIME_GRID_MIME, timeGridDragProps } from "./time-grid";
|
|
5
|
+
export {
|
|
6
|
+
PENCIL_STATES,
|
|
7
|
+
clashesOf,
|
|
8
|
+
clock,
|
|
9
|
+
durationWords,
|
|
10
|
+
gridLanes,
|
|
11
|
+
hoursOf,
|
|
12
|
+
isWaiting,
|
|
13
|
+
loadOf,
|
|
14
|
+
lookOf,
|
|
15
|
+
namesOf,
|
|
16
|
+
newlyInked,
|
|
17
|
+
planKeepAll,
|
|
18
|
+
previewOf,
|
|
19
|
+
readItem,
|
|
20
|
+
tagPlacement,
|
|
21
|
+
toHHMM,
|
|
22
|
+
toMin,
|
|
23
|
+
walkProposal,
|
|
24
|
+
waitingOrder,
|
|
25
|
+
} from "./grid-model";
|