Add swipe controls to move back and forth in albums
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 1;
|
line-height: 1em;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
letter-spacing: normal;
|
letter-spacing: normal;
|
||||||
word-wrap: normal;
|
word-wrap: normal;
|
||||||
@@ -496,7 +496,7 @@ select {
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
background: var(--input_background);
|
background: var(--input_background);
|
||||||
padding: 3px 4px;
|
padding: 3px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: var(--input_text);
|
color: var(--input_text);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -543,7 +543,7 @@ input[type="color"]:active,
|
|||||||
select:active {
|
select:active {
|
||||||
box-shadow: inset 4px 4px 6px var(--shadow_color);
|
box-shadow: inset 4px 4px 6px var(--shadow_color);
|
||||||
/* Exactly 3px offset compared to the inactive padding to give a depth effect */
|
/* Exactly 3px offset compared to the inactive padding to give a depth effect */
|
||||||
padding: 6px 1px 0px 7px;
|
padding: 6px 0px 0px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button_highlight {
|
.button_highlight {
|
||||||
@@ -654,7 +654,7 @@ input[type="datetime-local"] {
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background: var(--input_background);
|
background: var(--input_background);
|
||||||
padding: 3px 4px;
|
padding: 3px;
|
||||||
box-shadow: inset 1px 1px 0px 0px var(--shadow_color);
|
box-shadow: inset 1px 1px 0px 0px var(--shadow_color);
|
||||||
/* override user-agent style */
|
/* override user-agent style */
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
|
@@ -372,8 +372,9 @@ const keyboard_event = evt => {
|
|||||||
{#if !disable_menu}
|
{#if !disable_menu}
|
||||||
<button
|
<button
|
||||||
on:click={toolbar_toggle}
|
on:click={toolbar_toggle}
|
||||||
class="button_toggle_toolbar round"
|
class="round"
|
||||||
class:button_highlight={toolbar_visible}
|
class:button_highlight={toolbar_visible}
|
||||||
|
style="line-height: 1em;"
|
||||||
title="Open or close the toolbar">
|
title="Open or close the toolbar">
|
||||||
<i class="icon">menu</i>
|
<i class="icon">menu</i>
|
||||||
</button>
|
</button>
|
||||||
|
@@ -94,7 +94,6 @@ export const set_item = idx => {
|
|||||||
.nav_button{
|
.nav_button{
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list_navigator {
|
.list_navigator {
|
||||||
@@ -107,9 +106,9 @@ export const set_item = idx => {
|
|||||||
|
|
||||||
.file_button {
|
.file_button {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 48px;
|
height: 2.6em;
|
||||||
width: 220px;
|
width: 220px;
|
||||||
margin: 4px 3px;
|
margin: 2px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -124,7 +123,7 @@ export const set_item = idx => {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-width: 2px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: var(--input_background);
|
border-color: var(--input_background);
|
||||||
box-shadow: 1px 1px 0px 0px var(--shadow_color);
|
box-shadow: 1px 1px 0px 0px var(--shadow_color);
|
||||||
|
@@ -182,9 +182,12 @@ const report_description = () => {
|
|||||||
|
|
||||||
{#each abuse_categories as cat}
|
{#each abuse_categories as cat}
|
||||||
{#if cat.filter === undefined || cat.filter(file.mime_type) }
|
{#if cat.filter === undefined || cat.filter(file.mime_type) }
|
||||||
<label class="type_label" for="type_{cat.type}">
|
<label for="type_{cat.type}">
|
||||||
<input class="type_button" type="radio" bind:group={abuse_type} id="type_{cat.type}" name="abuse_type" value="{cat.type}">
|
<input type="radio" bind:group={abuse_type} id="type_{cat.type}" name="abuse_type" value="{cat.type}">
|
||||||
<span class="type_desc"><b>{cat.name}</b>: {cat.desc}</span>
|
<div>
|
||||||
|
<b>{cat.name}</b><br/>
|
||||||
|
{cat.desc}
|
||||||
|
</div>
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
@@ -193,11 +196,11 @@ const report_description = () => {
|
|||||||
<h3>Report multiple files?</h3>
|
<h3>Report multiple files?</h3>
|
||||||
<label for="report_single">
|
<label for="report_single">
|
||||||
<input type="radio" bind:group={single_or_all} id="report_single" name="single_or_all" value="single">
|
<input type="radio" bind:group={single_or_all} id="report_single" name="single_or_all" value="single">
|
||||||
Report only the selected file ({file.name})
|
<div>Report only the selected file ({file.name})</div>
|
||||||
</label>
|
</label>
|
||||||
<label for="report_all" style="border-bottom: none;">
|
<label for="report_all" style="border-bottom: none;">
|
||||||
<input type="radio" bind:group={single_or_all} id="report_all" name="single_or_all" value="all">
|
<input type="radio" bind:group={single_or_all} id="report_all" name="single_or_all" value="all">
|
||||||
Report all {list.files.length} files in this album
|
<div>Report all {list.files.length} files in this album</div>
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -270,9 +273,22 @@ const report_description = () => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
label {
|
label {
|
||||||
display: block;
|
padding: 0.2em;
|
||||||
border-bottom: 1px var(--separator) solid;
|
display: flex;
|
||||||
padding: 0.5em;
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
label > input {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
label > div {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
padding: 0 0.2em;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--separator);
|
||||||
|
}
|
||||||
|
input[type="radio"]:checked+div {
|
||||||
|
border-color: var(--highlight_color);
|
||||||
}
|
}
|
||||||
.spinner_container {
|
.spinner_container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -294,15 +310,4 @@ label {
|
|||||||
.report_form > textarea {
|
.report_form > textarea {
|
||||||
height: 5em;
|
height: 5em;
|
||||||
}
|
}
|
||||||
.type_label {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
.type_button {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
.type_label {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@@ -59,7 +59,7 @@ export const set_file = async file => {
|
|||||||
{:else if viewer_type === "rate_limit"}
|
{:else if viewer_type === "rate_limit"}
|
||||||
<RateLimit bind:this={viewer} on:download></RateLimit>
|
<RateLimit bind:this={viewer} on:download></RateLimit>
|
||||||
{:else if viewer_type === "image"}
|
{:else if viewer_type === "image"}
|
||||||
<Image bind:this={viewer} on:loading></Image>
|
<Image bind:this={viewer} is_list={is_list} on:prev on:next on:loading></Image>
|
||||||
{:else if viewer_type === "video"}
|
{:else if viewer_type === "video"}
|
||||||
<Video bind:this={viewer} is_list={is_list} on:loading on:download on:prev on:next on:reload></Video>
|
<Video bind:this={viewer} is_list={is_list} on:loading on:download on:prev on:next on:reload></Video>
|
||||||
{:else if viewer_type === "audio"}
|
{:else if viewer_type === "audio"}
|
||||||
|
@@ -1,20 +1,36 @@
|
|||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import { swipe_nav } from "./SwipeNavigate.svelte";
|
||||||
let dispatch = createEventDispatcher()
|
let dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export const set_file = f => file = f
|
export const set_file = f => {
|
||||||
|
file = f
|
||||||
|
dispatch("loading", true)
|
||||||
|
}
|
||||||
let file = {
|
let file = {
|
||||||
id: "",
|
id: "",
|
||||||
name: "",
|
name: "",
|
||||||
mime_type: "",
|
mime_type: "",
|
||||||
get_href: "",
|
get_href: "",
|
||||||
}
|
}
|
||||||
|
export let is_list = false
|
||||||
|
|
||||||
let container
|
let container
|
||||||
let zoom = false
|
let zoom = false
|
||||||
let x, y = 0
|
let x, y = 0
|
||||||
let dragging = false
|
let dragging = false
|
||||||
|
|
||||||
|
// For some reason the dblclick event is firing twice during testing.. So here's
|
||||||
|
// an event debouncer
|
||||||
|
let last_dblclick = 0
|
||||||
|
const double_click = e => {
|
||||||
|
let now = Date.now()
|
||||||
|
if (now - last_dblclick > 500) {
|
||||||
|
zoom = !zoom
|
||||||
|
}
|
||||||
|
last_dblclick = now
|
||||||
|
}
|
||||||
|
|
||||||
const mousedown = (e) => {
|
const mousedown = (e) => {
|
||||||
if (!dragging && e.which === 1 && zoom) {
|
if (!dragging && e.which === 1 && zoom) {
|
||||||
x = e.pageX
|
x = e.pageX
|
||||||
@@ -48,20 +64,35 @@ const mouseup = (e) => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let container_style = ""
|
||||||
|
const on_load = () => {
|
||||||
|
dispatch("loading", false)
|
||||||
|
container_style = ""
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:mousemove={mousemove} on:mouseup={mouseup} />
|
<svelte:window on:mousemove={mousemove} on:mouseup={mouseup} />
|
||||||
|
|
||||||
<div bind:this={container} class="container" class:zoom>
|
<div
|
||||||
|
bind:this={container}
|
||||||
|
class="container"
|
||||||
|
class:zoom
|
||||||
|
use:swipe_nav={!zoom && is_list}
|
||||||
|
on:style={e => container_style = e.detail}
|
||||||
|
on:prev
|
||||||
|
on:next
|
||||||
|
style={container_style}
|
||||||
|
>
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||||
<img
|
<img
|
||||||
on:loadstart={() => {dispatch("loading", true)}}
|
on:load={on_load}
|
||||||
on:load={() => {dispatch("loading", false)}}
|
on:error={on_load}
|
||||||
on:error={() => {dispatch("loading", false)}}
|
on:dblclick={double_click}
|
||||||
on:dblclick={() => {zoom = !zoom}}
|
on:doubletap={double_click}
|
||||||
on:doubletap={() => {zoom = !zoom}}
|
|
||||||
on:mousedown={mousedown}
|
on:mousedown={mousedown}
|
||||||
class="image" class:zoom
|
class="image"
|
||||||
|
class:zoom
|
||||||
src={file.get_href}
|
src={file.get_href}
|
||||||
alt={file.name} />
|
alt={file.name} />
|
||||||
</div>
|
</div>
|
||||||
|
76
svelte/src/file_viewer/viewers/SwipeNavigate.svelte
Normal file
76
svelte/src/file_viewer/viewers/SwipeNavigate.svelte
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<script context="module">
|
||||||
|
export const swipe_nav = (node, swipe_enabled) => {
|
||||||
|
let start_x = 0
|
||||||
|
let start_y = 0
|
||||||
|
let render_offset = 0
|
||||||
|
let enabled = swipe_enabled
|
||||||
|
|
||||||
|
const touchstart = e => {
|
||||||
|
start_x = e.touches[0].clientX
|
||||||
|
start_y = e.touches[0].clientY
|
||||||
|
render_offset = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const touchmove = e => {
|
||||||
|
if (!enabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset_x = e.touches[0].clientX - start_x
|
||||||
|
let abs_x = Math.abs(offset_x)
|
||||||
|
let abs_y = Math.abs(e.touches[0].clientY - start_y)
|
||||||
|
let neg = offset_x < 0 ? -1 : 1
|
||||||
|
|
||||||
|
if (abs_x > 100 && abs_y < abs_x/3) {
|
||||||
|
set_offset((abs_x-100)*neg, false)
|
||||||
|
} else {
|
||||||
|
set_offset(0, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const touchend = e => {
|
||||||
|
if (!enabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (render_offset > 100) {
|
||||||
|
set_offset(1000, true)
|
||||||
|
node.dispatchEvent(new CustomEvent("prev"))
|
||||||
|
} else if (render_offset < -100) {
|
||||||
|
set_offset(-1000, true)
|
||||||
|
node.dispatchEvent(new CustomEvent("next"))
|
||||||
|
} else {
|
||||||
|
set_offset(0, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const set_offset = (off, animate) => {
|
||||||
|
render_offset = off
|
||||||
|
|
||||||
|
let detail = "transform: translateX("+off+"px);"
|
||||||
|
if (animate) {
|
||||||
|
detail += "transition: transform 500ms;"
|
||||||
|
}
|
||||||
|
|
||||||
|
node.dispatchEvent(new CustomEvent("style", {detail: detail}))
|
||||||
|
}
|
||||||
|
|
||||||
|
node.addEventListener("touchstart", touchstart)
|
||||||
|
node.addEventListener("touchmove", touchmove)
|
||||||
|
node.addEventListener("touchend", touchend)
|
||||||
|
|
||||||
|
return {
|
||||||
|
update(swipe_enabled) {
|
||||||
|
enabled = swipe_enabled
|
||||||
|
if (!enabled) {
|
||||||
|
render_offset = 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroy() {
|
||||||
|
node.removeEventListener("touchstart", touchstart)
|
||||||
|
node.removeEventListener("touchmove", touchmove)
|
||||||
|
node.removeEventListener("touchend", touchend)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@@ -56,11 +56,11 @@ const state_update = async (base) => {
|
|||||||
<CustomBanner path={state.path}/>
|
<CustomBanner path={state.path}/>
|
||||||
</FileManager>
|
</FileManager>
|
||||||
{:else if viewer_type === "audio"}
|
{:else if viewer_type === "audio"}
|
||||||
<Audio bind:this={viewer} fs_navigator={fs_navigator} state={state}>
|
<Audio state={state} bind:this={viewer} fs_navigator={fs_navigator}>
|
||||||
<CustomBanner path={state.path}/>
|
<CustomBanner path={state.path}/>
|
||||||
</Audio>
|
</Audio>
|
||||||
{:else if viewer_type === "image"}
|
{:else if viewer_type === "image"}
|
||||||
<Image state={state} on:open_sibling/>
|
<Image state={state} bind:this={viewer} on:open_sibling/>
|
||||||
{:else if viewer_type === "video"}
|
{:else if viewer_type === "video"}
|
||||||
<Video state={state} bind:this={viewer} on:open_sibling/>
|
<Video state={state} bind:this={viewer} on:open_sibling/>
|
||||||
{:else if viewer_type === "pdf"}
|
{:else if viewer_type === "pdf"}
|
||||||
|
@@ -1,12 +1,20 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { createEventDispatcher } from "svelte";
|
||||||
|
import { swipe_nav } from "../../file_viewer/viewers/SwipeNavigate.svelte";
|
||||||
import { fs_path_url } from "../FilesystemUtil";
|
import { fs_path_url } from "../FilesystemUtil";
|
||||||
|
|
||||||
|
let dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let state
|
export let state
|
||||||
let container
|
let container
|
||||||
let zoom = false
|
let zoom = false
|
||||||
let x, y = 0
|
let x, y = 0
|
||||||
let dragging = false
|
let dragging = false
|
||||||
|
let container_style = ""
|
||||||
|
|
||||||
|
export const update = () => {
|
||||||
|
container_style = ""
|
||||||
|
}
|
||||||
|
|
||||||
const mousedown = (e) => {
|
const mousedown = (e) => {
|
||||||
if (!dragging && e.which === 1 && zoom) {
|
if (!dragging && e.which === 1 && zoom) {
|
||||||
@@ -45,7 +53,16 @@ const mouseup = (e) => {
|
|||||||
|
|
||||||
<svelte:window on:mousemove={mousemove} on:mouseup={mouseup} />
|
<svelte:window on:mousemove={mousemove} on:mouseup={mouseup} />
|
||||||
|
|
||||||
<div bind:this={container} class="container" class:zoom>
|
<div
|
||||||
|
bind:this={container}
|
||||||
|
class="container"
|
||||||
|
class:zoom
|
||||||
|
use:swipe_nav={!zoom}
|
||||||
|
on:style={e => container_style = e.detail}
|
||||||
|
on:prev={() => dispatch("open_sibling", -1)}
|
||||||
|
on:next={() => dispatch("open_sibling", 1)}
|
||||||
|
style={container_style}
|
||||||
|
>
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||||
<img
|
<img
|
||||||
on:dblclick={() => {zoom = !zoom}}
|
on:dblclick={() => {zoom = !zoom}}
|
||||||
|
Reference in New Issue
Block a user