Skip to content
Merged
38 changes: 38 additions & 0 deletions src/api/groups.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { _adaptParamsToGetQuery } from '@/api/utils.service'
import useAPI from '@/composables/useAPI'
import { ProjectModel } from '@/models/project.model'
import { PeopleGroupModel } from '@/models/invitation.model'
import { ImageModel } from '@/models/image.model'
import { BaseLocationModel, LocationModel } from '@/models/location.model'

// HIERARCHY
Expand Down Expand Up @@ -234,3 +235,40 @@ export async function postGroupLocation(
}
)
}

export function getGroupGallery(organizationCode: string, groupId: number, config = {}) {
return useAPI<PaginationResult<ImageModel>>(
`organization/${organizationCode}/people-group/${groupId}/gallery/`,
{
...config,
}
)
}

export function deleteGroupGallery(
organizationCode: string,
groupId: number,
imageId: number,
config = {}
) {
return useAPI<ImageModel>(
`organization/${organizationCode}/people-group/${groupId}/gallery/${imageId}/`,
{
...config,
method: 'DELETE',
}
)
}

export function postGroupGallery(
organizationCode: string,
groupId: number,
body: FormData,
config = {}
) {
return useAPI<ImageModel>(`organization/${organizationCode}/people-group/${groupId}/gallery/`, {
...config,
body,
method: 'POST',
})
}
23 changes: 23 additions & 0 deletions src/api/v2/group.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getGroupMember as fetchGetGroupMember,
getGroupSimilar as fetchGetGroupSimilar,
getSubGroup as fetchGetSubGroup,
getGroupGallery as fetchGroupGallery,
getGroupProjectsLocation as fetchGroupProjectsLocation,
} from '@/api/groups.service'
import useAsyncAPI from '@/composables/useAsyncAPI'
Expand Down Expand Up @@ -166,3 +167,25 @@ export const getGroupProjectsLocation = (
}
)
}

export const getGroupGallery = (
organizationCode: RefOrRaw<OrganizationModel['code']>,
groupId: RefOrRaw<GroupModel['id']>,
config = {}
) => {
const key = computed(() => `${unref(organizationCode)}::group::${unref(groupId)}::gallery`)

return useAsyncPaginationAPI(
key,
async ({ config }) => {
return fetchGroupGallery(unref(organizationCode), unref(groupId), {
...DEFAULT_CONFIG,
...config,
})
},
{
watch: onlyRefs([organizationCode, groupId]),
...config,
}
)
}
10 changes: 10 additions & 0 deletions src/app/useGroupPagesRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ export default function useGroupPagesRoutes() {
name: 'groupLocations',
component: () => import('../pages/GroupPageV2/Tabs/Locations/GroupLocationsTab.vue'),
},
{
path: 'gallery',
name: 'groupGallery',
component: () => import('../pages/GroupPageV2/Tabs/Gallery/GroupGalleryTab.vue'),
},
// retro compat
{
path: 'Edit',
Expand All @@ -69,6 +74,11 @@ export default function useGroupPagesRoutes() {
name: 'groupProjectsEdit',
component: () => import('../pages/GroupPageV2/Tabs/Projects/GroupProjectsEditTab.vue'),
},
{
path: 'gallery/edit',
name: 'groupGalleryEdit',
component: () => import('../pages/GroupPageV2/Tabs/Gallery/GroupGalleryTab.vue'),
},
],
props: true,
},
Expand Down
31 changes: 31 additions & 0 deletions src/components/base/gallery/GalleryDeleteModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<template>
<ConfirmModal
:title="$t('gallery.delete')"
:asyncing="loading"
@close="$emit('close')"
@submit="$emit('submit')"
>
<div class="gallery-container">
<span class="m-auto">{{ $t('gallery.confirm-delete') }}</span>
<GalleryItem :image="image" class="pointer-events-none" size="full" />
</div>
</ConfirmModal>
</template>

<script setup lang="ts">
import GalleryItem from '@/components/base/gallery/GalleryItem.vue'
import ConfirmModal from '@/components/base/modal/ConfirmModal.vue'
import { ImageModel } from '@/models/image.model'

defineProps<{ image: ImageModel; loading?: boolean }>()
defineEmits(['close', 'submit'])
</script>

<style lang="scss">
.gallery-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
</style>
197 changes: 197 additions & 0 deletions src/components/base/gallery/GalleryDrawer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
<template>
<BaseDrawer
:is-opened="isOpened"
:title="$t('gallery.title')"
class="full transparent"
no-footer
@close="$emit('close')"
>
<template #header_clear>
<span class="gallery-info">{{ selectedIndex + 1 }} / {{ pagination.count.value }}</span>
</template>
<FetchLoader :status="status" class-loading="loader-text" class-error="loader-text">
<div class="gallery-switch">
<div class="gallery-button">
<LpiButton
v-show="canPrev"
class="skeletons-background"
btn-icon="ArrowLeft"
:aria-label="$t('pagination.previous')"
:disabled="!canPrev"
@click="prev"
/>
</div>
<div class="container">
<Transition appear :name="transition">
<GalleryItem
:key="localindex"
:image="imageSelected"
class="gallery-item"
size="original"
/>
</Transition>
</div>
<div class="gallery-button">
<LpiButton
v-show="canNext"
class="skeletons-background"
btn-icon="ArrowRight"
:aria-label="$t('pagination.next')"
:disabled="!canNext"
@click="next"
/>
</div>
</div>
</FetchLoader>
</BaseDrawer>
</template>

<script setup lang="ts">
import LpiButton from '@/components/base/button/LpiButton.vue'
import GalleryItem from '@/components/base/gallery/GalleryItem.vue'
import { DEFAULT_IMAGE_PATATOID } from '@/composables/usePatatoids'
import { urlToImageModel } from '@/functs/imageSizesUtils'
import { ImageModel } from '@/models/image.model'
import { AsyncDataRequestStatus } from 'nuxt/app'
import { usePublicURL } from '@/composables/usePublic'

const props = withDefaults(
defineProps<{
isOpened: boolean
images?: ImageModel[]
pagination: Pagination
selected?: ImageModel
status?: AsyncDataRequestStatus
}>(),
{
status: 'success',
images: () => [],
selected: null,
}
)

defineEmits(['close'])

const localindex = ref(null)
const transition = ref('')

watch(
() => props.isOpened,
() => {
if (!props.isOpened) {
localindex.value = false
} else {
const selected = toRaw(props.selected)
localindex.value = props.images.findIndex((el) => el === selected)
}
}
)

// if error can't be charged
const DEFAULT_IMAGE = urlToImageModel(usePublicURL(DEFAULT_IMAGE_PATATOID))
const imageSelected = computed(() => props.images[localindex.value] ?? DEFAULT_IMAGE)
const selectedIndex = computed(() => props.pagination.offset.value + localindex.value)

// this change the "transition" postion
// if we press `previous` we go to left
// otherwise to right
watch(
selectedIndex,
(newIndex, oldIndex) => {
if (newIndex < oldIndex) {
transition.value = 'slide-left'
} else {
transition.value = 'slide-right'
}
},
{ flush: 'pre', immediate: true }
)

const canPrev = computed(() => selectedIndex.value !== 0)
const prev = () => {
if (localindex.value <= 0) {
props.pagination.prev()
localindex.value = props.pagination.limit.value - 1
} else {
localindex.value -= 1
}
}
const canNext = computed(() => selectedIndex.value + 1 < props.pagination.count.value)
const next = () => {
if (localindex.value + 1 >= props.pagination.limit.value) {
props.pagination.next()
localindex.value = 0
} else {
localindex.value += 1
}
}
</script>

<style lang="scss" scoped>
.gallery-switch {
display: grid;
grid-template-columns: pxToRem(40px) 1fr pxToRem(40px);
margin: 0 auto;
width: 100%;
height: calc(100% - 64px);
}

.gallery-info {
color: white;
font-weight: bold;
padding: 0 1rem;
font-style: italic;
font-size: small;
opacity: 0.4;
}

.gallery-button {
margin: 0 auto;
display: flex;
justify-content: center;
align-items: center;
}

.gallery-item {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
}

.slide-right-leave-active,
.slide-right-enter-active,
.slide-left-leave-active,
.slide-left-enter-active {
transition: transform 0.2s;
transform: translateX(0);
}

.slide-left-enter-from,
.slide-right-leave-to {
transform: translateX(-100vw);
}

.slide-left-leave-to,
.slide-right-enter-from {
transform: translateX(100vw);
}

.container {
position: relative;
}
</style>

<style lang="scss">
.loader-text {
color: white;

.loading {
opacity: 0.9;
}

svg {
fill: white;
}
}
</style>
Loading