Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 574063e9a9 | |||
| 168824c58b | |||
| 245d414e6d | |||
| a895edb069 |
File diff suppressed because one or more lines are too long
@@ -0,0 +1,44 @@
|
||||
name: Linting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
lint-and-build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22.14.0"
|
||||
|
||||
- name: Enable Corepack and Install Correct Yarn Version
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare yarn@$(jq -r .packageManager package.json | cut -d'@' -f2) --activate
|
||||
yarn --version
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
**/node_modules
|
||||
.yarn/cache
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install --immutable
|
||||
|
||||
- name: Run Linter
|
||||
run: yarn lint
|
||||
|
||||
- name: Run generate
|
||||
run: yarn generate
|
||||
@@ -0,0 +1,47 @@
|
||||
node_modules
|
||||
build
|
||||
dist
|
||||
dist2
|
||||
dist-*
|
||||
coverage
|
||||
.nyc_output
|
||||
packages/server/reports*
|
||||
packages/preview-service/public/**/*
|
||||
packages/objectloader/examples/browser/objectloader.web.js
|
||||
packages/viewer/example/speckleviewer.web.js
|
||||
|
||||
.output
|
||||
.nuxt
|
||||
**/nuxt-modules/**/templates/*.js
|
||||
packages/frontend-2/lib/common/generated/**/*
|
||||
packages/dui3/lib/common/generated/**/*
|
||||
packages/server/introspected-schema.graphql
|
||||
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.yarn
|
||||
|
||||
# Profiler output
|
||||
events.json
|
||||
|
||||
# Prettier doesn't understand the syntax inside the Yaml files, because of the brackets
|
||||
utils/helm/speckle-server/templates
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
.venv
|
||||
venv
|
||||
|
||||
.*.{ts,js,vue,tsx,jsx}
|
||||
**/generated/**/*
|
||||
**/generated/graphql.ts
|
||||
|
||||
storybook-static
|
||||
.tshy
|
||||
.tshy-build
|
||||
|
||||
packages/fileimport-service/ifc-dotnet/
|
||||
packages/fileimport-service/stl/
|
||||
packages/fileimport-service/obj/
|
||||
|
||||
packages/shared/html
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"endOfLine": "auto",
|
||||
"bracketSpacing": true,
|
||||
"vueIndentScriptAndStyle": false,
|
||||
"htmlWhitespaceSensitivity": "ignore",
|
||||
"printWidth": 88,
|
||||
"singleQuote": true
|
||||
}
|
||||
@@ -60,7 +60,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { DUIAccount } from '~~/store/accounts'
|
||||
import { TrashIcon } from '@heroicons/vue/24/outline'
|
||||
import { type BaseBridge } from '~/lib/bridge/base'
|
||||
import type { BaseBridge } from '~/lib/bridge/base'
|
||||
|
||||
const { $accountBinding } = useNuxtApp()
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ import { useAccountStore } from '~/store/accounts'
|
||||
import type { ApolloError } from '@apollo/client/errors'
|
||||
import { formatVersionParams } from '~/lib/common/helpers/jsonSchema'
|
||||
import { useJsonFormsChangeHandler } from '~/lib/core/composables/jsonSchema'
|
||||
import {isArray} from 'lodash-es'
|
||||
|
||||
const props = defineProps<{
|
||||
projectId: string
|
||||
|
||||
@@ -7,37 +7,40 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import type { IDirectSelectionSendFilter, ISendFilter } from 'lib/models/card/send'
|
||||
import { useHostAppStore } from '~~/store/hostApp'
|
||||
import { useSelectionStore } from '~~/store/selection'
|
||||
import { storeToRefs } from "pinia";
|
||||
import type {
|
||||
IDirectSelectionSendFilter,
|
||||
ISendFilter,
|
||||
} from "~/lib/models/card/send";
|
||||
import { useHostAppStore } from "~~/store/hostApp";
|
||||
import { useSelectionStore } from "~~/store/selection";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:filter', filter: ISendFilter): void
|
||||
}>()
|
||||
(e: "update:filter", filter: ISendFilter): void;
|
||||
}>();
|
||||
|
||||
const store = useHostAppStore()
|
||||
const { selectionFilter } = storeToRefs(store)
|
||||
const store = useHostAppStore();
|
||||
const { selectionFilter } = storeToRefs(store);
|
||||
|
||||
const selectionStore = useSelectionStore()
|
||||
const { selectionInfo } = storeToRefs(selectionStore)
|
||||
const selectionStore = useSelectionStore();
|
||||
const { selectionInfo } = storeToRefs(selectionStore);
|
||||
|
||||
defineProps<{
|
||||
filter: IDirectSelectionSendFilter
|
||||
}>()
|
||||
filter: IDirectSelectionSendFilter;
|
||||
}>();
|
||||
|
||||
watch(
|
||||
selectionInfo,
|
||||
(newValue) => {
|
||||
const filter = { ...selectionFilter.value } as IDirectSelectionSendFilter
|
||||
filter.selectedObjectIds = newValue.selectedObjectIds
|
||||
filter.summary = newValue.summary as string
|
||||
emit('update:filter', filter)
|
||||
const filter = { ...selectionFilter.value } as IDirectSelectionSendFilter;
|
||||
filter.selectedObjectIds = newValue.selectedObjectIds;
|
||||
filter.summary = newValue.summary as string;
|
||||
emit("update:filter", filter);
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
selectionStore.refreshSelectionFromHostApp()
|
||||
})
|
||||
selectionStore.refreshSelectionFromHostApp();
|
||||
});
|
||||
</script>
|
||||
|
||||
+40
-34
@@ -9,21 +9,25 @@
|
||||
<CheckCircleIcon class="w-4 stroke-green-500 text-green-500" />
|
||||
</div>
|
||||
<div v-else-if="reportItem.status === 3">
|
||||
<ExclamationTriangleIcon class="w-4 text-warning"></ExclamationTriangleIcon>
|
||||
<ExclamationTriangleIcon
|
||||
class="w-4 text-warning"
|
||||
></ExclamationTriangleIcon>
|
||||
</div>
|
||||
<div v-else>
|
||||
<ExclamationCircleIcon class="w-4 text-danger"></ExclamationCircleIcon>
|
||||
<ExclamationCircleIcon
|
||||
class="w-4 text-danger"
|
||||
></ExclamationCircleIcon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs transition truncate">
|
||||
<span v-if="reportItem.status === 1">
|
||||
{{ reportItem.sourceType?.split('.').reverse()[0] }} >
|
||||
{{ reportItem.sourceType?.split(".").reverse()[0] }} >
|
||||
</span>
|
||||
|
||||
<span>
|
||||
{{
|
||||
reportItem.resultType
|
||||
? reportItem.resultType?.split('.').reverse()[0]
|
||||
? reportItem.resultType?.split(".").reverse()[0]
|
||||
: reportItem.error?.message
|
||||
}}
|
||||
</span>
|
||||
@@ -71,65 +75,67 @@ import {
|
||||
CheckCircleIcon,
|
||||
ChevronUpIcon,
|
||||
ChevronDownIcon,
|
||||
ArrowTopRightOnSquareIcon
|
||||
} from '@heroicons/vue/24/solid'
|
||||
import type { ConversionResult } from '~/lib/conversions/conversionResult'
|
||||
import { useAccountStore } from '~/store/accounts'
|
||||
import type { IModelCard } from 'lib/models/card'
|
||||
import { useHostAppStore } from '~/store/hostApp'
|
||||
ArrowTopRightOnSquareIcon,
|
||||
} from "@heroicons/vue/24/solid";
|
||||
import type { ConversionResult } from "~/lib/conversions/conversionResult";
|
||||
import { useAccountStore } from "~/store/accounts";
|
||||
import type { IModelCard } from "~/lib/models/card";
|
||||
import { useHostAppStore } from "~/store/hostApp";
|
||||
|
||||
const app = useNuxtApp()
|
||||
const hostAppStore = useHostAppStore()
|
||||
const accStore = useAccountStore()
|
||||
const app = useNuxtApp();
|
||||
const hostAppStore = useHostAppStore();
|
||||
const accStore = useAccountStore();
|
||||
|
||||
const showDetails = ref<boolean>(false)
|
||||
const showDetails = ref<boolean>(false);
|
||||
|
||||
const props = defineProps<{
|
||||
reportItem: ConversionResult
|
||||
}>()
|
||||
reportItem: ConversionResult;
|
||||
}>();
|
||||
|
||||
const cardBase = inject('cardBase') as IModelCard
|
||||
const cardBase = inject("cardBase") as IModelCard;
|
||||
|
||||
const isSender = computed(() =>
|
||||
hostAppStore.models
|
||||
.find((m) => m.modelCardId === cardBase.modelCardId)
|
||||
?.typeDiscriminator.toLowerCase()
|
||||
.includes('sender')
|
||||
)
|
||||
.includes("sender")
|
||||
);
|
||||
|
||||
const acc = accStore.accounts.find((acc) => acc.accountInfo.id === cardBase.accountId)
|
||||
const acc = accStore.accounts.find(
|
||||
(acc) => acc.accountInfo.id === cardBase.accountId
|
||||
);
|
||||
|
||||
const details = computed(() =>
|
||||
props.reportItem.error
|
||||
? props.reportItem.error.stackTrace
|
||||
: `${props.reportItem.sourceType} > ${props.reportItem.resultType}`
|
||||
)
|
||||
);
|
||||
|
||||
const openObjectOnWeb = () => {
|
||||
// This is a POC implementation. Later we will highlight object(s) within the model. Currently it is done by 'Isolate' filter on viewer but there is no direct URL to achieve this.
|
||||
const url = `${acc?.accountInfo.serverInfo.url}/projects/${cardBase?.projectId}/models/${props.reportItem.sourceId}`
|
||||
app.$openUrl(url)
|
||||
}
|
||||
const url = `${acc?.accountInfo.serverInfo.url}/projects/${cardBase?.projectId}/models/${props.reportItem.sourceId}`;
|
||||
app.$openUrl(url);
|
||||
};
|
||||
|
||||
const highlightObject = () => {
|
||||
// sender reports highlight in source app
|
||||
if (cardBase.typeDiscriminator.toLowerCase().includes('send')) {
|
||||
app.$baseBinding.highlightObjects([props.reportItem.sourceId])
|
||||
return
|
||||
if (cardBase.typeDiscriminator.toLowerCase().includes("send")) {
|
||||
app.$baseBinding.highlightObjects([props.reportItem.sourceId]);
|
||||
return;
|
||||
}
|
||||
|
||||
// receive reports that are ok highliht in source app
|
||||
if (props.reportItem.status === 1 && props.reportItem.resultId) {
|
||||
app.$baseBinding.highlightObjects([props.reportItem.resultId])
|
||||
return
|
||||
app.$baseBinding.highlightObjects([props.reportItem.resultId]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
navigator.clipboard.writeText(text)
|
||||
}
|
||||
navigator.clipboard.writeText(text);
|
||||
};
|
||||
|
||||
const toggleDetails = () => {
|
||||
showDetails.value = !showDetails.value
|
||||
}
|
||||
showDetails.value = !showDetails.value;
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -10,10 +10,14 @@
|
||||
<FormButton
|
||||
full-width
|
||||
class="flex items-center"
|
||||
@click="$openUrl('https://app.speckle.systems/workspaces/actions/create')"
|
||||
@click="
|
||||
$openUrl(
|
||||
'https://app.speckle.systems/workspaces/actions/create'
|
||||
)
|
||||
"
|
||||
>
|
||||
<div class="min-w-0 truncate flex-grow">
|
||||
<span>{{ 'Create a workspace' }}</span>
|
||||
<span>{{ "Create a workspace" }}</span>
|
||||
</div>
|
||||
<ArrowTopRightOnSquareIcon class="w-4" />
|
||||
</FormButton>
|
||||
@@ -73,7 +77,9 @@
|
||||
/>
|
||||
<div class="flex justify-between items-center space-x-2">
|
||||
<ProjectCreateWorkspaceDialog
|
||||
v-if="selectedWorkspace && selectedWorkspace.id !== 'personalProject'"
|
||||
v-if="
|
||||
selectedWorkspace && selectedWorkspace.id !== 'personalProject'
|
||||
"
|
||||
:workspace="selectedWorkspace"
|
||||
@project:created="(result : ProjectListProjectItemFragment) => handleProjectCreated(result)"
|
||||
>
|
||||
@@ -126,143 +132,169 @@
|
||||
color="outline"
|
||||
@click="loadMore"
|
||||
>
|
||||
{{ hasReachedEnd ? 'No more projects found' : 'Load older projects' }}
|
||||
{{ hasReachedEnd ? "No more projects found" : "Load older projects" }}
|
||||
</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ChevronDownIcon, ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/outline'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { PlusIcon } from '@heroicons/vue/20/solid'
|
||||
import type { DUIAccount } from '~/store/accounts'
|
||||
import { useAccountStore } from '~/store/accounts'
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ArrowTopRightOnSquareIcon,
|
||||
} from "@heroicons/vue/24/outline";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { PlusIcon } from "@heroicons/vue/20/solid";
|
||||
import type { DUIAccount } from "~/store/accounts";
|
||||
import { useAccountStore } from "~/store/accounts";
|
||||
import {
|
||||
activeWorkspaceQuery,
|
||||
projectsListQuery,
|
||||
serverInfoQuery,
|
||||
setActiveWorkspaceMutation,
|
||||
workspacesListQuery
|
||||
} from '~/lib/graphql/mutationsAndQueries'
|
||||
import { useMutation, provideApolloClient, useQuery } from '@vue/apollo-composable'
|
||||
workspacesListQuery,
|
||||
} from "~/lib/graphql/mutationsAndQueries";
|
||||
import {
|
||||
useMutation,
|
||||
provideApolloClient,
|
||||
useQuery,
|
||||
} from "@vue/apollo-composable";
|
||||
import type {
|
||||
ProjectListProjectItemFragment,
|
||||
WorkspaceListWorkspaceItemFragment
|
||||
} from 'lib/common/generated/gql/graphql'
|
||||
import { useMixpanel } from '~/lib/core/composables/mixpanel'
|
||||
import { useConfigStore } from '~/store/config'
|
||||
WorkspaceListWorkspaceItemFragment,
|
||||
} from "~/lib/common/generated/gql/graphql";
|
||||
import { useMixpanel } from "~/lib/core/composables/mixpanel";
|
||||
import { useConfigStore } from "~/store/config";
|
||||
|
||||
const { trackEvent } = useMixpanel()
|
||||
const { $openUrl } = useNuxtApp()
|
||||
const { trackEvent } = useMixpanel();
|
||||
const { $openUrl } = useNuxtApp();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: 'next',
|
||||
e: "next",
|
||||
accountId: string,
|
||||
project: ProjectListProjectItemFragment,
|
||||
workspace?: WorkspaceListWorkspaceItemFragment // NOTE: this nullabilities will disappear whenever we are workspace only
|
||||
): void
|
||||
(e: 'search-text-update', text: string | undefined): void
|
||||
}>()
|
||||
): void;
|
||||
(e: "search-text-update", text: string | undefined): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
isSender: boolean
|
||||
showNewProject?: boolean
|
||||
isSender: boolean;
|
||||
showNewProject?: boolean;
|
||||
/**
|
||||
* For the send wizard - not allowing selecting projects we can't write to.
|
||||
*/
|
||||
disableNoWriteAccessProjects?: boolean
|
||||
disableNoWriteAccessProjects?: boolean;
|
||||
}>(),
|
||||
{
|
||||
showNewProject: true,
|
||||
disableNoWriteAccessProjects: false
|
||||
disableNoWriteAccessProjects: false,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const searchText = ref<string>()
|
||||
const newProjectName = ref<string>()
|
||||
const accountStore = useAccountStore()
|
||||
const configStore = useConfigStore()
|
||||
const { activeAccount } = storeToRefs(accountStore)
|
||||
const searchText = ref<string>();
|
||||
const newProjectName = ref<string>();
|
||||
const accountStore = useAccountStore();
|
||||
const configStore = useConfigStore();
|
||||
const { activeAccount } = storeToRefs(accountStore);
|
||||
|
||||
const accountId = computed(() => activeAccount.value.accountInfo.id)
|
||||
const accountId = computed(() => activeAccount.value.accountInfo.id);
|
||||
|
||||
watch(searchText, () => {
|
||||
newProjectName.value = searchText.value
|
||||
emit('search-text-update', searchText.value)
|
||||
})
|
||||
newProjectName.value = searchText.value;
|
||||
emit("search-text-update", searchText.value);
|
||||
});
|
||||
|
||||
// TODO: this function is never triggered!! remove or evaluate
|
||||
const selectAccount = (account: DUIAccount) => {
|
||||
refetchServerInfo() // to be able to understand workspaces enabled or not
|
||||
refetchActiveWorkspace()
|
||||
refetchWorkspaces()
|
||||
void trackEvent('DUI3 Action', { name: 'Account Select' }, account.accountInfo.id)
|
||||
}
|
||||
refetchServerInfo(); // to be able to understand workspaces enabled or not
|
||||
refetchActiveWorkspace();
|
||||
refetchWorkspaces();
|
||||
void trackEvent(
|
||||
"DUI3 Action",
|
||||
{ name: "Account Select" },
|
||||
account.accountInfo.id
|
||||
);
|
||||
};
|
||||
|
||||
const handleProjectCreated = (result: ProjectListProjectItemFragment) => {
|
||||
refetch() // Sorts the list with newly created project otherwise it will put the project at the bottom.
|
||||
emit('next', accountId.value, result)
|
||||
}
|
||||
refetch(); // Sorts the list with newly created project otherwise it will put the project at the bottom.
|
||||
emit("next", accountId.value, result);
|
||||
};
|
||||
|
||||
const { result: serverInfoResult, refetch: refetchServerInfo } = useQuery(
|
||||
serverInfoQuery,
|
||||
() => ({}),
|
||||
() => ({ clientId: accountId.value, debounce: 500, fetchPolicy: 'network-only' })
|
||||
)
|
||||
() => ({
|
||||
clientId: accountId.value,
|
||||
debounce: 500,
|
||||
fetchPolicy: "network-only",
|
||||
})
|
||||
);
|
||||
|
||||
const workspacesEnabled = computed(
|
||||
() => serverInfoResult.value?.serverInfo.workspaces.workspacesEnabled
|
||||
)
|
||||
);
|
||||
|
||||
const { result: workspacesResult, refetch: refetchWorkspaces } = useQuery(
|
||||
workspacesListQuery,
|
||||
() => ({
|
||||
limit: 100
|
||||
limit: 100,
|
||||
}),
|
||||
() => ({ clientId: accountId.value, debounce: 500, fetchPolicy: 'network-only' })
|
||||
)
|
||||
() => ({
|
||||
clientId: accountId.value,
|
||||
debounce: 500,
|
||||
fetchPolicy: "network-only",
|
||||
})
|
||||
);
|
||||
|
||||
const workspaces = computed(() => workspacesResult.value?.activeUser?.workspaces.items)
|
||||
const workspaces = computed(
|
||||
() => workspacesResult.value?.activeUser?.workspaces.items
|
||||
);
|
||||
|
||||
const { result: activeWorkspaceResult, refetch: refetchActiveWorkspace } = useQuery(
|
||||
activeWorkspaceQuery,
|
||||
() => ({}),
|
||||
() => ({ clientId: accountId.value, debounce: 500, fetchPolicy: 'network-only' })
|
||||
)
|
||||
const { result: activeWorkspaceResult, refetch: refetchActiveWorkspace } =
|
||||
useQuery(
|
||||
activeWorkspaceQuery,
|
||||
() => ({}),
|
||||
() => ({
|
||||
clientId: accountId.value,
|
||||
debounce: 500,
|
||||
fetchPolicy: "network-only",
|
||||
})
|
||||
);
|
||||
|
||||
const activeWorkspace = computed(() => {
|
||||
const userSelectedWorkspaceId = configStore.userSelectedWorkspaceId
|
||||
const userSelectedWorkspaceId = configStore.userSelectedWorkspaceId;
|
||||
if (userSelectedWorkspaceId) {
|
||||
const previouslySelectedWorkspace = workspaces.value?.find(
|
||||
(w) => w.id === userSelectedWorkspaceId
|
||||
)
|
||||
);
|
||||
if (previouslySelectedWorkspace) {
|
||||
return previouslySelectedWorkspace
|
||||
return previouslySelectedWorkspace;
|
||||
}
|
||||
}
|
||||
// fallback to activeWorkspace query result
|
||||
return activeWorkspaceResult.value?.activeUser
|
||||
?.activeWorkspace as WorkspaceListWorkspaceItemFragment
|
||||
})
|
||||
?.activeWorkspace as WorkspaceListWorkspaceItemFragment;
|
||||
});
|
||||
|
||||
const selectedWorkspace = ref<WorkspaceListWorkspaceItemFragment | undefined>(
|
||||
activeWorkspace.value
|
||||
)
|
||||
);
|
||||
|
||||
watch(
|
||||
workspaces,
|
||||
(newItems) => {
|
||||
if (newItems && newItems.length > 0) {
|
||||
selectedWorkspace.value = activeWorkspace.value ?? newItems[0]
|
||||
selectedWorkspace.value = activeWorkspace.value ?? newItems[0];
|
||||
} else {
|
||||
selectedWorkspace.value = undefined
|
||||
selectedWorkspace.value = undefined;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
);
|
||||
|
||||
const handleProjectCardClick = (project: ProjectListProjectItemFragment) => {
|
||||
if (
|
||||
@@ -270,108 +302,112 @@ const handleProjectCardClick = (project: ProjectListProjectItemFragment) => {
|
||||
? project.permissions.canPublish.authorized
|
||||
: project.permissions.canLoad.authorized
|
||||
) {
|
||||
emit('next', accountId.value, project, selectedWorkspace.value)
|
||||
emit("next", accountId.value, project, selectedWorkspace.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleWorkspaceSelected = async (
|
||||
newSelectedWorkspace: WorkspaceListWorkspaceItemFragment
|
||||
) => {
|
||||
selectedWorkspace.value = newSelectedWorkspace
|
||||
selectedWorkspace.value = newSelectedWorkspace;
|
||||
const account = computed(() => {
|
||||
return accountStore.accounts.find(
|
||||
(acc) => acc.accountInfo.id === accountId.value
|
||||
) as DUIAccount
|
||||
})
|
||||
) as DUIAccount;
|
||||
});
|
||||
const { mutate } = provideApolloClient(account.value.client)(() =>
|
||||
useMutation(setActiveWorkspaceMutation)
|
||||
)
|
||||
);
|
||||
try {
|
||||
await mutate({ slug: newSelectedWorkspace.slug })
|
||||
await mutate({ slug: newSelectedWorkspace.slug });
|
||||
} catch (error) {
|
||||
// I dont believe we should throw toast for this, but good to be critical on console
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
configStore.setUserSelectedWorkspace(newSelectedWorkspace.id)
|
||||
}
|
||||
configStore.setUserSelectedWorkspace(newSelectedWorkspace.id);
|
||||
};
|
||||
|
||||
// This is a hack for people who don't have a workspace and have personal projects only.
|
||||
const timeoutWait = ref(false)
|
||||
const timeoutWait = ref(false);
|
||||
|
||||
const filtersReady = computed(
|
||||
() => selectedWorkspace.value !== undefined || timeoutWait.value
|
||||
)
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
timeoutWait.value = true
|
||||
}, 1000)
|
||||
})
|
||||
timeoutWait.value = true;
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
const {
|
||||
result: projectsResult,
|
||||
loading,
|
||||
fetchMore,
|
||||
refetch
|
||||
refetch,
|
||||
} = useQuery(
|
||||
projectsListQuery,
|
||||
() => ({
|
||||
limit: 10, // stupid hack, increased it since we do manual filter to be able to see more project, see below TODO note, once we have `personalOnly` filter, decrease back to 10
|
||||
filter: {
|
||||
search: (searchText.value || '').trim() || null,
|
||||
search: (searchText.value || "").trim() || null,
|
||||
workspaceId:
|
||||
selectedWorkspace.value?.id === 'personalProject'
|
||||
selectedWorkspace.value?.id === "personalProject"
|
||||
? null
|
||||
: selectedWorkspace.value?.id,
|
||||
includeImplicitAccess: true,
|
||||
personalOnly: selectedWorkspace.value?.id === 'personalProject'
|
||||
}
|
||||
personalOnly: selectedWorkspace.value?.id === "personalProject",
|
||||
},
|
||||
}),
|
||||
() => ({
|
||||
enabled: filtersReady.value,
|
||||
clientId: accountId.value,
|
||||
debounce: 500,
|
||||
fetchPolicy: 'network-only'
|
||||
fetchPolicy: "network-only",
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
const projects = computed(() =>
|
||||
selectedWorkspace.value?.id === 'personalProject' // TODO: we need to replace this logic with `personalOnly` filter when it is implemented into app.speckle.systems
|
||||
selectedWorkspace.value?.id === "personalProject" // TODO: we need to replace this logic with `personalOnly` filter when it is implemented into app.speckle.systems
|
||||
? projectsResult.value?.activeUser?.projects.items.filter(
|
||||
(i) => i.workspaceId === null
|
||||
)
|
||||
: projectsResult.value?.activeUser?.projects.items
|
||||
)
|
||||
const hasReachedEnd = ref(false)
|
||||
);
|
||||
const hasReachedEnd = ref(false);
|
||||
|
||||
watch(searchText, () => {
|
||||
hasReachedEnd.value = false
|
||||
})
|
||||
hasReachedEnd.value = false;
|
||||
});
|
||||
|
||||
watch(projectsResult, (newVal) => {
|
||||
if (
|
||||
newVal &&
|
||||
newVal.activeUser &&
|
||||
newVal?.activeUser?.projects.items.length >= newVal?.activeUser?.projects.totalCount
|
||||
newVal?.activeUser?.projects.items.length >=
|
||||
newVal?.activeUser?.projects.totalCount
|
||||
) {
|
||||
hasReachedEnd.value = true
|
||||
hasReachedEnd.value = true;
|
||||
} else {
|
||||
hasReachedEnd.value = false
|
||||
hasReachedEnd.value = false;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const loadMore = () => {
|
||||
fetchMore({
|
||||
variables: { cursor: projectsResult.value?.activeUser?.projects.cursor },
|
||||
updateQuery: (previousResult, { fetchMoreResult }) => {
|
||||
if (!fetchMoreResult || fetchMoreResult.activeUser?.projects.items.length === 0) {
|
||||
hasReachedEnd.value = true
|
||||
return previousResult
|
||||
if (
|
||||
!fetchMoreResult ||
|
||||
fetchMoreResult.activeUser?.projects.items.length === 0
|
||||
) {
|
||||
hasReachedEnd.value = true;
|
||||
return previousResult;
|
||||
}
|
||||
|
||||
if (!previousResult.activeUser || !fetchMoreResult.activeUser)
|
||||
return previousResult
|
||||
return previousResult;
|
||||
|
||||
return {
|
||||
activeUser: {
|
||||
@@ -383,12 +419,12 @@ const loadMore = () => {
|
||||
totalCount: fetchMoreResult?.activeUser?.projects.totalCount,
|
||||
items: [
|
||||
...previousResult.activeUser.projects.items,
|
||||
...fetchMoreResult.activeUser.projects.items
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
...fetchMoreResult.activeUser.projects.items,
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
: item.role === 'workspace:guest'
|
||||
? 'You do not have write access on this workspace.'
|
||||
: undefined,
|
||||
disabled: !(item.readOnly || item.role === 'workspace:guest')
|
||||
disabled: !(item.readOnly || item.role === 'workspace:guest'),
|
||||
}"
|
||||
class="flex items-center"
|
||||
>
|
||||
@@ -41,45 +41,51 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useQuery } from '@vue/apollo-composable'
|
||||
import { workspacesListQuery } from '~/lib/graphql/mutationsAndQueries'
|
||||
import type { WorkspaceListWorkspaceItemFragment } from 'lib/common/generated/gql/graphql'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useAccountStore } from '~/store/accounts'
|
||||
import { ref, computed } from "vue";
|
||||
import { useQuery } from "@vue/apollo-composable";
|
||||
import { workspacesListQuery } from "~/lib/graphql/mutationsAndQueries";
|
||||
import type { WorkspaceListWorkspaceItemFragment } from "~/lib/common/generated/gql/graphql";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useAccountStore } from "~/store/accounts";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: 'update:selectedWorkspace',
|
||||
e: "update:selectedWorkspace",
|
||||
value: WorkspaceListWorkspaceItemFragment | undefined
|
||||
): void
|
||||
}>()
|
||||
): void;
|
||||
}>();
|
||||
|
||||
const accountStore = useAccountStore()
|
||||
const { activeAccount } = storeToRefs(accountStore)
|
||||
const accountId = computed(() => activeAccount.value.accountInfo.id)
|
||||
const accountStore = useAccountStore();
|
||||
const { activeAccount } = storeToRefs(accountStore);
|
||||
const accountId = computed(() => activeAccount.value.accountInfo.id);
|
||||
|
||||
const searchText = ref<string>()
|
||||
const searchText = ref<string>();
|
||||
|
||||
const { result: workspacesResult } = useQuery(
|
||||
workspacesListQuery,
|
||||
() => ({
|
||||
limit: 5,
|
||||
filter: {
|
||||
search: (searchText.value || '').trim() || null
|
||||
}
|
||||
search: (searchText.value || "").trim() || null,
|
||||
},
|
||||
}),
|
||||
() => ({ clientId: accountId.value, debounce: 500, fetchPolicy: 'network-only' })
|
||||
)
|
||||
() => ({
|
||||
clientId: accountId.value,
|
||||
debounce: 500,
|
||||
fetchPolicy: "network-only",
|
||||
})
|
||||
);
|
||||
|
||||
const workspaces = computed(() => workspacesResult.value?.activeUser?.workspaces.items)
|
||||
const selectedWorkspace = ref<WorkspaceListWorkspaceItemFragment>()
|
||||
const workspaces = computed(
|
||||
() => workspacesResult.value?.activeUser?.workspaces.items
|
||||
);
|
||||
const selectedWorkspace = ref<WorkspaceListWorkspaceItemFragment>();
|
||||
|
||||
watch(selectedWorkspace, (newVal) => {
|
||||
emit('update:selectedWorkspace', newVal)
|
||||
})
|
||||
emit("update:selectedWorkspace", newVal);
|
||||
});
|
||||
|
||||
// Utility function to check if the user cannot create a workspace
|
||||
const userCantCreateWorkspace = (item: WorkspaceListWorkspaceItemFragment) =>
|
||||
(!!item?.role && item.role === 'workspace:guest') || !!item.readOnly
|
||||
(!!item?.role && item.role === "workspace:guest") || !!item.readOnly;
|
||||
</script>
|
||||
|
||||
+53
-7
@@ -1,7 +1,19 @@
|
||||
import { omit } from 'lodash-es'
|
||||
import { baseConfigs, globals, getESMDirname } from '../../eslint.config.mjs'
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
import pluginVueA11y from 'eslint-plugin-vuejs-accessibility'
|
||||
import globals from 'globals'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { dirname } from 'path'
|
||||
import prettierConfig from 'eslint-config-prettier'
|
||||
import js from '@eslint/js'
|
||||
|
||||
/**
|
||||
* Feed in import.meta.url in your .mjs module to get the equivalent of __dirname
|
||||
* @param {string} importMetaUrl
|
||||
*/
|
||||
export const getESMDirname = (importMetaUrl) => {
|
||||
return dirname(fileURLToPath(importMetaUrl))
|
||||
}
|
||||
|
||||
const configs = await withNuxt([
|
||||
{
|
||||
@@ -62,6 +74,7 @@ const configs = await withNuxt([
|
||||
'@typescript-eslint/require-await': 'error',
|
||||
'no-undef': 'off',
|
||||
|
||||
'@typescript-eslint/no-empty-object-type': 'off', // too restrictive
|
||||
'@typescript-eslint/unified-signatures': 'off', // DX sucks in vue event definitions
|
||||
'@typescript-eslint/no-dynamic-delete': 'off', // too restrictive
|
||||
'@typescript-eslint/restrict-template-expressions': 'off', // too restrictive
|
||||
@@ -78,10 +91,7 @@ const configs = await withNuxt([
|
||||
{
|
||||
files: ['**/*.vue'],
|
||||
rules: {
|
||||
'vue/component-tags-order': [
|
||||
'error',
|
||||
{ order: ['docs', 'template', 'script', 'style'] }
|
||||
],
|
||||
'vue/block-order': ['error', { order: ['docs', 'template', 'script', 'style'] }],
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/component-name-in-template-casing': [
|
||||
@@ -116,10 +126,46 @@ const configs = await withNuxt([
|
||||
'./lib/common/generated/**/*',
|
||||
'storybook-static',
|
||||
'.nuxt/**',
|
||||
'.output/**'
|
||||
'.output/**',
|
||||
'**/dist/**',
|
||||
'**/dist-*/**',
|
||||
'**/public/**',
|
||||
'**/events.json',
|
||||
'**/generated/**/*'
|
||||
]
|
||||
},
|
||||
...baseConfigs
|
||||
{
|
||||
files: ['**/*.mjs'],
|
||||
languageOptions: {
|
||||
sourceType: 'module'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.cjs'],
|
||||
languageOptions: {
|
||||
sourceType: 'commonjs'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.{js,mjs,cjs}', '**/.*.{js,mjs,cjs}'],
|
||||
...js.configs.recommended
|
||||
},
|
||||
prettierConfig,
|
||||
{
|
||||
rules: {
|
||||
camelcase: [
|
||||
1,
|
||||
{
|
||||
properties: 'always'
|
||||
}
|
||||
],
|
||||
'no-var': 'error',
|
||||
'no-alert': 'error',
|
||||
eqeqeq: 'error',
|
||||
'prefer-const': 'warn',
|
||||
'object-shorthand': 'warn'
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
export default configs
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import { ApolloClient, gql } from '@apollo/client/core'
|
||||
import { ApolloClients } from '@vue/apollo-composable'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
import type { Account } from '~/lib/bindings/definitions/IBasicConnectorBinding'
|
||||
import { resolveClientConfig } from '~/lib/core/configs/apollo'
|
||||
|
||||
export type DUIAccount = {
|
||||
/** account info coming from the host app */
|
||||
accountInfo: Account
|
||||
/** the graphql client; a bit superflous */
|
||||
client?: ApolloClient<unknown>
|
||||
/** whether an intial serverinfo query succeeded. */
|
||||
isValid: boolean
|
||||
}
|
||||
|
||||
export type DUIAccountsState = {
|
||||
accounts: Ref<DUIAccount[]>
|
||||
validAccounts: ComputedRef<DUIAccount[]>
|
||||
refreshAccounts: () => Promise<void>
|
||||
defaultAccount: ComputedRef<DUIAccount | undefined>
|
||||
loading: Ref<boolean>
|
||||
}
|
||||
|
||||
const AccountsInjectionKey = 'DUI_ACCOUNTS_STATE'
|
||||
|
||||
/**
|
||||
* Use this composable to set up the account bindings and graphql clients at the top of the app.
|
||||
* TODO: Properly handle cases when user was not connected to the internet,
|
||||
* and then actually got connected.
|
||||
*/
|
||||
export function useAccountsSetup(): DUIAccountsState {
|
||||
const app = useNuxtApp()
|
||||
const $baseBinding = app.$baseBinding
|
||||
|
||||
const accounts = ref<DUIAccount[]>([])
|
||||
|
||||
const apolloClients = {} as Record<string, ApolloClient<unknown>>
|
||||
|
||||
// Tries to connect to the accounts and sets their is valid prop to false if fails.
|
||||
const testAccounts = async (accs: DUIAccount[]) => {
|
||||
const accountTestQuery = gql`
|
||||
query AcccountTestQuery {
|
||||
serverInfo {
|
||||
version
|
||||
name
|
||||
company
|
||||
}
|
||||
}
|
||||
`
|
||||
for (const acc of accs) {
|
||||
if (!acc.client) continue
|
||||
try {
|
||||
await acc.client.query({ query: accountTestQuery })
|
||||
acc.isValid = true
|
||||
} catch {
|
||||
// TODO: properly dispose and kill this client. It's unclear how to do it.
|
||||
acc.isValid = false
|
||||
// NOTE: we do not want to delete the client, as we might want to "refresh" in
|
||||
// case the user was not connected to the interweb.
|
||||
// acc.client.disableNetworkFetches = true
|
||||
// acc.client.stop()
|
||||
// delete acc.client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
// Matches local accounts coming from the host app to app state.
|
||||
const refreshAccounts = async () => {
|
||||
loading.value = true
|
||||
|
||||
const accs = await $baseBinding.getAccounts()
|
||||
// We create a whole new list of accounts that will replace the old list. This way we ensure we drop
|
||||
// out of scope old accounts that not exist anymore (TODO: test), and we don't need to do complex diffing.
|
||||
const newAccs = [] as DUIAccount[]
|
||||
for (const acc of accs) {
|
||||
const existing = accounts.value.find((a) => a.accountInfo.id === acc.id)
|
||||
if (existing) {
|
||||
newAccs.push(existing as DUIAccount)
|
||||
continue
|
||||
}
|
||||
|
||||
const client = new ApolloClient(
|
||||
resolveClientConfig({
|
||||
httpEndpoint: new URL('/graphql', acc.serverInfo.url).href,
|
||||
authToken: () => acc.token
|
||||
})
|
||||
)
|
||||
|
||||
apolloClients[acc.id] = client
|
||||
|
||||
newAccs.push({
|
||||
accountInfo: acc,
|
||||
client,
|
||||
isValid: true
|
||||
})
|
||||
}
|
||||
// We test accounts here so we try to prevent the app from querying/using invalid accounts.
|
||||
await testAccounts(newAccs)
|
||||
// Once we have tested the new accounts, finally set them.
|
||||
accounts.value = newAccs
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
void refreshAccounts() // Promise that we do not want to await (convention with void)
|
||||
|
||||
const defaultAccount = computed(() =>
|
||||
accounts.value.find((acc) => acc.accountInfo.isDefault)
|
||||
)
|
||||
|
||||
const validAccounts = computed(() => {
|
||||
return accounts.value.filter((a) => a.isValid)
|
||||
})
|
||||
|
||||
const accState = {
|
||||
accounts,
|
||||
defaultAccount,
|
||||
validAccounts,
|
||||
refreshAccounts,
|
||||
loading
|
||||
}
|
||||
|
||||
app.vueApp.provide(ApolloClients, apolloClients)
|
||||
provide(AccountsInjectionKey, accState)
|
||||
|
||||
return accState // as DUIAccountsState
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this composable to access the users' local accounts and their corresponding graphql client.
|
||||
*/
|
||||
export function useInjectedAccounts(): DUIAccountsState {
|
||||
const state = inject(AccountsInjectionKey) as DUIAccountsState
|
||||
return state
|
||||
}
|
||||
@@ -1,30 +1,33 @@
|
||||
import type { IBinding, IBindingSharedEvents } from 'lib/bindings/definitions/IBinding'
|
||||
import type {
|
||||
IBinding,
|
||||
IBindingSharedEvents,
|
||||
} from "~/lib/bindings/definitions/IBinding";
|
||||
|
||||
export const IAccountBindingKey = 'accountsBinding'
|
||||
export const IAccountBindingKey = "accountsBinding";
|
||||
|
||||
export interface IAccountBinding extends IBinding<IAccountBindingEvents> {
|
||||
getAccounts: () => Promise<Account[]>
|
||||
removeAccount: (accountId: string) => Promise<void>
|
||||
getAccounts: () => Promise<Account[]>;
|
||||
removeAccount: (accountId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
// An almost 1-1 mapping of what we need from the Core accounts class.
|
||||
export type Account = {
|
||||
id: string
|
||||
isDefault: boolean
|
||||
token: string
|
||||
id: string;
|
||||
isDefault: boolean;
|
||||
token: string;
|
||||
serverInfo: {
|
||||
name: string
|
||||
url: string
|
||||
frontend2: boolean
|
||||
}
|
||||
name: string;
|
||||
url: string;
|
||||
frontend2: boolean;
|
||||
};
|
||||
userInfo: {
|
||||
id: string
|
||||
avatar: string
|
||||
email: string
|
||||
name: string
|
||||
commits: { totalCount: number }
|
||||
streams: { totalCount: number }
|
||||
}
|
||||
}
|
||||
id: string;
|
||||
avatar: string;
|
||||
email: string;
|
||||
name: string;
|
||||
commits: { totalCount: number };
|
||||
streams: { totalCount: number };
|
||||
};
|
||||
};
|
||||
|
||||
export interface IAccountBindingEvents extends IBindingSharedEvents {}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import type { ConversionResult } from 'lib/conversions/conversionResult'
|
||||
import type { IModelCardSharedEvents } from '~/lib/models/card'
|
||||
import type { CardSetting } from '~/lib/models/card/setting'
|
||||
import type { ConversionResult } from "~/lib/conversions/conversionResult";
|
||||
import type { IModelCardSharedEvents } from "~/lib/models/card";
|
||||
import type { CardSetting } from "~/lib/models/card/setting";
|
||||
import type {
|
||||
IBinding,
|
||||
IBindingSharedEvents
|
||||
} from '~~/lib/bindings/definitions/IBinding'
|
||||
IBindingSharedEvents,
|
||||
} from "~~/lib/bindings/definitions/IBinding";
|
||||
|
||||
export const IReceiveBindingKey = 'receiveBinding'
|
||||
export const IReceiveBindingKey = "receiveBinding";
|
||||
|
||||
export interface IReceiveBinding extends IBinding<IReceiveBindingEvents> {
|
||||
receive: (modelCardId: string) => Promise<void>
|
||||
getReceiveSettings: () => Promise<CardSetting[]>
|
||||
cancelReceive: (modelId: string) => Promise<void>
|
||||
receive: (modelCardId: string) => Promise<void>;
|
||||
getReceiveSettings: () => Promise<CardSetting[]>;
|
||||
cancelReceive: (modelId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface IReceiveBindingEvents
|
||||
@@ -19,8 +19,8 @@ export interface IReceiveBindingEvents
|
||||
IModelCardSharedEvents {
|
||||
// See note oon timeout in bridge v2; we might not need this
|
||||
setModelReceiveResult: (args: {
|
||||
modelCardId: string
|
||||
bakedObjectIds: string[]
|
||||
conversionResults: ConversionResult[]
|
||||
}) => void
|
||||
modelCardId: string;
|
||||
bakedObjectIds: string[];
|
||||
conversionResults: ConversionResult[];
|
||||
}) => void;
|
||||
}
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import type { ISendFilter } from '~~/lib/models/card/send'
|
||||
import type { ISendFilter } from "~~/lib/models/card/send";
|
||||
import type {
|
||||
IBinding,
|
||||
IBindingSharedEvents
|
||||
} from '~~/lib/bindings/definitions/IBinding'
|
||||
import type { CardSetting } from '~/lib/models/card/setting'
|
||||
import type { IModelCardSharedEvents } from '~/lib/models/card'
|
||||
import type { ConversionResult } from 'lib/conversions/conversionResult'
|
||||
import type { CreateVersionArgs } from '~/lib/bridge/server'
|
||||
IBindingSharedEvents,
|
||||
} from "~~/lib/bindings/definitions/IBinding";
|
||||
import type { CardSetting } from "~/lib/models/card/setting";
|
||||
import type { IModelCardSharedEvents } from "~/lib/models/card";
|
||||
import type { ConversionResult } from "~/lib/conversions/conversionResult";
|
||||
import type { CreateVersionArgs } from "~/lib/bridge/server";
|
||||
|
||||
export const ISendBindingKey = 'sendBinding'
|
||||
export const ISendBindingKey = "sendBinding";
|
||||
|
||||
export interface ISendBinding extends IBinding<ISendBindingEvents> {
|
||||
getSendFilters: () => Promise<ISendFilter[]>
|
||||
getSendSettings: () => Promise<CardSetting[]>
|
||||
send: (modelId: string) => Promise<void>
|
||||
cancelSend: (modelId: string) => Promise<void>
|
||||
getSendFilters: () => Promise<ISendFilter[]>;
|
||||
getSendSettings: () => Promise<CardSetting[]>;
|
||||
send: (modelId: string) => Promise<void>;
|
||||
cancelSend: (modelId: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface ISendBindingEvents
|
||||
extends IBindingSharedEvents,
|
||||
IModelCardSharedEvents {
|
||||
refreshSendFilters: () => void
|
||||
setModelsExpired: (modelCardIds: string[]) => void
|
||||
refreshSendFilters: () => void;
|
||||
setModelsExpired: (modelCardIds: string[]) => void;
|
||||
setModelSendResult: (args: {
|
||||
modelCardId: string
|
||||
versionId: string
|
||||
sendConversionResults: ConversionResult[]
|
||||
}) => void
|
||||
modelCardId: string;
|
||||
versionId: string;
|
||||
sendConversionResults: ConversionResult[];
|
||||
}) => void;
|
||||
setIdMap: (args: {
|
||||
modelCardId: string
|
||||
idMap: Record<string, string>
|
||||
newSelectedObjectIds: string[]
|
||||
}) => void
|
||||
modelCardId: string;
|
||||
idMap: Record<string, string>;
|
||||
newSelectedObjectIds: string[];
|
||||
}) => void;
|
||||
/**
|
||||
* Use whenever want to cancel model card progress, it is used on Archicad so far since send operation blocks the UI thread.
|
||||
*/
|
||||
triggerCancel: (modelCardId: string) => void
|
||||
triggerCreateVersion: (args: CreateVersionArgs) => void
|
||||
triggerCancel: (modelCardId: string) => void;
|
||||
triggerCreateVersion: (args: CreateVersionArgs) => void;
|
||||
}
|
||||
|
||||
+39
-39
@@ -1,54 +1,54 @@
|
||||
import crs from 'crypto-random-string'
|
||||
import type { AutomationRunItemFragment } from 'lib/common/generated/gql/graphql'
|
||||
import type { ConversionResult } from 'lib/conversions/conversionResult'
|
||||
import type { CardSetting } from 'lib/models/card/setting'
|
||||
import type { IDiscriminatedObject } from '~~/lib/bindings/definitions/common'
|
||||
import { DiscriminatedObject } from '~~/lib/bindings/definitions/common'
|
||||
import crs from "crypto-random-string";
|
||||
import type { AutomationRunItemFragment } from "~/lib/common/generated/gql/graphql";
|
||||
import type { ConversionResult } from "~/lib/conversions/conversionResult";
|
||||
import type { CardSetting } from "~/lib/models/card/setting";
|
||||
import type { IDiscriminatedObject } from "~~/lib/bindings/definitions/common";
|
||||
import { DiscriminatedObject } from "~~/lib/bindings/definitions/common";
|
||||
|
||||
export interface IModelCard extends IDiscriminatedObject {
|
||||
modelCardId: string
|
||||
modelId: string
|
||||
projectId: string
|
||||
workspaceId?: string
|
||||
workspaceSlug?: string
|
||||
accountId: string
|
||||
serverUrl: string
|
||||
expired: boolean
|
||||
progress?: ModelCardProgress
|
||||
settings?: CardSetting[]
|
||||
error?: { errorMessage: string; dismissible: boolean }
|
||||
report?: ConversionResult[]
|
||||
automationRuns?: AutomationRunItemFragment[]
|
||||
modelCardId: string;
|
||||
modelId: string;
|
||||
projectId: string;
|
||||
workspaceId?: string;
|
||||
workspaceSlug?: string;
|
||||
accountId: string;
|
||||
serverUrl: string;
|
||||
expired: boolean;
|
||||
progress?: ModelCardProgress;
|
||||
settings?: CardSetting[];
|
||||
error?: { errorMessage: string; dismissible: boolean };
|
||||
report?: ConversionResult[];
|
||||
automationRuns?: AutomationRunItemFragment[];
|
||||
}
|
||||
|
||||
export class ModelCard extends DiscriminatedObject implements IModelCard {
|
||||
modelCardId: string
|
||||
modelId!: string
|
||||
projectId!: string
|
||||
workspaceId?: string
|
||||
workspaceSlug?: string
|
||||
accountId!: string
|
||||
serverUrl!: string
|
||||
expired: boolean
|
||||
progress: ModelCardProgress | undefined
|
||||
settings: CardSetting[] | undefined
|
||||
modelCardId: string;
|
||||
modelId!: string;
|
||||
projectId!: string;
|
||||
workspaceId?: string;
|
||||
workspaceSlug?: string;
|
||||
accountId!: string;
|
||||
serverUrl!: string;
|
||||
expired: boolean;
|
||||
progress: ModelCardProgress | undefined;
|
||||
settings: CardSetting[] | undefined;
|
||||
|
||||
constructor(typeDiscriminator: string) {
|
||||
super(typeDiscriminator)
|
||||
this.modelCardId = crs({ length: 20 })
|
||||
this.expired = false
|
||||
super(typeDiscriminator);
|
||||
this.modelCardId = crs({ length: 20 });
|
||||
this.expired = false;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IModelCardSharedEvents {
|
||||
setModelError: (args: { modelCardId: string; error: string }) => void
|
||||
setModelError: (args: { modelCardId: string; error: string }) => void;
|
||||
setModelProgress: (args: {
|
||||
modelCardId: string
|
||||
progress?: ModelCardProgress
|
||||
}) => void
|
||||
modelCardId: string;
|
||||
progress?: ModelCardProgress;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export type ModelCardProgress = {
|
||||
status: string
|
||||
progress?: number
|
||||
}
|
||||
status: string;
|
||||
progress?: number;
|
||||
};
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import type { ConversionResult } from 'lib/conversions/conversionResult'
|
||||
import type { ConversionResult } from "~/lib/conversions/conversionResult";
|
||||
|
||||
export type ModelCardNotification = {
|
||||
modelCardId: string
|
||||
text: string
|
||||
level: 'info' | 'danger' | 'warning' | 'success'
|
||||
modelCardId: string;
|
||||
text: string;
|
||||
level: "info" | "danger" | "warning" | "success";
|
||||
cta?: {
|
||||
name: string
|
||||
action: () => void
|
||||
}
|
||||
name: string;
|
||||
action: () => void;
|
||||
};
|
||||
/**
|
||||
* If set, will display a view report button next to cta
|
||||
*/
|
||||
report?: ConversionResult[]
|
||||
report?: ConversionResult[];
|
||||
// TODO figure out re report button
|
||||
dismissible: boolean
|
||||
timeout?: number
|
||||
}
|
||||
dismissible: boolean;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
+5
-3
@@ -55,11 +55,11 @@
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^5.0.5",
|
||||
"@graphql-codegen/client-preset": "^4.3.0",
|
||||
"@nuxt/eslint": "^0.3.13",
|
||||
"@nuxt/eslint": "^1.3.1",
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
"@parcel/watcher": "^2.5.1",
|
||||
"@types/apollo-upload-client": "^17.0.1",
|
||||
"@types/eslint": "^8.56.10",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/node": "^18",
|
||||
"@typescript-eslint/eslint-plugin": "^8.20.0",
|
||||
@@ -89,7 +89,9 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"core-js": "3.22.4",
|
||||
"core-js-compat/semver": "^7.5.4"
|
||||
"core-js-compat/semver": "^7.5.4",
|
||||
"@babel/plugin-transform-classes/globals": "13.13.0",
|
||||
"@babel/traverse/globals": "13.13.0"
|
||||
},
|
||||
"packageManager": "yarn@4.9.1"
|
||||
}
|
||||
|
||||
+2
-3
@@ -15,7 +15,6 @@ import { onError, type ErrorResponse } from '@apollo/client/link/error'
|
||||
import { getMainDefinition } from '@apollo/client/utilities'
|
||||
import { setContext } from '@apollo/client/link/context'
|
||||
import { useHostAppStore } from '~/store/hostApp'
|
||||
import type { ToastNotification } from '@speckle/ui-components'
|
||||
import { ToastNotificationType } from '@speckle/ui-components'
|
||||
|
||||
export type DUIAccount = {
|
||||
@@ -86,7 +85,7 @@ export const useAccountStore = defineStore('accountStore', () => {
|
||||
try {
|
||||
await acc.client.query({ query: accountTestQuery })
|
||||
acc.isValid = true
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// TODO: properly dispose and kill this client. It's unclear how to do it.
|
||||
acc.isValid = false
|
||||
// NOTE: we do not want to delete the client, as we might want to "refresh" in
|
||||
@@ -116,7 +115,7 @@ export const useAccountStore = defineStore('accountStore', () => {
|
||||
if (res.graphQLErrors) {
|
||||
if (
|
||||
res.graphQLErrors?.some(
|
||||
(err) => err.extensions.code === 'SSO_SESSION_MISSING_OR_EXPIRED_ERROR'
|
||||
(err) => err.extensions?.code === 'SSO_SESSION_MISSING_OR_EXPIRED_ERROR'
|
||||
)
|
||||
) {
|
||||
hostAppStore.setNotification({
|
||||
|
||||
+29
-29
@@ -1,50 +1,50 @@
|
||||
import type { ConnectorConfig } from 'lib/bindings/definitions/IConfigBinding'
|
||||
import { defineStore } from 'pinia'
|
||||
import type { ConnectorConfig } from "~/lib/bindings/definitions/IConfigBinding";
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useConfigStore = defineStore('configStore', () => {
|
||||
const { $configBinding } = useNuxtApp()
|
||||
export const useConfigStore = defineStore("configStore", () => {
|
||||
const { $configBinding } = useNuxtApp();
|
||||
|
||||
const hasConfigBindings = ref(!!$configBinding)
|
||||
const hasConfigBindings = ref(!!$configBinding);
|
||||
|
||||
const userSelectedWorkspaceId = ref<string>()
|
||||
const userSelectedWorkspaceId = ref<string>();
|
||||
|
||||
const config = ref<ConnectorConfig>({ darkTheme: true })
|
||||
const config = ref<ConnectorConfig>({ darkTheme: true });
|
||||
|
||||
const isDarkTheme = computed(() => {
|
||||
return config.value?.darkTheme
|
||||
})
|
||||
const isDevMode = ref(false)
|
||||
return config.value?.darkTheme;
|
||||
});
|
||||
const isDevMode = ref(false);
|
||||
|
||||
const toggleTheme = () => {
|
||||
config.value.darkTheme = !config.value.darkTheme
|
||||
$configBinding.updateConfig(config.value)
|
||||
}
|
||||
config.value.darkTheme = !config.value.darkTheme;
|
||||
$configBinding.updateConfig(config.value);
|
||||
};
|
||||
|
||||
const setUserSelectedWorkspace = (workspaceId: string) => {
|
||||
userSelectedWorkspaceId.value = workspaceId
|
||||
userSelectedWorkspaceId.value = workspaceId;
|
||||
try {
|
||||
$configBinding.setUserSelectedWorkspaceId(workspaceId)
|
||||
$configBinding.setUserSelectedWorkspaceId(workspaceId);
|
||||
} catch (error) {
|
||||
console.warn(error) // for the users who do not have latest version with workspace config handling
|
||||
console.warn(error); // for the users who do not have latest version with workspace config handling
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const isInitialized = ref(false)
|
||||
const isInitialized = ref(false);
|
||||
|
||||
const init = async () => {
|
||||
if (!$configBinding) return
|
||||
config.value = await $configBinding.getConfig()
|
||||
const workspacesConfig = await $configBinding.getWorkspacesConfig()
|
||||
if (!$configBinding) return;
|
||||
config.value = await $configBinding.getConfig();
|
||||
const workspacesConfig = await $configBinding.getWorkspacesConfig();
|
||||
if (workspacesConfig && workspacesConfig.userSelectedWorkspaceId) {
|
||||
userSelectedWorkspaceId.value = workspacesConfig.userSelectedWorkspaceId
|
||||
userSelectedWorkspaceId.value = workspacesConfig.userSelectedWorkspaceId;
|
||||
}
|
||||
}
|
||||
init()
|
||||
};
|
||||
init();
|
||||
|
||||
const getIsDevMode = async () =>
|
||||
(isDevMode.value = await $configBinding.getIsDevMode())
|
||||
(isDevMode.value = await $configBinding.getIsDevMode());
|
||||
|
||||
void getIsDevMode()
|
||||
void getIsDevMode();
|
||||
|
||||
return {
|
||||
isInitialized,
|
||||
@@ -54,6 +54,6 @@ export const useConfigStore = defineStore('configStore', () => {
|
||||
isDevMode,
|
||||
userSelectedWorkspaceId,
|
||||
toggleTheme,
|
||||
setUserSelectedWorkspace
|
||||
}
|
||||
})
|
||||
setUserSelectedWorkspace,
|
||||
};
|
||||
});
|
||||
|
||||
+335
-311
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user