Align Receive Card Settings Expiration with Send Card Pattern

This commit is contained in:
bimgeek
2026-04-02 21:39:09 +03:00
parent db88c8eeaa
commit b8790150e4
6 changed files with 109 additions and 7 deletions
+12
View File
@@ -17,6 +17,7 @@
v-if="hasSettings"
:model-card-id="props.modelCard.modelCardId"
:settings="props.modelCard.settings"
:default-settings="settingsDefaults as unknown as CardSetting[]"
>
<template #activator="{ toggle }">
<button class="action action-normal" @click="toggle()">
@@ -69,6 +70,8 @@ import {
Bars3Icon
} from '@heroicons/vue/24/outline'
import type { IModelCard } from '~/lib/models/card'
import type { CardSetting } from '~/lib/models/card/setting'
import { useHostAppStore } from '~/store/hostApp'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { issuesListQuery } from '~/lib/issues/graphql/queries'
import { useAccountStore } from '~/store/accounts'
@@ -76,6 +79,7 @@ import { storeToRefs } from 'pinia'
import { useQuery } from '@vue/apollo-composable'
const { trackEvent } = useMixpanel()
const store = useHostAppStore()
const openModelCardActionsDialog = ref(false)
const emit = defineEmits(['view', 'view-versions', 'copy-model-link', 'remove'])
@@ -85,6 +89,14 @@ const props = defineProps<{
modelCard: IModelCard
}>()
const isSender = computed(() =>
props.modelCard.typeDiscriminator.includes('SenderModelCard')
)
const settingsDefaults = computed(() =>
isSender.value ? store.sendSettings : store.receiveSettings
)
const hasSettings = computed(() => {
return !!props.modelCard.settings
})
+37 -4
View File
@@ -103,11 +103,13 @@ import type { IReceiverModelCard } from '~/lib/models/card/receiver'
import { versionDetailsQuery } from '~/lib/graphql/mutationsAndQueries'
import type { VersionListItemFragment } from '~/lib/common/generated/gql/graphql'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { useSettingsTracking } from '~/lib/core/composables/trackSettings'
import { useInterval, watchOnce } from '@vueuse/core'
import { useAccountStore } from '~~/store/accounts'
import type { CardSetting } from '~/lib/models/card/setting'
const { trackEvent } = useMixpanel()
const { trackSettingsChange } = useSettingsTracking()
const app = useNuxtApp()
const accountStore = useAccountStore()
@@ -157,17 +159,48 @@ const isExpired = computed(() => {
return props.modelCard.latestVersionId !== props.modelCard.selectedVersionId
})
const handleUpdateSettings = async (settings: CardSetting[]) => {
await store.patchModel(props.modelCard.modelCardId, {
settings
})
// Buffer settings changes while the version dialog is open, then apply on close.
// This matches the send card's batch-then-save pattern (SendSettingsDialog).
let pendingSettings: CardSetting[] | undefined
let settingsWereChanged = false
let versionWasSelected = false
const handleUpdateSettings = (settings: CardSetting[]) => {
pendingSettings = settings
settingsWereChanged = true
}
watch(openVersionsDialog, async (isOpen) => {
if (isOpen) {
// Reset flags when dialog opens
pendingSettings = undefined
settingsWereChanged = false
versionWasSelected = false
return
}
// Dialog closed — apply buffered settings if they changed
// and the user didn't select a version (which triggers a fresh receive anyway)
if (settingsWereChanged && pendingSettings && !versionWasSelected) {
trackSettingsChange(
'Load Card Settings Updated',
pendingSettings,
store.receiveSettings || []
)
await store.patchModel(props.modelCard.modelCardId, {
settings: pendingSettings,
expired: true
})
}
})
// Cancels any in progress receive AND load selected version
const handleVersionSelection = async (
selectedVersion: VersionListItemFragment,
latestVersion: VersionListItemFragment
) => {
versionWasSelected = true
openVersionsDialog.value = false
void trackEvent('DUI3 Action', {
name: 'Load Card Version Change',
+3 -2
View File
@@ -8,7 +8,7 @@
>
<ModelSettings
:expandable="false"
:default-settings="(store.sendSettings as unknown as CardSetting[])"
:default-settings="(props.defaultSettings ?? store.sendSettings) as unknown as CardSetting[]"
:settings="props.settings"
@update:settings="updateSettings"
></ModelSettings>
@@ -32,6 +32,7 @@ const { trackSettingsChange } = useSettingsTracking()
const props = defineProps<{
settings?: CardSetting[]
modelCardId: string
defaultSettings?: CardSetting[]
}>()
const store = useHostAppStore()
@@ -51,7 +52,7 @@ const saveSettings = async () => {
trackSettingsChange(
'Model Card Settings Updated',
newSettings,
store.sendSettings || []
props.defaultSettings || store.sendSettings || []
)
await store.patchModel(props.modelCardId, {
+53
View File
@@ -5342,6 +5342,8 @@ export type ServerAutomateInfo = {
export type ServerConfiguration = {
__typename?: 'ServerConfiguration';
blobSizeLimitBytes: Scalars['Int']['output'];
/** Origin URL of the dashboards service */
dashboardsOrigin?: Maybe<Scalars['String']['output']>;
/** Email verification code timeout in minutes */
emailVerificationTimeoutMinutes: Scalars['Int']['output'];
/** Active server-level feature flags */
@@ -5464,6 +5466,7 @@ export enum ServerRole {
ServerAdmin = 'SERVER_ADMIN',
ServerArchivedUser = 'SERVER_ARCHIVED_USER',
ServerGuest = 'SERVER_GUEST',
ServerSupport = 'SERVER_SUPPORT',
ServerUser = 'SERVER_USER'
}
@@ -6987,6 +6990,13 @@ export type Workspace = {
plan?: Maybe<WorkspacePlan>;
/** Shows the plan prices localized for the given workspace */
planPrices?: Maybe<WorkspacePaidPlanPrices>;
/**
* Bulk project activity data for the workspace timeline widget.
* Returns versions created within a date window, grouped by project.
* First call discovers top N projects; pass the returned cursor to load older data.
* Internal API may change without notice.
*/
projectActivityTimeline?: Maybe<WorkspaceProjectActivityTimelineResult>;
projects: ProjectCollection;
/** A Workspace is marked as readOnly if its trial period is finished or a paid plan is subscribed but payment has failed */
readOnly: Scalars['Boolean']['output'];
@@ -7048,6 +7058,11 @@ export type WorkspaceIssueLabelsArgs = {
};
export type WorkspaceProjectActivityTimelineArgs = {
input: WorkspaceProjectActivityTimelineInput;
};
export type WorkspaceProjectsArgs = {
cursor?: InputMaybe<Scalars['String']['input']>;
filter?: InputMaybe<WorkspaceProjectsFilter>;
@@ -7186,6 +7201,7 @@ export enum WorkspaceFeatureName {
DomainDiscoverability = 'domainDiscoverability',
EmbedPrivateProjects = 'embedPrivateProjects',
ExclusiveMembership = 'exclusiveMembership',
Frontend3 = 'frontend3',
HideSpeckleBranding = 'hideSpeckleBranding',
Issues = 'issues',
Markup = 'markup',
@@ -7684,6 +7700,43 @@ export enum WorkspacePlans {
Unlimited = 'unlimited'
}
export type WorkspaceProjectActivityTimelineInput = {
/**
* Opaque cursor from a previous response. When provided, withProjectRoleOnly and projectLimit are ignored.
* Encodes the locked-in project set and next date boundary.
*/
cursor?: InputMaybe<Scalars['String']['input']>;
/**
* Size of each date window in days. Default: 7.
* Used for both discovery (now - N days) and pagination (cursor.before - N days).
* Can change between pages the cursor locks the project set and boundary,
* while this controls how far back from that boundary to look.
*/
dateRangeDays?: InputMaybe<Scalars['Int']['input']>;
/** Max projects to discover by updatedAt DESC. Default: 20. Ignored when cursor is provided. */
projectLimit?: InputMaybe<Scalars['Int']['input']>;
/**
* Only return projects where the active user has an explicit project role.
* Used on first call (discovery). Ignored when cursor is provided (as projects are pre-determined then).
*/
withProjectRoleOnly?: InputMaybe<Scalars['Boolean']['input']>;
};
export type WorkspaceProjectActivityTimelineProjectGroup = {
__typename?: 'WorkspaceProjectActivityTimelineProjectGroup';
project: Project;
/** Versions within the date range, ordered by createdAt DESC. */
versions: Array<Version>;
};
export type WorkspaceProjectActivityTimelineResult = {
__typename?: 'WorkspaceProjectActivityTimelineResult';
/** Opaque cursor for loading older data. Null when no more data is available. */
cursor?: Maybe<Scalars['String']['output']>;
/** Projects with their versions, ordered by most recent version DESC. */
projectGroups: Array<WorkspaceProjectActivityTimelineProjectGroup>;
};
export type WorkspaceProjectCreateInput = {
description?: InputMaybe<Scalars['String']['input']>;
name?: InputMaybe<Scalars['String']['input']>;
+3
View File
@@ -2,6 +2,9 @@ import legacy from '@vitejs/plugin-legacy'
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devServer: {
port: 8082
},
typescript: {
shim: false,
strict: true
+1 -1
View File
@@ -514,7 +514,7 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
app.$receiveBinding?.on('setModelsExpired', (modelCardIds) => {
documentModelStore.value.models
.filter((m) => modelCardIds.includes(m.modelCardId))
.forEach((model: IReceiverModelCard) => {
.forEach((model) => {
model.error = undefined
model.expired = true
})