Skip to content

Commit

Permalink
Feat (dui3): shared files (#2459)
Browse files Browse the repository at this point in the history
* POC implementation

* Check account and server matches to define client id

* Handle write access request and removal on collaboration

* Correct selected accounts and server urls

* Subscribe to project visibility changes

* Extract functionality to accounts

* Align receiver

* Remove unnecessary logic on readonly for receiver

* Disable request access button till FE2 ready

* feat(dui3): improves styling of no write access & prevents selection of no write access projects from publish wizard

---------

Co-authored-by: Dimitrie Stefanescu <[email protected]>
  • Loading branch information
oguzhankoral and didimitrie authored Jul 9, 2024
1 parent ac6f7aa commit f75647a
Show file tree
Hide file tree
Showing 16 changed files with 358 additions and 68 deletions.
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

0 comments on commit f75647a

Please sign in to comment.