Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 93 additions & 87 deletions packages/web-app-preview/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,20 @@
<div
v-else
ref="preview"
class="flex size-full"
class="!flex size-full"
tabindex="-1"
@keydown.left="goToPrev"
@keydown.right="goToNext"
@keydown.down="goToNext"
@keydown.up="goToPrev"
>
<photo-roll
v-if="photoRollEnabled"
class="bg-role-surface-container w-1/5 hidden md:block"
:items="photoRollFiles"
:active-index="activeIndex"
@select="onSelectPhotoRollItem"
/>
<div
class="stage size-full flex flex-col text-center"
:class="{ lightbox: isFullScreenModeActivated }"
Expand Down Expand Up @@ -57,6 +66,7 @@
:show-image-controls="activeMediaFileCached?.isImage && !activeMediaFileCached?.isError"
:show-delete-button="isDeleteButtonVisible"
:current-image-rotation="currentImageRotation"
:photo-roll-enabled="photoRollEnabled"
@set-rotation-right="imageRotateRight"
@set-rotation-left="imageRotateLeft"
@set-zoom="imageZoom"
Expand All @@ -66,6 +76,7 @@
@toggle-previous="goToPrev"
@toggle-next="goToNext"
@delete-resource="$emit('delete:resource')"
@toggle-photo-roll="photoRollEnabled = !photoRollEnabled"
/>
</div>
</div>
Expand All @@ -78,9 +89,10 @@ import {
unref,
PropType,
nextTick,
getCurrentInstance,
watch,
Ref
Ref,
onBeforeUnmount,
onMounted
} from 'vue'
import omit from 'lodash-es/omit'
import { IncomingShareResource, Resource } from '@opencloud-eu/web-client'
Expand Down Expand Up @@ -109,6 +121,7 @@ import MediaControls from './components/MediaControls.vue'
import MediaAudio from './components/Sources/MediaAudio.vue'
import MediaImage from './components/Sources/MediaImage.vue'
import MediaVideo from './components/Sources/MediaVideo.vue'
import PhotoRoll from './components/PhotoRoll.vue'
import { CachedFile } from './helpers/types'
import {
useFileTypes,
Expand All @@ -120,15 +133,15 @@ import { mimeTypes } from './mimeTypes'
import { RouteLocationRaw } from 'vue-router'

export const appId = 'preview'
const PRELOAD_COUNT = 5

export default defineComponent({
name: 'Preview',
components: {
MediaControls,
MediaAudio,
MediaImage,
MediaVideo
MediaVideo,
PhotoRoll
},
props: {
activeFiles: { type: Object as PropType<Resource[]>, required: true },
Expand Down Expand Up @@ -164,6 +177,7 @@ export default defineComponent({
const cachedFiles = ref<Record<string, CachedFile>>({})
const folderLoaded = ref(false)
const isAutoPlayEnabled = ref(true)
const photoRollEnabled = ref(true)
const preview = ref<HTMLElement>()
const keyBindings: string[] = []

Expand All @@ -184,6 +198,18 @@ export default defineComponent({
})
})

const photoRollFiles = computed(() => {
const files = Object.values(unref(cachedFiles))
const filteredIds = unref(filteredFiles).map((f) => f.id)
const idToIndex = new Map(filteredIds.map((id, idx) => [id, idx]))

return files.sort((a, b) => {
const indexA = idToIndex.get(a.id) ?? -1
const indexB = idToIndex.get(b.id) ?? -1
return indexA - indexB
})
})

const sortBy = computed(() => {
if (!unref(contextRouteQuery)) {
return 'name'
Expand Down Expand Up @@ -240,7 +266,7 @@ export default defineComponent({
const cachedFile: CachedFile = {
id: file.id,
name: file.name,
url: undefined,
url: ref<string | undefined>(undefined),
ext: file.extension,
mimeType: file.mimeType,
isVideo: isFileTypeVideo(file),
Expand All @@ -253,7 +279,7 @@ export default defineComponent({

try {
if (cachedFile.isImage) {
cachedFile.url = await previewService.loadPreview(
cachedFile.url.value = await previewService.loadPreview(
{
space: unref(space),
resource: file,
Expand All @@ -265,7 +291,7 @@ export default defineComponent({
)
return
}
cachedFile.url = await props.getUrlForResource(unref(space), file)
cachedFile.url.value = await props.getUrlForResource(unref(space), file)
} catch (e) {
console.error(e)
cachedFile.isError.value = true
Expand Down Expand Up @@ -318,7 +344,27 @@ export default defineComponent({
} as RouteLocationRaw)
}

const instance = getCurrentInstance()
const preloadImages = () => {
for (const file of unref(filteredFiles)) {
loadFileIntoCache(file)
}
}

const setActiveFile = () => {
for (let i = 0; i < unref(filteredFiles).length; i++) {
const filterAttr = isLocationSharesActive(router, 'files-shares-with-me')
? 'remoteItemId'
: 'fileId'

// match the given file id with the filtered files to get the current index
if (unref(filteredFiles)[i][filterAttr] === unref(fileId)) {
activeIndex.value = i
return
}

activeIndex.value = 0
}
}

watch(
() => props.currentFileContext,
Expand All @@ -332,7 +378,7 @@ export default defineComponent({
folderLoaded.value = true
}

;(instance.proxy as any).setActiveFile()
setActiveFile()
},
{ immediate: true }
)
Expand Down Expand Up @@ -377,6 +423,38 @@ export default defineComponent({
{ immediate: true }
)

const onSelectPhotoRollItem = (index: number) => {
activeIndex.value = index
updateLocalHistory()
}

onMounted(() => {
// keep a local history for this component
window.addEventListener('popstate', setActiveFile)
emit('register:onDeleteResourceCallback', onDeleteResourceCallback)
keyBindings.push(
bindKeyAction({ modifier: Modifier.Ctrl, primary: Key.Backspace }, () =>
emit('delete:resource', unref(activeFilteredFile))
)
)
keyBindings.push(
bindKeyAction({ primary: Key.Delete }, () =>
emit('delete:resource', unref(activeFilteredFile))
)
)
})

onBeforeUnmount(() => {
window.removeEventListener('popstate', setActiveFile)
keyBindings.forEach((keyBindingId) => {
removeKeyAction(keyBindingId)
})

Object.values(unref(cachedFiles)).forEach((cachedFile) => {
props.revokeUrl(unref(cachedFile.url))
})
})

return {
...useImageControls(),
...useFullScreenMode(),
Expand All @@ -386,7 +464,8 @@ export default defineComponent({
activeMediaFileCached,
cachedFiles,
filteredFiles,
updateLocalHistory,
photoRollEnabled,
onSelectPhotoRollItem,
isAutoPlayEnabled,
preview,
isFileTypeImage,
Expand All @@ -395,10 +474,9 @@ export default defineComponent({
onDeleteResourceCallback,
goToNext,
goToPrev,
keyBindings,
bindKeyAction,
removeKeyAction,
isDeleteButtonVisible
isDeleteButtonVisible,
photoRollFiles,
preloadImages
}
},

Expand All @@ -415,78 +493,6 @@ export default defineComponent({

this.currentImageRotation = 0
}
},

mounted() {
// keep a local history for this component
window.addEventListener('popstate', this.handleLocalHistoryEvent)
this.$emit('register:onDeleteResourceCallback', this.onDeleteResourceCallback)
this.keyBindings.push(
this.bindKeyAction({ modifier: Modifier.Ctrl, primary: Key.Backspace }, () =>
this.$emit('delete:resource', this.activeFilteredFile)
)
)
this.keyBindings.push(
this.bindKeyAction({ primary: Key.Delete }, () =>
this.$emit('delete:resource', this.activeFilteredFile)
)
)
},

beforeUnmount() {
window.removeEventListener('popstate', this.handleLocalHistoryEvent)
this.keyBindings.forEach((keyBindingId) => {
this.removeKeyAction(keyBindingId)
})

Object.values(this.cachedFiles).forEach((cachedFile) => {
this.revokeUrl(cachedFile.url)
})
},

methods: {
setActiveFile() {
for (let i = 0; i < this.filteredFiles.length; i++) {
const filterAttr = isLocationSharesActive(this.$router, 'files-shares-with-me')
? 'remoteItemId'
: 'fileId'

// match the given file id with the filtered files to get the current index
if (this.filteredFiles[i][filterAttr] === this.fileId) {
this.activeIndex = i
return
}

this.activeIndex = 0
}
},
// react to PopStateEvent ()
handleLocalHistoryEvent() {
this.setActiveFile()
},
preloadImages() {
const preloadFile = (preloadFileIndex: number) => {
const cycleIndex =
(((this.activeIndex + preloadFileIndex) % this.filteredFiles.length) +
this.filteredFiles.length) %
this.filteredFiles.length

const file = this.filteredFiles[cycleIndex]
this.loadFileIntoCache(file)
}

for (let followingFileIndex = 1; followingFileIndex <= PRELOAD_COUNT; followingFileIndex++) {
preloadFile(followingFileIndex)
}

for (
let previousFileIndex = -1;
previousFileIndex >= PRELOAD_COUNT * -1;
previousFileIndex--
) {
preloadFile(previousFileIndex)
}
}
}
})
</script>
25 changes: 24 additions & 1 deletion packages/web-app-preview/src/components/MediaControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@
>
<oc-icon size="large" name="arrow-drop-right" />
</oc-button>
<oc-button
v-oc-tooltip="togglePhotoRollDescription"
class="raw-hover-surface p-1 hidden md:flex"
data-testid="toggle-photo-roll"
appearance="raw"
:aria-label="togglePhotoRollDescription"
@click="$emit('togglePhotoRoll')"
>
<oc-icon name="side-bar" :fill-type="photoRollEnabled ? 'fill' : 'line'" />
</oc-button>
<div class="flex">
<oc-button
v-oc-tooltip="
Expand Down Expand Up @@ -146,6 +156,10 @@ export default defineComponent({
currentImageRotation: {
type: Number,
default: 0
},
photoRollEnabled: {
type: Boolean,
default: true
}
},
emits: [
Expand All @@ -157,7 +171,8 @@ export default defineComponent({
'toggleNext',
'togglePrevious',
'resetImage',
'deleteResource'
'deleteResource',
'togglePhotoRoll'
],
setup(props) {
const { $gettext } = useGettext()
Expand All @@ -181,10 +196,18 @@ export default defineComponent({
})
})

const togglePhotoRollDescription = computed(() => {
if (props.photoRollEnabled) {
return $gettext('Hide photo roll')
}
return $gettext('Show photo roll')
})

return {
screenreaderFileCount,
ariaHiddenFileCount,
resourceDeleteDescription,
togglePhotoRollDescription,
enterFullScreenDescription: $gettext('Enter full screen mode'),
exitFullScreenDescription: $gettext('Exit full screen mode'),
imageShrinkDescription: $gettext('Shrink the image (⇧ + Mouse wheel)'),
Expand Down
Loading