adds video support
This commit is contained in:
Binary file not shown.
91
db/migrations/1742128778_updated_trails.go
Normal file
91
db/migrations/1742128778_updated_trails.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
"github.com/pocketbase/pocketbase/models/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("e864strfxo14pm4")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
edit_photos := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "aqbpyawe",
|
||||||
|
"name": "photos",
|
||||||
|
"type": "file",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"mimeTypes": [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/vnd.mozilla.apng",
|
||||||
|
"image/png",
|
||||||
|
"image/webp",
|
||||||
|
"image/svg+xml",
|
||||||
|
"image/heic",
|
||||||
|
"video/mp4"
|
||||||
|
],
|
||||||
|
"thumbs": [],
|
||||||
|
"maxSelect": 99,
|
||||||
|
"maxSize": 20971520,
|
||||||
|
"protected": false
|
||||||
|
}
|
||||||
|
}`), edit_photos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(edit_photos)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
}, func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("e864strfxo14pm4")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
edit_photos := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "aqbpyawe",
|
||||||
|
"name": "photos",
|
||||||
|
"type": "file",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"mimeTypes": [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/vnd.mozilla.apng",
|
||||||
|
"image/png",
|
||||||
|
"image/webp",
|
||||||
|
"image/svg+xml",
|
||||||
|
"image/heic"
|
||||||
|
],
|
||||||
|
"thumbs": [],
|
||||||
|
"maxSelect": 99,
|
||||||
|
"maxSize": 20971520,
|
||||||
|
"protected": false
|
||||||
|
}
|
||||||
|
}`), edit_photos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(edit_photos)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
94
db/migrations/1742138390_updated_trails.go
Normal file
94
db/migrations/1742138390_updated_trails.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
"github.com/pocketbase/pocketbase/models/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("e864strfxo14pm4")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
edit_photos := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "aqbpyawe",
|
||||||
|
"name": "photos",
|
||||||
|
"type": "file",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"mimeTypes": [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/vnd.mozilla.apng",
|
||||||
|
"image/png",
|
||||||
|
"image/webp",
|
||||||
|
"image/svg+xml",
|
||||||
|
"image/heic",
|
||||||
|
"video/mp4",
|
||||||
|
"video/webm",
|
||||||
|
"video/ogg"
|
||||||
|
],
|
||||||
|
"thumbs": [],
|
||||||
|
"maxSelect": 99,
|
||||||
|
"maxSize": 20971520,
|
||||||
|
"protected": false
|
||||||
|
}
|
||||||
|
}`), edit_photos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(edit_photos)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
}, func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("e864strfxo14pm4")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
edit_photos := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "aqbpyawe",
|
||||||
|
"name": "photos",
|
||||||
|
"type": "file",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"mimeTypes": [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/vnd.mozilla.apng",
|
||||||
|
"image/png",
|
||||||
|
"image/webp",
|
||||||
|
"image/svg+xml",
|
||||||
|
"image/heic",
|
||||||
|
"video/mp4"
|
||||||
|
],
|
||||||
|
"thumbs": [],
|
||||||
|
"maxSelect": 99,
|
||||||
|
"maxSize": 20971520,
|
||||||
|
"protected": false
|
||||||
|
}
|
||||||
|
}`), edit_photos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(edit_photos)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
93
db/migrations/1742140105_updated_summit_logs.go
Normal file
93
db/migrations/1742140105_updated_summit_logs.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
"github.com/pocketbase/pocketbase/models/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
edit_photos := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "ixnksbkt",
|
||||||
|
"name": "photos",
|
||||||
|
"type": "file",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"mimeTypes": [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/vnd.mozilla.apng",
|
||||||
|
"image/webp",
|
||||||
|
"image/svg+xml",
|
||||||
|
"image/heic",
|
||||||
|
"video/ogg",
|
||||||
|
"video/mp4",
|
||||||
|
"video/webm"
|
||||||
|
],
|
||||||
|
"thumbs": [],
|
||||||
|
"maxSelect": 99,
|
||||||
|
"maxSize": 20971520,
|
||||||
|
"protected": false
|
||||||
|
}
|
||||||
|
}`), edit_photos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(edit_photos)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
}, func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("dd2l9a4vxpy2ni8")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
edit_photos := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "ixnksbkt",
|
||||||
|
"name": "photos",
|
||||||
|
"type": "file",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"mimeTypes": [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/vnd.mozilla.apng",
|
||||||
|
"image/webp",
|
||||||
|
"image/svg+xml",
|
||||||
|
"image/heic"
|
||||||
|
],
|
||||||
|
"thumbs": [],
|
||||||
|
"maxSelect": 99,
|
||||||
|
"maxSize": 20971520,
|
||||||
|
"protected": false
|
||||||
|
}
|
||||||
|
}`), edit_photos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(edit_photos)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
91
db/migrations/1742143702_updated_waypoints.go
Normal file
91
db/migrations/1742143702_updated_waypoints.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pocketbase/dbx"
|
||||||
|
"github.com/pocketbase/pocketbase/daos"
|
||||||
|
m "github.com/pocketbase/pocketbase/migrations"
|
||||||
|
"github.com/pocketbase/pocketbase/models/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m.Register(func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("goeo2ubp103rzp9")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
edit_photos := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "tfhs3juh",
|
||||||
|
"name": "photos",
|
||||||
|
"type": "file",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"mimeTypes": [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/vnd.mozilla.apng",
|
||||||
|
"image/webp",
|
||||||
|
"image/svg+xml",
|
||||||
|
"video/ogg",
|
||||||
|
"video/mp4",
|
||||||
|
"video/webm"
|
||||||
|
],
|
||||||
|
"thumbs": [],
|
||||||
|
"maxSelect": 99,
|
||||||
|
"maxSize": 20971520,
|
||||||
|
"protected": false
|
||||||
|
}
|
||||||
|
}`), edit_photos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(edit_photos)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
}, func(db dbx.Builder) error {
|
||||||
|
dao := daos.New(db);
|
||||||
|
|
||||||
|
collection, err := dao.FindCollectionByNameOrId("goeo2ubp103rzp9")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update
|
||||||
|
edit_photos := &schema.SchemaField{}
|
||||||
|
if err := json.Unmarshal([]byte(`{
|
||||||
|
"system": false,
|
||||||
|
"id": "tfhs3juh",
|
||||||
|
"name": "photos",
|
||||||
|
"type": "file",
|
||||||
|
"required": false,
|
||||||
|
"presentable": false,
|
||||||
|
"unique": false,
|
||||||
|
"options": {
|
||||||
|
"mimeTypes": [
|
||||||
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/vnd.mozilla.apng",
|
||||||
|
"image/webp",
|
||||||
|
"image/svg+xml"
|
||||||
|
],
|
||||||
|
"thumbs": [],
|
||||||
|
"maxSelect": 99,
|
||||||
|
"maxSize": 20971520,
|
||||||
|
"protected": false
|
||||||
|
}
|
||||||
|
}`), edit_photos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
collection.Schema.AddField(edit_photos)
|
||||||
|
|
||||||
|
return dao.SaveCollection(collection)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { isVideoURL } from "$lib/util/file_util";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -38,6 +39,10 @@
|
|||||||
class="group relative h-32 w-32 rounded-xl bg-cover bg-no-repeat"
|
class="group relative h-32 w-32 rounded-xl bg-cover bg-no-repeat"
|
||||||
style="background-image: url({src});"
|
style="background-image: url({src});"
|
||||||
>
|
>
|
||||||
|
{#if isVideoURL(src)}
|
||||||
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
|
<video autoplay loop class="h-32 w-32 rounded-xl object-cover" {src}></video>
|
||||||
|
{/if}
|
||||||
{#if isThumbnail && showThumbnailControls}
|
{#if isThumbnail && showThumbnailControls}
|
||||||
<i
|
<i
|
||||||
class="fa fa-file-image absolute top-2 right-2 text-primary bg-white rounded-full px-[10px] py-2 shadow-lg"
|
class="fa fa-file-image absolute top-2 right-2 text-primary bg-white rounded-full px-[10px] py-2 shadow-lg"
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { isVideoURL } from "$lib/util/file_util";
|
||||||
|
import PhotoSwipeVideoPlugin from "$lib/vendor/photo-swipe-video-plugin";
|
||||||
import type { DataSource } from "photoswipe";
|
import type { DataSource } from "photoswipe";
|
||||||
import PhotoSwipeLightbox from "photoswipe/lightbox";
|
import PhotoSwipeLightbox from "photoswipe/lightbox";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
@@ -17,28 +19,53 @@
|
|||||||
let lightboxDataSource: DataSource;
|
let lightboxDataSource: DataSource;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
lightboxDataSource = photos.map((p) => ({
|
lightboxDataSource = photos.map((p) => {
|
||||||
src: p,
|
if (isVideoURL(p)) {
|
||||||
}));
|
return {
|
||||||
|
type: "video",
|
||||||
|
videoSrc: p,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
src: p,
|
||||||
|
};
|
||||||
|
});
|
||||||
lightbox = new PhotoSwipeLightbox({
|
lightbox = new PhotoSwipeLightbox({
|
||||||
dataSource: lightboxDataSource,
|
dataSource: lightboxDataSource,
|
||||||
pswpModule: async () => await import("photoswipe"),
|
pswpModule: async () => await import("photoswipe"),
|
||||||
});
|
});
|
||||||
|
const videoPlugin = new PhotoSwipeVideoPlugin(lightbox);
|
||||||
|
|
||||||
lightbox.init();
|
lightbox.init();
|
||||||
|
|
||||||
lightbox.on("beforeOpen", () => {
|
lightbox.on("beforeOpen", () => {
|
||||||
const pswp = lightbox.pswp;
|
const pswp = lightbox.pswp;
|
||||||
const ds = pswp?.options?.dataSource;
|
const ds = pswp?.options?.dataSource;
|
||||||
|
|
||||||
if (Array.isArray(ds)) {
|
if (Array.isArray(ds)) {
|
||||||
for (let idx = 0, len = ds.length; idx < len; idx++) {
|
for (let idx = 0, len = ds.length; idx < len; idx++) {
|
||||||
const item = ds[idx];
|
const item = ds[idx];
|
||||||
const img = new Image();
|
if (item.type === "video") {
|
||||||
img.onload = () => {
|
const v = document.createElement("video");
|
||||||
item.width = img.naturalWidth;
|
v.addEventListener(
|
||||||
item.height = img.naturalHeight;
|
"loadedmetadata",
|
||||||
pswp?.refreshSlideContent(idx);
|
function () {
|
||||||
};
|
item.width = this.videoWidth;
|
||||||
img.src = item.src as string;
|
item.height = this.videoHeight;
|
||||||
|
pswp?.refreshSlideContent(idx);
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
v.src = item.videoSrc as string
|
||||||
|
} else {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
item.width = img.naturalWidth;
|
||||||
|
item.height = img.naturalHeight;
|
||||||
|
pswp?.refreshSlideContent(idx);
|
||||||
|
};
|
||||||
|
img.src = item.src as string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Activity } from "$lib/models/activity";
|
import type { Activity } from "$lib/models/activity";
|
||||||
import type { UserAnonymous } from "$lib/models/user";
|
import type { UserAnonymous } from "$lib/models/user";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL, isVideoURL } from "$lib/util/file_util";
|
||||||
import {
|
import {
|
||||||
formatDistance,
|
formatDistance,
|
||||||
formatElevation,
|
formatElevation,
|
||||||
@@ -77,15 +77,34 @@
|
|||||||
: 'grid-cols-1'}"
|
: 'grid-cols-1'}"
|
||||||
>
|
>
|
||||||
{#each activity.photos as photo, i}
|
{#each activity.photos as photo, i}
|
||||||
<img
|
{#if isVideoURL(photo)}
|
||||||
class="object-cover h-full max-h-80 w-full"
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
class:row-span-2={i == 0 && activity.photos.length > 2}
|
<video
|
||||||
src={getFileURL(
|
class="object-cover h-full max-h-80 w-full"
|
||||||
{ collectionId: activity.type + "s", id: activity.id },
|
autoplay
|
||||||
photo,
|
loop
|
||||||
)}
|
src={getFileURL(
|
||||||
alt=""
|
{
|
||||||
/>
|
collectionId: activity.type + "s",
|
||||||
|
id: activity.id,
|
||||||
|
},
|
||||||
|
photo,
|
||||||
|
)}
|
||||||
|
></video>
|
||||||
|
{:else}
|
||||||
|
<img
|
||||||
|
class="object-cover h-full max-h-80 w-full"
|
||||||
|
class:row-span-2={i == 0 && activity.photos.length > 2}
|
||||||
|
src={getFileURL(
|
||||||
|
{
|
||||||
|
collectionId: activity.type + "s",
|
||||||
|
id: activity.id,
|
||||||
|
},
|
||||||
|
photo,
|
||||||
|
)}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
||||||
import emptyStateTrailDark from "$lib/assets/svgs/empty_states/empty_state_trail_dark.svg";
|
import emptyStateTrailDark from "$lib/assets/svgs/empty_states/empty_state_trail_dark.svg";
|
||||||
import emptyStateTrailLight from "$lib/assets/svgs/empty_states/empty_state_trail_light.svg";
|
import emptyStateTrailLight from "$lib/assets/svgs/empty_states/empty_state_trail_light.svg";
|
||||||
import type { SummitLog } from "$lib/models/summit_log";
|
import type { SummitLog } from "$lib/models/summit_log";
|
||||||
@@ -7,7 +6,7 @@
|
|||||||
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
|
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
|
||||||
|
|
||||||
import { theme } from "$lib/stores/theme_store";
|
import { theme } from "$lib/stores/theme_store";
|
||||||
import { getFileURL, readAsDataURLAsync } from "$lib/util/file_util";
|
import { getFileURL, isVideoURL, readAsDataURLAsync } from "$lib/util/file_util";
|
||||||
import {
|
import {
|
||||||
formatDistance,
|
formatDistance,
|
||||||
formatElevation,
|
formatElevation,
|
||||||
@@ -55,12 +54,24 @@
|
|||||||
<div class="p-4 my-2 border border-input-border rounded-xl">
|
<div class="p-4 my-2 border border-input-border rounded-xl">
|
||||||
<div class="flex items-center gap-x-4">
|
<div class="flex items-center gap-x-4">
|
||||||
<div class="h-24 aspect-square shrink-0 rounded-xl overflow-hidden">
|
<div class="h-24 aspect-square shrink-0 rounded-xl overflow-hidden">
|
||||||
<img
|
{#if isVideoURL(thumbnail)}
|
||||||
id="header-img"
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
class="object-cover h-full"
|
<video
|
||||||
src={thumbnail}
|
controls={false}
|
||||||
alt=""
|
loop
|
||||||
/>
|
class="object-cover h-full w-full"
|
||||||
|
onmouseenter={(e) => (e.target as any).play()}
|
||||||
|
onmouseleave={(e) => (e.target as any).pause()}
|
||||||
|
src={thumbnail}
|
||||||
|
></video>
|
||||||
|
{:else}
|
||||||
|
<img
|
||||||
|
id="header-img"
|
||||||
|
class="object-cover h-full w-full"
|
||||||
|
src={thumbnail}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="basis-full">
|
<div class="basis-full">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SummitLog } from "$lib/models/summit_log";
|
import type { SummitLog } from "$lib/models/summit_log";
|
||||||
|
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL, isVideoURL } from "$lib/util/file_util";
|
||||||
import {
|
import {
|
||||||
formatDistance,
|
formatDistance,
|
||||||
formatElevation,
|
formatElevation,
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
showDescription = false,
|
showDescription = false,
|
||||||
showPhotos = false,
|
showPhotos = false,
|
||||||
onopen,
|
onopen,
|
||||||
ontext
|
ontext,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let gallery: PhotoGallery;
|
let gallery: PhotoGallery;
|
||||||
@@ -81,13 +81,27 @@
|
|||||||
onclick={() => gallery.openGallery()}
|
onclick={() => gallery.openGallery()}
|
||||||
>
|
>
|
||||||
{#each imgSrc as img, i}
|
{#each imgSrc as img, i}
|
||||||
<img
|
{#if isVideoURL(img)}
|
||||||
class="absolute h-full rounded-xl object-cover"
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
style="top: {4 * i}px; right: {4 *
|
<video
|
||||||
i}px; transform: rotate(-{i * 5}deg)"
|
controls={false}
|
||||||
src={img}
|
loop
|
||||||
alt="waypoint"
|
class="absolute h-full w-full rounded-xl object-cover"
|
||||||
/>
|
style="top: {4 * i}px; right: {4 *
|
||||||
|
i}px; transform: rotate(-{i * 5}deg)"
|
||||||
|
onmouseenter={(e) => (e.target as any).play()}
|
||||||
|
onmouseleave={(e) => (e.target as any).pause()}
|
||||||
|
src={img}
|
||||||
|
></video>
|
||||||
|
{:else}
|
||||||
|
<img
|
||||||
|
class="absolute h-full rounded-xl object-cover"
|
||||||
|
style="top: {4 * i}px; right: {4 *
|
||||||
|
i}px; transform: rotate(-{i * 5}deg)"
|
||||||
|
src={img}
|
||||||
|
alt="waypoint"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -167,12 +167,11 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (waypoints) {
|
waypoints;
|
||||||
untrack(() => {
|
untrack(() => {
|
||||||
showWaypoints();
|
showWaypoints();
|
||||||
refreshElevationProfile();
|
refreshElevationProfile();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function getData(trails: Trail[]) {
|
function getData(trails: Trail[]) {
|
||||||
@@ -280,7 +279,7 @@
|
|||||||
right: 16,
|
right: 16,
|
||||||
bottom:
|
bottom:
|
||||||
16 +
|
16 +
|
||||||
(epc?.isProfileShown
|
(epc?.isProfileShown && !elevationProfileContainer
|
||||||
? map!.getContainer().clientHeight * 0.3
|
? map!.getContainer().clientHeight * 0.3
|
||||||
: 0),
|
: 0),
|
||||||
},
|
},
|
||||||
@@ -663,6 +662,13 @@
|
|||||||
markers.push(marker);
|
markers.push(marker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
markers = markers.filter((marker) => {
|
||||||
|
if (!waypoints.find((w) => w.id == marker._element.id)) {
|
||||||
|
marker.remove();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideWaypoints() {
|
function hideWaypoints() {
|
||||||
|
|||||||
@@ -87,7 +87,10 @@
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let photoFile = file;
|
let photoFile = file;
|
||||||
if (!file.type.startsWith("image")) {
|
if (
|
||||||
|
!file.type.startsWith("image") &&
|
||||||
|
!["video/mp4", "video/ogg", "video/webm"].includes(file.type)
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
} else if (file.type === "image/heic") {
|
} else if (file.type === "image/heic") {
|
||||||
const heic2any = (await import("heic2any")).default;
|
const heic2any = (await import("heic2any")).default;
|
||||||
@@ -151,7 +154,7 @@
|
|||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
id="{id}-photo-input"
|
id="{id}-photo-input"
|
||||||
accept="image/*"
|
accept="image/*,video/mp4"
|
||||||
multiple={true}
|
multiple={true}
|
||||||
style="display: none;"
|
style="display: none;"
|
||||||
onchange={() => handlePhotoSelection()}
|
onchange={() => handlePhotoSelection()}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import { pb } from "$lib/pocketbase";
|
import { pb } from "$lib/pocketbase";
|
||||||
import { theme } from "$lib/stores/theme_store";
|
import { theme } from "$lib/stores/theme_store";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL, isVideoURL } from "$lib/util/file_util";
|
||||||
import {
|
import {
|
||||||
formatDistance,
|
formatDistance,
|
||||||
formatElevation,
|
formatElevation,
|
||||||
@@ -55,7 +55,24 @@
|
|||||||
<div
|
<div
|
||||||
class="relative w-full basis-full max-h-48 overflow-hidden rounded-t-2xl"
|
class="relative w-full basis-full max-h-48 overflow-hidden rounded-t-2xl"
|
||||||
>
|
>
|
||||||
<img loading="lazy" class="w-full h-full" id="header-img" src={thumbnail} alt="" />
|
{#if isVideoURL(thumbnail)}
|
||||||
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
|
<video
|
||||||
|
id="header-img"
|
||||||
|
class="w-full h-full object-cover"
|
||||||
|
autoplay
|
||||||
|
loop
|
||||||
|
src={thumbnail}
|
||||||
|
></video>
|
||||||
|
{:else}
|
||||||
|
<img
|
||||||
|
loading="lazy"
|
||||||
|
class="w-full h-full"
|
||||||
|
id="header-img"
|
||||||
|
src={thumbnail}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if (trail.public || trailIsShared) && pb.authStore.model}
|
{#if (trail.public || trailIsShared) && pb.authStore.model}
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
comments_update,
|
comments_update,
|
||||||
} from "$lib/stores/comment_store";
|
} from "$lib/stores/comment_store";
|
||||||
import { currentUser } from "$lib/stores/user_store";
|
import { currentUser } from "$lib/stores/user_store";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL, isVideoURL } from "$lib/util/file_util";
|
||||||
import {
|
import {
|
||||||
formatDistance,
|
formatDistance,
|
||||||
formatElevation,
|
formatElevation,
|
||||||
@@ -172,12 +172,23 @@
|
|||||||
>
|
>
|
||||||
<div class="trail-info-panel-header">
|
<div class="trail-info-panel-header">
|
||||||
<section class="relative h-80">
|
<section class="relative h-80">
|
||||||
<img
|
{#if isVideoURL(thumbnail)}
|
||||||
class="w-full h-80"
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
class:rounded-t-3xl={mode !== "list"}
|
<video
|
||||||
src={thumbnail}
|
class="w-full h-80 object-cover rounded-t-3xl"
|
||||||
alt=""
|
autoplay
|
||||||
/>
|
loop
|
||||||
|
src={thumbnail}
|
||||||
|
></video>
|
||||||
|
{:else}
|
||||||
|
<img
|
||||||
|
class="w-full h-80"
|
||||||
|
class:rounded-t-3xl={mode !== "list"}
|
||||||
|
src={thumbnail}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="absolute bottom-0 w-full h-full bg-gradient-to-b from-transparent to-black opacity-60"
|
class="absolute bottom-0 w-full h-full bg-gradient-to-b from-transparent to-black opacity-60"
|
||||||
></div>
|
></div>
|
||||||
@@ -215,7 +226,10 @@
|
|||||||
class="flex absolute justify-between items-end w-full bottom-8 left-0 px-8 gap-y-4"
|
class="flex absolute justify-between items-end w-full bottom-8 left-0 px-8 gap-y-4"
|
||||||
>
|
>
|
||||||
<div class="text-white overflow-hidden">
|
<div class="text-white overflow-hidden">
|
||||||
<h4 title={trail.name} class="text-4xl font-bold line-clamp-3">
|
<h4
|
||||||
|
title={trail.name}
|
||||||
|
class="text-4xl font-bold line-clamp-3"
|
||||||
|
>
|
||||||
{trail.name}
|
{trail.name}
|
||||||
</h4>
|
</h4>
|
||||||
{#if trail.date}
|
{#if trail.date}
|
||||||
@@ -414,12 +428,27 @@
|
|||||||
{#each trail.photos ?? [] as photo, i}
|
{#each trail.photos ?? [] as photo, i}
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||||
<img
|
{#if isVideoURL(photo)}
|
||||||
class="rounded-xl cursor-pointer hover:scale-105 transition-transform"
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
onclick={() => gallery.openGallery(i)}
|
<video
|
||||||
src={getFileURL(trail, photo)}
|
controls={false}
|
||||||
alt=""
|
loop
|
||||||
/>
|
class="rounded-xl cursor-pointer hover:scale-105 transition-transform"
|
||||||
|
onclick={() => gallery.openGallery(i)}
|
||||||
|
onmouseenter={(e) =>
|
||||||
|
(e.target as any).play()}
|
||||||
|
onmouseleave={(e) =>
|
||||||
|
(e.target as any).pause()}
|
||||||
|
src={getFileURL(trail, photo)}
|
||||||
|
></video>
|
||||||
|
{:else}
|
||||||
|
<img
|
||||||
|
class="rounded-xl cursor-pointer hover:scale-105 transition-transform"
|
||||||
|
onclick={() => gallery.openGallery(i)}
|
||||||
|
src={getFileURL(trail, photo)}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import { pb } from "$lib/pocketbase";
|
import { pb } from "$lib/pocketbase";
|
||||||
import { theme } from "$lib/stores/theme_store";
|
import { theme } from "$lib/stores/theme_store";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL, isVideoURL } from "$lib/util/file_util";
|
||||||
import {
|
import {
|
||||||
formatDistance,
|
formatDistance,
|
||||||
formatElevation,
|
formatElevation,
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
} from "$lib/util/format_util";
|
} from "$lib/util/format_util";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import ShareInfo from "../share_info.svelte";
|
import ShareInfo from "../share_info.svelte";
|
||||||
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
trail: Trail;
|
trail: Trail;
|
||||||
@@ -21,18 +20,34 @@
|
|||||||
|
|
||||||
let { trail, showDescription = true }: Props = $props();
|
let { trail, showDescription = true }: Props = $props();
|
||||||
|
|
||||||
let thumbnail = $derived(trail.photos.length
|
let thumbnail = $derived(
|
||||||
? getFileURL(trail, trail.photos[trail.thumbnail ?? 0])
|
trail.photos.length
|
||||||
: $theme === "light"
|
? getFileURL(trail, trail.photos[trail.thumbnail ?? 0])
|
||||||
? emptyStateTrailLight
|
: $theme === "light"
|
||||||
: emptyStateTrailDark);
|
? emptyStateTrailLight
|
||||||
|
: emptyStateTrailDark,
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
class="flex gap-8 p-4 rounded-xl border border-input-border cursor-pointer hover:bg-secondary-hover transition-colors items-center"
|
class="flex gap-8 p-4 rounded-xl border border-input-border cursor-pointer hover:bg-secondary-hover transition-colors items-center"
|
||||||
>
|
>
|
||||||
<div class="shrink-0">
|
<div class="shrink-0">
|
||||||
<img class="h-28 w-28 object-cover rounded-xl" src={thumbnail} alt="" />
|
{#if isVideoURL(thumbnail)}
|
||||||
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
|
<video
|
||||||
|
class="h-28 w-28 object-cover rounded-xl"
|
||||||
|
autoplay
|
||||||
|
loop
|
||||||
|
src={thumbnail}
|
||||||
|
></video>
|
||||||
|
{:else}
|
||||||
|
<img
|
||||||
|
class="h-28 w-28 object-cover rounded-xl"
|
||||||
|
src={thumbnail}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0 basis-full">
|
<div class="min-w-0 basis-full">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL, isVideoURL } from "$lib/util/file_util";
|
||||||
import { formatDistance } from "$lib/util/format_util";
|
import { formatDistance } from "$lib/util/format_util";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import PhotoGallery from "../photo_gallery.svelte";
|
import PhotoGallery from "../photo_gallery.svelte";
|
||||||
@@ -52,23 +52,40 @@
|
|||||||
: 'grid-cols-1'} cursor-pointer"
|
: 'grid-cols-1'} cursor-pointer"
|
||||||
>
|
>
|
||||||
{#each wp.photos as photo, j}
|
{#each wp.photos as photo, j}
|
||||||
<img
|
{#if isVideoURL(photo)}
|
||||||
onclick={() => gallery[i].openGallery(j)}
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
role="presentation"
|
<video
|
||||||
class="w-full object-cover {j == 0 &&
|
controls={false}
|
||||||
wp.photos.length > 2
|
loop
|
||||||
? 'row-span-2 h-80'
|
class="w-full object-cover {j == 0 &&
|
||||||
: 'h-[159.5px]'}"
|
wp.photos.length > 2
|
||||||
src={getFileURL(wp, photo)}
|
? 'row-span-2 h-80'
|
||||||
alt=""
|
: 'h-[159.5px]'}"
|
||||||
/>
|
onclick={() => gallery[i].openGallery(j)}
|
||||||
|
onmouseenter={(e) => (e.target as any).play()}
|
||||||
|
onmouseleave={(e) => (e.target as any).pause()}
|
||||||
|
src={getFileURL(wp, photo)}
|
||||||
|
></video>
|
||||||
|
{:else}
|
||||||
|
<img
|
||||||
|
onclick={() => gallery[i].openGallery(j)}
|
||||||
|
role="presentation"
|
||||||
|
class="w-full object-cover {j == 0 &&
|
||||||
|
wp.photos.length > 2
|
||||||
|
? 'row-span-2 h-80'
|
||||||
|
: 'h-[159.5px]'}"
|
||||||
|
src={getFileURL(wp, photo)}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<h5 class="text-xl font-semibold">{wp.name}</h5>
|
<h5 class="text-xl font-semibold">{wp.name}</h5>
|
||||||
<span class="text-sm text-gray-500"
|
<span class="text-sm text-gray-500"
|
||||||
><i class="fa fa-location-dot mr-1"></i> {wp.lat.toFixed(5)}, {wp.lon.toFixed(5)}</span
|
><i class="fa fa-location-dot mr-1"></i>
|
||||||
|
{wp.lat.toFixed(5)}, {wp.lon.toFixed(5)}</span
|
||||||
>
|
>
|
||||||
<p class="whitespace-pre-line">
|
<p class="whitespace-pre-line">
|
||||||
{wp.description}
|
{wp.description}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Waypoint } from "$lib/models/waypoint";
|
import type { Waypoint } from "$lib/models/waypoint";
|
||||||
import { getFileURL, readAsDataURLAsync } from "$lib/util/file_util";
|
import {
|
||||||
|
getFileURL,
|
||||||
|
isVideoURL,
|
||||||
|
readAsDataURLAsync,
|
||||||
|
} from "$lib/util/file_util";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
|
import Dropdown, { type DropdownItem } from "../base/dropdown.svelte";
|
||||||
import { browser } from "$app/environment";
|
import { browser } from "$app/environment";
|
||||||
@@ -60,13 +64,27 @@
|
|||||||
onclick={mode == "show" ? () => gallery.openGallery() : undefined}
|
onclick={mode == "show" ? () => gallery.openGallery() : undefined}
|
||||||
>
|
>
|
||||||
{#each imgSrc as img, i}
|
{#each imgSrc as img, i}
|
||||||
<img
|
{#if isVideoURL(img)}
|
||||||
class="absolute h-full rounded-xl object-cover aspect-square"
|
<!-- svelte-ignore a11y_media_has_caption -->
|
||||||
style="top: {6 * i}px; right: {6 *
|
<video
|
||||||
i}px; transform: rotate(-{i * 5}deg)"
|
controls={false}
|
||||||
src={img}
|
loop
|
||||||
alt="waypoint"
|
class="absolute h-full rounded-xl object-cover aspect-square"
|
||||||
/>
|
style="top: {6 * i}px; right: {6 *
|
||||||
|
i}px; transform: rotate(-{i * 5}deg)"
|
||||||
|
onmouseenter={(e) => (e.target as any).play()}
|
||||||
|
onmouseleave={(e) => (e.target as any).pause()}
|
||||||
|
src={img}
|
||||||
|
></video>
|
||||||
|
{:else}
|
||||||
|
<img
|
||||||
|
class="absolute h-full rounded-xl object-cover aspect-square"
|
||||||
|
style="top: {6 * i}px; right: {6 *
|
||||||
|
i}px; transform: rotate(-{i * 5}deg)"
|
||||||
|
src={img}
|
||||||
|
alt="waypoint"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ export function readAsDataURLAsync(file: File) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isVideoURL(url: string) {
|
||||||
|
if(url.startsWith("data")) {
|
||||||
|
return url.startsWith("data:video")
|
||||||
|
}
|
||||||
|
return url.includes("mp4") || url.includes("ogg") || url.includes("webm")
|
||||||
|
}
|
||||||
|
|
||||||
export function saveAs(data: Blob, fileName: string) {
|
export function saveAs(data: Blob, fileName: string) {
|
||||||
var a = document.createElement("a") as HTMLAnchorElement;
|
var a = document.createElement("a") as HTMLAnchorElement;
|
||||||
a.setAttribute("style", "display: none");
|
a.setAttribute("style", "display: none");
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ export function createMarkerFromWaypoint(waypoint: Waypoint, onDragEnd?: (marker
|
|||||||
marker.on("dragend", () => onDragEnd(marker, waypoint.id,));
|
marker.on("dragend", () => onDragEnd(marker, waypoint.id,));
|
||||||
}
|
}
|
||||||
|
|
||||||
waypoint.marker = marker
|
|
||||||
return marker;
|
return marker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
web/src/lib/vendor/photo-swipe-video-plugin/index.ts
vendored
Normal file
14
web/src/lib/vendor/photo-swipe-video-plugin/index.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type PhotoSwipeLightbox from 'photoswipe/lightbox';
|
||||||
|
import { defaultOptions, type PhotoSwipeVideoPluginOptions } from './options';
|
||||||
|
import VideoContentSetup from './video-content-setup';
|
||||||
|
|
||||||
|
class PhotoSwipeVideoPlugin {
|
||||||
|
constructor(lightbox: PhotoSwipeLightbox, options?: PhotoSwipeVideoPluginOptions) {
|
||||||
|
new VideoContentSetup(lightbox, {
|
||||||
|
...defaultOptions,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PhotoSwipeVideoPlugin;
|
||||||
18
web/src/lib/vendor/photo-swipe-video-plugin/options.ts
vendored
Normal file
18
web/src/lib/vendor/photo-swipe-video-plugin/options.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export type PhotoSwipeVideoPluginOptions = {
|
||||||
|
videoAttributes: {
|
||||||
|
controls: string,
|
||||||
|
playsinline: string,
|
||||||
|
preload: string,
|
||||||
|
},
|
||||||
|
autoplay: boolean,
|
||||||
|
preventDragOffset: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultOptions: PhotoSwipeVideoPluginOptions = {
|
||||||
|
videoAttributes: { controls: '', playsinline: '', preload: 'auto' },
|
||||||
|
autoplay: true,
|
||||||
|
|
||||||
|
// prevent drag/swipe gesture over the bottom part of video
|
||||||
|
// set to 0 to disable
|
||||||
|
preventDragOffset: 40
|
||||||
|
};
|
||||||
250
web/src/lib/vendor/photo-swipe-video-plugin/video-content-setup.ts
vendored
Normal file
250
web/src/lib/vendor/photo-swipe-video-plugin/video-content-setup.ts
vendored
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
import type PhotoSwipe from "photoswipe";
|
||||||
|
import type PhotoSwipeLightbox from "photoswipe/lightbox";
|
||||||
|
import type { Content } from "photoswipe/lightbox";
|
||||||
|
import type { SlideData } from "photoswipe";
|
||||||
|
import type { PhotoSwipeVideoPluginOptions } from "./options";
|
||||||
|
|
||||||
|
type VideoContentData = {
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
type: 'video',
|
||||||
|
msrc: string,
|
||||||
|
videoSrc: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
type VideoContent = Content & {
|
||||||
|
element: HTMLVideoElement
|
||||||
|
data: VideoContentData
|
||||||
|
_videoPosterImg: HTMLImageElement | null
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVideoContent(content?: Content | SlideData) {
|
||||||
|
if (!content) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (content && content.data && content.data.type === 'video');
|
||||||
|
}
|
||||||
|
|
||||||
|
class VideoContentSetup {
|
||||||
|
options: PhotoSwipeVideoPluginOptions;
|
||||||
|
constructor(lightbox: PhotoSwipeLightbox, options: PhotoSwipeVideoPluginOptions) {
|
||||||
|
this.options = options;
|
||||||
|
|
||||||
|
this.initLightboxEvents(lightbox);
|
||||||
|
lightbox.on('init', () => {
|
||||||
|
this.initPswpEvents(lightbox.pswp!);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initLightboxEvents(lightbox: PhotoSwipeLightbox) {
|
||||||
|
lightbox.on('contentLoad', this.onContentLoad.bind(this));
|
||||||
|
lightbox.on('contentDestroy', this.onContentDestroy.bind(this));
|
||||||
|
lightbox.on('contentActivate', this.onContentActivate.bind(this));
|
||||||
|
lightbox.on('contentDeactivate', this.onContentDeactivate.bind(this));
|
||||||
|
lightbox.on('contentAppend', this.onContentAppend.bind(this));
|
||||||
|
lightbox.on('contentResize', this.onContentResize.bind(this));
|
||||||
|
|
||||||
|
lightbox.addFilter('isKeepingPlaceholder', this.isKeepingPlaceholder.bind(this));
|
||||||
|
lightbox.addFilter('isContentZoomable', this.isContentZoomable.bind(this));
|
||||||
|
lightbox.addFilter('useContentPlaceholder', this.useContentPlaceholder.bind(this));
|
||||||
|
|
||||||
|
lightbox.addFilter('domItemData', (itemData, element, linkEl) => {
|
||||||
|
if (itemData.type === 'video' && linkEl) {
|
||||||
|
if (linkEl.dataset.pswpVideoSources) {
|
||||||
|
itemData.videoSources = JSON.parse(linkEl.dataset.pswpVideoSources);
|
||||||
|
} else if (linkEl.dataset.pswpVideoSrc) {
|
||||||
|
itemData.videoSrc = linkEl.dataset.pswpVideoSrc;
|
||||||
|
} else {
|
||||||
|
itemData.videoSrc = linkEl.href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return itemData;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initPswpEvents(pswp: PhotoSwipe) {
|
||||||
|
pswp.on('pointerDown', (e) => {
|
||||||
|
const slide = pswp.currSlide;
|
||||||
|
if (isVideoContent(slide) && this.options.preventDragOffset) {
|
||||||
|
const origEvent = e.originalEvent;
|
||||||
|
if (origEvent.type === 'pointerdown') {
|
||||||
|
const videoHeight = Math.ceil((slide?.height ?? 0) * (slide?.currZoomLevel ?? 0));
|
||||||
|
const verticalEnding = videoHeight + (slide?.bounds.center.y ?? 0);
|
||||||
|
const pointerYPos = origEvent.pageY - pswp.offset.y;
|
||||||
|
if (pointerYPos > verticalEnding - this.options.preventDragOffset
|
||||||
|
&& pointerYPos < verticalEnding) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// do not append video on nearby slides
|
||||||
|
pswp.on('appendHeavy', (e) => {
|
||||||
|
if (isVideoContent(e.slide) && !e.slide.isActive) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pswp.on('close', () => {
|
||||||
|
if (isVideoContent(pswp.currSlide?.content)) {
|
||||||
|
// Switch from zoom to fade closing transition,
|
||||||
|
// as zoom transition is choppy for videos
|
||||||
|
if (!pswp.options.showHideAnimationType
|
||||||
|
|| pswp.options.showHideAnimationType === 'zoom') {
|
||||||
|
pswp.options.showHideAnimationType = 'fade';
|
||||||
|
}
|
||||||
|
|
||||||
|
// pause video when closing
|
||||||
|
this.pauseVideo(pswp.currSlide?.content as VideoContent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onContentDestroy({ content }: { content: Content }) {
|
||||||
|
if (isVideoContent(content)) {
|
||||||
|
const c = content as VideoContent
|
||||||
|
if (c._videoPosterImg) {
|
||||||
|
c._videoPosterImg.onload = c._videoPosterImg.onerror = null;
|
||||||
|
c._videoPosterImg = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onContentResize(e: any) {
|
||||||
|
if (isVideoContent(e.content)) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const width = e.width;
|
||||||
|
const height = e.height;
|
||||||
|
const content = e.content;
|
||||||
|
|
||||||
|
if (content.element) {
|
||||||
|
content.element.style.width = width + 'px';
|
||||||
|
content.element.style.height = height + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.slide && content.slide.placeholder) {
|
||||||
|
// override placeholder size, so it more accurately matches the video
|
||||||
|
const placeholderElStyle = content.slide.placeholder.element.style;
|
||||||
|
placeholderElStyle.transform = 'none';
|
||||||
|
placeholderElStyle.width = width + 'px';
|
||||||
|
placeholderElStyle.height = height + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
isKeepingPlaceholder(isZoomable: boolean, content: Content) {
|
||||||
|
if (isVideoContent(content)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isZoomable;
|
||||||
|
}
|
||||||
|
|
||||||
|
isContentZoomable(isZoomable: boolean, content: Content) {
|
||||||
|
if (isVideoContent(content)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isZoomable;
|
||||||
|
}
|
||||||
|
|
||||||
|
onContentActivate({ content }: { content: Content }) {
|
||||||
|
if (isVideoContent(content) && this.options.autoplay) {
|
||||||
|
this.playVideo(content as VideoContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onContentDeactivate({ content }: { content: Content }) {
|
||||||
|
if (isVideoContent(content)) {
|
||||||
|
this.pauseVideo(content as VideoContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onContentAppend(e: any) {
|
||||||
|
if (isVideoContent(e.content)) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.content.isAttached = true;
|
||||||
|
e.content.appendImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onContentLoad(e: any) {
|
||||||
|
const content = e.content; // todo: videocontent
|
||||||
|
|
||||||
|
if (!isVideoContent(e.content)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop default content load
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (content.element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
content.state = 'loading';
|
||||||
|
content.type = 'video'; // TODO: move this to pswp core?
|
||||||
|
|
||||||
|
content.element = document.createElement('video');
|
||||||
|
|
||||||
|
if (this.options.videoAttributes) {
|
||||||
|
for (let key in this.options.videoAttributes) {
|
||||||
|
content.element.setAttribute(key, this.options.videoAttributes[key as keyof PhotoSwipeVideoPluginOptions['videoAttributes']] || '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content.onLoaded();
|
||||||
|
// content.element.setAttribute('poster', content.data.msrc);
|
||||||
|
|
||||||
|
// this.preloadVideoPoster(content, content.data.msrc);
|
||||||
|
|
||||||
|
// content.element.style.position = 'absolute';
|
||||||
|
content.element.style.left = "0";
|
||||||
|
content.element.style.top = "0";
|
||||||
|
|
||||||
|
if (content.data.videoSrc) {
|
||||||
|
content.element.src = content.data.videoSrc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preloadVideoPoster(content: VideoContent, src: string) {
|
||||||
|
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: VideoContent) {
|
||||||
|
if (content.element) {
|
||||||
|
content.element.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseVideo(content?: VideoContent) {
|
||||||
|
if (!content) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (content.element) {
|
||||||
|
content.element.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useContentPlaceholder(usePlaceholder: boolean, content: Content) {
|
||||||
|
if (isVideoContent(content)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return usePlaceholder;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VideoContentSetup;
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
extend: validator({
|
extend: validator({
|
||||||
schema: ClientTrailCreateSchema,
|
schema: ClientTrailCreateSchema,
|
||||||
}),
|
}),
|
||||||
onSubmit: async (form) => {
|
onSubmit: async (form) => {
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
try {
|
||||||
const htmlForm = document.getElementById(
|
const htmlForm = document.getElementById(
|
||||||
@@ -404,10 +404,12 @@
|
|||||||
function deleteWaypoint(index: number) {
|
function deleteWaypoint(index: number) {
|
||||||
const wp = $formData.expand!.waypoints?.splice(index, 1);
|
const wp = $formData.expand!.waypoints?.splice(index, 1);
|
||||||
$formData.waypoints.splice(index, 1);
|
$formData.waypoints.splice(index, 1);
|
||||||
$formData.expand!.waypoints = $formData.expand!.waypoints;
|
|
||||||
|
|
||||||
wp?.[0]?.marker?.remove();
|
if(!$formData.expand!.waypoints?.length) {
|
||||||
|
$formData.expand!.waypoints = []
|
||||||
|
}
|
||||||
|
$formData.expand!.waypoints = $formData.expand!.waypoints;
|
||||||
|
|
||||||
// updateTrailOnMap();
|
// updateTrailOnMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1033,7 +1035,7 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{#each $formData.expand?.waypoints ?? [] as waypoint, i}
|
{#each $formData.expand?.waypoints ?? [] as waypoint, i}
|
||||||
<li onmouseenter={() => openMarkerPopup(waypoint)}>
|
<li onmouseenter={() => openMarkerPopup(waypoint)} onmouseleave={() => openMarkerPopup(waypoint)}>
|
||||||
<WaypointCard
|
<WaypointCard
|
||||||
{waypoint}
|
{waypoint}
|
||||||
mode="edit"
|
mode="edit"
|
||||||
|
|||||||
Reference in New Issue
Block a user