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()}"`);
|
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) {
|
export default WithShell(async function(render) {
|
||||||
const $page = createElement(`<div class="component_page_viewerpage"></div>`);
|
const $page = createElement(`<div class="component_page_viewerpage"></div>`);
|
||||||
|
|
@ -58,7 +68,7 @@ export default WithShell(async function(render) {
|
||||||
// feature: render viewer application
|
// feature: render viewer application
|
||||||
effect(rxjs.of(getConfig("mime", {})).pipe(
|
effect(rxjs.of(getConfig("mime", {})).pipe(
|
||||||
rxjs.map((mimes) => opener(basename(getCurrentPath()), mimes)),
|
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 });
|
module.default(createRender($page), { ...opts, acl$: options(), getFilename, getDownloadUrl });
|
||||||
}))),
|
}))),
|
||||||
rxjs.catchError(ctrlError()),
|
rxjs.catchError(ctrlError()),
|
||||||
|
|
|
||||||
|
|
@ -76,11 +76,8 @@ body:not(.dark-mode) .component_imageviewer .component_image_container .fullscre
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
transition: 0.15s ease min-width;
|
transition: 0.15s ease min-width;
|
||||||
background: #949290;
|
|
||||||
color: var(--dark);
|
|
||||||
}
|
|
||||||
.dark-mode .component_imageviewer .images_aside {
|
|
||||||
background: #f2f2f2;
|
background: #f2f2f2;
|
||||||
|
color: var(--dark);
|
||||||
}
|
}
|
||||||
.component_imageviewer .images_aside.open {
|
.component_imageviewer .images_aside.open {
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
|
|
@ -129,6 +126,12 @@ body:not(.dark-mode) .component_imageviewer .component_image_container .fullscre
|
||||||
height: 18px;
|
height: 18px;
|
||||||
float: right;
|
float: right;
|
||||||
cursor: pointer;
|
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"] {
|
.component_imageviewer .images_aside [data-bind="body"] {
|
||||||
padding: 10px 20px 0px 20px;
|
padding: 10px 20px 0px 20px;
|
||||||
|
|
@ -154,7 +157,7 @@ body:not(.dark-mode) .component_imageviewer .component_image_container .fullscre
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
border-top: 1px solid #ffffff15;
|
border-top: 1px solid #0000000a;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
|
|
@ -163,5 +166,5 @@ body:not(.dark-mode) .component_imageviewer .component_image_container .fullscre
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
.component_imageviewer .images_aside .meta_key .value {
|
.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 t from "../../locales/index.js";
|
||||||
import ctrlError from "../ctrl_error.js";
|
import ctrlError from "../ctrl_error.js";
|
||||||
|
|
||||||
import { transition } from "./common.js";
|
|
||||||
|
|
||||||
import componentMetadata, { init as initMetadata } from "./application_image_metadata.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 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";
|
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="component_image_container">
|
||||||
<div class="images_wrapper">
|
<div class="images_wrapper">
|
||||||
<img class="photo idle hidden" draggable="false" />
|
<img class="photo idle hidden" draggable="false" />
|
||||||
|
<div data-bind="component_navigation"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="images_aside scroll-y"></div>
|
<div class="images_aside scroll-y"></div>
|
||||||
<div class="component_pager hidden"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
render($page);
|
render($page);
|
||||||
transition(qs($page, ".component_image_container"));
|
|
||||||
|
|
||||||
const $imgContainer = qs($page, ".images_wrapper");
|
const $imgContainer = qs($page, ".images_wrapper");
|
||||||
const $photo = qs($page, "img.photo");
|
const $photo = qs($page, "img.photo");
|
||||||
|
|
@ -97,14 +94,14 @@ export default function(render, { getFilename, getDownloadUrl, mime, hasMenubar
|
||||||
return ctrlError()(err);
|
return ctrlError()(err);
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
componentPager(createRender(qs($page, ".component_pager")));
|
componentToolbox(createRender(qs($page, "[data-bind=\"component_navigation\"]")), { $img: $photo });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function init() {
|
export function init() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
loadCSS(import.meta.url, "./application_image.css"),
|
loadCSS(import.meta.url, "./application_image.css"),
|
||||||
loadCSS(import.meta.url, "./component_menubar.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 }];
|
return ["editor", { mime }];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMimeType(file, mimes = {}) {
|
export function getMimeType(file, mimes = {}) {
|
||||||
return mimes[file.split(".").slice(-1)[0].toLowerCase()] || "text/plain";
|
return mimes[file.split(".").slice(-1)[0].toLowerCase()] || "text/plain";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue