Improved slideshow to support ?holiday URL matching
Added docker config Signed-off-by: James Ketrenos <james_gitlab@ketrenos.com>
This commit is contained in:
parent
55a9c16e7b
commit
c2f72df80a
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!entrypoint.sh
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,3 +3,5 @@ node_modules
|
||||
./elements
|
||||
frontend/bower_components
|
||||
pictures
|
||||
db
|
||||
*.db
|
||||
|
52
Dockerfile
Normal file
52
Dockerfile
Normal file
@ -0,0 +1,52 @@
|
||||
FROM ubuntu:disco
|
||||
|
||||
RUN apt-get update
|
||||
|
||||
RUN apt-get install -y wget
|
||||
|
||||
# Upgrade Node
|
||||
RUN wget -qO- https://deb.nodesource.com/setup_10.x | bash -
|
||||
RUN DEBIAN_FRONTEND=NONINTERACTIVE apt-get install -y \
|
||||
nodejs \
|
||||
gcc \
|
||||
g++ \
|
||||
make \
|
||||
cmake
|
||||
|
||||
# You can then install the latest npm, polymer-cli, and bower:
|
||||
RUN npm install --global npm@latest npx
|
||||
|
||||
# Speed up face-recognition and dev tools
|
||||
RUN apt-get install -y libopenblas-dev cmake
|
||||
|
||||
# NEF processing uses ufraw-batch
|
||||
RUN apt-get install -y ufraw-batch
|
||||
|
||||
# Create a user with sudo access
|
||||
RUN DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
sudo
|
||||
|
||||
# NOTE: Requires 'sudo' package to already be installed
|
||||
RUN groupadd -g 1000 user \
|
||||
&& useradd --no-log-init \
|
||||
-s /bin/bash \
|
||||
-u 1000 \
|
||||
-m \
|
||||
-g user \
|
||||
-G sudo \
|
||||
-p $(echo "user" | openssl passwd -stdin) user
|
||||
|
||||
# Set 'sudo' to NOPASSWD for all container users
|
||||
RUN sed -i -e 's,%sudo.*,%sudo ALL=(ALL) NOPASSWD:ALL,g' /etc/sudoers
|
||||
|
||||
COPY /entrypoint.sh /entrypoint.sh
|
||||
|
||||
RUN DEBIAN_FRONTEND=noninteractive \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
git
|
||||
|
||||
USER user
|
||||
WORKDIR /website
|
||||
|
||||
CMD [ "/entrypoint.sh" ]
|
47
README.md
47
README.md
@ -28,47 +28,6 @@ sudo apt install -y ufraw-batch
|
||||
|
||||
### Create `photos` user for DB
|
||||
|
||||
You will need to know the root password for your mariadb installation. If you
|
||||
need to reset the root password, you can perform the following:
|
||||
|
||||
```bash
|
||||
export PASSWORD=m4g1cP4ssw0rd
|
||||
sudo service mysql stop
|
||||
sudo mysqld_safe --skip-grant-tables &
|
||||
sleep 1 # mysqld_safe can take a bit of time to come online
|
||||
sudo mysql << EOF
|
||||
use mysql;
|
||||
UPDATE user SET PASSWORD=PASSWORD("${PASSWORD}") WHERE USER='root';
|
||||
UPDATE user SET plugin='mysql_native_password' WHERE USER='root';
|
||||
FLUSH PRIVILEGES;
|
||||
QUIT
|
||||
EOF
|
||||
sudo service mysql stop
|
||||
sudo service mysql start
|
||||
```
|
||||
|
||||
Once you know the root password, you can then create a new user and DB
|
||||
for the photo app via:
|
||||
|
||||
### Create the USER
|
||||
|
||||
```bash
|
||||
U='photos'
|
||||
P='p4$$w0rd'
|
||||
mysql -u root --password=${PASSWORD} << EOF
|
||||
CREATE USER '${U}'@'localhost' IDENTIFIED BY '${P}';
|
||||
EOF
|
||||
```
|
||||
|
||||
### Create the DB 'photos'
|
||||
|
||||
```bash
|
||||
U='photos'
|
||||
P='p4$$w0rd'
|
||||
D='photos'
|
||||
mysql -u root --password=${PASSWORD} << EOF
|
||||
DROP DATABASE IF EXISTS ${D};
|
||||
CREATE DATABASE ${D} CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
GRANT ALL PRIVILEGES ON ${D}.* TO '${U}'@'localhost' IDENTIFIED BY '${P}';
|
||||
EOF
|
||||
```
|
||||
Photos is currently using sqlite, which means you don't need to
|
||||
do anything fancy beyond having run 'npm install' to get sequelize
|
||||
and sqlite3 installed.
|
||||
|
@ -1,14 +1,14 @@
|
||||
{
|
||||
"db": {
|
||||
"photos": {
|
||||
"host": "sqlite:photos.db",
|
||||
"host": "sqlite:db/photos.db",
|
||||
"options": {
|
||||
"logging" : false,
|
||||
"timezone": "+00:00"
|
||||
}
|
||||
},
|
||||
"users": {
|
||||
"host": "sqlite:users.db",
|
||||
"host": "sqlite:db/users.db",
|
||||
"options": {
|
||||
"logging" : false,
|
||||
"timezone": "+00:00"
|
||||
|
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
@ -0,0 +1,30 @@
|
||||
version: '3.1'
|
||||
|
||||
services:
|
||||
|
||||
# db:
|
||||
# image: mariadb
|
||||
# restart: always
|
||||
# environment:
|
||||
# MYSQL_ROOT_PASSWORD: photos
|
||||
# PHOTOS_DB_USER: photos
|
||||
# PHOTOS_DB_PASSWD: ph0t0z
|
||||
# PHOTOS_DB: photos
|
||||
# volumes:
|
||||
# - ${PWD}/db:/var/lib/mysql
|
||||
# - ./init.sql:/data/application/init.sql
|
||||
|
||||
photos:
|
||||
user: ${USER_ID}
|
||||
build: .
|
||||
image: photos:latest
|
||||
container_name: photos
|
||||
# depends_on:
|
||||
# - db
|
||||
restart: always
|
||||
ports:
|
||||
- 8123:8123
|
||||
volumes:
|
||||
- ${PWD}/photos:/photos
|
||||
- ${PWD}/db:/db
|
||||
- ${PWD}:/website
|
5
entrypoint.sh
Executable file
5
entrypoint.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
while true; do
|
||||
npm start
|
||||
sleep 3
|
||||
done
|
@ -1,167 +1,225 @@
|
||||
<html>
|
||||
<script src="src/ketr-photos/fetch.js"></script>
|
||||
<base href="/">
|
||||
<body>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#photo {
|
||||
position: fixed;
|
||||
display: inline-block;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
background-size: contain;
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
#placeholder {
|
||||
position: absolute;
|
||||
left: -1000px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#info {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
padding: 0.5em 1em;
|
||||
background-color: #444;
|
||||
color: white;
|
||||
font-size: 1.5em;
|
||||
line-height: 1.5em;
|
||||
font-family: Arial;
|
||||
box-shadow: 0px -0.25em 1em black;
|
||||
}
|
||||
</style>
|
||||
<img id="placeholder"></img>
|
||||
<div id="photo">
|
||||
</div>
|
||||
<div id="info">
|
||||
Loading photoset...
|
||||
</div>
|
||||
<script>
|
||||
var base, placeholder, info, photos = [], photoIndex;
|
||||
|
||||
function shuffle(array) {
|
||||
var index = array.length, tmp, random;
|
||||
|
||||
while (index) {
|
||||
random = Math.floor(Math.random() * index);
|
||||
index--;
|
||||
|
||||
// And swap it with the current element.
|
||||
tmp = array[index];
|
||||
array[index] = array[random];
|
||||
array[random] = tmp;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
function loadPhoto(index) {
|
||||
var photo = photos[index];
|
||||
placeholder.src = base + photo.path /* + "thumbs/scaled/"*/ + photo.filename;
|
||||
}
|
||||
|
||||
function nextPhoto() {
|
||||
photoIndex = (photoIndex + 1) % photos.length;
|
||||
loadPhoto(photoIndex);
|
||||
}
|
||||
|
||||
var scheduled = false;
|
||||
function schedule() {
|
||||
if (scheduled) {
|
||||
clearTimeout(scheduled);
|
||||
}
|
||||
scheduled = setTimeout(function () {
|
||||
scheduled = undefined;
|
||||
nextPhoto();
|
||||
}, 15 * 1000);
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var tmp = document.querySelector("base");
|
||||
if (tmp) {
|
||||
base = new URL(tmp.href).pathname.replace(/\/$/, "") + "/"; /* Make sure there is a trailing slash */
|
||||
} else {
|
||||
base = "/";
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", function(event) {
|
||||
if (event.keyCode == 37) { /* left */
|
||||
if (photoIndex == 0) {
|
||||
photoIndex = photos.length;
|
||||
}
|
||||
photoIndex--;
|
||||
loadPhoto(photoIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.keyCode == 39) { /* right */
|
||||
photoIndex = (photoIndex + 1) % photos.length;
|
||||
loadPhoto(photoIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(event.keyCode);
|
||||
});
|
||||
|
||||
info = document.getElementById("info");
|
||||
placeholder = document.getElementById("placeholder");
|
||||
|
||||
placeholder.onload = function() {
|
||||
var item = photos[photoIndex],
|
||||
days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
|
||||
months = [ "January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December" ],
|
||||
taken = new Date(item.taken);
|
||||
|
||||
info.textContent = "Photo taken on " +
|
||||
days[taken.getDay()] + ", " +
|
||||
months[taken.getMonth()] + " " +
|
||||
taken.getDate() + " " +
|
||||
taken.getFullYear();
|
||||
|
||||
photo.style.backgroundImage = "url(" + placeholder.src + ")";
|
||||
|
||||
schedule();
|
||||
};
|
||||
|
||||
placeholder.onerror = function() {
|
||||
info.textContent = "Error loading photo. Trying next photo.";
|
||||
nextPhoto();
|
||||
}
|
||||
|
||||
window.fetch("api/v1/photos/holiday/memorial%20day", function(error, xhr) {
|
||||
if (error && xhr.status != 404) {
|
||||
console.log("Unable to fetch holiday: " + error);
|
||||
return;
|
||||
}
|
||||
|
||||
var results;
|
||||
try {
|
||||
results = JSON.parse(xhr.responseText);
|
||||
} catch (___) {
|
||||
info.textContent = "Unable to parse results"
|
||||
return;
|
||||
}
|
||||
|
||||
if (results.items.length) {
|
||||
photos = shuffle(results.items);
|
||||
photoIndex = -1;
|
||||
nextPhoto();
|
||||
} else {
|
||||
info.textContent = "No photos found.";
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
<html>
|
||||
<base href="/photos/">
|
||||
<body>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#photo {
|
||||
position: fixed;
|
||||
display: inline-block;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
background-size: contain;
|
||||
background-color: #222;
|
||||
}
|
||||
|
||||
#placeholder {
|
||||
position: absolute;
|
||||
left: -1000px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#footer {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
opacity: 0.6;
|
||||
background: linear-gradient(45deg, rgba(16, 16, 16, 1), transparent);
|
||||
color: white;
|
||||
font-size: 1.5em;
|
||||
line-height: 1.5em;
|
||||
font-family: Arial;
|
||||
box-shadow: 0px -0.25em 1em black;
|
||||
text-shadow: 0 1px 0 black;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#info {
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
#loading {
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
</style>
|
||||
<img id="placeholder"></img>
|
||||
<div id="photo">
|
||||
</div>
|
||||
<div id="footer">
|
||||
<div id="info">Loading photoset...</div>
|
||||
<div id="loading"></div>
|
||||
</div>
|
||||
<script>
|
||||
var base, placeholder, info, photos = [], photoIndex;
|
||||
|
||||
function shuffle(array) {
|
||||
var index = array.length, tmp, random;
|
||||
|
||||
while (index) {
|
||||
random = Math.floor(Math.random() * index);
|
||||
index--;
|
||||
|
||||
// And swap it with the current element.
|
||||
tmp = array[index];
|
||||
array[index] = array[random];
|
||||
array[random] = tmp;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
var countdown = 15;
|
||||
|
||||
const days = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
|
||||
months = [ "January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December" ];
|
||||
|
||||
function loadPhoto(index) {
|
||||
const photo = photos[index],
|
||||
xml = new XMLHttpRequest(),
|
||||
url = base + photo.path /* + "thumbs/scaled/"*/ + photo.filename,
|
||||
taken = new Date(photo.taken);
|
||||
|
||||
document.getElementById("loading").textContent = "0%";
|
||||
|
||||
xml.onprogress = function (event) {
|
||||
var alpha = 0;
|
||||
if (event.total) {
|
||||
alpha = event.loaded / event.total;
|
||||
document.getElementById("loading").textContent = Math.ceil(100 * alpha) + "%";
|
||||
} else {
|
||||
document.getElementById("loading").textContent = "0%";
|
||||
}
|
||||
};
|
||||
|
||||
xml.onload = function(event) {
|
||||
info.textContent =
|
||||
days[taken.getDay()] + ", " +
|
||||
months[taken.getMonth()] + " " +
|
||||
taken.getDate() + " " +
|
||||
taken.getFullYear();
|
||||
document.getElementById("photo").style.backgroundImage = "url(" + url + ")";
|
||||
countdown = 15;
|
||||
tick();
|
||||
}
|
||||
|
||||
xml.onerror = function(event) {
|
||||
info.textContent = "Error loading photo. Trying next photo.";
|
||||
nextPhoto();
|
||||
}
|
||||
|
||||
xml.open("GET", url, true);
|
||||
xml.send();
|
||||
}
|
||||
|
||||
function nextPhoto() {
|
||||
photoIndex = (photoIndex + 1) % photos.length;
|
||||
loadPhoto(photoIndex);
|
||||
}
|
||||
|
||||
var scheduled = false;
|
||||
|
||||
function tick() {
|
||||
if (scheduled) {
|
||||
clearTimeout(scheduled);
|
||||
}
|
||||
document.getElementById("loading").textContent = countdown + "s";
|
||||
if (countdown > 0) {
|
||||
/* If there is a timer running, then decrement the counter */
|
||||
if (scheduled) {
|
||||
countdown--;
|
||||
}
|
||||
scheduled = setTimeout(tick, 1000);
|
||||
} else {
|
||||
scheduled = null;
|
||||
countdown = 15;
|
||||
nextPhoto();
|
||||
}
|
||||
}
|
||||
|
||||
function schedule() {
|
||||
if (scheduled) {
|
||||
clearTimeout(scheduled);
|
||||
}
|
||||
tick();
|
||||
}
|
||||
|
||||
var paused = false;
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var tmp = document.querySelector("base");
|
||||
if (tmp) {
|
||||
base = new URL(tmp.href).pathname.replace(/\/$/, "") + "/"; /* Make sure there is a trailing slash */
|
||||
} else {
|
||||
base = "/";
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", function(event) {
|
||||
if (event.keyCode == 32) { /* space */
|
||||
paused = !paused;
|
||||
if (!paused) {
|
||||
tick();
|
||||
} else {
|
||||
document.getElementById("loading").textContent = "||";
|
||||
clearTimeout(scheduled);
|
||||
scheduled = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.keyCode == 37) { /* left */
|
||||
if (photoIndex == 0) {
|
||||
photoIndex = photos.length;
|
||||
}
|
||||
photoIndex--;
|
||||
loadPhoto(photoIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.keyCode == 39) { /* right */
|
||||
photoIndex = (photoIndex + 1) % photos.length;
|
||||
loadPhoto(photoIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(event.keyCode);
|
||||
});
|
||||
|
||||
info = document.getElementById("info");
|
||||
|
||||
/* Trim off everything up to and including the ? (if its there) */
|
||||
var parts = window.location.href.match(/^.*\?(.*)$/);
|
||||
if (parts) {
|
||||
holiday = parts[1];
|
||||
} else {
|
||||
holiday = "memorial day";
|
||||
}
|
||||
window.fetch("api/v1/photos/holiday/" + holiday.replace(/ +/g, "%20"))
|
||||
.then(res => res.json()).then(function(data) {
|
||||
if (data.items.length) {
|
||||
info.textContent = photos.length + " photos found. Shuffling.";
|
||||
photos = shuffle(data.items);
|
||||
photoIndex = -1;
|
||||
nextPhoto();
|
||||
} else {
|
||||
info.textContent = "No photos found for " + holiday + ".";
|
||||
}
|
||||
}).catch(function(error) {
|
||||
console.error(error);
|
||||
info.textContent = "Unable to fetch holiday :(";
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
@ -12,6 +12,9 @@
|
||||
},
|
||||
"author": "James Ketrenos",
|
||||
"license": "Apache-2.0",
|
||||
"unused": {
|
||||
"face-recognition": "^0.9.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"bluebird": "^3.5.3",
|
||||
"body-parser": "^1.18.3",
|
||||
@ -21,7 +24,6 @@
|
||||
"exif-reader": "github:paras20xx/exif-reader",
|
||||
"express": "^4.16.4",
|
||||
"express-session": "^1.15.6",
|
||||
"face-recognition": "^0.9.4",
|
||||
"handlebars": "^4.0.12",
|
||||
"ldapauth-fork": "^4.0.2",
|
||||
"ldapjs": "^1.0.2",
|
||||
|
Loading…
x
Reference in New Issue
Block a user