Merge branch 'master' into stable24
commit
b2d8ce09a5
|
@ -264,14 +264,6 @@ class Index extends Command
|
|||
return;
|
||||
}
|
||||
|
||||
// skip 'IMDB' in path
|
||||
if (false !== strpos($folderPath, 'IMDB')) {
|
||||
$this->output->writeln('Skipping folder '.$folderPath.' because of IMDB');
|
||||
$this->previousLineLength = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$nodes = $folder->getDirectoryListing();
|
||||
|
||||
foreach ($nodes as &$node) {
|
||||
|
|
|
@ -72,13 +72,21 @@ class TimelineQuery
|
|||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
$exif = [];
|
||||
if (!$basic && !empty($row['exif'])) {
|
||||
try {
|
||||
$exif = json_decode($row['exif'], true);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'fileid' => (int) $row['fileid'],
|
||||
'dayid' => (int) $row['dayid'],
|
||||
'datetaken' => $utcTs,
|
||||
'w' => (int) $row['w'],
|
||||
'h' => (int) $row['h'],
|
||||
'exif' => $basic ? [] : json_decode($row['exif'], true),
|
||||
'exif' => $exif,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
140
src/App.vue
140
src/App.vue
|
@ -8,74 +8,20 @@
|
|||
'remove-gap': removeOuterGap,
|
||||
}"
|
||||
>
|
||||
<NcAppNavigation v-if="showNavigation">
|
||||
<NcAppNavigation v-if="showNavigation" ref="nav">
|
||||
<template id="app-memories-navigation" #list>
|
||||
<NcAppNavigationItem
|
||||
:to="{ name: 'timeline' }"
|
||||
:title="t('memories', 'Timeline')"
|
||||
v-for="item in navItems"
|
||||
:key="item.name"
|
||||
:to="{ name: item.name }"
|
||||
:title="item.title"
|
||||
@click="linkClick"
|
||||
exact
|
||||
>
|
||||
<ImageMultiple slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
<NcAppNavigationItem
|
||||
:to="{ name: 'folders' }"
|
||||
:title="t('memories', 'Folders')"
|
||||
>
|
||||
<FolderIcon slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
<NcAppNavigationItem
|
||||
:to="{ name: 'favorites' }"
|
||||
:title="t('memories', 'Favorites')"
|
||||
>
|
||||
<Star slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
<NcAppNavigationItem
|
||||
:to="{ name: 'videos' }"
|
||||
:title="t('memories', 'Videos')"
|
||||
>
|
||||
<Video slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
<NcAppNavigationItem
|
||||
:to="{ name: 'albums' }"
|
||||
:title="t('memories', 'Albums')"
|
||||
v-if="showAlbums"
|
||||
>
|
||||
<AlbumIcon slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
<NcAppNavigationItem
|
||||
:to="{ name: 'people' }"
|
||||
:title="t('memories', 'People')"
|
||||
v-if="showPeople"
|
||||
>
|
||||
<PeopleIcon slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
<NcAppNavigationItem
|
||||
:to="{ name: 'archive' }"
|
||||
:title="t('memories', 'Archive')"
|
||||
>
|
||||
<ArchiveIcon slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
<NcAppNavigationItem
|
||||
:to="{ name: 'thisday' }"
|
||||
:title="t('memories', 'On this day')"
|
||||
>
|
||||
<CalendarIcon slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
<NcAppNavigationItem
|
||||
:to="{ name: 'tags' }"
|
||||
v-if="config_tagsEnabled"
|
||||
:title="t('memories', 'Tags')"
|
||||
>
|
||||
<TagsIcon slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
<NcAppNavigationItem
|
||||
:to="{ name: 'maps' }"
|
||||
v-if="config_mapsEnabled"
|
||||
:title="t('memories', 'Maps')"
|
||||
>
|
||||
<MapIcon slot="icon" :size="20" />
|
||||
<component :is="item.icon" slot="icon" :size="20" />
|
||||
</NcAppNavigationItem>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<NcAppNavigationSettings :title="t('memories', 'Settings')">
|
||||
<Settings />
|
||||
|
@ -102,6 +48,7 @@ import {
|
|||
} from "@nextcloud/vue";
|
||||
import { generateUrl } from "@nextcloud/router";
|
||||
import { getCurrentUser } from "@nextcloud/auth";
|
||||
import { translate as t } from "@nextcloud/l10n";
|
||||
|
||||
import Timeline from "./components/Timeline.vue";
|
||||
import Settings from "./components/Settings.vue";
|
||||
|
@ -150,6 +97,65 @@ export default class App extends Mixins(GlobalMixin, UserConfig) {
|
|||
|
||||
private metadataComponent!: Metadata;
|
||||
|
||||
private readonly navItemsAll = [
|
||||
{
|
||||
name: "timeline",
|
||||
icon: ImageMultiple,
|
||||
title: t("memories", "Timeline"),
|
||||
},
|
||||
{
|
||||
name: "folders",
|
||||
icon: FolderIcon,
|
||||
title: t("memories", "Folders"),
|
||||
},
|
||||
{
|
||||
name: "favorites",
|
||||
icon: Star,
|
||||
title: t("memories", "Favorites"),
|
||||
},
|
||||
{
|
||||
name: "videos",
|
||||
icon: Video,
|
||||
title: t("memories", "Videos"),
|
||||
},
|
||||
{
|
||||
name: "albums",
|
||||
icon: AlbumIcon,
|
||||
title: t("memories", "Albums"),
|
||||
if: (self: any) => self.showAlbums,
|
||||
},
|
||||
{
|
||||
name: "people",
|
||||
icon: PeopleIcon,
|
||||
title: t("memories", "People"),
|
||||
if: (self: any) => self.showPeople,
|
||||
},
|
||||
{
|
||||
name: "archive",
|
||||
icon: ArchiveIcon,
|
||||
title: t("memories", "Archive"),
|
||||
},
|
||||
{
|
||||
name: "thisday",
|
||||
icon: CalendarIcon,
|
||||
title: t("memories", "On this day"),
|
||||
},
|
||||
{
|
||||
name: "tags",
|
||||
icon: TagsIcon,
|
||||
title: t("memories", "Tags"),
|
||||
if: (self: any) => self.config_tagsEnabled,
|
||||
},
|
||||
{
|
||||
name: "maps",
|
||||
icon: MapIcon,
|
||||
title: t("memories", "Maps"),
|
||||
if: (self: any) => self.config_mapsEnabled,
|
||||
},
|
||||
];
|
||||
|
||||
private navItems = [];
|
||||
|
||||
get ncVersion() {
|
||||
const version = (<any>window.OC).config.version.split(".");
|
||||
return Number(version[0]);
|
||||
|
@ -183,6 +189,11 @@ export default class App extends Mixins(GlobalMixin, UserConfig) {
|
|||
mounted() {
|
||||
this.doRouteChecks();
|
||||
|
||||
// Populate navigation
|
||||
this.navItems = this.navItemsAll.filter(
|
||||
(item) => !item.if || item.if(this)
|
||||
);
|
||||
|
||||
// Store CSS variables modified
|
||||
const root = document.documentElement;
|
||||
const colorPrimary =
|
||||
|
@ -241,6 +252,11 @@ export default class App extends Mixins(GlobalMixin, UserConfig) {
|
|||
}
|
||||
}
|
||||
|
||||
linkClick() {
|
||||
const nav: any = this.$refs.nav;
|
||||
if (window.innerWidth <= 1024) nav?.toggleNavigation(false);
|
||||
}
|
||||
|
||||
doRouteChecks() {
|
||||
if (this.$route.name === "folder-share") {
|
||||
this.putFolderShareToken(this.$route.params.token);
|
||||
|
|
|
@ -98,82 +98,7 @@ class VideoContentSetup {
|
|||
if (!e.slide.isActive) {
|
||||
e.preventDefault();
|
||||
} else if (content.videoElement) {
|
||||
const fileid = content.data.photo.fileid;
|
||||
|
||||
// Create hls sources if enabled
|
||||
let sources: any[] = [];
|
||||
const baseUrl = generateUrl(
|
||||
`/apps/memories/api/video/transcode/${clientId}/${fileid}`
|
||||
);
|
||||
|
||||
if (!config_noTranscode) {
|
||||
sources.push({
|
||||
src: `${baseUrl}/index.m3u8`,
|
||||
type: "application/x-mpegURL",
|
||||
});
|
||||
}
|
||||
|
||||
sources.push({
|
||||
src: e.slide.data.src,
|
||||
});
|
||||
|
||||
const overrideNative = !videojs.browser.IS_SAFARI;
|
||||
content.videojs = videojs(content.videoElement, {
|
||||
fill: true,
|
||||
autoplay: true,
|
||||
controls: true,
|
||||
sources: sources,
|
||||
preload: "metadata",
|
||||
playbackRates: [0.5, 1, 1.5, 2],
|
||||
responsive: true,
|
||||
html5: {
|
||||
vhs: {
|
||||
overrideNative: overrideNative,
|
||||
withCredentials: false,
|
||||
},
|
||||
nativeAudioTracks: !overrideNative,
|
||||
nativeVideoTracks: !overrideNative,
|
||||
},
|
||||
});
|
||||
|
||||
content.videojs.on("error", () => {
|
||||
if (content.videojs.error().code === 4) {
|
||||
if (content.videojs.src().includes("m3u8")) {
|
||||
// HLS could not be streamed
|
||||
console.error("Video.js: HLS stream could not be opened.");
|
||||
content.videojs.src({
|
||||
src: e.slide.data.src,
|
||||
});
|
||||
this.updateRotation(content, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
content.videojs.qualityLevels();
|
||||
content.videojs.hlsQualitySelector({
|
||||
displayCurrentQuality: true,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
content.videojs
|
||||
.contentEl()
|
||||
.querySelectorAll("button")
|
||||
.forEach((b: HTMLButtonElement) => {
|
||||
b.classList.add("button-vue");
|
||||
});
|
||||
}, 500);
|
||||
|
||||
// Get correct orientation
|
||||
axios
|
||||
.get<any>(
|
||||
generateUrl("/apps/memories/api/image/info/{id}", {
|
||||
id: content.data.photo.fileid,
|
||||
})
|
||||
)
|
||||
.then((response) => {
|
||||
content.data.exif = response.data?.exif;
|
||||
this.updateRotation(content);
|
||||
});
|
||||
this.initVideo(content);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -189,15 +114,148 @@ class VideoContentSetup {
|
|||
pswp.options.showHideAnimationType = "fade";
|
||||
}
|
||||
|
||||
// pause video when closing
|
||||
this.pauseVideo(pswp.currSlide.content);
|
||||
// prevent more requests
|
||||
this.destroyVideo(pswp.currSlide.content);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initVideo(content: any) {
|
||||
if (!isVideoContent(content) || content.videojs) {
|
||||
return;
|
||||
}
|
||||
|
||||
content.videoElement = document.createElement("video");
|
||||
content.videoElement.className = "video-js";
|
||||
content.videoElement.setAttribute("poster", content.data.msrc);
|
||||
if (this.options.videoAttributes) {
|
||||
for (let key in this.options.videoAttributes) {
|
||||
content.videoElement.setAttribute(
|
||||
key,
|
||||
this.options.videoAttributes[key] || ""
|
||||
);
|
||||
}
|
||||
}
|
||||
content.element.appendChild(content.videoElement);
|
||||
|
||||
// Pause / play on click on mobile
|
||||
let touchAt = 0;
|
||||
content.videoElement.addEventListener("touchstart", (e) => {
|
||||
touchAt = e.timeStamp;
|
||||
});
|
||||
content.videoElement.addEventListener("touchend", (e) => {
|
||||
if (touchAt && e.timeStamp - touchAt < 200) {
|
||||
if (content.videojs.paused()) {
|
||||
content.videojs.play();
|
||||
} else {
|
||||
content.videojs.pause();
|
||||
}
|
||||
}
|
||||
touchAt = 0;
|
||||
});
|
||||
|
||||
const fileid = content.data.photo.fileid;
|
||||
|
||||
// Create hls sources if enabled
|
||||
let sources: any[] = [];
|
||||
const baseUrl = generateUrl(
|
||||
`/apps/memories/api/video/transcode/${clientId}/${fileid}`
|
||||
);
|
||||
|
||||
if (!config_noTranscode) {
|
||||
sources.push({
|
||||
src: `${baseUrl}/index.m3u8`,
|
||||
type: "application/x-mpegURL",
|
||||
});
|
||||
}
|
||||
|
||||
sources.push({
|
||||
src: content.data.src,
|
||||
});
|
||||
|
||||
const overrideNative = !videojs.browser.IS_SAFARI;
|
||||
content.videojs = videojs(content.videoElement, {
|
||||
fill: true,
|
||||
autoplay: true,
|
||||
controls: true,
|
||||
sources: sources,
|
||||
preload: "metadata",
|
||||
playbackRates: [0.5, 1, 1.5, 2],
|
||||
responsive: true,
|
||||
html5: {
|
||||
vhs: {
|
||||
overrideNative: overrideNative,
|
||||
withCredentials: false,
|
||||
},
|
||||
nativeAudioTracks: !overrideNative,
|
||||
nativeVideoTracks: !overrideNative,
|
||||
},
|
||||
});
|
||||
|
||||
content.videojs.on("error", () => {
|
||||
if (content.videojs.error().code === 4) {
|
||||
if (content.videojs.src().includes("m3u8")) {
|
||||
// HLS could not be streamed
|
||||
console.error("Video.js: HLS stream could not be opened.");
|
||||
content.videojs.src({
|
||||
src: content.data.src,
|
||||
});
|
||||
this.updateRotation(content, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
content.videojs.qualityLevels();
|
||||
content.videojs.hlsQualitySelector({
|
||||
displayCurrentQuality: true,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
content.videojs
|
||||
.contentEl()
|
||||
.querySelectorAll("button")
|
||||
.forEach((b: HTMLButtonElement) => {
|
||||
b.classList.add("button-vue");
|
||||
});
|
||||
|
||||
// iOS needs this
|
||||
content.videojs.play();
|
||||
}, 500);
|
||||
|
||||
// Get correct orientation
|
||||
axios
|
||||
.get<any>(
|
||||
generateUrl("/apps/memories/api/image/info/{id}", {
|
||||
id: content.data.photo.fileid,
|
||||
})
|
||||
)
|
||||
.then((response) => {
|
||||
content.data.exif = response.data?.exif;
|
||||
this.updateRotation(content);
|
||||
});
|
||||
}
|
||||
|
||||
destroyVideo(content: any) {
|
||||
if (isVideoContent(content) && content.videojs) {
|
||||
content.videojs.dispose();
|
||||
content.videojs = null;
|
||||
|
||||
const elem: HTMLDivElement = content.element;
|
||||
while (elem.lastElementChild) {
|
||||
elem.removeChild(elem.lastElementChild);
|
||||
}
|
||||
content.videoElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
updateRotation(content, val?: number) {
|
||||
if (!content.videojs || !content.videoElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rotation = val ?? Number(content.data.exif?.Rotation);
|
||||
const shouldRotate = content.videojs?.src().includes("m3u8");
|
||||
|
||||
if (rotation && shouldRotate) {
|
||||
let transform = `rotate(${rotation}deg)`;
|
||||
|
||||
|
@ -219,11 +277,6 @@ class VideoContentSetup {
|
|||
|
||||
onContentDestroy({ content }) {
|
||||
if (isVideoContent(content)) {
|
||||
if (content._videoPosterImg) {
|
||||
content._videoPosterImg.onload = content._videoPosterImg.onerror = null;
|
||||
content._videoPosterImg = null;
|
||||
}
|
||||
|
||||
if (content.videojs) {
|
||||
content.videojs.dispose();
|
||||
content.videojs = null;
|
||||
|
@ -271,15 +324,11 @@ class VideoContentSetup {
|
|||
}
|
||||
|
||||
onContentActivate({ content }) {
|
||||
if (isVideoContent(content) && this.options.autoplay) {
|
||||
this.playVideo(content);
|
||||
}
|
||||
this.initVideo(content);
|
||||
}
|
||||
|
||||
onContentDeactivate({ content }) {
|
||||
if (isVideoContent(content)) {
|
||||
this.pauseVideo(content);
|
||||
}
|
||||
this.destroyVideo(content);
|
||||
}
|
||||
|
||||
onContentAppend(e) {
|
||||
|
@ -319,19 +368,6 @@ class VideoContentSetup {
|
|||
content.state = "loading";
|
||||
content.type = "video"; // TODO: move this to pswp core?
|
||||
|
||||
content.videoElement = document.createElement("video");
|
||||
content.videoElement.className = "video-js";
|
||||
content.videoElement.setAttribute("controls", "true");
|
||||
|
||||
if (this.options.videoAttributes) {
|
||||
for (let key in this.options.videoAttributes) {
|
||||
content.videoElement.setAttribute(
|
||||
key,
|
||||
this.options.videoAttributes[key] || ""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
content.element = document.createElement("div");
|
||||
content.element.style.position = "absolute";
|
||||
content.element.style.left = 0;
|
||||
|
@ -339,34 +375,7 @@ class VideoContentSetup {
|
|||
content.element.style.width = "100%";
|
||||
content.element.style.height = "100%";
|
||||
|
||||
// content.videoElement.setAttribute("poster", content.data.msrc);
|
||||
// this.preloadVideoPoster(content, content.data.msrc);
|
||||
content.onLoaded();
|
||||
|
||||
content.element.appendChild(content.videoElement);
|
||||
}
|
||||
|
||||
preloadVideoPoster(content, src) {
|
||||
if (!content._videoPosterImg && src) {
|
||||
content._videoPosterImg = new Image();
|
||||
content._videoPosterImg.src = src;
|
||||
if (content._videoPosterImg.complete) {
|
||||
content.onLoaded();
|
||||
} else {
|
||||
content._videoPosterImg.onload = content._videoPosterImg.onerror =
|
||||
() => {
|
||||
content.onLoaded();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
playVideo(content) {
|
||||
content.videojs?.play();
|
||||
}
|
||||
|
||||
pauseVideo(content) {
|
||||
content.videojs?.pause();
|
||||
}
|
||||
|
||||
useContentPlaceholder(usePlaceholder, content) {
|
||||
|
|
Loading…
Reference in New Issue