commit
3e3361ddbc
|
@ -0,0 +1,5 @@
|
|||
yarn.lock
|
||||
node_modules
|
||||
.parcel-cache
|
||||
dist
|
||||
public
|
|
@ -0,0 +1,16 @@
|
|||
#! /usr/bin/bash
|
||||
|
||||
rm -rf dist
|
||||
|
||||
cd srv
|
||||
yarn build --no-source-maps > /dev/null 2>&1
|
||||
|
||||
cd ../web
|
||||
yarn build --no-source-maps > /dev/null 2>&1
|
||||
|
||||
cd ..
|
||||
mv srv/dist dist -f
|
||||
mv web/dist dist/web -f
|
||||
cp srv/node_modules dist/node_modules -rf
|
||||
cp srv/public dist/public -rf
|
||||
cp web/static/* dist/web/ -rf
|
|
@ -0,0 +1,7 @@
|
|||
#! /usr/bin/bash
|
||||
|
||||
cd web
|
||||
yarn dev&
|
||||
sleep 1
|
||||
cd ../srv
|
||||
yarn dev
|
|
@ -0,0 +1,9 @@
|
|||
#! /usr/bin/bash
|
||||
|
||||
if [ -s dist ]; then
|
||||
cd dist
|
||||
node server.js
|
||||
else
|
||||
./build
|
||||
$0
|
||||
fi
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "galerie-api",
|
||||
"source": "src/index.ts",
|
||||
"main": "dist/server.js",
|
||||
"types": "dist/types.d.ts",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "npx parcel watch & npx nodemon dist/server.js",
|
||||
"build": "npx parcel build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@parcel/packager-ts": "2.8.0",
|
||||
"@parcel/transformer-typescript-types": "2.8.0",
|
||||
"parcel": "latest",
|
||||
"typescript": ">=3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.8",
|
||||
"body-parser": "^1.20.1",
|
||||
"express": "^4.18.2",
|
||||
"jquery": "^3.6.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"license": "GPL-3.0-only",
|
||||
"sourceMap": false
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
Copyright 2022 0xf8
|
||||
|
||||
This file is part of galerie
|
||||
|
||||
Galerie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
Galerie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License along with Galerie. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import express from "express";
|
||||
import bodyparser from "body-parser";
|
||||
import sync from "./sync";
|
||||
|
||||
const app = express();
|
||||
const port = 8856;
|
||||
|
||||
app.use(bodyparser.text())
|
||||
app.get("/sync", sync);
|
||||
app.post("/sync", sync);
|
||||
|
||||
app.use("/img/", express.static("public/"));
|
||||
app.use("/", express.static("web/"));
|
||||
|
||||
app.listen(port, (err?: any) => {
|
||||
if (err) {
|
||||
return console.error(err);
|
||||
}
|
||||
return console.log(`Listening @ http://localhost:${port}`);
|
||||
});
|
|
@ -0,0 +1,81 @@
|
|||
import { createHash } from "crypto";
|
||||
import { Request, Response } from "express";
|
||||
import { readdirSync, existsSync, mkdirSync, statSync, readFileSync } from "fs";
|
||||
import path from "path";
|
||||
|
||||
class Sync {
|
||||
constructor() {
|
||||
this.root = path.join(process.cwd(), "public");
|
||||
|
||||
if (!existsSync(this.root)) {
|
||||
mkdirSync(this.root);
|
||||
}
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
async update() {
|
||||
return new Promise<void>((res, rej) => {
|
||||
this.files = readdirSync(this.root).filter(async (v) => {
|
||||
let f = path.join(this.root, v);
|
||||
if (existsSync(f)) {
|
||||
let s = statSync(f);
|
||||
return s.isFile() && s.size > 1;
|
||||
} else {
|
||||
await this.update();
|
||||
res();
|
||||
}
|
||||
})
|
||||
|
||||
let c = createHash("sha1");
|
||||
|
||||
this.files.forEach(async (v) => {
|
||||
let f = path.join(this.root, v);
|
||||
|
||||
if (existsSync(f))
|
||||
c.update(readFileSync(f))
|
||||
else {
|
||||
await this.update();
|
||||
res();
|
||||
}
|
||||
});
|
||||
|
||||
this.checksum = c.digest("hex");
|
||||
res();
|
||||
});
|
||||
}
|
||||
|
||||
root: string;
|
||||
files: string[];
|
||||
checksum: string;
|
||||
}
|
||||
|
||||
const state: Sync = new Sync();
|
||||
|
||||
let sync = async (req: Request, res: Response) => {
|
||||
switch (req.method) {
|
||||
case "GET": { // "Update me"
|
||||
await state.update();
|
||||
res.write(JSON.stringify({
|
||||
checksum: state.checksum,
|
||||
files: state.files
|
||||
}));
|
||||
break;
|
||||
}
|
||||
|
||||
case "POST": { // "Am I up to date?"
|
||||
await state.update();
|
||||
let checksum = req.body;
|
||||
|
||||
if (checksum == state.checksum)
|
||||
res.write('{"valid": true}');
|
||||
else
|
||||
res.write('{"valid": false}');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
res.end();
|
||||
}
|
||||
|
||||
export default sync;
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"alwaysStrict": true,
|
||||
"target": "ES6",
|
||||
"module": "CommonJS",
|
||||
"removeComments": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"node_modules/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "galerie-web",
|
||||
"source": [
|
||||
"www/index.html",
|
||||
"www/gallery.html",
|
||||
"www/settings.html"
|
||||
],
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "npx parcel serve",
|
||||
"build": "npx parcel build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@parcel/transformer-sass": "2.8.0",
|
||||
"parcel": "latest",
|
||||
"process": "^0.11.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/jquery": "^3.5.14",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/react": "^18.0.25",
|
||||
"@types/react-dom": "^18.0.8",
|
||||
"express": "^4.18.2",
|
||||
"jquery": "^3.6.1",
|
||||
"nodemon": "^2.0.20",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"license": "GPL-3.0-only",
|
||||
"sourceMap": false
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 318 B |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"alwaysStrict": true,
|
||||
"target": "ES6",
|
||||
"module": "CommonJS",
|
||||
"removeComments": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"*": [
|
||||
"node_modules/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import ReactDOM from "react-dom/client";
|
||||
import React from "react";
|
||||
|
||||
import { navbar } from "./navbar";
|
||||
|
||||
export default function App(Root) {
|
||||
const root = ReactDOM.createRoot(document.querySelector("root"));
|
||||
root.render(<>{ navbar() } { Root() }</>);
|
||||
return root;
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
// animating icons
|
||||
// --------------------------
|
||||
|
||||
.#{$fa-css-prefix}-beat {
|
||||
animation-name: #{$fa-css-prefix}-beat;
|
||||
animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s);
|
||||
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
|
||||
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s);
|
||||
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, ease-in-out);
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-bounce {
|
||||
animation-name: #{$fa-css-prefix}-bounce;
|
||||
animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s);
|
||||
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
|
||||
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s);
|
||||
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(0.280, 0.840, 0.420, 1));
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-fade {
|
||||
animation-name: #{$fa-css-prefix}-fade;
|
||||
animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s);
|
||||
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
|
||||
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s);
|
||||
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(.4,0,.6,1));
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-beat-fade {
|
||||
animation-name: #{$fa-css-prefix}-beat-fade;
|
||||
animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s);
|
||||
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
|
||||
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s);
|
||||
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, cubic-bezier(.4,0,.6,1));
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-flip {
|
||||
animation-name: #{$fa-css-prefix}-flip;
|
||||
animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s);
|
||||
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
|
||||
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s);
|
||||
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, ease-in-out);
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-shake {
|
||||
animation-name: #{$fa-css-prefix}-shake;
|
||||
animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s);
|
||||
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
|
||||
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s);
|
||||
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, linear);
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-spin {
|
||||
animation-name: #{$fa-css-prefix}-spin;
|
||||
animation-delay: var(--#{$fa-css-prefix}-animation-delay, 0s);
|
||||
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
|
||||
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 2s);
|
||||
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, linear);
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-spin-reverse {
|
||||
--#{$fa-css-prefix}-animation-direction: reverse;
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-pulse,
|
||||
.#{$fa-css-prefix}-spin-pulse {
|
||||
animation-name: #{$fa-css-prefix}-spin;
|
||||
animation-direction: var(--#{$fa-css-prefix}-animation-direction, normal);
|
||||
animation-duration: var(--#{$fa-css-prefix}-animation-duration, 1s);
|
||||
animation-iteration-count: var(--#{$fa-css-prefix}-animation-iteration-count, infinite);
|
||||
animation-timing-function: var(--#{$fa-css-prefix}-animation-timing, steps(8));
|
||||
}
|
||||
|
||||
// if agent or operating system prefers reduced motion, disable animations
|
||||
// see: https://www.smashingmagazine.com/2020/09/design-reduced-motion-sensitivities/
|
||||
// see: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.#{$fa-css-prefix}-beat,
|
||||
.#{$fa-css-prefix}-bounce,
|
||||
.#{$fa-css-prefix}-fade,
|
||||
.#{$fa-css-prefix}-beat-fade,
|
||||
.#{$fa-css-prefix}-flip,
|
||||
.#{$fa-css-prefix}-pulse,
|
||||
.#{$fa-css-prefix}-shake,
|
||||
.#{$fa-css-prefix}-spin,
|
||||
.#{$fa-css-prefix}-spin-pulse {
|
||||
animation-delay: -1ms;
|
||||
animation-duration: 1ms;
|
||||
animation-iteration-count: 1;
|
||||
transition-delay: 0s;
|
||||
transition-duration: 0s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes #{$fa-css-prefix}-beat {
|
||||
0%, 90% { transform: scale(1); }
|
||||
45% { transform: scale(var(--#{$fa-css-prefix}-beat-scale, 1.25)); }
|
||||
}
|
||||
|
||||
@keyframes #{$fa-css-prefix}-bounce {
|
||||
0% { transform: scale(1,1) translateY(0); }
|
||||
10% { transform: scale(var(--#{$fa-css-prefix}-bounce-start-scale-x, 1.1),var(--#{$fa-css-prefix}-bounce-start-scale-y, 0.9)) translateY(0); }
|
||||
30% { transform: scale(var(--#{$fa-css-prefix}-bounce-jump-scale-x, 0.9),var(--#{$fa-css-prefix}-bounce-jump-scale-y, 1.1)) translateY(var(--#{$fa-css-prefix}-bounce-height, -0.5em)); }
|
||||
50% { transform: scale(var(--#{$fa-css-prefix}-bounce-land-scale-x, 1.05),var(--#{$fa-css-prefix}-bounce-land-scale-y, 0.95)) translateY(0); }
|
||||
57% { transform: scale(1,1) translateY(var(--#{$fa-css-prefix}-bounce-rebound, -0.125em)); }
|
||||
64% { transform: scale(1,1) translateY(0); }
|
||||
100% { transform: scale(1,1) translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes #{$fa-css-prefix}-fade {
|
||||
50% { opacity: var(--#{$fa-css-prefix}-fade-opacity, 0.4); }
|
||||
}
|
||||
|
||||
@keyframes #{$fa-css-prefix}-beat-fade {
|
||||
0%, 100% {
|
||||
opacity: var(--#{$fa-css-prefix}-beat-fade-opacity, 0.4);
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(var(--#{$fa-css-prefix}-beat-fade-scale, 1.125));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes #{$fa-css-prefix}-flip {
|
||||
50% {
|
||||
transform: rotate3d(var(--#{$fa-css-prefix}-flip-x, 0), var(--#{$fa-css-prefix}-flip-y, 1), var(--#{$fa-css-prefix}-flip-z, 0), var(--#{$fa-css-prefix}-flip-angle, -180deg));
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes #{$fa-css-prefix}-shake {
|
||||
0% { transform: rotate(-15deg); }
|
||||
4% { transform: rotate(15deg); }
|
||||
8%, 24% { transform: rotate(-18deg); }
|
||||
12%, 28% { transform: rotate(18deg); }
|
||||
16% { transform: rotate(-22deg); }
|
||||
20% { transform: rotate(22deg); }
|
||||
32% { transform: rotate(-12deg); }
|
||||
36% { transform: rotate(12deg); }
|
||||
40%, 100% { transform: rotate(0deg); }
|
||||
}
|
||||
|
||||
@keyframes #{$fa-css-prefix}-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// bordered + pulled icons
|
||||
// -------------------------
|
||||
|
||||
.#{$fa-css-prefix}-border {
|
||||
border-color: var(--#{$fa-css-prefix}-border-color, #{$fa-border-color});
|
||||
border-radius: var(--#{$fa-css-prefix}-border-radius, #{$fa-border-radius});
|
||||
border-style: var(--#{$fa-css-prefix}-border-style, #{$fa-border-style});
|
||||
border-width: var(--#{$fa-css-prefix}-border-width, #{$fa-border-width});
|
||||
padding: var(--#{$fa-css-prefix}-border-padding, #{$fa-border-padding});
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-pull-left {
|
||||
float: left;
|
||||
margin-right: var(--#{$fa-css-prefix}-pull-margin, #{$fa-pull-margin});
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-pull-right {
|
||||
float: right;
|
||||
margin-left: var(--#{$fa-css-prefix}-pull-margin, #{$fa-pull-margin});
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// base icon class definition
|
||||
// -------------------------
|
||||
|
||||
.#{$fa-css-prefix} {
|
||||
font-family: var(--#{$fa-css-prefix}-style-family, '#{$fa-style-family}');
|
||||
font-weight: var(--#{$fa-css-prefix}-style, #{$fa-style});
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix},
|
||||
.#{$fa-css-prefix}-classic,
|
||||
.#{$fa-css-prefix}-sharp,
|
||||
.fas,
|
||||
.#{$fa-css-prefix}-solid,
|
||||
.far,
|
||||
.#{$fa-css-prefix}-regular,
|
||||
.fab,
|
||||
.#{$fa-css-prefix}-brands {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: var(--#{$fa-css-prefix}-display, #{$fa-display});
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
line-height: 1;
|
||||
text-rendering: auto;
|
||||
}
|
||||
|
||||
.fas,
|
||||
.#{$fa-css-prefix}-classic,
|
||||
.#{$fa-css-prefix}-solid,
|
||||
.far,
|
||||
.#{$fa-css-prefix}-regular {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
}
|
||||
|
||||
.fab,
|
||||
.#{$fa-css-prefix}-brands {
|
||||
font-family: 'Font Awesome 6 Brands';
|
||||
}
|
||||
|
||||
|
||||
%fa-icon {
|
||||
@include fa-icon;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// fixed-width icons
|
||||
// -------------------------
|
||||
|
||||
.#{$fa-css-prefix}-fw {
|
||||
text-align: center;
|
||||
width: $fa-fw-width;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
// functions
|
||||
// --------------------------
|
||||
|
||||
// fa-content: convenience function used to set content property
|
||||
@function fa-content($fa-var) {
|
||||
@return unquote("\"#{ $fa-var }\"");
|
||||
}
|
||||
|
||||
// fa-divide: Originally obtained from the Bootstrap https://github.com/twbs/bootstrap
|
||||
//
|
||||
// Licensed under: The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2011-2021 Twitter, Inc.
|
||||
// Copyright (c) 2011-2021 The Bootstrap Authors
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
@function fa-divide($dividend, $divisor, $precision: 10) {
|
||||
$sign: if($dividend > 0 and $divisor > 0, 1, -1);
|
||||
$dividend: abs($dividend);
|
||||
$divisor: abs($divisor);
|
||||
$quotient: 0;
|
||||
$remainder: $dividend;
|
||||
@if $dividend == 0 {
|
||||
@return 0;
|
||||
}
|
||||
@if $divisor == 0 {
|
||||
@error "Cannot divide by 0";
|
||||
}
|
||||
@if $divisor == 1 {
|
||||
@return $dividend;
|
||||
}
|
||||
@while $remainder >= $divisor {
|
||||
$quotient: $quotient + 1;
|
||||
$remainder: $remainder - $divisor;
|
||||
}
|
||||
@if $remainder > 0 and $precision > 0 {
|
||||
$remainder: fa-divide($remainder * 10, $divisor, $precision - 1) * .1;
|
||||
}
|
||||
@return ($quotient + $remainder) * $sign;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// specific icon class definition
|
||||
// -------------------------
|
||||
|
||||
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
|
||||
readers do not read off random characters that represent icons */
|
||||
|
||||
@each $name, $icon in $fa-icons {
|
||||
.#{$fa-css-prefix}-#{$name}::before { content: unquote("\"#{ $icon }\""); }
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// icons in a list
|
||||
// -------------------------
|
||||
|
||||
.#{$fa-css-prefix}-ul {
|
||||
list-style-type: none;
|
||||
margin-left: var(--#{$fa-css-prefix}-li-margin, #{$fa-li-margin});
|
||||
padding-left: 0;
|
||||
|
||||
> li { position: relative; }
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-li {
|
||||
left: calc(var(--#{$fa-css-prefix}-li-width, #{$fa-li-width}) * -1);
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: var(--#{$fa-css-prefix}-li-width, #{$fa-li-width});
|
||||
line-height: inherit;
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
// mixins
|
||||
// --------------------------
|
||||
|
||||
// base rendering for an icon
|
||||
@mixin fa-icon {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
font-weight: normal;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
// sets relative font-sizing and alignment (in _sizing)
|
||||
@mixin fa-size ($font-size) {
|
||||
font-size: fa-divide($font-size, $fa-size-scale-base) * 1em; // converts step in sizing scale into an em-based value that's relative to the scale's base
|
||||
line-height: fa-divide(1, $font-size) * 1em; // sets the line-height of the icon back to that of it's parent
|
||||
vertical-align: (fa-divide(6, $font-size) - fa-divide(3, 8)) * 1em; // vertically centers the icon taking into account the surrounding text's descender
|
||||
}
|
||||
|
||||
// only display content to screen readers
|
||||
// see: https://www.a11yproject.com/posts/2013-01-11-how-to-hide-content/
|
||||
// see: https://hugogiraudel.com/2016/10/13/css-hide-and-seek/
|
||||
@mixin fa-sr-only() {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
// use in conjunction with .sr-only to only display content when it's focused
|
||||
@mixin fa-sr-only-focusable() {
|
||||
&:not(:focus) {
|
||||
@include fa-sr-only();
|
||||
}
|
||||
}
|
||||
|
||||
// sets a specific icon family to use alongside style + icon mixins
|
||||
|
||||
// convenience mixins for declaring pseudo-elements by CSS variable,
|
||||
// including all style-specific font properties, and both the ::before
|
||||
// and ::after elements in the duotone case.
|
||||
@mixin fa-icon-solid($fa-var) {
|
||||
@extend %fa-icon;
|
||||
@extend .fa-solid;
|
||||
|
||||
&::before {
|
||||
content: unquote("\"#{ $fa-var }\"");
|
||||
}
|
||||
}
|
||||
|
||||
@mixin fa-icon-regular($fa-var) {
|
||||
@extend %fa-icon;
|
||||
@extend .fa-regular;
|
||||
|
||||
&::before {
|
||||
content: unquote("\"#{ $fa-var }\"");
|
||||
}
|
||||
}
|
||||
|
||||
@mixin fa-icon-brands($fa-var) {
|
||||
@extend %fa-icon;
|
||||
@extend .fa-brands;
|
||||
|
||||
&::before {
|
||||
content: unquote("\"#{ $fa-var }\"");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// rotating + flipping icons
|
||||
// -------------------------
|
||||
|
||||
.#{$fa-css-prefix}-rotate-90 {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-rotate-180 {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-rotate-270 {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-flip-horizontal {
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-flip-vertical {
|
||||
transform: scale(1, -1);
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-flip-both,
|
||||
.#{$fa-css-prefix}-flip-horizontal.#{$fa-css-prefix}-flip-vertical {
|
||||
transform: scale(-1, -1);
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-rotate-by {
|
||||
transform: rotate(var(--#{$fa-css-prefix}-rotate-angle, none));
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// screen-reader utilities
|
||||
// -------------------------
|
||||
|
||||
// only display content to screen readers
|
||||
.sr-only,
|
||||
.#{$fa-css-prefix}-sr-only {
|
||||
@include fa-sr-only;
|
||||
}
|
||||
|
||||
// use in conjunction with .sr-only to only display content when it's focused
|
||||
.sr-only-focusable,
|
||||
.#{$fa-css-prefix}-sr-only-focusable {
|
||||
@include fa-sr-only-focusable;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
|||
// sizing icons
|
||||
// -------------------------
|
||||
|
||||
// literal magnification scale
|
||||
@for $i from 1 through 10 {
|
||||
.#{$fa-css-prefix}-#{$i}x {
|
||||
font-size: $i * 1em;
|
||||
}
|
||||
}
|
||||
|
||||
// step-based scale (with alignment)
|
||||
@each $size, $value in $fa-sizes {
|
||||
.#{$fa-css-prefix}-#{$size} {
|
||||
@include fa-size($value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// stacking icons
|
||||
// -------------------------
|
||||
|
||||
.#{$fa-css-prefix}-stack {
|
||||
display: inline-block;
|
||||
height: 2em;
|
||||
line-height: 2em;
|
||||
position: relative;
|
||||
vertical-align: $fa-stack-vertical-align;
|
||||
width: $fa-stack-width;
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-stack-1x,
|
||||
.#{$fa-css-prefix}-stack-2x {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
z-index: var(--#{$fa-css-prefix}-stack-z-index, #{$fa-stack-z-index});
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-stack-1x {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-stack-2x {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.#{$fa-css-prefix}-inverse {
|
||||
color: var(--#{$fa-css-prefix}-inverse, #{$fa-inverse});
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,30 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2022 Fonticons, Inc.
|
||||
*/
|
||||
@import 'functions';
|
||||
@import 'variables';
|
||||
|
||||
:root, :host {
|
||||
--#{$fa-css-prefix}-style-family-brands: 'Font Awesome 6 Brands';
|
||||
--#{$fa-css-prefix}-font-brands: normal 400 1em/1 'Font Awesome 6 Brands';
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Brands';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: $fa-font-display;
|
||||
src: url('#{$fa-font-path}/fa-brands-400.woff2') format('woff2'),
|
||||
url('#{$fa-font-path}/fa-brands-400.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.fab,
|
||||
.#{$fa-css-prefix}-brands {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
@each $name, $icon in $fa-brand-icons {
|
||||
.#{$fa-css-prefix}-#{$name}:before { content: unquote("\"#{ $icon }\""); }
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,21 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2022 Fonticons, Inc.
|
||||
*/
|
||||
// Font Awesome core compile (Web Fonts-based)
|
||||
// -------------------------
|
||||
|
||||
@import 'functions';
|
||||
@import 'variables';
|
||||
@import 'mixins';
|
||||
@import 'core';
|
||||
@import 'sizing';
|
||||
@import 'fixed-width';
|
||||
@import 'list';
|
||||
@import 'bordered-pulled';
|
||||
@import 'animated';
|
||||
@import 'rotated-flipped';
|
||||
@import 'stacked';
|
||||
@import 'icons';
|
||||
@import 'screen-reader';
|
|
@ -0,0 +1,26 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2022 Fonticons, Inc.
|
||||
*/
|
||||
@import 'functions';
|
||||
@import 'variables';
|
||||
|
||||
:root, :host {
|
||||
--#{$fa-css-prefix}-style-family-classic: '#{ $fa-style-family }';
|
||||
--#{$fa-css-prefix}-font-regular: normal 400 1em/1 '#{ $fa-style-family }';
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: $fa-font-display;
|
||||
src: url('#{$fa-font-path}/fa-regular-400.woff2') format('woff2'),
|
||||
url('#{$fa-font-path}/fa-regular-400.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.far,
|
||||
.#{$fa-css-prefix}-regular {
|
||||
font-weight: 400;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2022 Fonticons, Inc.
|
||||
*/
|
||||
@import 'functions';
|
||||
@import 'variables';
|
||||
|
||||
:root, :host {
|
||||
--#{$fa-css-prefix}-style-family-classic: '#{ $fa-style-family }';
|
||||
--#{$fa-css-prefix}-font-solid: normal 900 1em/1 '#{ $fa-style-family }';
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 6 Free';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: $fa-font-display;
|
||||
src: url('#{$fa-font-path}/fa-solid-900.woff2') format('woff2'),
|
||||
url('#{$fa-font-path}/fa-solid-900.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.fas,
|
||||
.#{$fa-css-prefix}-solid {
|
||||
font-weight: 900;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
/*!
|
||||
* Font Awesome Free 6.2.0 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
* Copyright 2022 Fonticons, Inc.
|
||||
*/
|
||||
// V4 shims compile (Web Fonts-based)
|
||||
// -------------------------
|
||||
|
||||
@import 'functions';
|
||||
@import 'variables';
|
||||
@import 'shims';
|
|
@ -0,0 +1,27 @@
|
|||
<!--
|
||||
Copyright 2022 0xf8
|
||||
|
||||
This file is part of Galerie
|
||||
|
||||
Galerie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
Galerie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License along with Galerie. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Galerie</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="gallery.html.scss" type="text/css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<root>
|
||||
|
||||
</root>
|
||||
<script type="module" src="gallery.html.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,124 @@
|
|||
|
||||
@import url("style.scss");
|
||||
|
||||
span#title {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#display {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-basis: auto;
|
||||
flex-wrap: wrap;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
z-index: 1;
|
||||
|
||||
overflow: scroll;
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
|
||||
img.resource,
|
||||
video.resource {
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
height: 15em;
|
||||
width: auto;
|
||||
margin: 1em 0.5em;
|
||||
}
|
||||
|
||||
.resourceh:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
|
||||
z-index: 999;
|
||||
|
||||
.popout {
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
|
||||
align-self: center;
|
||||
|
||||
height: 98vh;
|
||||
width: 98vw;
|
||||
}
|
||||
|
||||
.contextmenu {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
padding: .4em 1em;
|
||||
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
|
||||
border: 2px rgba(0, 0, 0, 0.9) solid;
|
||||
border-radius: 8px;
|
||||
|
||||
max-width: 18vw;
|
||||
|
||||
height: max-content;
|
||||
width: max-content;
|
||||
|
||||
hr {
|
||||
width: 5em;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
:first-child {
|
||||
font-weight: 600;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
:last-child {
|
||||
margin-top: .4em;
|
||||
}
|
||||
|
||||
.option {
|
||||
font-size: .9em;
|
||||
|
||||
width: fit-content;
|
||||
height: auto;
|
||||
|
||||
border: 3px transparent solid;
|
||||
background: transparent;
|
||||
color: white;
|
||||
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.option:hover {
|
||||
color: black;
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
Copyright 2022 0xf8
|
||||
|
||||
This file is part of Galerie
|
||||
|
||||
Galerie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
Galerie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License along with Galerie. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import App from "./app";
|
||||
import React from "react";
|
||||
|
||||
import jQuery from "jquery";
|
||||
const $ = jQuery;
|
||||
|
||||
import Settings, { apiUrl } from "./settings";
|
||||
|
||||
const settings = new Settings();
|
||||
|
||||
let global_ActiveContextMenu: ContextMenu = null;
|
||||
let global_ActivePopout: Popout = null;
|
||||
|
||||
|
||||
// ===================
|
||||
// === ContextMenu ===
|
||||
// ===================
|
||||
|
||||
class ContextMenu {
|
||||
public menu: JQuery<HTMLElement>;
|
||||
private overlay: JQuery<HTMLElement>;
|
||||
|
||||
constructor(label: string, buttons: JQuery<HTMLElement>[], position: [any, any]) {
|
||||
this.overlay = $("<div class='overlay'>");
|
||||
this.menu = $("<div class='contextmenu'>");
|
||||
|
||||
let labelElement = $("<span>");
|
||||
labelElement.text(label);
|
||||
this.menu.append(labelElement);
|
||||
|
||||
for (let _b in buttons) this.menu.append(buttons[_b]);
|
||||
|
||||
this.menu.css("top", position[1]);
|
||||
this.menu.css("left", position[0]);
|
||||
|
||||
this.overlay.append(this.menu);
|
||||
}
|
||||
|
||||
public static async createButton(text: string) {
|
||||
return $("<button class='option'>").text(text);
|
||||
}
|
||||
|
||||
public async create(): Promise<boolean> {
|
||||
if (global_ActiveContextMenu != null)
|
||||
return false;
|
||||
global_ActiveContextMenu = this;
|
||||
|
||||
$("div#container").append(this.overlay);
|
||||
|
||||
$(document).one("click", async (ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
await this.destroy();
|
||||
})
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async destroy(): Promise<boolean> {
|
||||
if (global_ActiveContextMenu != this)
|
||||
return false;
|
||||
global_ActiveContextMenu = null;
|
||||
|
||||
this.overlay.remove();
|
||||
this.menu.remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Popout {
|
||||
private element: JQuery<HTMLElement>;
|
||||
private overlay: JQuery<HTMLElement>;
|
||||
|
||||
constructor(element: JQuery<HTMLElement>, src: string) {
|
||||
this.element = element.clone();
|
||||
|
||||
this.element.removeClass("resourceh resource");
|
||||
this.element.addClass("popout");
|
||||
|
||||
this.overlay = $("<div class='overlay'>")
|
||||
this.overlay.append(this.element);
|
||||
|
||||
this.element.on("contextmenu", Popout.createPopoutMenu(this.element, src));
|
||||
}
|
||||
|
||||
create() {
|
||||
if (global_ActivePopout != null)
|
||||
return;
|
||||
global_ActivePopout = this;
|
||||
|
||||
$("div#container").append(this.overlay);
|
||||
this.overlay.one("click", (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
this.destroy();
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (global_ActivePopout != this)
|
||||
return;
|
||||
global_ActivePopout = null;
|
||||
|
||||
this.overlay.remove();
|
||||
this.element.remove();
|
||||
}
|
||||
|
||||
static createPopoutMenu(element: JQuery<HTMLElement>, src: string) {
|
||||
return async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
let favoriteButton = await ContextMenu.createButton("Add to favorites");
|
||||
let blacklistButton = await ContextMenu.createButton("Add to blacklist");
|
||||
|
||||
let removeFButton = await ContextMenu.createButton("Remove from favorites");
|
||||
let removeBButton = await ContextMenu.createButton("Remove from blacklist");
|
||||
|
||||
let undoLFButton = await ContextMenu.createButton("Undo last favorite");
|
||||
let undoLBButton = await ContextMenu.createButton("Undo last blacklist");
|
||||
|
||||
let clearFButton = await ContextMenu.createButton("Clear favorites");
|
||||
let clearBButton = await ContextMenu.createButton("Clear blacklist");
|
||||
|
||||
let reloadButton = await ContextMenu.createButton("Reload images");
|
||||
let reloadOFButton = await ContextMenu.createButton("Reload and show only favorites");
|
||||
|
||||
let closeMenuButton = await ContextMenu.createButton("Close this menu");
|
||||
|
||||
let Menu = new ContextMenu("Manage resource", [
|
||||
favoriteButton, blacklistButton, $("<hr/>"),
|
||||
removeFButton, removeBButton, $("<hr/>"),
|
||||
undoLFButton, undoLBButton, $("<hr/>"),
|
||||
clearFButton, clearBButton, $("<hr/>"),
|
||||
reloadButton, reloadOFButton, closeMenuButton
|
||||
], [event.clientX, event.clientY]);
|
||||
|
||||
favoriteButton.on("click", async (ev) => {
|
||||
ev.preventDefault();
|
||||
await settings.favorites.push(src);
|
||||
});
|
||||
blacklistButton.on("click", async (ev) => {
|
||||
ev.preventDefault();
|
||||
element.remove();
|
||||
await settings.blacklist.push(src);
|
||||
});
|
||||
|
||||
removeFButton.on("click", async (ev) => {
|
||||
ev.preventDefault();
|
||||
await settings.favorites.delete(src);
|
||||
})
|
||||
removeBButton.on("click", async (ev) => {
|
||||
ev.preventDefault();
|
||||
await settings.blacklist.delete(src);
|
||||
})
|
||||
|
||||
undoLFButton.on("click", async (ev) => {
|
||||
ev.preventDefault();
|
||||
await settings.favorites.pop();
|
||||
});
|
||||
undoLBButton.on("click", async (ev) => {
|
||||
ev.preventDefault();
|
||||
await settings.blacklist.pop();
|
||||
});
|
||||
|
||||
clearFButton.on("click", async (ev) => {
|
||||
ev.preventDefault();
|
||||
await settings.favorites.clear();
|
||||
});
|
||||
clearBButton.on("click", async (ev) => {
|
||||
ev.preventDefault();
|
||||
await settings.blacklist.clear();
|
||||
})
|
||||
|
||||
reloadButton.on("click", async (ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
await Gallery.loadDisplay(await settings.cache.shuffle());
|
||||
})
|
||||
reloadOFButton.on("click", async (ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
await Gallery.loadDisplay(await settings.favorites.shuffle());
|
||||
})
|
||||
|
||||
closeMenuButton.on("click", async (ev) => {
|
||||
ev.preventDefault();
|
||||
|
||||
await Menu.destroy();
|
||||
})
|
||||
|
||||
Menu.create();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _log = (f, ...m: any) => {
|
||||
f(...m);
|
||||
}
|
||||
|
||||
let log = (...m: any[]) => {
|
||||
_log(console.log, ...m);
|
||||
}
|
||||
|
||||
let warn = (...m: any[]) => {
|
||||
_log(console.warn, ...m);
|
||||
}
|
||||
|
||||
let error = (...m: any[]) => {
|
||||
_log(console.error, ...m);
|
||||
}
|
||||
|
||||
let debug = (...m: any[]) => {
|
||||
_log(console.debug, ...m);
|
||||
}
|
||||
|
||||
// ===============
|
||||
// === Gallery ===
|
||||
// ===============
|
||||
|
||||
class gallery {
|
||||
constructor() {}
|
||||
|
||||
public async load(): Promise<void> {
|
||||
log("Validating cache");
|
||||
|
||||
await settings.cache.load();
|
||||
}
|
||||
|
||||
public async loadDisplay(data: string[]): Promise<void> {
|
||||
await this.clearDisplay();
|
||||
|
||||
if (data.length != 0)
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let f = await data.at(i);
|
||||
|
||||
if (await settings.blacklist.contains(f))
|
||||
continue;
|
||||
|
||||
this.createElement("img", f).then((e) => e.appendTo($("div#display")))
|
||||
}
|
||||
else {
|
||||
let t = $("<span style='margin-top: 3em'>There's nothing here. <a style='color: white; text-decoration: none' href='#'>Reload?</a></span>");
|
||||
t.children("a").on("click", async (e) => {
|
||||
e.preventDefault();
|
||||
this.loadDisplay(await settings.cache.shuffle());
|
||||
});
|
||||
// let t = <span>There's nothing here. <a href="#" onClick={ async () => this.loadDisplay(await settings.cache.shuffle()) }>Reload?</a></span>
|
||||
$(t).appendTo($("div#display"))
|
||||
}
|
||||
}
|
||||
|
||||
private async clearDisplay(): Promise<void> {
|
||||
$("#display").children().remove();
|
||||
}
|
||||
|
||||
private async createElement(tag: string, src: string): Promise<JQuery<HTMLElement>> {
|
||||
let element = $(`<${tag} class='resource resourceh'>`);
|
||||
element.prop("loop", true);
|
||||
element.prop("nocontrols", true);
|
||||
element.attr("src", `${apiUrl}img/${src}`);
|
||||
|
||||
let popoutHandler = async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const popout = new Popout(element, src);
|
||||
|
||||
popout.create();
|
||||
}
|
||||
|
||||
element.on("click", popoutHandler);
|
||||
|
||||
element.on("contextmenu", Popout.createPopoutMenu(element, src));
|
||||