From e53ac86778227ed9de64680c008e70f34b58faae Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Mon, 12 Jan 2026 15:42:47 -0800 Subject: [PATCH 1/8] Squash merge main commit 6daa419e6812a29abb918a5f3ea9f4559bb05d17 Author: Austin Mroz Date: Mon Jan 12 10:34:52 2026 -0800 Remove linear localization, fix output selection commit c0f8881b7b001b2f673794cc61c8c43f03aadf5e Author: Austin Mroz Date: Sun Jan 11 08:48:43 2026 -0800 Feedback cleanup Now compartmentalized for easier removal later Tried to do a sneaky partition on $attrs so popover can be styled and tf attributes still go to the div, but it didn't work out. commit f8a10278afd5ebd8dd9317e49a78f8db1b3f56a2 Author: Austin Mroz Date: Sat Jan 10 23:22:36 2026 -0800 Add feedback button Not happy with the script embed. But I think it's acceptable commit 3de4f13de16d493a91d8fddf238965981cf5f447 Author: Austin Mroz Date: Sat Jan 10 19:58:42 2026 -0800 Keep selection on new output commit 8e9b982218677d7e6e605b130f46423dabcaed47 Author: Austin Mroz Date: Sat Jan 10 11:34:12 2026 -0800 Fix output spinner height commit 7c869ea24b126a603b0a330a1aa08e746c942907 Author: Austin Mroz Date: Sat Jan 10 11:28:39 2026 -0800 Workflow state that doesn't break tests commit 7e1fa6e22c70d140a401612e195046fa95ecba68 Author: Austin Mroz Date: Sat Jan 10 09:41:26 2026 -0800 Remove drop indicators on mobile commit c6f2e174a9ef001dc44eca1a4b37964633f999c5 Author: Austin Mroz Date: Sat Jan 10 01:16:03 2026 -0800 Remove workflow filtering of outputs commit 1186897fd5115705f8a65df4d4c72cc8fff41625 Merge: 002b72be0 5029a0b32 Author: Austin Mroz Date: Fri Jan 9 16:15:33 2026 -0800 Merge main commit 002b72be0ecb4658bfad6d53502f824dc8566810 Author: Austin Mroz Date: Fri Jan 9 16:09:01 2026 -0800 fix another mobile z-index issue commit e51ea671e79bb05b9cbb167218cfc1e705ad7c67 Author: Austin Mroz Date: Fri Jan 9 15:53:28 2026 -0800 Move run button up, fix background, fix borders commit b94557a7ca658de586934429399d99aabe2206a4 Author: Austin Mroz Date: Fri Jan 9 15:45:49 2026 -0800 Rudimentary mobile support commit a4fc83fd4cb7c146c6da2b243c36f9d000bdf15a Author: Austin Mroz Date: Fri Jan 9 15:12:03 2026 -0800 Keep linear mode on refresh of temporary workflow commit 6a1efe6c91ae69cb4e8ae552dc5000b04f60fdaa Author: Austin Mroz Date: Fri Jan 9 12:28:47 2026 -0800 Fix restoring of linearState after load commit 15a649879e0f8f3c244ac763a284696f47cee8db Author: Austin Mroz Date: Fri Jan 9 10:54:36 2026 -0800 Design nits commit 3b349690cd438430620adc21520af8b716eb2add Author: Austin Mroz Date: Fri Jan 9 10:03:56 2026 -0800 Fix workflow title truncation commit ca1949cdf2b8c10e8482c6e0a83ba0f42f42b414 Author: Austin Mroz Date: Fri Jan 9 09:36:56 2026 -0800 Improve reliability of workflow tab indicator commit d1e504389f14f3ac54d6ebc16209b44fc8cc81fa Author: Austin Mroz Date: Thu Jan 8 21:13:18 2026 -0800 Rename LinearWorkflow to LinearControls commit 0884b1d6ae6d951c67dc058bf5f37ed3862780f3 Author: Austin Mroz Date: Thu Jan 8 21:07:49 2026 -0800 Further nits commit f1e04e8ff31c66577573a7002e49bae0262740af Author: Austin Mroz Date: Thu Jan 8 19:56:49 2026 -0800 Disable output filtering on cloud commit 8aed77d6b558c7f44cac2334749584c654709b0a Author: Austin Mroz Date: Thu Jan 8 19:22:50 2026 -0800 nits commit 96e004894b6782a2d7b22b82972a2da894c5276c Author: Austin Mroz Date: Thu Jan 8 17:45:59 2026 -0800 Remove custom element usage commit 00bd2ef781bdca0cefc9377d01a2e8b59adde77c Author: Austin Mroz Date: Thu Jan 8 17:12:05 2026 -0800 Use Popover component for workflow notes commit 03ceae327184d9490e1c8f63fda0fe9c6c8aa16d Author: Austin Mroz Date: Thu Jan 8 14:50:33 2026 -0800 DropZone event cleanup commit 51754460a349bc2d3698e2f3506ff4702713716d Author: Austin Mroz Date: Thu Jan 8 13:49:34 2026 -0800 Keep mode toggle visible if user has seen it commit 86049862c65e5d92b333b3ebb4b2ad2f03ae884c Author: Austin Mroz Date: Thu Jan 8 13:43:40 2026 -0800 Centralize linear mode workflow state tracking commit e36ff0d46721c6f028043a64c082cf57ae0e2059 Author: Austin Mroz Date: Thu Jan 8 13:30:32 2026 -0800 Support node bgcolor in linear mode commit c1ec8140bbff793319742062a4b0637a84d46fe7 Author: Austin Mroz Date: Thu Jan 8 13:15:57 2026 -0800 Nits: keep zoom state, event cleanup commit e4d958ee9b187eb7e02617135288fc137a690197 Author: Austin Mroz Date: Thu Jan 8 12:32:51 2026 -0800 ModeSwitch toggles regardless of button clicked commit ec9b4302eef42f99d11885300f189cf18f7df89e Author: Austin Mroz Date: Thu Jan 8 10:22:46 2026 -0800 Fix missing localization commit d0cc44d962f400e3d82213fe04a0e911550bd651 Author: Austin Mroz Date: Wed Jan 7 23:23:18 2026 -0800 Move center preview to component commit aa91b52f6be4964c384dd93b5ecad1ba0cb4b36b Author: Austin Mroz Date: Wed Jan 7 22:46:46 2026 -0800 Move workflow/widgets into component commit 532b8f0454ff4dfa0b87bab8c44fd97d24facbcf Author: Austin Mroz Date: Wed Jan 7 21:50:09 2026 -0800 Fix scrollToTop button, respect sidebar location commit 42025ab6d4b4b92534cd1fd911aaac570e441614 Author: Austin Mroz Date: Wed Jan 7 21:31:56 2026 -0800 Magically fix build commit 12e9bf6e340e6cce00e1b469b2489c8714c0a882 Author: Austin Mroz Date: Wed Jan 7 20:22:27 2026 -0800 Refactor outputs into sub-component commit 4baf1cf35531bf4e3b4a57cf5cd0ac890d59a876 Author: Austin Mroz Date: Wed Jan 7 14:23:19 2026 -0800 Sizing cleanup commit ab39a394d1c1479e1ec94deda23a6d79c9800a2e Author: Austin Mroz Date: Wed Jan 7 13:49:59 2026 -0800 Fix pending jobs count commit 129d3b0aa48ab4603558195156d348435d570361 Author: Austin Mroz Date: Wed Jan 7 13:34:32 2026 -0800 Update floating items to reflect sidebar location commit fc55fa9005712cf6abdfa4ac7f9ec96e74448193 Author: Austin Mroz Date: Wed Jan 7 12:17:40 2026 -0800 More reliable dimension tracking commit 835a0fcc3e2b6efa9b5bdee66c537a0c2e761798 Author: Austin Mroz Date: Wed Jan 7 11:44:43 2026 -0800 Use teleport for panel content Simpler than fully untangling dependencies, but still pushes towards it commit 7cfe9593400ed1cf3dcae239a93d8c3bb8c1a565 Author: Austin Mroz Date: Wed Jan 7 10:56:41 2026 -0800 Fix guard on selected item watch commit 4e1d46613ffb9206ebf3550647d96ff1a6e27bf0 Author: Austin Mroz Date: Tue Jan 6 20:39:00 2026 -0800 Prevent deselecting output commit 21559b079dcbc1f246cd4d19ce6b98c36db16b81 Author: Austin Mroz Date: Tue Jan 6 18:26:50 2026 -0800 Update locales commit d9547516dd952c9d00c2c86054c68c22a2ecf5ff Author: Austin Mroz Date: Tue Jan 6 18:15:37 2026 -0800 Move note popover, widget styling commit 2845320ddf263dcf84154516d4a57dd8f90017c2 Author: Austin Mroz Date: Tue Jan 6 11:45:05 2026 -0800 Workflows/Templates fixes Match styles Make workflows button functional When opening a workflow or template from linear mode that does not explicitly have a value set for linear mode, open the workflow or template in linear mode Known issue: workflows sidebar pane has a higher min-width than the outputs panel. This is not an easy fix and I may have to re-implement our own splitter component to smooth over the annoyances I've faced commit 664150b6a366be32e9caa6f24f4f717ca029db9c Author: Austin Mroz Date: Tue Jan 6 10:13:15 2026 -0800 Make download all download all commit 6071b63077975335d4d1042adf17dd2990a7aabd Author: Austin Mroz Date: Tue Jan 6 00:16:59 2026 -0800 Remove Primevue divider. Move tabular-nums up commit 82fac5bc0bfecdf01184d726fb925dfce2dcdb59 Author: Austin Mroz Date: Tue Jan 6 00:16:59 2026 -0800 Use tabular nums for item stats Potentially worth adding additional padding, but this is a small win commit 10752c31af9b9de2e6a87a632ffeba9e3e2e7ec1 Author: Austin Mroz Date: Tue Jan 6 00:11:41 2026 -0800 Remove click handler, reset only scroll commit 0ef5c91a828d9cbe5f34197af125be3195a05187 Author: Austin Mroz Date: Mon Jan 5 22:52:40 2026 -0800 Use temp variable when toggling linearMode This seems to make the workflow indicator more consistent commit cfa3800ae32d30b789814c62aa417ea5eb559f6e Author: Austin Mroz Date: Mon Jan 5 22:45:14 2026 -0800 Add badge to outputs if multiple jobs queued commit 6200722c39da7af534fe9d7ec2424d05b41333eb Author: Austin Mroz Date: Mon Jan 5 22:22:28 2026 -0800 Minor component nits commit f01bb4d8d25118a1ec7a3dee5511ebb1c393dc4a Author: Austin Mroz Date: Mon Jan 5 21:52:53 2026 -0800 Add workflow info popover commit 5c693c5d238e86e9baf1b28736a89d0322bcdcde Author: Austin Mroz Date: Mon Jan 5 21:27:59 2026 -0800 Popover polish commit 8ffcf5a143cd1574b235247bdf11e3646d2b86ef Author: Austin Mroz Date: Mon Jan 5 21:03:13 2026 -0800 Widget border cleanup commit 41ea0387d73b82f61ccf9a5bc50563e7f7c22a46 Author: Austin Mroz Date: Mon Jan 5 20:34:30 2026 -0800 Sizing cleanup commit fc07cd15b0b5f7fe59a5fe226d7a7adc5ed5bdb2 Author: Austin Mroz Date: Mon Jan 5 17:09:45 2026 -0800 Fix SplitterPanel outline. Unintended scrolling commit 1166e2a0e69e8291370cab15704ac92b817e8f93 Author: Austin Mroz Date: Mon Jan 5 16:14:20 2026 -0800 On output click, only select. Don't load commit 6fdfc72019bcdbc6c74db3819bbeb8ad6d3e0b73 Author: Austin Mroz Date: Mon Jan 5 16:12:01 2026 -0800 Make cancel button square commit 8a8325353bab558c9c8737685e8ec6a0961f17c8 Author: Austin Mroz Date: Mon Jan 5 15:57:42 2026 -0800 Remove ghost div on controless number widgets commit f8b6981fec8406816bf87835459bc061f4ef20d1 Author: Austin Mroz Date: Mon Jan 5 15:56:06 2026 -0800 Minor comment cleanup commit 5a05a6b9f5a0adb33a2bef954f43e954c1b2fd30 Author: Austin Mroz Date: Mon Jan 5 15:51:18 2026 -0800 Implement cancel button interrupting execution Was just placeholder commit 522ad67cedb269d9329d8da005282899376c6ded Author: Austin Mroz Date: Mon Jan 5 15:50:12 2026 -0800 Fix dropzone border commit 5cf57ff8e86f3371922dfd0f642b095d0a200345 Author: Austin Mroz Date: Mon Jan 5 13:39:03 2026 -0800 Add click handler to DropZone commit 24500415cf5ad6f5609aa0431684e4123538dfe8 Author: Austin Mroz Date: Mon Jan 5 11:40:45 2026 -0800 Fix swapping from linear to normal workflow There was an attempted fitView with 0 visible area Not super happy with location of fix - FitView shouldn't cause total loss of functionality if called from invalid state - As previously discussed, this fitview call should probably be moved to only apply on newly loaded workflow, not restored workflows commit 7cf8e89f448e236b9185d3adbbb9b4bc17159823 Author: Austin Mroz Date: Mon Jan 5 10:42:51 2026 -0800 Filter linear outputs to current workflow commit 1d611e857ceddb3369388c956b11abb3be2630d0 Author: Austin Mroz Date: Mon Jan 5 09:34:43 2026 -0800 Add resolution indicator for video previews Attempting to future proof things a little here. I'm conflicted on current implementation vs slot based implementation (which also requires a siblingElement lookup) vs dedicated components for each type media being previewed commit 44bd2469ef9a05acb1ea2eeea0fdb1a9de0840a7 Author: Austin Mroz Date: Wed Dec 31 17:59:50 2025 -0800 Fix bad enum commit a780efb5ff91e5c118fe785b98191d72b46f69b8 Author: Austin Mroz Date: Wed Dec 31 17:56:14 2025 -0800 Add linear indicator to workflow tab commit e6529445861814a539d6042d36361170c6c3d2a8 Author: Austin Mroz Date: Wed Dec 31 17:47:29 2025 -0800 Sidebar cleanup, toggle as feature flag commit dbd1c30c98d1a28f9fbedce52b5b7402798dc0ec Author: Austin Mroz Date: Wed Dec 31 13:13:58 2025 -0800 3d support commit 0addee8fb12b0185b6dbf40fde676bb2ad7fb6ff Author: Austin Mroz Date: Wed Dec 31 12:20:44 2025 -0800 Improve mediaType handling commit eee096120a794d98dac722441dc73d09762e6baa Author: Austin Mroz Date: Wed Dec 31 11:41:07 2025 -0800 Preview dimensions for images Need to put some further thought into handling other types Adding a global ref here is bad. Creating stub components for each preview type is bad Each preview type needs different handling for calculation of it's dimensions/type commit 38479c4a40158c69e333e816a8e51a657a536b9c Author: Austin Mroz Date: Wed Dec 31 11:17:05 2025 -0800 Persist enabling of linear mode to workflow commit 8f83468eaaf7d85012fd229ad155fb210c8027c4 Author: Austin Mroz Date: Wed Dec 31 11:15:47 2025 -0800 Round linear toggle, fix scoll reset padding commit d1dd276335c86003f11a34415ae8688794e316b3 Author: Austin Mroz Date: Wed Dec 31 11:02:56 2025 -0800 Allow center image to expand beyond initial size commit 9202b80ee25925ff4981518227f9fa58cfcadf6c Author: Austin Mroz Date: Tue Dec 30 22:04:37 2025 -0800 Inset border, default dropIndicator for LoadImage commit 1151350ee4b2db163bd1a3e5c71c149d0eb1cff9 Author: Austin Mroz Date: Tue Dec 30 21:09:05 2025 -0800 Drag/drop support for linear mode Causes some border woes which need fixing commit 09021b605c5d56fc212be2277594d1f8d1bda526 Author: Austin Mroz Date: Tue Dec 30 19:51:47 2025 -0800 Disable display of markdown/note nodes commit 3cbdc30517e91c0cf43c8c53ab94c0e41487ac88 Author: Austin Mroz Date: Tue Dec 30 19:48:13 2025 -0800 Allow navigating history with up/down keys commit cdb422fefcfe95c7cb8f7af2963d12ec9b6f3e61 Author: Austin Mroz Date: Tue Dec 30 19:14:44 2025 -0800 Placeholder support for text output Since text outputs are not currently exposed in history, there's not way to test this and the label doesn't actually exist commit b145566a7ff3005d02f46275b0074b17f5510a45 Author: Austin Mroz Date: Tue Dec 30 18:30:54 2025 -0800 Placeholder icons for non-image outputs commit 76c722c656ecb523eaa54a46f39d30a063cd8890 Author: Austin Mroz Date: Tue Dec 30 17:02:57 2025 -0800 Support right sidebar commit aa6a0580ec04dde5b0e6478205e906862801243b Author: Austin Mroz Date: Tue Dec 30 16:38:48 2025 -0800 Analog scrolling for touchpad commit a9e5989a3ad586053d3413c3bc5fe002bfad01f6 Author: Austin Mroz Date: Tue Dec 30 15:30:29 2025 -0800 Add Popover component,with extra options Extra options are implemented through commands that seem to not function. Further investigation is needed commit c7d1869e410be152b1a21e7d1c2d3838624b28df Author: Austin Mroz Date: Tue Dec 30 10:09:49 2025 -0800 Preview and rerun cleanup commit df974e56a28d576c8453478fafc9aaa97490d243 Author: Austin Mroz Date: Mon Dec 29 14:34:57 2025 -0800 Implement reuse and rerun buttons commit 79bd8a4dd28c4c0729c9ec107e062e1b9db0f21f Author: Austin Mroz Date: Mon Dec 29 11:16:58 2025 -0800 Update histroy for new outputs, spinner for active A spinner is now displayed when a job is actively executing - Spinner has slightly incorrect sizing - Only one spinner is displayed regardless of output count History tracking change makes me very unhappy. I want to clean this up to correctly leverage vue reactivity in the future, but required functionality comes first commit 5e740c6efeab752ad7fed17fd9b771e77c6e8145 Author: Austin Mroz Date: Mon Dec 29 10:14:38 2025 -0800 Re-enable previews in naive way commit 0beebd030237e3562de09c5f42b6324aeffa4e14 Author: Austin Mroz Date: Mon Dec 29 09:52:33 2025 -0800 Display audio outputs, div cleanup commit 63ad1b289ba50cd7fd35c4606858c88122147a41 Author: Austin Mroz Date: Sat Dec 27 19:09:23 2025 -0800 Reset zoom on preview change commit 7051b5e491f83c2d5c105bdf9e10bce76a5c2f55 Author: Austin Mroz Date: Sat Dec 27 18:35:29 2025 -0800 Center zoom on mouse commit a7a7ea348fa9364ca0a6f07d366eb9af8ef7bee9 Author: Austin Mroz Date: Tue Dec 23 20:30:24 2025 -0800 Minor fixes: video size, no job toast on enter commit a96e0dec0c30d69ddea6e8febac52f82f94ff4dd Author: Austin Mroz Date: Tue Dec 23 17:52:18 2025 -0800 Initial pan/zoom implementation commit 1f17f5f1b50ed9ab59161a1e324314df913a6b0e Author: Austin Mroz Date: Tue Dec 23 14:46:19 2025 -0800 Popover cleanup - a working queue indicator commit 49ede05221ccbac017ee51db72a724eb79f9300b Author: Austin Mroz Date: Tue Dec 23 13:29:26 2025 -0800 Skeleton queue button changes commit d5d995de80d0c26cee23b231178683f5c61c2c85 Author: Austin Mroz Date: Mon Dec 22 17:52:18 2025 -0800 Initial main panel video support commit 75cc8b8b59f56353887d85cd0223a53df8ece41a Author: Austin Mroz Date: Mon Dec 22 16:10:51 2025 -0800 Toggle button cleanup Experimented with ToggleGroups, but it was too much effort and YAGNI Add icon and descriptions for other media types commit b77f028097617927043137c4dc0f4d488b199600 Author: Austin Mroz Date: Mon Dec 22 14:01:25 2025 -0800 Prune unused export commit 4cb0eb6fec062b5b3e3354e69c48f8625fb6fdfe Author: Austin Mroz Date: Mon Dec 22 12:57:59 2025 -0800 Setup infinite scroll for outputs commit d1b4ec44df0e3738fed079663a3b35c54c066e23 Author: Austin Mroz Date: Mon Dec 22 11:20:28 2025 -0800 Fix node reactivity in linear mode commit 1c007d650d615d111c73a25e95fb92a03cee0ccf Author: Austin Mroz Date: Fri Dec 19 15:25:17 2025 -0800 Add sidebar Conflicted on this one. Will likely argue for inlining history commit 24523a53970bcbc32bccde805e3462b7eac68645 Author: Austin Mroz Date: Fri Dec 19 12:03:21 2025 -0800 Ugly growth hack commit 7ef03de7fbb78533f0fd4a8e2d3c2010d69ddf4c Author: Austin Mroz Date: Fri Dec 19 11:57:02 2025 -0800 Add icon for image mediatype commit 842043999c1ccc2117870cde596855a744cbc95c Author: Austin Mroz Date: Fri Dec 19 11:33:52 2025 -0800 Scoll into view for history panel Functional, but I don't like the implementation specifics, will return to later commit 461ee6c6d613971cd4c66aed7a46862389eaed86 Author: Austin Mroz Date: Fri Dec 19 10:55:09 2025 -0800 Side panel cleanup commit 302990ca9bb8390be7d4dd1adabf8e572e141efe Author: Austin Mroz Date: Thu Dec 18 22:42:50 2025 -0800 Buttons, new and old commit a35b87354faf5bf3cc76f8a9303798878a5a7830 Author: Austin Mroz Date: Thu Dec 18 21:28:27 2025 -0800 Add generation info display commit 735a60e6d70917d07c7b5b0d5b33f3821d7c772a Author: Austin Mroz Date: Thu Dec 18 15:50:43 2025 -0800 Allow resizing history commit 5455c85a296614de0b833d80ad9c8110274f74c1 Author: Austin Mroz Date: Thu Dec 18 15:11:21 2025 -0800 Initial scorll implementation commit 323649557fdc576037a3be007f0ac945f0ff14db Author: Austin Mroz Date: Wed Dec 17 23:39:19 2025 -0800 WIP sidebar update --- packages/design-system/src/css/style.css | 3 + src/components/sidebar/ModeToggle.vue | 28 ++ src/components/sidebar/SideToolbar.vue | 11 +- .../sidebar/tabs/WorkflowsSidebarTab.vue | 1 + src/components/topbar/WorkflowTab.vue | 4 + src/components/ui/Popover.vue | 76 +++++ src/components/ui/TypeformPopoverButton.vue | 29 ++ src/components/ui/ZoomPane.vue | 59 ++++ src/composables/graph/useGraphNodeManager.ts | 11 - src/composables/useCoreCommands.ts | 7 +- src/composables/useFeatureFlags.ts | 7 + src/locales/en/main.json | 16 +- src/platform/remoteConfig/types.ts | 1 + .../workflow/core/services/workflowService.ts | 9 + .../management/stores/workflowStore.ts | 2 - .../composables/useTemplateWorkflows.ts | 23 +- .../extensions/linearMode/DropZone.vue | 52 +++ .../extensions/linearMode/ImagePreview.vue | 44 +++ .../extensions/linearMode/LinearControls.vue | 291 +++++++++++++++++ .../extensions/linearMode/LinearPreview.vue | 182 +++++++++++ .../extensions/linearMode/OutputHistory.vue | 300 ++++++++++++++++++ .../extensions/linearMode/VideoPreview.vue | 27 ++ .../extensions/linearMode/mediaTypes.ts | 33 ++ .../vueNodes/components/LGraphNode.vue | 16 +- .../vueNodes/components/NodeHeader.vue | 23 +- .../vueNodes/utils/nodeStyleUtils.ts | 13 +- .../components/WidgetInputNumberInput.vue | 5 +- src/scripts/app.ts | 2 + src/stores/imagePreviewStore.ts | 13 +- src/utils/dateTimeUtil.ts | 14 + src/views/GraphView.vue | 2 +- src/views/LinearView.vue | 298 +++++++++-------- vite.config.mts | 3 + 33 files changed, 1357 insertions(+), 248 deletions(-) create mode 100644 src/components/sidebar/ModeToggle.vue create mode 100644 src/components/ui/Popover.vue create mode 100644 src/components/ui/TypeformPopoverButton.vue create mode 100644 src/components/ui/ZoomPane.vue create mode 100644 src/renderer/extensions/linearMode/DropZone.vue create mode 100644 src/renderer/extensions/linearMode/ImagePreview.vue create mode 100644 src/renderer/extensions/linearMode/LinearControls.vue create mode 100644 src/renderer/extensions/linearMode/LinearPreview.vue create mode 100644 src/renderer/extensions/linearMode/OutputHistory.vue create mode 100644 src/renderer/extensions/linearMode/VideoPreview.vue create mode 100644 src/renderer/extensions/linearMode/mediaTypes.ts diff --git a/packages/design-system/src/css/style.css b/packages/design-system/src/css/style.css index 551e26c62e4..df8fe4a2385 100644 --- a/packages/design-system/src/css/style.css +++ b/packages/design-system/src/css/style.css @@ -247,6 +247,7 @@ --inverted-background-hover: var(--color-charcoal-600); --warning-background: var(--color-gold-400); --warning-background-hover: var(--color-gold-500); + --success-background: var(--color-jade-600); --border-default: var(--color-smoke-600); --border-subtle: var(--color-smoke-400); --muted-background: var(--color-smoke-700); @@ -372,6 +373,7 @@ --inverted-background-hover: var(--color-smoke-200); --warning-background: var(--color-gold-600); --warning-background-hover: var(--color-gold-500); + --success-background: var(--color-jade-600); --border-default: var(--color-charcoal-200); --border-subtle: var(--color-charcoal-300); --muted-background: var(--color-charcoal-100); @@ -516,6 +518,7 @@ --color-inverted-background-hover: var(--inverted-background-hover); --color-warning-background: var(--warning-background); --color-warning-background-hover: var(--warning-background-hover); + --color-success-background: var(--success-background); --color-border-default: var(--border-default); --color-border-subtle: var(--border-subtle); --color-muted-background: var(--muted-background); diff --git a/src/components/sidebar/ModeToggle.vue b/src/components/sidebar/ModeToggle.vue new file mode 100644 index 00000000000..31e03960125 --- /dev/null +++ b/src/components/sidebar/ModeToggle.vue @@ -0,0 +1,28 @@ + + diff --git a/src/components/sidebar/SideToolbar.vue b/src/components/sidebar/SideToolbar.vue index 8f0d800cb81..a8aaad758de 100644 --- a/src/components/sidebar/SideToolbar.vue +++ b/src/components/sidebar/SideToolbar.vue @@ -44,6 +44,7 @@ + @@ -51,15 +52,17 @@ + + diff --git a/src/components/ui/TypeformPopoverButton.vue b/src/components/ui/TypeformPopoverButton.vue new file mode 100644 index 00000000000..e6808e8bc87 --- /dev/null +++ b/src/components/ui/TypeformPopoverButton.vue @@ -0,0 +1,29 @@ + + diff --git a/src/components/ui/ZoomPane.vue b/src/components/ui/ZoomPane.vue new file mode 100644 index 00000000000..60ea6533bd0 --- /dev/null +++ b/src/components/ui/ZoomPane.vue @@ -0,0 +1,59 @@ + + diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index 786e23f4e86..6c055917a63 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -207,17 +207,6 @@ export function safeWidgetMapper( } } -export function isValidWidgetValue(value: unknown): value is WidgetValue { - return ( - value === null || - value === undefined || - typeof value === 'string' || - typeof value === 'number' || - typeof value === 'boolean' || - typeof value === 'object' - ) -} - export function useGraphNodeManager(graph: LGraph): GraphNodeManager { // Get layout mutations composable const { createNode, deleteNode, setSource } = useLayoutMutations() diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 2025eb66c59..5bd569085ad 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -1235,7 +1235,12 @@ export function useCoreCommands(): ComfyCommand[] { id: 'Comfy.ToggleLinear', icon: 'pi pi-database', label: 'toggle linear mode', - function: () => (canvasStore.linearMode = !canvasStore.linearMode) + function: () => { + const newMode = !canvasStore.linearMode + app.rootGraph.extra.linearMode = newMode + workflowStore.activeWorkflow?.changeTracker?.checkState() + canvasStore.linearMode = newMode + } } ] diff --git a/src/composables/useFeatureFlags.ts b/src/composables/useFeatureFlags.ts index 3388140adab..4e7368bc6f5 100644 --- a/src/composables/useFeatureFlags.ts +++ b/src/composables/useFeatureFlags.ts @@ -16,6 +16,7 @@ export enum ServerFeatureFlag { PRIVATE_MODELS_ENABLED = 'private_models_enabled', ONBOARDING_SURVEY_ENABLED = 'onboarding_survey_enabled', HUGGINGFACE_MODEL_IMPORT_ENABLED = 'huggingface_model_import_enabled', + LINEAR_TOGGLE_ENABLED = 'linear_toggle_enabled', ASYNC_MODEL_UPLOAD_ENABLED = 'async_model_upload_enabled' } @@ -77,6 +78,12 @@ export function useFeatureFlags() { ) ) }, + get linearToggleEnabled() { + return ( + remoteConfig.value.linear_toggle_enabled ?? + api.getServerFeature(ServerFeatureFlag.LINEAR_TOGGLE_ENABLED, false) + ) + }, get asyncModelUploadEnabled() { return ( remoteConfig.value.async_model_upload_enabled ?? diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 6ef5343acee..b60523a239a 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -190,6 +190,7 @@ "failed": "Failed", "cancelled": "Cancelled", "job": "Job", + "asset": "{count} assets | {count} asset | {count} assets", "untitled": "Untitled", "emDash": "—", "enabling": "Enabling {id}", @@ -677,7 +678,8 @@ "filterImage": "Image", "filterVideo": "Video", "filterAudio": "Audio", - "filter3D": "3D" + "filter3D": "3D", + "filterText": "Text" }, "backToAssets": "Back to all assets", "searchAssets": "Search Assets", @@ -1182,7 +1184,7 @@ "Experimental: Enable AssetAPI": "Experimental: Enable AssetAPI", "Canvas Performance": "Canvas Performance", "Help Center": "Help Center", - "toggle linear mode": "toggle linear mode", + "toggle linear mode": "toggle simple mode", "Toggle Queue Panel V2": "Toggle Queue Panel V2", "Toggle Theme (Dark/Light)": "Toggle Theme (Dark/Light)", "Undo": "Undo", @@ -2471,8 +2473,14 @@ "message": "Switch back to Nodes 2.0 anytime from the main menu." }, "linearMode": { - "share": "Share", - "openWorkflow": "Open Workflow" + "linearMode": "Simple Mode", + "beta": "Beta - Give Feedback", + "graphMode": "Graph Mode", + "dragAndDropImage": "Drag and drop an image", + "runCount": "Run count:", + "rerun": "Rerun", + "reuseParameters": "Reuse Parameters", + "downloadAll": "Download All" }, "missingNodes": { "cloud": { diff --git a/src/platform/remoteConfig/types.ts b/src/platform/remoteConfig/types.ts index f4e51115b07..7de4c2da227 100644 --- a/src/platform/remoteConfig/types.ts +++ b/src/platform/remoteConfig/types.ts @@ -40,5 +40,6 @@ export type RemoteConfig = { private_models_enabled?: boolean onboarding_survey_enabled?: boolean huggingface_model_import_enabled?: boolean + linear_toggle_enabled?: boolean async_model_upload_enabled?: boolean } diff --git a/src/platform/workflow/core/services/workflowService.ts b/src/platform/workflow/core/services/workflowService.ts index 37c4ef5e5a4..395ff3c1298 100644 --- a/src/platform/workflow/core/services/workflowService.ts +++ b/src/platform/workflow/core/services/workflowService.ts @@ -11,6 +11,7 @@ import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' +import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { useWorkflowThumbnail } from '@/renderer/core/thumbnail/useWorkflowThumbnail' import { app } from '@/scripts/app' import { blankGraph, defaultGraph } from '@/scripts/defaultGraph' @@ -311,6 +312,11 @@ export const useWorkflowService = () => { workflowData: ComfyWorkflowJSON ) => { const workflowStore = useWorkspaceStore().workflow + if ( + workflowData.extra?.linearMode !== undefined || + !workflowData.nodes.length + ) + useCanvasStore().linearMode = !!workflowData.extra?.linearMode if (value === null || typeof value === 'string') { const path = value as string | null @@ -332,6 +338,9 @@ export const useWorkflowService = () => { } } + workflowData.extra ??= {} + workflowData.extra.linearMode == useCanvasStore().linearMode + const tempWorkflow = workflowStore.createNewTemporary( path ? appendJsonExt(path) : undefined, workflowData diff --git a/src/platform/workflow/management/stores/workflowStore.ts b/src/platform/workflow/management/stores/workflowStore.ts index 6f47c844547..7a3df1942c6 100644 --- a/src/platform/workflow/management/stores/workflowStore.ts +++ b/src/platform/workflow/management/stores/workflowStore.ts @@ -13,7 +13,6 @@ import type { ComfyWorkflowJSON, NodeId } from '@/platform/workflow/validation/schemas/workflowSchema' -import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { useWorkflowThumbnail } from '@/renderer/core/thumbnail/useWorkflowThumbnail' import { api } from '@/scripts/api' import { app as comfyApp } from '@/scripts/app' @@ -334,7 +333,6 @@ export const useWorkflowStore = defineStore('workflow', () => { tabActivationHistory.value.shift() } - useCanvasStore().linearMode = !!loadedWorkflow.activeState.extra?.linearMode return loadedWorkflow } diff --git a/src/platform/workflow/templates/composables/useTemplateWorkflows.ts b/src/platform/workflow/templates/composables/useTemplateWorkflows.ts index d3bea7fb9b4..a5ee9c3b9ba 100644 --- a/src/platform/workflow/templates/composables/useTemplateWorkflows.ts +++ b/src/platform/workflow/templates/composables/useTemplateWorkflows.ts @@ -121,28 +121,7 @@ export function useTemplateWorkflows() { if (!template || !template.sourceModule) return false // Use the stored source module for loading - const actualSourceModule = template.sourceModule - json = await fetchTemplateJson(id, actualSourceModule) - - // Use source module for name - const workflowName = - actualSourceModule === 'default' - ? t(`templateWorkflows.template.${id}`, id) - : id - - if (isCloud) { - useTelemetry()?.trackTemplate({ - workflow_name: id, - template_source: actualSourceModule - }) - } - - dialogStore.closeDialog() - await app.loadGraphData(json, true, true, workflowName, { - openSource: 'template' - }) - - return true + sourceModule = template.sourceModule } // Regular case for normal categories diff --git a/src/renderer/extensions/linearMode/DropZone.vue b/src/renderer/extensions/linearMode/DropZone.vue new file mode 100644 index 00000000000..d83ea91296c --- /dev/null +++ b/src/renderer/extensions/linearMode/DropZone.vue @@ -0,0 +1,52 @@ + + diff --git a/src/renderer/extensions/linearMode/ImagePreview.vue b/src/renderer/extensions/linearMode/ImagePreview.vue new file mode 100644 index 00000000000..e13dba7ceb4 --- /dev/null +++ b/src/renderer/extensions/linearMode/ImagePreview.vue @@ -0,0 +1,44 @@ + + diff --git a/src/renderer/extensions/linearMode/LinearControls.vue b/src/renderer/extensions/linearMode/LinearControls.vue new file mode 100644 index 00000000000..9855691344c --- /dev/null +++ b/src/renderer/extensions/linearMode/LinearControls.vue @@ -0,0 +1,291 @@ + + diff --git a/src/renderer/extensions/linearMode/LinearPreview.vue b/src/renderer/extensions/linearMode/LinearPreview.vue new file mode 100644 index 00000000000..b5cf0c9fdfa --- /dev/null +++ b/src/renderer/extensions/linearMode/LinearPreview.vue @@ -0,0 +1,182 @@ + + diff --git a/src/renderer/extensions/linearMode/OutputHistory.vue b/src/renderer/extensions/linearMode/OutputHistory.vue new file mode 100644 index 00000000000..d0a906d43bd --- /dev/null +++ b/src/renderer/extensions/linearMode/OutputHistory.vue @@ -0,0 +1,300 @@ + + diff --git a/src/renderer/extensions/linearMode/VideoPreview.vue b/src/renderer/extensions/linearMode/VideoPreview.vue new file mode 100644 index 00000000000..57504fc25ff --- /dev/null +++ b/src/renderer/extensions/linearMode/VideoPreview.vue @@ -0,0 +1,27 @@ + + diff --git a/src/renderer/extensions/linearMode/mediaTypes.ts b/src/renderer/extensions/linearMode/mediaTypes.ts new file mode 100644 index 00000000000..1f77b34f59d --- /dev/null +++ b/src/renderer/extensions/linearMode/mediaTypes.ts @@ -0,0 +1,33 @@ +import { t } from '@/i18n' + +import type { ResultItemImpl } from '@/stores/queueStore' + +export type StatItem = { content?: string; iconClass?: string } +export const mediaTypes: Record = { + '3d': { + content: t('sideToolbar.mediaAssets.filter3D'), + iconClass: 'icon-[lucide--box]' + }, + audio: { + content: t('sideToolbar.mediaAssets.filterAudio'), + iconClass: 'icon-[lucide--audio-lines]' + }, + images: { + content: t('sideToolbar.mediaAssets.filterImage'), + iconClass: 'icon-[lucide--image]' + }, + text: { + content: t('sideToolbar.mediaAssets.filterText'), + iconClass: 'icon-[lucide--text]' + }, + video: { + content: t('sideToolbar.mediaAssets.filterVideo'), + iconClass: 'icon-[lucide--video]' + } +} + +export function getMediaType(output?: ResultItemImpl) { + if (!output) return '' + if (output.isVideo) return 'video' + return output.mediaType +} diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index 8f27c189e6e..9f21ddaa41d 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -40,7 +40,7 @@ transform: `translate(${position.x ?? 0}px, ${(position.y ?? 0) - LiteGraph.NODE_TITLE_HEIGHT}px)`, zIndex: zIndex, opacity: nodeOpacity, - '--component-node-background': nodeBodyBackgroundColor + '--component-node-background': applyLightThemeColor(nodeData.bgcolor) } ]" v-bind="remainingPointerHandlers" @@ -168,7 +168,6 @@ import { applyLightThemeColor } from '@/renderer/extensions/vueNodes/utils/nodeS import { app } from '@/scripts/app' import { useExecutionStore } from '@/stores/executionStore' import { useNodeOutputStore } from '@/stores/imagePreviewStore' -import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { isTransparent } from '@/utils/colorUtil' import { getLocatorIdFromNodeData, @@ -228,19 +227,6 @@ const bypassed = computed( ) const muted = computed((): boolean => nodeData.mode === LGraphEventMode.NEVER) -const nodeBodyBackgroundColor = computed(() => { - const colorPaletteStore = useColorPaletteStore() - - if (!nodeData.bgcolor) { - return '' - } - - return applyLightThemeColor( - nodeData.bgcolor, - Boolean(colorPaletteStore.completedActivePalette.light_theme) - ) -}) - const nodeOpacity = computed(() => { const globalOpacity = useSettingStore().get('Comfy.Node.Opacity') ?? 1 diff --git a/src/renderer/extensions/vueNodes/components/NodeHeader.vue b/src/renderer/extensions/vueNodes/components/NodeHeader.vue index fe99c5d620f..0f24cf873a0 100644 --- a/src/renderer/extensions/vueNodes/components/NodeHeader.vue +++ b/src/renderer/extensions/vueNodes/components/NodeHeader.vue @@ -11,7 +11,10 @@ headerShapeClass ) " - :style="headerStyle" + :style="{ + backgroundColor: applyLightThemeColor(nodeData?.color), + opacity: useSettingStore().get('Comfy.Node.Opacity') ?? 1 + }" :data-testid="`node-header-${nodeData?.id || ''}`" @dblclick="handleDoubleClick" > @@ -104,7 +107,6 @@ import NodeBadge from '@/renderer/extensions/vueNodes/components/NodeBadge.vue' import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips' import { applyLightThemeColor } from '@/renderer/extensions/vueNodes/utils/nodeStyleUtils' import { app } from '@/scripts/app' -import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { normalizeI18nKey } from '@/utils/formatUtil' import { getLocatorIdFromNodeData, @@ -156,23 +158,6 @@ const enterSubgraphTooltipConfig = computed(() => { return createTooltipConfig(st('enterSubgraph', 'Enter Subgraph')) }) -const headerStyle = computed(() => { - const colorPaletteStore = useColorPaletteStore() - - const opacity = useSettingStore().get('Comfy.Node.Opacity') ?? 1 - - if (!nodeData?.color) { - return { backgroundColor: '', opacity } - } - - const headerColor = applyLightThemeColor( - nodeData.color, - Boolean(colorPaletteStore.completedActivePalette.light_theme) - ) - - return { backgroundColor: headerColor, opacity } -}) - const resolveTitle = (info: VueNodeData | undefined) => { const title = (info?.title ?? '').trim() if (title.length > 0) return title diff --git a/src/renderer/extensions/vueNodes/utils/nodeStyleUtils.ts b/src/renderer/extensions/vueNodes/utils/nodeStyleUtils.ts index 143684d137e..57a0b97177a 100644 --- a/src/renderer/extensions/vueNodes/utils/nodeStyleUtils.ts +++ b/src/renderer/extensions/vueNodes/utils/nodeStyleUtils.ts @@ -1,14 +1,13 @@ +import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { adjustColor } from '@/utils/colorUtil' /** * Applies light theme color adjustments to a color */ -export function applyLightThemeColor( - color: string, - isLightTheme: boolean -): string { - if (!color || !isLightTheme) { - return color - } +export function applyLightThemeColor(color?: string): string { + if (!color) return '' + + if (!useColorPaletteStore().completedActivePalette.light_theme) return color + return adjustColor(color, { lightness: 0.5 }) } diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue index fb6be5cb8d7..bcd5ce5df48 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputNumberInput.vue @@ -117,7 +117,10 @@ const buttonTooltip = computed(() => { -
+
diff --git a/src/scripts/app.ts b/src/scripts/app.ts index edcc64517e0..258214dd6e1 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -1224,6 +1224,8 @@ export class ComfyApp { // Fit view if no nodes visible in restored viewport this.canvas.ds.computeVisibleArea(this.canvas.viewport) if ( + this.canvas.visible_area.width && + this.canvas.visible_area.height && !anyItemOverlapsRect( this.rootGraph._nodes, this.canvas.visible_area diff --git a/src/stores/imagePreviewStore.ts b/src/stores/imagePreviewStore.ts index ee8ae759bd5..346011d45d9 100644 --- a/src/stores/imagePreviewStore.ts +++ b/src/stores/imagePreviewStore.ts @@ -40,7 +40,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => { const { nodeIdToNodeLocatorId, nodeToNodeLocatorId } = useWorkflowStore() const { executionIdToNodeLocatorId } = useExecutionStore() const scheduledRevoke: Record void }> = {} - const latestOutput = ref([]) + const latestPreview = ref([]) function scheduleRevoke(locator: NodeLocatorId, cb: () => void) { scheduledRevoke[locator]?.stop() @@ -147,13 +147,6 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => { } } - //TODO:Preview params and deduplication - latestOutput.value = - (outputs as ExecutedWsMessage['output'])?.images?.map((image) => { - const imgUrlPart = new URLSearchParams(image) - const rand = app.getRandParam() - return api.apiURL(`/view?${imgUrlPart}${rand}`) - }) ?? [] app.nodeOutputs[nodeLocatorId] = outputs nodeOutputs.value[nodeLocatorId] = outputs } @@ -221,7 +214,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => { scheduledRevoke[nodeLocatorId].stop() delete scheduledRevoke[nodeLocatorId] } - latestOutput.value = previewImages + latestPreview.value = previewImages app.nodePreviewImages[nodeLocatorId] = previewImages nodePreviewImages.value[nodeLocatorId] = previewImages } @@ -391,6 +384,6 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => { // State nodeOutputs, nodePreviewImages, - latestOutput + latestPreview } }) diff --git a/src/utils/dateTimeUtil.ts b/src/utils/dateTimeUtil.ts index 13c739b0db1..d72bd6101dd 100644 --- a/src/utils/dateTimeUtil.ts +++ b/src/utils/dateTimeUtil.ts @@ -75,3 +75,17 @@ export const formatClockTime = (ts: number, locale: string): string => { second: '2-digit' }).format(d) } + +export function formatDuration(durationSeconds?: number) { + if (durationSeconds == undefined) return '' + const hours = (durationSeconds / 60 ** 2) | 0 + const minutes = ((durationSeconds % 60 ** 2) / 60) | 0 + const seconds = (durationSeconds % 60) | 0 + const parts = [] + + if (hours > 0) parts.push(`${hours}h`) + if (minutes > 0) parts.push(`${minutes}m`) + if (seconds > 0) parts.push(`${seconds}s`) + + return parts.join(' ') +} diff --git a/src/views/GraphView.vue b/src/views/GraphView.vue index e60df5692ad..6dc309c1a9e 100644 --- a/src/views/GraphView.vue +++ b/src/views/GraphView.vue @@ -215,7 +215,7 @@ const onStatus = async (e: CustomEvent) => { await queueStore.update() // Only update assets if the assets sidebar is currently open // When sidebar is closed, AssetsSidebarTab.vue will refresh on mount - if (sidebarTabStore.activeSidebarTabId === 'assets') { + if (sidebarTabStore.activeSidebarTabId === 'assets' || linearMode.value) { await assetsStore.updateHistory() } } diff --git a/src/views/LinearView.vue b/src/views/LinearView.vue index 4ce74a4d092..22f255f13c9 100644 --- a/src/views/LinearView.vue +++ b/src/views/LinearView.vue @@ -1,107 +1,47 @@ diff --git a/vite.config.mts b/vite.config.mts index e5f07072653..713de6866b0 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -446,6 +446,9 @@ export default defineConfig({ if (id.includes('/vue') || id.includes('pinia')) { return 'vendor-vue' } + if (id.includes('reka-ui')) { + return 'vendor-reka-ui' + } return 'vendor-other' } From ffc951898b6b25c272214766410450d51a7c3744 Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Mon, 12 Jan 2026 19:13:35 -0800 Subject: [PATCH 2/8] typoes, add pending state --- src/components/sidebar/ModeToggle.vue | 2 +- src/components/topbar/WorkflowTab.vue | 2 +- .../workflow/core/services/workflowService.ts | 2 +- src/renderer/extensions/linearMode/OutputHistory.vue | 11 +++++++++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/sidebar/ModeToggle.vue b/src/components/sidebar/ModeToggle.vue index 31e03960125..ca94be771d6 100644 --- a/src/components/sidebar/ModeToggle.vue +++ b/src/components/sidebar/ModeToggle.vue @@ -17,7 +17,7 @@ const canvasStore = useCanvasStore() diff --git a/src/platform/workflow/core/services/workflowService.ts b/src/platform/workflow/core/services/workflowService.ts index 395ff3c1298..1923bb731d5 100644 --- a/src/platform/workflow/core/services/workflowService.ts +++ b/src/platform/workflow/core/services/workflowService.ts @@ -339,7 +339,7 @@ export const useWorkflowService = () => { } workflowData.extra ??= {} - workflowData.extra.linearMode == useCanvasStore().linearMode + workflowData.extra.linearMode = useCanvasStore().linearMode const tempWorkflow = workflowStore.createNewTemporary( path ? appendJsonExt(path) : undefined, diff --git a/src/renderer/extensions/linearMode/OutputHistory.vue b/src/renderer/extensions/linearMode/OutputHistory.vue index d0a906d43bd..5e08a95aa10 100644 --- a/src/renderer/extensions/linearMode/OutputHistory.vue +++ b/src/renderer/extensions/linearMode/OutputHistory.vue @@ -223,11 +223,18 @@ useEventListener(document.body, 'keydown', (e: KeyboardEvent) => { class="h-24 md:h-full min-w-24 grow-1 p-3 overflow-x-auto overflow-y-clip md:overflow-y-auto md:overflow-x-clip md:border-r-1 border-node-component-border flex md:flex-col items-center contain-size" >
- + +
diff --git a/src/renderer/extensions/linearMode/OutputHistory.vue b/src/renderer/extensions/linearMode/OutputHistory.vue index 3f59aebe2d2..2e3bd1a5e29 100644 --- a/src/renderer/extensions/linearMode/OutputHistory.vue +++ b/src/renderer/extensions/linearMode/OutputHistory.vue @@ -38,11 +38,7 @@ defineProps<{ }>() const emit = defineEmits<{ updateSelection: [ - selection: [ - AssetItem | undefined, - ResultItemImpl | undefined, - [number, number] - ] + selection: [AssetItem | undefined, ResultItemImpl | undefined, boolean] ] }>() @@ -55,7 +51,7 @@ watch(selectedIndex, () => { emit('updateSelection', [ outputs.media.value[index], selectedOutput.value, - selectedIndex.value + selectedIndex.value[0] <= 0 ]) }) @@ -103,10 +99,10 @@ const selectedOutput = computed(() => { watch( () => outputs.media.value, (newAssets, oldAssets) => { - if (newAssets.length === oldAssets.length) return - if (selectedIndex.value[0] <= 0 && selectedIndex.value[1] === 0) { + if (newAssets.length === oldAssets.length || oldAssets.length === 0) return + if (selectedIndex.value[0] <= 0) { //force update - selectedIndex.value = [selectedIndex.value[0], 0] + selectedIndex.value = [0, 0] return } diff --git a/src/views/LinearView.vue b/src/views/LinearView.vue index 22f255f13c9..c8c9706201c 100644 --- a/src/views/LinearView.vue +++ b/src/views/LinearView.vue @@ -34,7 +34,7 @@ whenever( const selectedItem = ref() const selectedOutput = ref() -const selectedIndex = ref<[number, number]>([0, 0]) +const canShowPreview = ref(true) const outputHistoryRef = useTemplateRef('outputHistoryRef') const topLeftRef = useTemplateRef('topLeftRef') @@ -58,7 +58,7 @@ const linearWorkflowRef = useTemplateRef('linearWorkflowRef')
Date: Mon, 12 Jan 2026 20:44:48 -0800 Subject: [PATCH 6/8] use extractVueNodeData instead of safeWidgetMapper Less code, and ensures widgets is shallowReactive --- src/composables/graph/useGraphNodeManager.ts | 148 +++++++++--------- .../extensions/linearMode/LinearControls.vue | 13 +- 2 files changed, 76 insertions(+), 85 deletions(-) diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index 6c055917a63..348765d783e 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -157,7 +157,7 @@ const normalizeWidgetValue = (value: unknown): WidgetValue => { return undefined } -export function safeWidgetMapper( +function safeWidgetMapper( node: LGraphNode, slotMetadata: Map ): (widget: IBaseWidget) => SafeWidgetData { @@ -207,6 +207,79 @@ export function safeWidgetMapper( } } +// Extract safe data from LiteGraph node for Vue consumption +export function extractVueNodeData(node: LGraphNode): VueNodeData { + // Determine subgraph ID - null for root graph, string for subgraphs + const subgraphId = + node.graph && 'id' in node.graph && node.graph !== node.graph.rootGraph + ? String(node.graph.id) + : null + // Extract safe widget data + const slotMetadata = new Map() + + const reactiveWidgets = shallowReactive(node.widgets ?? []) + Object.defineProperty(node, 'widgets', { + get() { + return reactiveWidgets + }, + set(v) { + reactiveWidgets.splice(0, reactiveWidgets.length, ...v) + } + }) + const reactiveInputs = shallowReactive(node.inputs ?? []) + Object.defineProperty(node, 'inputs', { + get() { + return reactiveInputs + }, + set(v) { + reactiveInputs.splice(0, reactiveInputs.length, ...v) + } + }) + + const safeWidgets = reactiveComputed(() => { + node.inputs?.forEach((input, index) => { + if (!input?.widget?.name) return + slotMetadata.set(input.widget.name, { + index, + linked: input.link != null + }) + }) + return node.widgets?.map(safeWidgetMapper(node, slotMetadata)) ?? [] + }) + + const nodeType = + node.type || + node.constructor?.comfyClass || + node.constructor?.title || + node.constructor?.name || + 'Unknown' + + const apiNode = node.constructor?.nodeData?.api_node ?? false + const badges = node.badges + + return { + id: String(node.id), + title: typeof node.title === 'string' ? node.title : '', + type: nodeType, + mode: node.mode || 0, + titleMode: node.title_mode, + selected: node.selected || false, + executing: false, // Will be updated separately based on execution state + subgraphId, + apiNode, + badges, + hasErrors: !!node.has_errors, + widgets: safeWidgets, + inputs: reactiveInputs, + outputs: node.outputs ? [...node.outputs] : undefined, + flags: node.flags ? { ...node.flags } : undefined, + color: node.color || undefined, + bgcolor: node.bgcolor || undefined, + resizable: node.resizable, + shape: node.shape + } +} + export function useGraphNodeManager(graph: LGraph): GraphNodeManager { // Get layout mutations composable const { createNode, deleteNode, setSource } = useLayoutMutations() @@ -240,79 +313,6 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { } } - // Extract safe data from LiteGraph node for Vue consumption - function extractVueNodeData(node: LGraphNode): VueNodeData { - // Determine subgraph ID - null for root graph, string for subgraphs - const subgraphId = - node.graph && 'id' in node.graph && node.graph !== node.graph.rootGraph - ? String(node.graph.id) - : null - // Extract safe widget data - const slotMetadata = new Map() - - const reactiveWidgets = shallowReactive(node.widgets ?? []) - Object.defineProperty(node, 'widgets', { - get() { - return reactiveWidgets - }, - set(v) { - reactiveWidgets.splice(0, reactiveWidgets.length, ...v) - } - }) - const reactiveInputs = shallowReactive(node.inputs ?? []) - Object.defineProperty(node, 'inputs', { - get() { - return reactiveInputs - }, - set(v) { - reactiveInputs.splice(0, reactiveInputs.length, ...v) - } - }) - - const safeWidgets = reactiveComputed(() => { - node.inputs?.forEach((input, index) => { - if (!input?.widget?.name) return - slotMetadata.set(input.widget.name, { - index, - linked: input.link != null - }) - }) - return node.widgets?.map(safeWidgetMapper(node, slotMetadata)) ?? [] - }) - - const nodeType = - node.type || - node.constructor?.comfyClass || - node.constructor?.title || - node.constructor?.name || - 'Unknown' - - const apiNode = node.constructor?.nodeData?.api_node ?? false - const badges = node.badges - - return { - id: String(node.id), - title: typeof node.title === 'string' ? node.title : '', - type: nodeType, - mode: node.mode || 0, - titleMode: node.title_mode, - selected: node.selected || false, - executing: false, // Will be updated separately based on execution state - subgraphId, - apiNode, - badges, - hasErrors: !!node.has_errors, - widgets: safeWidgets, - inputs: reactiveInputs, - outputs: node.outputs ? [...node.outputs] : undefined, - flags: node.flags ? { ...node.flags } : undefined, - color: node.color || undefined, - bgcolor: node.bgcolor || undefined, - resizable: node.resizable, - shape: node.shape - } - } - // Get access to original LiteGraph node (non-reactive) const getNode = (id: string): LGraphNode | undefined => { return nodeRefs.get(id) diff --git a/src/renderer/extensions/linearMode/LinearControls.vue b/src/renderer/extensions/linearMode/LinearControls.vue index 9855691344c..7b693a7554c 100644 --- a/src/renderer/extensions/linearMode/LinearControls.vue +++ b/src/renderer/extensions/linearMode/LinearControls.vue @@ -6,7 +6,7 @@ import { computed, ref, shallowRef } from 'vue' import Popover from '@/components/ui/Popover.vue' import Button from '@/components/ui/button/Button.vue' -import { safeWidgetMapper } from '@/composables/graph/useGraphNodeManager' +import { extractVueNodeData } from '@/composables/graph/useGraphNodeManager' import { t } from '@/i18n' import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode' import SubscribeToRunButton from '@/platform/cloud/subscription/components/SubscribeToRun.vue' @@ -50,8 +50,6 @@ useEventListener( ) function nodeToNodeData(node: LGraphNode) { - const mapper = safeWidgetMapper(node, new Map()) - const widgets = node.widgets?.map(mapper) ?? [] const dropIndicator = node.type !== 'LoadImage' ? undefined @@ -61,14 +59,7 @@ function nodeToNodeData(node: LGraphNode) { } return { - executing: false, - bgcolor: node.bgcolor || undefined, - id: `${node.id}`, - mode: 0, - selected: false, - title: node.title, - type: node.type, - widgets, + ...extractVueNodeData(node), dropIndicator, onDragDrop: node.onDragDrop, From cad7aa0ccb8c6ab1f0ac29793a411be6142b56ea Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Tue, 13 Jan 2026 08:55:35 -0800 Subject: [PATCH 7/8] Yet further attempts at solving sticky state workflowData is a clone of an already initialized graph, as a result, cahnging it changes the initial state without modifying the active state of the graph. In the ideal case, where the linear flag should be set, this should be the other way around. A save should be possible to apply this modified flag, rather than discarding it. This flag is not set to false on load of a normal workflow. Setting linear to false requires explicit toggling of linear state. This ensures that workflows do not become modified on load unless the user is actively utilizing linear mode. --- src/platform/workflow/core/services/workflowService.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/workflow/core/services/workflowService.ts b/src/platform/workflow/core/services/workflowService.ts index 1923bb731d5..8dc6cbdcf1f 100644 --- a/src/platform/workflow/core/services/workflowService.ts +++ b/src/platform/workflow/core/services/workflowService.ts @@ -338,8 +338,10 @@ export const useWorkflowService = () => { } } - workflowData.extra ??= {} - workflowData.extra.linearMode = useCanvasStore().linearMode + if (useCanvasStore().linearMode) { + app.rootGraph.extra ??= {} + app.rootGraph.extra.linearMode = true + } const tempWorkflow = workflowStore.createNewTemporary( path ? appendJsonExt(path) : undefined, From 4abff4eb006ac769af54255e267aa7c7ea6f89ad Mon Sep 17 00:00:00 2001 From: Austin Mroz Date: Tue, 13 Jan 2026 20:03:14 -0800 Subject: [PATCH 8/8] Prune slot metadata You shouldn't see slots in linear mode --- src/renderer/extensions/linearMode/LinearControls.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/extensions/linearMode/LinearControls.vue b/src/renderer/extensions/linearMode/LinearControls.vue index 7b693a7554c..36e3514e691 100644 --- a/src/renderer/extensions/linearMode/LinearControls.vue +++ b/src/renderer/extensions/linearMode/LinearControls.vue @@ -57,9 +57,11 @@ function nodeToNodeData(node: LGraphNode) { iconClass: 'icon-[lucide--image]', label: t('linearMode.dragAndDropImage') } + const nodeData = extractVueNodeData(node) + for (const widget of nodeData.widgets ?? []) widget.slotMetadata = undefined return { - ...extractVueNodeData(node), + ...nodeData, dropIndicator, onDragDrop: node.onDragDrop,