Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat (dui3): shared files #2459

Merged
140 changes: 118 additions & 22 deletions packages/dui3/components/common/ProjectModelGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,33 @@
v-if="projectDetails && !projectError"
class="p-2 bg-foundation dark:bg-neutral-700/10 rounded-md shadow"
>
<!-- <div
v-if="isProjectReadOnly"
class="px-2 py-1 mb-1 flex w-full items-center text-xs text-foreground-2 justify-between bg-white rounded-md transition group shadow"
>
<div v-if="writeAccessRequested">Write access request is pending...</div>
<div v-else class="flex w-full items-center justify-between">
You do not have write access to this project.
TODO: Enable later when FE2 is ready for accepting/denying requested accesses
<button
v-if="isProjectReadOnly"
v-tippy="'Request Write Access'"
class="hover:text-primary"
>
<LockClosedIcon
class="w-4"
@click.stop="
requestWriteAccess(),
trackEvent(
'DUI3 Action',
{ name: 'Request Write Access' },
projectAccount?.accountInfo.id
)
"
/>
</button>
</div>
</div> -->
<button
class="flex w-full items-center text-foreground-2 justify-between hover:bg-blue-500/10 rounded-md transition group"
@click="showModels = !showModels"
Expand All @@ -16,8 +43,14 @@
</div>
</div>

<div class="rounded-md px-2 flex items-center space-x-2 justify-end">
<button v-tippy="'Open project in browser'" class="hover:text-primary">
<div class="">
<button
v-tippy="'Open project in browser'"
class="hover:text-primary flex items-center space-x-2 p-2"
>
<div class="text-xs text-left truncate select-none">
{{ projectDetails.role ? projectDetails.role.split(':')[1] : '' }}
</div>
<ArrowTopRightOnSquareIcon
class="w-4"
@click.stop="
Expand All @@ -29,12 +62,13 @@
</div>
</button>

<div v-show="showModels" class="space-y-4 mt-3 pb-2">
<div v-show="showModels" class="space-y-4 mt-3 pb-1">
<ModelSender
v-for="model in project.senders"
:key="model.modelCardId"
:model-card="model"
:project="project"
:readonly="isProjectReadOnly"
/>
<ModelReceiver
v-for="model in project.receivers"
Expand All @@ -48,7 +82,7 @@
v-if="projectError"
class="px-2 py-4 bg-foundation dark:bg-neutral-700/10 rounded-md shadow"
>
<CommonAlert color="info" with-dismiss @dismiss="projectError = undefined">
<CommonAlert color="danger" with-dismiss @dismiss="projectError = undefined">
<template #title>
Whoops - project
<code>{{ project.projectId }}</code>
Expand All @@ -74,7 +108,9 @@ import { useHostAppStore } from '~~/store/hostApp'
import { useAccountStore } from '~~/store/accounts'
import {
projectDetailsQuery,
versionCreatedSubscription
versionCreatedSubscription,
userProjectsUpdatedSubscription,
projectUpdatedSubscription
} from '~~/lib/graphql/mutationsAndQueries'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import type { ApolloError } from '@apollo/client/errors'
Expand All @@ -88,17 +124,27 @@ const props = defineProps<{
project: ProjectModelGroup
}>()

const hasAccountMatch = !!accountStore.accounts.find(
(acc) => acc.accountInfo.id === props.project.accountId
const showModels = ref(true)
const writeAccessRequested = ref(false)

const hasAccountMatch = computed(() =>
accountStore.isAccountExistsById(props.project.accountId)
)

const projectAccount = computed(() =>
accountStore.accountWithFallback(props.project.accountId, props.project.serverUrl)
)
const { result: projectDetailsResult, onError } = useQuery(

const clientId = projectAccount.value.accountInfo.id

const {
result: projectDetailsResult,
onError,
refetch: refetchProjectDetails
} = useQuery(
projectDetailsQuery,
() => ({ projectId: props.project.projectId }),
() => ({
clientId: hasAccountMatch
? props.project.accountId
: accountStore.activeAccount.accountInfo.id
})
() => ({ clientId })
)

const projectError = ref<string>()
Expand All @@ -107,12 +153,66 @@ onError((err: ApolloError) => {
})
const projectDetails = computed(() => projectDetailsResult.value?.project)

const showModels = ref(true)
const isProjectReadOnly = computed(() => {
if (!projectDetails.value) return true

const projectUrl = computed(() => {
const acc = accountStore.accounts.find(
(acc) => acc.accountInfo.id === props.project.accountId
if (
projectDetails.value?.role === null ||
projectDetails.value?.role === 'stream:reviewer'
)
return true
return false
})

// Enable later when FE2 is ready for accepting/denying requested accesses
// const hasServerMatch = computed(() =>
// accountStore.isAccountExistsByServer(props.project.serverUrl)
// )

// const requestWriteAccess = async () => {
// if (hasServerMatch.value) {
// const { mutate } = provideApolloClient((projectAccount.value as DUIAccount).client)(
// () => useMutation(requestProjectAccess)
// )
// const res = await mutate({
// input: projectDetails.value?.id as string
// })
// writeAccessRequested.value = true
// // TODO: It throws if it has already pending request, handle it!
// console.log(res)
// }
// }

const { onResult: userProjectsUpdated } = useSubscription(
userProjectsUpdatedSubscription,
() => ({}),
() => ({ clientId })
)

const { onResult: projectUpdated } = useSubscription(
projectUpdatedSubscription,
() => ({ projectId: props.project.projectId }),
() => ({ clientId })
)

// to catch changes on visibility of project
projectUpdated((res) => {
// TODO: FIX needed: whenever project visibility changed from "discoverable" to "private", we can't get message if the `clientId` is not part of the team
// validated with Fabians this is a current behavior.
if (!res.data) return
projectError.value = undefined // clean error, refetch will set it if any
refetchProjectDetails()
})

// to catch changes on team of the project
userProjectsUpdated((res) => {
if (!res.data) return
refetchProjectDetails()
writeAccessRequested.value = false
})

const projectUrl = computed(() => {
const acc = accountStore.accounts.find((acc) => acc.accountInfo.id === clientId)
return `${acc?.accountInfo.serverInfo.url as string}/projects/${
props.project.projectId
}`
Expand All @@ -122,11 +222,7 @@ const projectUrl = computed(() => {
const { onResult } = useSubscription(
versionCreatedSubscription,
() => ({ projectId: props.project.projectId }),
() => ({
clientId: hasAccountMatch
? props.project.accountId
: accountStore.activeAccount.accountInfo.id
})
() => ({ clientId })
)

onResult((res) => {
Expand Down
72 changes: 52 additions & 20 deletions packages/dui3/components/model/CardBase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
<div v-if="modelData" class="relative px-2 py-2">
<div class="relative flex items-center space-x-2 min-w-0">
<div class="text-foreground-2 mt-[2px] flex items-center -space-x-2 relative">
<!-- CTA button -->
<button
v-if="!noWriteAccess"
v-tippy="buttonTooltip"
class="z-10 transition hover:scale-110 rounded-full hover:shadow-md bg-foundation text-primary"
@click.stop="$emit('manual-publish-or-load')"
Expand All @@ -21,6 +23,15 @@
</template>
</button>

<button
v-else
class="z-10 transition rounded-full hover:shadow-md bg-foundation"
>
<!-- <ExclamationCircleIcon class="w-8 text-danger" /> -->
<ArrowUpCircleIcon v-if="isSender" class="w-8 text-danger" />
<ArrowDownCircleIcon v-else class="w-8 text-danger" />
</button>

<UserAvatar
:user="modelData.author"
size="sm"
Expand Down Expand Up @@ -49,7 +60,7 @@
<div v-else class="px-1 py-1">Error loading data.</div>

<!-- Slot to allow senders or receivers to hoist their own buttons/ui -->
<div class="px-2">
<div v-if="!noWriteAccess" class="px-2">
<slot></slot>
</div>

Expand All @@ -73,34 +84,51 @@
}}
</div>
</div>

<!-- Card States: Expiry, errors, new version created, etc. -->
<slot name="states"></slot>
<div v-if="!noWriteAccess">
<slot name="states"></slot>
</div>
<div v-else>
<CommonModelNotification
:notification="{
modelCardId: modelCard.modelCardId,
dismissible: false,
level: 'danger',
text: 'You do not have write access: you cannot update this model. Contact the project owner!'
}"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { useQuery } from '@vue/apollo-composable'
import { modelDetailsQuery } from '~/lib/graphql/mutationsAndQueries'
import { CommonLoadingProgressBar } from '@speckle/ui-components'
// import { CursorArrowRaysIcon } from '@heroicons/vue/24/outline'
import { XCircleIcon } from '@heroicons/vue/20/solid'
import { ArrowUpCircleIcon, ArrowDownCircleIcon } from '@heroicons/vue/24/solid'
import type { ProjectModelGroup } from '~~/store/hostApp'
import { useHostAppStore } from '~~/store/hostApp'
import type { IModelCard } from '~~/lib/models/card'
import type { DUIAccount } from '~/store/accounts'
import { useAccountStore } from '~/store/accounts'
import type { ISenderModelCard } from 'lib/models/card/send'
import type { IReceiverModelCard } from '~/lib/models/card/receiver'
import { useMixpanel } from '~/lib/core/composables/mixpanel'

const app = useNuxtApp()
const store = useHostAppStore()
const accStore = useAccountStore()
const { trackEvent } = useMixpanel()

const props = defineProps<{
modelCard: IModelCard
project: ProjectModelGroup
}>()
const props = withDefaults(
defineProps<{
modelCard: IModelCard
project: ProjectModelGroup
readonly?: boolean
}>(),
{
readonly: false
}
)

defineEmits<{
(e: 'manual-publish-or-load'): void
Expand All @@ -114,25 +142,24 @@ const buttonTooltip = computed(() => {
: 'Load model'
})

const projectAccount = computed(() =>
accStore.accountWithFallback(props.project.accountId, props.project.serverUrl)
)

const clientId = projectAccount.value.accountInfo.id

const { result: modelResult, loading } = useQuery(
modelDetailsQuery,
() => ({
projectId: props.project.projectId,
modelId: props.modelCard.modelId
}),
() => ({ clientId: props.modelCard.accountId })
() => ({ clientId })
)

const modelData = computed(() => modelResult.value?.project.model)
const queryData = computed(() => modelResult.value?.project)

const store = useHostAppStore()
const accStore = useAccountStore()

const acc = accStore.accounts.find(
(acc) => acc.accountInfo.id === props.modelCard.accountId
) as DUIAccount

provide<IModelCard>('cardBase', props.modelCard)

const isSender = computed(() => {
Expand Down Expand Up @@ -162,13 +189,13 @@ const viewModel = () => {
// previously with DUI2, it was Stream View but actually it is "Version View" now. Also having conflict with old/new terminology.
trackEvent('DUI3 Action', { name: 'Version View' }, props.modelCard.accountId)
app.$baseBinding.openUrl(
`${acc?.accountInfo.serverInfo.url}/projects/${props.modelCard?.projectId}/models/${props.modelCard.modelId}`
`${projectAccount.value.accountInfo.serverInfo.url}/projects/${props.modelCard?.projectId}/models/${props.modelCard.modelId}`
)
}

const viewModelVersions = () => {
app.$baseBinding.openUrl(
`${acc?.accountInfo.serverInfo.url}/projects/${props.modelCard?.projectId}/models/${props.modelCard.modelId}/versions`
`${projectAccount.value.accountInfo.serverInfo.url}/projects/${props.modelCard?.projectId}/models/${props.modelCard.modelId}/versions`
)
}

Expand All @@ -183,7 +210,8 @@ defineExpose({
})

const cardBgColor = computed(() => {
if (props.modelCard.error) return 'bg-red-500/10 hover:bg-red-500/20'
if (props.modelCard.error || noWriteAccess.value)
return 'bg-red-500/10 hover:bg-red-500/20'
if (props.modelCard.expired) return 'bg-blue-500/10 hover:bg-blue-500/20'
if (
(props.modelCard as ISenderModelCard).latestCreatedVersionId ||
Expand All @@ -198,4 +226,8 @@ const cardBgColor = computed(() => {
return 'bg-orange-500/10'
return 'bg-foundation hover:bg-blue-500/10'
})

const noWriteAccess = computed(() => {
return props.readonly && isSender.value
})
</script>
Loading