mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-06 08:22:24 +01:00
feature (image): add page change & UX improvements
This commit is contained in:
parent
2adad52308
commit
67f3ccc2cb
8 changed files with 147 additions and 89 deletions
|
|
@ -50,6 +50,16 @@ function loadModule(appName) {
|
|||
throw new ApplicationError("Internal Error", `Unknown opener app "${appName}" at "${getCurrentPath()}"`);
|
||||
}
|
||||
};
|
||||
const loadModuleWithMemory = (function() {
|
||||
const memory = {};
|
||||
return function(appName) {
|
||||
if (memory[appName]) return Promise.resolve(memory[appName]);
|
||||
return loadModule(appName).then((module) => {
|
||||
memory[appName] = module;
|
||||
return module;
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
export default WithShell(async function(render) {
|
||||
const $page = createElement(`<div class="component_page_viewerpage"></div>`);
|
||||
|
|
@ -58,7 +68,7 @@ export default WithShell(async function(render) {
|
|||
// feature: render viewer application
|
||||
effect(rxjs.of(getConfig("mime", {})).pipe(
|
||||
rxjs.map((mimes) => opener(basename(getCurrentPath()), mimes)),
|
||||
rxjs.mergeMap(([opener, opts]) => rxjs.from(loadModule(opener)).pipe(rxjs.switchMap(async(module) => {
|
||||
rxjs.mergeMap(([opener, opts]) => rxjs.from(loadModuleWithMemory(opener)).pipe(rxjs.switchMap(async(module) => {
|
||||
module.default(createRender($page), { ...opts, acl$: options(), getFilename, getDownloadUrl });
|
||||
}))),
|
||||
rxjs.catchError(ctrlError()),
|
||||
|
|
|
|||
|
|
@ -76,11 +76,8 @@ body:not(.dark-mode) .component_imageviewer .component_image_container .fullscre
|
|||
z-index: 1;
|
||||
min-width: 0px;
|
||||
transition: 0.15s ease min-width;
|
||||
background: #949290;
|
||||
color: var(--dark);
|
||||
}
|
||||
.dark-mode .component_imageviewer .images_aside {
|
||||
background: #f2f2f2;
|
||||
color: var(--dark);
|
||||
}
|
||||
.component_imageviewer .images_aside.open {
|
||||
min-width: 300px;
|
||||
|
|
@ -129,6 +126,12 @@ body:not(.dark-mode) .component_imageviewer .component_image_container .fullscre
|
|||
height: 18px;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
margin: -5px -5px 0 0;
|
||||
}
|
||||
.component_imageviewer .images_aside .header .component_icon:hover {
|
||||
background: #0000000a;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.component_imageviewer .images_aside [data-bind="body"] {
|
||||
padding: 10px 20px 0px 20px;
|
||||
|
|
@ -154,7 +157,7 @@ body:not(.dark-mode) .component_imageviewer .component_image_container .fullscre
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 5px 0;
|
||||
border-top: 1px solid #ffffff15;
|
||||
border-top: 1px solid #0000000a;
|
||||
padding-top: 5px;
|
||||
text-align: right;
|
||||
font-size: 0.85em;
|
||||
|
|
@ -163,5 +166,5 @@ body:not(.dark-mode) .component_imageviewer .component_image_container .fullscre
|
|||
margin-right: 5px;
|
||||
}
|
||||
.component_imageviewer .images_aside .meta_key .value {
|
||||
color: var(--bg-color);
|
||||
color: var(--light);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,11 +14,9 @@ import notification from "../../components/notification.js";
|
|||
import t from "../../locales/index.js";
|
||||
import ctrlError from "../ctrl_error.js";
|
||||
|
||||
import { transition } from "./common.js";
|
||||
|
||||
import componentMetadata, { init as initMetadata } from "./application_image_metadata.js";
|
||||
import componentToolbox, { init as initToolbox } from "./application_image_toolbox.js";
|
||||
import ctrlDownloader, { init as initDownloader } from "./application_downloader.js";
|
||||
import componentPager, { init as initPager } from "./component_pager.js";
|
||||
|
||||
import { renderMenubar, buttonDownload, buttonFullscreen } from "./component_menubar.js";
|
||||
|
||||
|
|
@ -33,14 +31,13 @@ export default function(render, { getFilename, getDownloadUrl, mime, hasMenubar
|
|||
<div class="component_image_container">
|
||||
<div class="images_wrapper">
|
||||
<img class="photo idle hidden" draggable="false" />
|
||||
<div data-bind="component_navigation"></div>
|
||||
</div>
|
||||
<div class="images_aside scroll-y"></div>
|
||||
<div class="component_pager hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
render($page);
|
||||
transition(qs($page, ".component_image_container"));
|
||||
|
||||
const $imgContainer = qs($page, ".images_wrapper");
|
||||
const $photo = qs($page, "img.photo");
|
||||
|
|
@ -97,14 +94,14 @@ export default function(render, { getFilename, getDownloadUrl, mime, hasMenubar
|
|||
return ctrlError()(err);
|
||||
}),
|
||||
));
|
||||
componentPager(createRender(qs($page, ".component_pager")));
|
||||
componentToolbox(createRender(qs($page, "[data-bind=\"component_navigation\"]")), { $img: $photo });
|
||||
}
|
||||
|
||||
export function init() {
|
||||
return Promise.all([
|
||||
loadCSS(import.meta.url, "./application_image.css"),
|
||||
loadCSS(import.meta.url, "./component_menubar.css"),
|
||||
initPager(), initMetadata(),
|
||||
initToolbox(), initMetadata(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
31
public/assets/pages/viewerpage/application_image_toolbox.css
Normal file
31
public/assets/pages/viewerpage/application_image_toolbox.css
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/* PAGINATION */
|
||||
.component_pager {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
opacity: 0.2;
|
||||
margin: auto;
|
||||
padding: 15px;
|
||||
color: #f2f2f2;
|
||||
z-index: 1;
|
||||
}
|
||||
.component_pager.left { left: 0; padding-right: 150px; }
|
||||
.component_pager.right { right: 0; padding-left: 150px; }
|
||||
.component_pager:hover {
|
||||
opacity: 0.9;
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
.component_pager a { margin: auto; }
|
||||
.component_pager a svg {
|
||||
transition: opacity 0.2s ease;
|
||||
width: 37px;
|
||||
padding: 5px;
|
||||
background: var(--dark);
|
||||
border-radius: 50%;
|
||||
margin: auto;
|
||||
opacity: 0.75;
|
||||
}
|
||||
.component_pager a svg:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
91
public/assets/pages/viewerpage/application_image_toolbox.js
Normal file
91
public/assets/pages/viewerpage/application_image_toolbox.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { createFragment } from "../../lib/skeleton/index.js";
|
||||
import rxjs from "../../lib/rx.js";
|
||||
import { qs } from "../../lib/dom.js";
|
||||
import { join } from "../../lib/path.js";
|
||||
import { animate, slideXOut } from "../../lib/animate.js";
|
||||
import { loadCSS } from "../../helpers/loader.js";
|
||||
import { get as getConfig } from "../../model/config.js";
|
||||
|
||||
import { getCurrentPath, getFilename } from "./common.js";
|
||||
import { getMimeType } from "./mimetype.js";
|
||||
import fscache from "../filespage/cache.js";
|
||||
import { sort } from "../filespage/helper.js";
|
||||
import { getState$ as getParams$, init as initParams } from "../filespage/state_config.js";
|
||||
|
||||
export default async function(render, { $img }) {
|
||||
const lsCache = await fscache().get(join(location, getCurrentPath() + "/../"));
|
||||
if (!lsCache) return;
|
||||
const params = await getParams$().pipe(rxjs.first()).toPromise();
|
||||
const files = sort(lsCache.files, params["sort"], params["order"]);
|
||||
const currentFilename = getFilename();
|
||||
const mimeTypes = getConfig("mime", {});
|
||||
const state = {
|
||||
prev: null,
|
||||
curr: null,
|
||||
next: null,
|
||||
length: 0,
|
||||
};
|
||||
for (let i=0; i<files.length; i++) {
|
||||
const filename = files[i].name;
|
||||
if (!getMimeType(filename, mimeTypes).startsWith("image/")) {
|
||||
continue;
|
||||
}
|
||||
state.length += 1;
|
||||
if (currentFilename === filename) {
|
||||
state.curr = i;
|
||||
} else if (state.curr === null) {
|
||||
state.prev = i;
|
||||
} else {
|
||||
state.next = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (state.length <= 1) return;
|
||||
const $page = createFragment(`
|
||||
<div class="component_pager left hidden">
|
||||
<a data-link>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="15 18 9 12 15 6"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
<div class="component_pager right hidden">
|
||||
<a>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="9 18 15 12 9 6"></polyline>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
`);
|
||||
if (state.prev !== null) updateDOM({
|
||||
$el: $page.children[0],
|
||||
name: files[state.prev].name,
|
||||
$img,
|
||||
});
|
||||
if (state.next !== null) updateDOM({
|
||||
$el: $page.children[1],
|
||||
name: files[state.next].name,
|
||||
$img,
|
||||
});
|
||||
render($page);
|
||||
}
|
||||
|
||||
function updateDOM({ $el, name, $img }) {
|
||||
const $link = qs($el, "a");
|
||||
$link.onclick = async(e) => {
|
||||
if (e.target.hasAttribute("data-link")) return;
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
await animate($img, { keyframes: slideXOut(-10), time: 200 });
|
||||
$link.setAttribute("data-link", "true");
|
||||
$link.click();
|
||||
};
|
||||
$link.setAttribute("href", "/view" + join(location, getCurrentPath() + "/../" + name));
|
||||
$el.classList.remove("hidden");
|
||||
}
|
||||
|
||||
export function init() {
|
||||
return Promise.all([
|
||||
loadCSS(import.meta.url, "./application_image_toolbox.css"),
|
||||
initParams(),
|
||||
]);
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
.component_pager {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
.component_pager .wrapper {
|
||||
text-align: center;
|
||||
color: var(--super-light);
|
||||
margin: 0 auto;
|
||||
padding: 15px 0;
|
||||
text-shadow: 0px 0px 2px var(--dark);
|
||||
}
|
||||
.component_pager .wrapper > span {
|
||||
display: inline-block;
|
||||
background: var(--dark);
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
box-shadow: rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px, rgba(0, 0, 0, 0.2) 0px 2px 4px -1px;
|
||||
}
|
||||
.component_pager .wrapper > span .pager {
|
||||
line-height: 22px;
|
||||
}
|
||||
.component_pager .wrapper > span .pager .separator {
|
||||
padding: 0 5px;
|
||||
}
|
||||
.component_pager .wrapper > span .component_icon {
|
||||
width: 24px;
|
||||
}
|
||||
.component_pager .wrapper > span form {
|
||||
display: inline-block;
|
||||
}
|
||||
.component_pager .wrapper > span form input[type="number"] {
|
||||
padding: 0;
|
||||
text-align: right;
|
||||
background: inherit;
|
||||
border: none;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
.component_pager .wrapper > span form input[type="number"]::-webkit-inner-spin-button, .component_pager .wrapper > span form input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import { createElement } from "../../lib/skeleton/index.js";
|
||||
import { loadCSS } from "../../helpers/loader.js";
|
||||
|
||||
export default function(render) {
|
||||
render(createElement(`
|
||||
<div class="wrapper no-select">
|
||||
<span>
|
||||
<a href="/view/Documents/assets/background.png">
|
||||
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6I2YyZjJmMjtmaWxsLW9wYWNpdHk6MTtzdHJva2Utd2lkdGg6MS41MTE4MTEwMjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZSIgZD0ibSAxNiw3LjE2IC00LjU4LDQuNTkgNC41OCw0LjU5IC0xLjQxLDEuNDEgLTYsLTYgNiwtNiB6IiAvPgogIDxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wLS4yNWgyNHYyNEgweiIgLz4KPC9zdmc+Cg==" alt="arrow_left_white">
|
||||
</a>
|
||||
<label class="pager">
|
||||
<form>
|
||||
<input class="prevent" type="number" value="2" style="width: 12px;">
|
||||
</form>
|
||||
<span class="separator">/</span>
|
||||
<span>2</span>
|
||||
</label>
|
||||
<a href="/view/Documents/assets/background.png">
|
||||
<img class="component_icon" draggable="false" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+CiAgPHBhdGggc3R5bGU9ImZpbGw6I2YyZjJmMjtmaWxsLW9wYWNpdHk6MTtzdHJva2Utd2lkdGg6MS41MTE4MTEwMjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZSIgZD0iTTguNTkgMTYuMzRsNC41OC00LjU5LTQuNTgtNC41OUwxMCA1Ljc1bDYgNi02IDZ6IiAvPgogIDxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wLS4yNWgyNHYyNEgweiIgLz4KPC9zdmc+Cg==" alt="arrow_right_white">
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
`));
|
||||
}
|
||||
|
||||
export function init() {
|
||||
return loadCSS(import.meta.url, "./component_pager.css");
|
||||
}
|
||||
|
|
@ -41,6 +41,6 @@ export function opener(file = "", mimes) {
|
|||
return ["editor", { mime }];
|
||||
}
|
||||
|
||||
function getMimeType(file, mimes = {}) {
|
||||
export function getMimeType(file, mimes = {}) {
|
||||
return mimes[file.split(".").slice(-1)[0].toLowerCase()] || "text/plain";
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue