From 84d30fe121fc65774098dc8cd540a2059f5a7ac2 Mon Sep 17 00:00:00 2001
From: --list <18093452+simula-r@users.noreply.github.com>
Date: Mon, 19 Jan 2026 23:27:38 -0800
Subject: [PATCH] feat: FF the feature and also added isCloud checks and misc
styling fixes
---
src/components/dialog/GlobalDialog.vue | 14 +-
.../dialog/content/setting/UserPanel.vue | 2 +-
.../content/setting/WorkspacePanelContent.vue | 52 +-
src/components/graph/GraphCanvas.vue | 11 +-
src/components/topbar/CurrentUserButton.vue | 45 +-
src/components/topbar/CurrentUserPopover.vue | 288 ++---
.../topbar/CurrentUserPopoverWorkspace.vue | 346 ++++++
src/composables/useFeatureFlags.ts | 12 +-
src/locales/en/main.json | 7 +
.../components/SubscriptionPanel.vue | 18 +-
.../SubscriptionPanelContentLegacy.vue | 357 ++++++
... => SubscriptionPanelContentWorkspace.vue} | 42 +-
.../components/SettingDialogContent.vue | 84 +-
.../settings/composables/useSettingUI.ts | 95 +-
.../composables/useInviteUrlLoader.test.ts | 9 +-
.../stores/teamWorkspaceStore.test.ts | 1069 +++++++++++++++++
.../workspace/stores/teamWorkspaceStore.ts | 5 +-
src/services/dialogService.ts | 110 +-
18 files changed, 2206 insertions(+), 360 deletions(-)
create mode 100644 src/components/topbar/CurrentUserPopoverWorkspace.vue
create mode 100644 src/platform/cloud/subscription/components/SubscriptionPanelContentLegacy.vue
rename src/platform/cloud/subscription/components/{SubscriptionPanelContent.vue => SubscriptionPanelContentWorkspace.vue} (91%)
create mode 100644 src/platform/workspace/stores/teamWorkspaceStore.test.ts
diff --git a/src/components/dialog/GlobalDialog.vue b/src/components/dialog/GlobalDialog.vue
index 6bf3eab44..bb3b98478 100644
--- a/src/components/dialog/GlobalDialog.vue
+++ b/src/components/dialog/GlobalDialog.vue
@@ -6,7 +6,9 @@
v-model:visible="item.visible"
:class="[
'global-dialog',
- item.key === 'global-settings' ? 'settings-dialog' : ''
+ item.key === 'global-settings' && teamWorkspacesEnabled
+ ? 'settings-dialog-workspace'
+ : ''
]"
v-bind="item.dialogComponentProps"
:pt="item.dialogComponentProps.pt"
@@ -41,8 +43,13 @@
@@ -59,12 +66,13 @@ const dialogStore = useDialogStore()
@apply pt-0;
}
-.settings-dialog {
+/* Workspace mode: wider settings dialog */
+.settings-dialog-workspace {
width: 100%;
max-width: 1440px;
}
-.settings-dialog .p-dialog-content {
+.settings-dialog-workspace .p-dialog-content {
width: 100%;
}
diff --git a/src/components/dialog/content/setting/UserPanel.vue b/src/components/dialog/content/setting/UserPanel.vue
index 8f8f77cd3..60bc6dba3 100644
--- a/src/components/dialog/content/setting/UserPanel.vue
+++ b/src/components/dialog/content/setting/UserPanel.vue
@@ -1,5 +1,5 @@
-
+
{{ $t('userSettings.title') }}
diff --git a/src/components/dialog/content/setting/WorkspacePanelContent.vue b/src/components/dialog/content/setting/WorkspacePanelContent.vue
index 146c6eb6c..473f8ceaf 100644
--- a/src/components/dialog/content/setting/WorkspacePanelContent.vue
+++ b/src/components/dialog/content/setting/WorkspacePanelContent.vue
@@ -98,7 +98,7 @@ import { useI18n } from 'vue-i18n'
import WorkspaceProfilePic from '@/components/common/WorkspaceProfilePic.vue'
import MembersPanelContent from '@/components/dialog/content/setting/MembersPanelContent.vue'
import Button from '@/components/ui/button/Button.vue'
-import SubscriptionPanelContent from '@/platform/cloud/subscription/components/SubscriptionPanelContent.vue'
+import SubscriptionPanelContent from '@/platform/cloud/subscription/components/SubscriptionPanelContentWorkspace.vue'
import { useWorkspaceUI } from '@/platform/workspace/composables/useWorkspaceUI'
import { useTeamWorkspaceStore } from '@/platform/workspace/stores/teamWorkspaceStore'
import { useDialogService } from '@/services/dialogService'
@@ -111,7 +111,8 @@ const { t } = useI18n()
const {
showLeaveWorkspaceDialog,
showDeleteWorkspaceDialog,
- showInviteMemberDialog
+ showInviteMemberDialog,
+ showEditWorkspaceDialog
} = useDialogService()
const workspaceStore = useTeamWorkspaceStore()
const { workspaceName, members, isInviteLimitReached, isWorkspaceSubscribed } =
@@ -130,6 +131,10 @@ function handleDeleteWorkspace() {
showDeleteWorkspaceDialog()
}
+function handleEditWorkspace() {
+ showEditWorkspaceDialog()
+}
+
// Disable delete when workspace has an active subscription (to prevent accidental deletion)
// Use workspace's own subscription status, not the global isActiveSubscription
const isDeleteDisabled = computed(
@@ -157,30 +162,37 @@ function handleInviteMember() {
}
const menuItems = computed(() => {
- const action = uiConfig.value.workspaceMenuAction
- if (!action) return []
+ const items = []
- if (action === 'delete') {
- return [
- {
- label: t('workspacePanel.menu.deleteWorkspace'),
- icon: 'pi pi-trash',
- class: isDeleteDisabled.value
- ? 'text-danger/50 cursor-not-allowed'
- : 'text-danger',
- disabled: isDeleteDisabled.value,
- command: isDeleteDisabled.value ? undefined : handleDeleteWorkspace
- }
- ]
+ // Add edit option for owners
+ if (uiConfig.value.showEditWorkspaceMenuItem) {
+ items.push({
+ label: t('workspacePanel.menu.editWorkspace'),
+ icon: 'pi pi-pencil',
+ command: handleEditWorkspace
+ })
}
- return [
- {
+ const action = uiConfig.value.workspaceMenuAction
+ if (action === 'delete') {
+ items.push({
+ label: t('workspacePanel.menu.deleteWorkspace'),
+ icon: 'pi pi-trash',
+ class: isDeleteDisabled.value
+ ? 'text-danger/50 cursor-not-allowed'
+ : 'text-danger',
+ disabled: isDeleteDisabled.value,
+ command: isDeleteDisabled.value ? undefined : handleDeleteWorkspace
+ })
+ } else if (action === 'leave') {
+ items.push({
label: t('workspacePanel.menu.leaveWorkspace'),
icon: 'pi pi-sign-out',
command: handleLeaveWorkspace
- }
- ]
+ })
+ }
+
+ return items
})
onMounted(() => {
diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue
index 206928cd8..60b0e6d26 100644
--- a/src/components/graph/GraphCanvas.vue
+++ b/src/components/graph/GraphCanvas.vue
@@ -126,11 +126,13 @@ import { useNodeBadge } from '@/composables/node/useNodeBadge'
import { useCanvasDrop } from '@/composables/useCanvasDrop'
import { useContextMenuTranslation } from '@/composables/useContextMenuTranslation'
import { useCopy } from '@/composables/useCopy'
+import { useFeatureFlags } from '@/composables/useFeatureFlags'
import { useGlobalLitegraph } from '@/composables/useGlobalLitegraph'
import { usePaste } from '@/composables/usePaste'
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
import { mergeCustomNodesI18n, t } from '@/i18n'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
+import { isCloud } from '@/platform/distribution/types'
import { useLitegraphSettings } from '@/platform/settings/composables/useLitegraphSettings'
import { CORE_SETTINGS } from '@/platform/settings/constants/coreSettings'
import { useSettingStore } from '@/platform/settings/settingStore'
@@ -139,7 +141,6 @@ import { useWorkflowService } from '@/platform/workflow/core/services/workflowSe
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useWorkflowAutoSave } from '@/platform/workflow/persistence/composables/useWorkflowAutoSave'
import { useWorkflowPersistence } from '@/platform/workflow/persistence/composables/useWorkflowPersistence'
-import { useInviteUrlLoader } from '@/platform/workspace/composables/useInviteUrlLoader'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'
@@ -395,7 +396,7 @@ const loadCustomNodesI18n = async () => {
const comfyAppReady = ref(false)
const workflowPersistence = useWorkflowPersistence()
-const inviteUrlLoader = useInviteUrlLoader()
+const { flags } = useFeatureFlags()
useCanvasDrop(canvasRef)
useLitegraphSettings()
useNodeBadge()
@@ -462,7 +463,11 @@ onMounted(async () => {
await workflowPersistence.loadTemplateFromUrlIfPresent()
// Accept workspace invite from URL if present (e.g., ?invite=TOKEN)
- await inviteUrlLoader.loadInviteFromUrl()
+ if (isCloud && flags.teamWorkspacesEnabled) {
+ const { useInviteUrlLoader } =
+ await import('@/platform/workspace/composables/useInviteUrlLoader')
+ await useInviteUrlLoader().loadInviteFromUrl()
+ }
// Initialize release store to fetch releases from comfy-api (fire-and-forget)
const { useReleaseStore } =
diff --git a/src/components/topbar/CurrentUserButton.vue b/src/components/topbar/CurrentUserButton.vue
index c3c9777bb..849a9ba25 100644
--- a/src/components/topbar/CurrentUserButton.vue
+++ b/src/components/topbar/CurrentUserButton.vue
@@ -1,4 +1,4 @@
-
+
@@ -30,11 +36,17 @@
:show-arrow="false"
:pt="{
root: {
- class: 'rounded-lg'
+ class: 'rounded-lg w-80'
}
}"
>
-
+
+
+
+
@@ -42,23 +54,44 @@
diff --git a/src/composables/useFeatureFlags.ts b/src/composables/useFeatureFlags.ts
index a3d2e9767..ca54bb9c6 100644
--- a/src/composables/useFeatureFlags.ts
+++ b/src/composables/useFeatureFlags.ts
@@ -1,5 +1,6 @@
import { computed, reactive, readonly } from 'vue'
+import { isCloud } from '@/platform/distribution/types'
import { remoteConfig } from '@/platform/remoteConfig/remoteConfig'
import { api } from '@/scripts/api'
@@ -95,11 +96,12 @@ export function useFeatureFlags() {
)
},
get teamWorkspacesEnabled() {
- return true
- // return (
- // remoteConfig.value.team_workspaces_enabled ??
- // api.getServerFeature(ServerFeatureFlag.TEAM_WORKSPACES_ENABLED, false)
- // )
+ if (!isCloud) return false
+
+ return (
+ remoteConfig.value.team_workspaces_enabled ??
+ api.getServerFeature(ServerFeatureFlag.TEAM_WORKSPACES_ENABLED, false)
+ )
}
})
diff --git a/src/locales/en/main.json b/src/locales/en/main.json
index 68d5cd19c..95edda35f 100644
--- a/src/locales/en/main.json
+++ b/src/locales/en/main.json
@@ -2059,6 +2059,7 @@
"subscribeToComfyCloud": "Subscribe to Comfy Cloud",
"workspaceNotSubscribed": "This workspace is not on a subscription",
"subscriptionRequiredMessage": "A subscription is required for members to run workflows on Cloud",
+ "contactOwnerToSubscribe": "Contact the workspace owner to subscribe",
"description": "Choose the best plan for you",
"haveQuestions": "Have questions or wondering about enterprise?",
"contactUs": "Contact us",
@@ -2136,10 +2137,16 @@
"createNewWorkspace": "create a new one."
},
"menu": {
+ "editWorkspace": "Edit workspace details",
"leaveWorkspace": "Leave Workspace",
"deleteWorkspace": "Delete Workspace",
"deleteWorkspaceDisabledTooltip": "Cancel your workspace's active subscription first"
},
+ "editWorkspaceDialog": {
+ "title": "Edit workspace details",
+ "nameLabel": "Workspace name",
+ "save": "Save"
+ },
"leaveDialog": {
"title": "Leave this workspace?",
"message": "You won't be able to join again unless you contact the workspace owner.",
diff --git a/src/platform/cloud/subscription/components/SubscriptionPanel.vue b/src/platform/cloud/subscription/components/SubscriptionPanel.vue
index 029fedb4c..e395cb7d1 100644
--- a/src/platform/cloud/subscription/components/SubscriptionPanel.vue
+++ b/src/platform/cloud/subscription/components/SubscriptionPanel.vue
@@ -17,7 +17,10 @@
-
+
+
+
+
import TabPanel from 'primevue/tabpanel'
+import { defineAsyncComponent } from 'vue'
import CloudBadge from '@/components/topbar/CloudBadge.vue'
import Button from '@/components/ui/button/Button.vue'
import { useExternalLink } from '@/composables/useExternalLink'
-import SubscriptionPanelContent from '@/platform/cloud/subscription/components/SubscriptionPanelContent.vue'
+import { useFeatureFlags } from '@/composables/useFeatureFlags'
+import SubscriptionPanelContentLegacy from '@/platform/cloud/subscription/components/SubscriptionPanelContentLegacy.vue'
import { useSubscription } from '@/platform/cloud/subscription/composables/useSubscription'
import { useSubscriptionActions } from '@/platform/cloud/subscription/composables/useSubscriptionActions'
+import { isCloud } from '@/platform/distribution/types'
+
+const SubscriptionPanelContentWorkspace = defineAsyncComponent(
+ () =>
+ import('@/platform/cloud/subscription/components/SubscriptionPanelContentWorkspace.vue')
+)
+
+const { flags } = useFeatureFlags()
+const teamWorkspacesEnabled = isCloud && flags.teamWorkspacesEnabled
const { buildDocsUrl, docsPaths } = useExternalLink()
diff --git a/src/platform/cloud/subscription/components/SubscriptionPanelContentLegacy.vue b/src/platform/cloud/subscription/components/SubscriptionPanelContentLegacy.vue
new file mode 100644
index 000000000..a6d5f063b
--- /dev/null
+++ b/src/platform/cloud/subscription/components/SubscriptionPanelContentLegacy.vue
@@ -0,0 +1,357 @@
+
+
+
+
+
+
+
+ {{ subscriptionTierName }}
+
+
+ ${{ tierPrice }}
+ {{ $t('subscription.perMonth') }}
+
+
+
+ {{
+ $t('subscription.expiresDate', {
+ date: formattedEndDate
+ })
+ }}
+
+
+ {{
+ $t('subscription.renewsDate', {
+ date: formattedRenewalDate
+ })
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('subscription.totalCredits') }}
+
+
+
+ {{ totalCredits }}
+
+
+
+
+
+
+
+ |
+
+ {{ includedCreditsDisplay }}
+ |
+
+ {{ creditsRemainingLabel }}
+ |
+
+
+ |
+
+ {{ prepaidCredits }}
+ |
+
+ {{ $t('subscription.creditsYouveAdded') }}
+ |
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('subscription.yourPlanIncludes') }}
+
+
+
+
+
+
+ {{ benefit.value }}
+
+
+ {{ benefit.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/platform/cloud/subscription/components/SubscriptionPanelContent.vue b/src/platform/cloud/subscription/components/SubscriptionPanelContentWorkspace.vue
similarity index 91%
rename from src/platform/cloud/subscription/components/SubscriptionPanelContent.vue
rename to src/platform/cloud/subscription/components/SubscriptionPanelContentWorkspace.vue
index 564088777..7605849e8 100644
--- a/src/platform/cloud/subscription/components/SubscriptionPanelContent.vue
+++ b/src/platform/cloud/subscription/components/SubscriptionPanelContentWorkspace.vue
@@ -22,7 +22,19 @@
-
+
+
+
+
+ {{ $t('subscription.workspaceNotSubscribed') }}
+
+
+ {{ $t('subscription.contactOwnerToSubscribe') }}
+
+
+
+
+
@@ -125,7 +128,7 @@
- {{ isOwnerUnsubscribed ? '0' : totalCredits }}
+ {{ showZeroState ? '0' : totalCredits }}
@@ -140,7 +143,7 @@
height="1rem"
/>
{{
- isOwnerUnsubscribed ? '0 / 0' : includedCreditsDisplay
+ showZeroState ? '0 / 0' : includedCreditsDisplay
}}
@@ -155,7 +158,7 @@
height="1rem"
/>
{{
- isOwnerUnsubscribed ? '0' : prepaidCredits
+ showZeroState ? '0' : prepaidCredits
}}
|
|