Merge pull request #3 from tonimelisma/rewrite

Rewrite fastgallery
pull/7/head v0.3.0
Toni Melisma 3 years ago committed by GitHub
commit 71ba499818
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
name: build
on: [ push, pull_request ]
on: [push, pull_request]
jobs:
build:
@ -10,38 +10,37 @@ jobs:
CGO_CFLAGS_ALLOW: -Xpreprocessor
strategy:
matrix:
os: [ubuntu-18.04, ubuntu-20.04, macOS-10.15, macos-11.0]
os: [ubuntu-18.04, ubuntu-20.04, macOS-10.15]
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.16
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.13
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Install linux deps
if: matrix.os == 'ubuntu-18.04' || matrix.os == 'ubuntu-20.04'
run: |
sudo add-apt-repository -y ppa:tonimelisma/ppa
sudo apt-get -y install libvips-dev
- name: Install macos deps
if: matrix.os == 'macos-10.15' || matrix.os == 'macos-11.0'
run: |
brew install vips
- name: Get dependencies
run: |
go get -v -t -d ./...
- name: Build
run: go build -v ./cmd/fastgallery
- name: Install linux deps
if: matrix.os == 'ubuntu-18.04' || matrix.os == 'ubuntu-20.04'
run: |
sudo add-apt-repository -y ppa:tonimelisma/ppa
sudo apt-get -y install libvips-dev
- name: Install macos deps
if: matrix.os == 'macos-10.15'
run: |
brew install vips
- name: Get dependencies
run: |
go get -v -t -d ./...
- name: Build
run: go build -v ./cmd/fastgallery
- name: Test
run: go test -v -coverprofile=profile.cov ./...
- name: Test
run: go test -v -coverprofile=profile.cov ./...
- name: Coveralls
if: matrix.os == 'ubuntu-20.04'
uses: shogo82148/actions-goveralls@v1
with:
path-to-profile: profile.cov
- name: Coveralls
if: matrix.os == 'ubuntu-20.04'
uses: shogo82148/actions-goveralls@v1
with:
path-to-profile: profile.cov

3
.gitignore vendored

@ -6,9 +6,6 @@
*.dylib
# temp media files
*.mp4
*.jpg
*.heic
.DS_Store
# Test binary, built with `go test -c`

@ -5,11 +5,15 @@
"version": "0.2.0",
"configurations": [
{
"name": "Attach to Process",
"name": "Launch file",
"type": "go",
"request": "attach",
"mode": "local",
"processId": 585791
"request": "launch",
"mode": "debug",
"program": "${file}",
"args": [
"../../testing/source/",
"../../testing/gallery/"
]
}
]
}

@ -5,25 +5,24 @@
- Both photo and video support
- Deals with any file formats (including HEIC and HEVC)
- Only updates changed files, runs incrementally
- If aborted, can continue from where it stopped and clean-up unfinished files
- Will delete removed source files from gallery
- Uses relative paths (safe for using in subdirectory or S3)
- Minimal bloat (no third party frontend libraries, minimal CSS)
- Minimal bloat (vanilla JS frontend, minimal CSS)
*Please note that fastgallery is still beta, I am actively working on it*
*Fastgallery has just been rewritten to better handle incremental updates of the gallery.
*The command-line syntax changed to a more sane version, please see below
## Examples
Please see [https://www.melisma.fi/fastgallery-examples/](https://www.melisma.fi/fastgallery-examples/)
## Dependencies
### MacOS
For dependencies, use Homebrew to install:
`brew install vips ffmpeg`
### Ubuntu Linux
For Ubuntu 18.04 bionic or 20.04 focal, first add my PPA for latest libvips with HEIF support:
`sudo add-apt-repository ppa:tonimelisma/ppa`
@ -33,20 +32,17 @@ Then install libvips42 for images and optionally ffmpeg (if you need video suppo
`apt-get install libvips42 ffmpeg`
## Install
1. Download the latest release and unpack
1. Download the latest release
2. Run ```make build```
3. Copy ```bin/fastgallery``` to ```/usr/local/bin```
4. Copy contents of ```assets/``` to ```/usr/local/share/fastgallery```
## Usage
`fastgallery -o /var/www/html/gallery ~/Dropbox/Pictures`
## Usage
`fastgallery ~/Dropbox/Pictures /var/www/html/gallery`
## Roadmap
For the prioritised roadmap, please see https://github.com/tonimelisma/fastgallery/projects/1
## Third party libraries
- [govips](https://github.com/davidbyttow/govips), lightning fast image processing and resizing library
- [govips](https://github.com/davidbyttow/govips), lightning fast image processing and resizing library in Go/C
- [Feather](https://github.com/feathericons/feather) icons, simple and beautiful
- [Primer](https://github.com/primer/css) CSS, Github's in-house design system

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

File diff suppressed because it is too large Load Diff

@ -1,44 +1,344 @@
package main
import (
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestIsVideoFile(t *testing.T) {
assert.Equal(t, isVideoFile("test.txt"), false, "false positive")
assert.Equal(t, isVideoFile(""), false, "false positive")
assert.Equal(t, isVideoFile("test.mp4"), true, "false negative")
assert.Equal(t, isVideoFile("test.mov"), true, "false negative")
assert.Equal(t, isVideoFile("test.3gp"), true, "false negative")
assert.Equal(t, isVideoFile("test.avi"), true, "false negative")
assert.Equal(t, isVideoFile("test.mts"), true, "false negative")
assert.Equal(t, isVideoFile("test.m4v"), true, "false negative")
assert.Equal(t, isVideoFile("test.mpg"), true, "false negative")
assert.Equal(t, isVideoFile("test.MP4"), true, "false negative")
}
func TestIsImageFile(t *testing.T) {
assert.Equal(t, isImageFile("test.txt"), false, "false positive")
assert.Equal(t, isImageFile(""), false, "false positive")
assert.Equal(t, isImageFile("test.jpg"), true, "false negative")
assert.Equal(t, isImageFile("test.JPG"), true, "false negative")
assert.Equal(t, isImageFile("test.jpeg"), true, "false negative")
assert.Equal(t, isImageFile("test.heic"), true, "false negative")
assert.Equal(t, isImageFile("test.png"), true, "false negative")
assert.Equal(t, isImageFile("test.tif"), true, "false negative")
assert.Equal(t, isImageFile("test.tiff"), true, "false negative")
}
func TestIsMediaFile(t *testing.T) {
assert.Equal(t, isMediaFile("test.txt"), false, "false positive")
assert.Equal(t, isMediaFile("test.jpg"), true, "false negative")
assert.Equal(t, isMediaFile("test.mp4"), true, "false negative")
var exitCount = 0
func testExit(ret int) {
exitCount = exitCount + 1
return
}
func TestValidateSourceAndGallery(t *testing.T) {
originalExit := exit
defer func() { exit = originalExit }()
exit = testExit
tempDir, err := os.MkdirTemp("", "fastgallery-test-")
if err != nil {
t.Error("couldn't create temporary directory")
}
defer os.RemoveAll(tempDir)
exitCountBefore := exitCount
_, _ = validateSourceAndGallery(tempDir+"/nonexistent", tempDir+"/gallery")
assert.EqualValues(t, exitCountBefore+1, exitCount, "validateArgs did not exit")
exitCountBefore = exitCount
_, _ = validateSourceAndGallery(tempDir, tempDir+"/gallery/nonexistent")
assert.EqualValues(t, exitCountBefore+1, exitCount, "validateArgs did not exit")
exitCountBefore = exitCount
_, _ = validateSourceAndGallery(tempDir, tempDir+"/gallery")
assert.EqualValues(t, exitCountBefore, exitCount, "validateArgs did not exit")
}
func TestIsDirectory(t *testing.T) {
tempDir, err := os.MkdirTemp("", "fastgallery-test-")
if err != nil {
t.Error("couldn't create temporary directory")
}
defer os.RemoveAll(tempDir)
err = os.Mkdir(tempDir+"/subdir", 0755)
if err != nil {
t.Error("couldn't create subdirectory")
}
defer os.RemoveAll(tempDir + "/subdir")
assert.True(t, isDirectory(tempDir+"/subdir"))
err = os.Symlink(tempDir+"/subdir", tempDir+"/symlink")
if err != nil {
t.Error("couldn't create symlink")
}
defer os.RemoveAll(tempDir + "/symlink")
assert.True(t, isDirectory(tempDir+"/symlink"))
emptyFile, err := os.Create(tempDir + "/file")
if err != nil {
t.Error("couldn't create symlink")
}
defer emptyFile.Close()
defer os.RemoveAll(tempDir + "/file")
assert.False(t, isDirectory(tempDir+"/file"))
assert.False(t, isDirectory(tempDir+"/nonexistent"))
}
func TestExists(t *testing.T) {
tempDir, err := os.MkdirTemp("", "fastgallery-test-")
if err != nil {
t.Error("couldn't create temporary directory")
}
defer os.RemoveAll(tempDir)
emptyFile, err := os.Create(tempDir + "/file")
if err != nil {
t.Error("couldn't create symlink")
}
defer emptyFile.Close()
defer os.RemoveAll(tempDir + "/file")
assert.True(t, exists(tempDir+"/file"))
assert.False(t, exists(tempDir+"/nonexistent"))
}
func TestDirHasMediaFiles(t *testing.T) {
tempDir, err := os.MkdirTemp("", "fastgallery-test-")
if err != nil {
t.Error("couldn't create temporary directory")
}
defer os.RemoveAll(tempDir)
emptyFile, err := os.Create(tempDir + "/file.raw")
if err != nil {
t.Error("couldn't create symlink")
}
defer emptyFile.Close()
defer os.RemoveAll(tempDir + "/file.raw")
assert.True(t, dirHasMediafiles(tempDir))
}
func TestDirHasMediaFilesFailing(t *testing.T) {
tempDir, err := os.MkdirTemp("", "fastgallery-test-")
if err != nil {
t.Error("couldn't create temporary directory")
}
defer os.RemoveAll(tempDir)
emptyFile, err := os.Create(tempDir + "/file.txt")
if err != nil {
t.Error("couldn't create symlink")
}
defer emptyFile.Close()
defer os.RemoveAll(tempDir + "/file.txt")
assert.False(t, dirHasMediafiles(tempDir))
}
func TestDirHasMediaFilesRecurse(t *testing.T) {
tempDir, err := os.MkdirTemp("", "fastgallery-test-")
if err != nil {
t.Error("couldn't create temporary directory")
}
defer os.RemoveAll(tempDir)
err = os.Mkdir(tempDir+"/subdir", 0755)
if err != nil {
t.Error("couldn't create subdirectory")
}
defer os.RemoveAll(tempDir + "/subdir")
emptyFile, err := os.Create(tempDir + "/subdir/file.jpg")
if err != nil {
t.Error("couldn't create symlink")
}
defer emptyFile.Close()
defer os.RemoveAll(tempDir + "/subdir/file.jpg")
assert.True(t, dirHasMediafiles(tempDir))
}
func TestDirHasMediaFilesRecurseFailing(t *testing.T) {
tempDir, err := os.MkdirTemp("", "fastgallery-test-")
if err != nil {
t.Error("couldn't create temporary directory")
}
defer os.RemoveAll(tempDir)
err = os.Mkdir(tempDir+"/subdir", 0755)
if err != nil {
t.Error("couldn't create subdirectory")
}
defer os.RemoveAll(tempDir + "/subdir")
emptyFile, err := os.Create(tempDir + "/subdir/file.txt")
if err != nil {
t.Error("couldn't create symlink")
}
defer emptyFile.Close()
defer os.RemoveAll(tempDir + "/subdir/file.txt")
assert.False(t, dirHasMediafiles(tempDir))
}
func TestIsXxxFile(t *testing.T) {
assert.True(t, isVideoFile("test.mp4"))
assert.False(t, isVideoFile("test.jpg"))
assert.False(t, isVideoFile("test.txt"))
assert.True(t, isImageFile("test.jpg"))
assert.False(t, isImageFile("test.mp4"))
assert.False(t, isImageFile("test.txt"))
assert.True(t, isMediaFile("test.mp4"))
assert.True(t, isMediaFile("test.jpg"))
assert.False(t, isMediaFile("test.txt"))
}
func TestCopyRootAssets(t *testing.T) {
tempDir, err := os.MkdirTemp("", "fastgallery-test-")
if err != nil {
t.Error("couldn't create temporary directory")
}
defer os.RemoveAll(tempDir)
var tempGallery directory
tempGallery.absPath = tempDir
config := initializeConfig()
copyRootAssets(tempGallery, false, config)
assert.FileExists(t, tempDir+"/back.png")
assert.FileExists(t, tempDir+"/folder.png")
assert.FileExists(t, tempDir+"/fastgallery.css")
assert.FileExists(t, tempDir+"/fastgallery.js")
assert.FileExists(t, tempDir+"/feather.min.js")
assert.FileExists(t, tempDir+"/primer.css")
}
func TestStripExtension(t *testing.T) {
assert.Equal(t, stripExtension("../tmp/filename.txt"), "../tmp/filename", "mismatch")
assert.Equal(t, stripExtension("/tmp/filename.txt"), "/tmp/filename", "mismatch")
assert.Equal(t, stripExtension("filename.txt"), "filename", "mismatch")
assert.Equal(t, "file", stripExtension("file.jpg"))
assert.NotEqual(t, "file", stripExtension("file/"))
}
func TestReservedDirectory(t *testing.T) {
myConfig := initializeConfig()
assert.True(t, reservedDirectory(myConfig.files.thumbnailDir, myConfig))
assert.True(t, reservedDirectory(myConfig.files.fullsizeDir, myConfig))
assert.True(t, reservedDirectory(myConfig.files.originalDir, myConfig))
assert.False(t, reservedDirectory("diipadaapa", myConfig))
}
func TestCreateDirectory(t *testing.T) {
tempDir, err := os.MkdirTemp("", "fastgallery-test-")
if err != nil {
t.Error("couldn't create temporary directory")
}
defer os.RemoveAll(tempDir)
myConfig := initializeConfig()
createDirectory(tempDir+"/xyz", true, myConfig.files.directoryMode)
assert.NoDirExists(t, tempDir+"/xyz")
createDirectory(tempDir+"/xyz", false, myConfig.files.directoryMode)
assert.DirExists(t, tempDir+"/xyz")
os.RemoveAll(tempDir + "/xyz")
}
func TestCreateDirectoryTree(t *testing.T) {
myConfig := initializeConfig()
tempDir, err := os.MkdirTemp("", "fastgallery-test-")
if err != nil {
t.Error("couldn't create temporary directory")
}
defer os.RemoveAll(tempDir)
// Create source directory with two files, a subdir with third file
err = os.Mkdir(tempDir+"/source", 0755)
if err != nil {
t.Error("couldn't create source subdirectory")
}
defer os.RemoveAll(tempDir + "/source")
emptyFile, err := os.Create(tempDir + "/source/file.jpg")
if err != nil {
t.Error("couldn't create file")
}
defer emptyFile.Close()
defer os.RemoveAll(tempDir + "/source/file.jpg")
emptyFile2, err := os.Create(tempDir + "/source/file2.jpg")
if err != nil {
t.Error("couldn't create file2")
}
defer emptyFile2.Close()
defer os.RemoveAll(tempDir + "/source/file2.jpg")
err = os.Mkdir(tempDir+"/source/subdir", 0755)
if err != nil {
t.Error("couldn't create source subdirectory's subdirectory")
}
defer os.RemoveAll(tempDir + "/source/subdir")
emptyFile3, err := os.Create(tempDir + "/source/subdir/file.jpg")
if err != nil {
t.Error("couldn't create file in subdir")
}
defer emptyFile3.Close()
defer os.RemoveAll(tempDir + "/source/subdir/file.jpg")
// Create gallery subdirectory with one matching file
err = os.Mkdir(tempDir+"/gallery", 0755)
if err != nil {
t.Error("couldn't create gallery subdirectory")
}
defer os.RemoveAll(tempDir + "/gallery")
err = os.Mkdir(tempDir+"/gallery/"+myConfig.files.fullsizeDir, 0755)
if err != nil {
t.Error("couldn't create gallery subdirectory for fullsize")
}
defer os.RemoveAll(tempDir + "/gallery/" + myConfig.files.fullsizeDir)
err = os.Mkdir(tempDir+"/gallery/"+myConfig.files.thumbnailDir, 0755)
if err != nil {
t.Error("couldn't create gallery subdirectory for thumbnail")
}
defer os.RemoveAll(tempDir + "/gallery/" + myConfig.files.thumbnailDir)
err = os.Mkdir(tempDir+"/gallery/"+myConfig.files.originalDir, 0755)
if err != nil {
t.Error("couldn't create gallery subdirectory for original")
}
defer os.RemoveAll(tempDir + "/gallery/" + myConfig.files.originalDir)
emptyFile4, err := os.Create(tempDir + "/gallery/" + myConfig.files.originalDir + "/file.jpg")
if err != nil {
t.Error("couldn't create original gallery file")
}
defer emptyFile4.Close()
defer os.RemoveAll(tempDir + "/gallery/" + myConfig.files.originalDir + "/file.jpg")
emptyFile5, err := os.Create(tempDir + "/gallery/" + myConfig.files.thumbnailDir + "/file.jpg")
if err != nil {
t.Error("couldn't create original gallery file")
}
defer emptyFile5.Close()
defer os.RemoveAll(tempDir + "/gallery/" + myConfig.files.thumbnailDir + "/file.jpg")
// Ensure thumbnail file is newer than source file
err = os.Chtimes(tempDir+"/gallery/"+myConfig.files.thumbnailDir+"/file.jpg", time.Now(), time.Now())
if err != nil {
t.Error("couldn't change mtime/atime")
}
emptyFile6, err := os.Create(tempDir + "/gallery/" + myConfig.files.fullsizeDir + "/file.jpg")
if err != nil {
t.Error("couldn't create original gallery file")
}
defer emptyFile6.Close()
defer os.RemoveAll(tempDir + "/gallery/" + myConfig.files.fullsizeDir + "/file.jpg")
source := createDirectoryTree(tempDir+"/source", "")
gallery := createDirectoryTree(tempDir+"/gallery", "")
compareDirectoryTrees(&source, &gallery, myConfig)
changes := countChanges(source)
assert.EqualValues(t, 2, changes)
}
// TODO tests for
// createGallery
// - exists, doesn't exist, some gallery files exist / some don't
// - thumbnail modified earlier than original or vice versa

File diff suppressed because it is too large Load Diff

@ -0,0 +1,44 @@
package main
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsVideoFile(t *testing.T) {
assert.Equal(t, isVideoFile("test.txt"), false, "false positive")
assert.Equal(t, isVideoFile(""), false, "false positive")
assert.Equal(t, isVideoFile("test.mp4"), true, "false negative")
assert.Equal(t, isVideoFile("test.mov"), true, "false negative")
assert.Equal(t, isVideoFile("test.3gp"), true, "false negative")
assert.Equal(t, isVideoFile("test.avi"), true, "false negative")
assert.Equal(t, isVideoFile("test.mts"), true, "false negative")
assert.Equal(t, isVideoFile("test.m4v"), true, "false negative")
assert.Equal(t, isVideoFile("test.mpg"), true, "false negative")
assert.Equal(t, isVideoFile("test.MP4"), true, "false negative")
}
func TestIsImageFile(t *testing.T) {
assert.Equal(t, isImageFile("test.txt"), false, "false positive")
assert.Equal(t, isImageFile(""), false, "false positive")
assert.Equal(t, isImageFile("test.jpg"), true, "false negative")
assert.Equal(t, isImageFile("test.JPG"), true, "false negative")
assert.Equal(t, isImageFile("test.jpeg"), true, "false negative")
assert.Equal(t, isImageFile("test.heic"), true, "false negative")
assert.Equal(t, isImageFile("test.png"), true, "false negative")
assert.Equal(t, isImageFile("test.tif"), true, "false negative")
assert.Equal(t, isImageFile("test.tiff"), true, "false negative")
}
func TestIsMediaFile(t *testing.T) {
assert.Equal(t, isMediaFile("test.txt"), false, "false positive")
assert.Equal(t, isMediaFile("test.jpg"), true, "false negative")
assert.Equal(t, isMediaFile("test.mp4"), true, "false negative")
}
func TestStripExtension(t *testing.T) {
assert.Equal(t, stripExtension("../tmp/filename.txt"), "../tmp/filename", "mismatch")
assert.Equal(t, stripExtension("/tmp/filename.txt"), "/tmp/filename", "mismatch")
assert.Equal(t, stripExtension("filename.txt"), "filename", "mismatch")
}

@ -1,14 +1,19 @@
module github.com/tonimelisma/fastgallery
module fastgallery
go 1.15
go 1.16
require (
github.com/cheggaaa/pb/v3 v3.0.5
github.com/davidbyttow/govips/v2 v2.2.0
github.com/alexflint/go-arg v1.3.0
github.com/cheggaaa/pb/v3 v3.0.6
github.com/davidbyttow/govips/v2 v2.5.0
github.com/fatih/color v1.10.0 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-runewidth v0.0.10 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/stretchr/testify v1.6.1
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 // indirect
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb // indirect
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect
golang.org/x/sys v0.0.0-20210227040730-b0d1d43c014d // indirect
golang.org/x/text v0.3.5 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
)

@ -1,67 +1,72 @@
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/cheggaaa/pb/v3 v3.0.5 h1:lmZOti7CraK9RSjzExsY53+WWfub9Qv13B5m4ptEoPE=
github.com/cheggaaa/pb/v3 v3.0.5/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw=
github.com/alexflint/go-arg v1.3.0 h1:UfldqSdFWeLtoOuVRosqofU4nmhI1pYEbT4ZFS34Bdo=
github.com/alexflint/go-arg v1.3.0/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf2y2768kM=
github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70=
github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw=
github.com/cheggaaa/pb/v3 v3.0.6 h1:ULPm1wpzvj60FvmCrX7bIaB80UgbhI+zSaQJKRfCbAs=
github.com/cheggaaa/pb/v3 v3.0.6/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidbyttow/govips/v2 v2.2.0 h1:O21ykVQNJ7LYxpa7yG6TN2g2VF/WonOksljCD2jQK4o=
github.com/davidbyttow/govips/v2 v2.2.0/go.mod h1:goq38QD8XEMz2aWEeucEZqRxAWsemIN40vbUqfPfTAw=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/davidbyttow/govips/v2 v2.5.0 h1:CLSVkXwZYfF7bOR5bZwlUFL1TIWXwyuNF33UrlKscM4=
github.com/davidbyttow/govips/v2 v2.5.0/go.mod h1:goq38QD8XEMz2aWEeucEZqRxAWsemIN40vbUqfPfTAw=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI=
golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210227040730-b0d1d43c014d h1:9fH9JvLNoSpsDWcXJ4dSE3lZW99Z3OCUZLr07g60U6o=
golang.org/x/sys v0.0.0-20210227040730-b0d1d43c014d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -0,0 +1 @@
/home/toni/sdk/go1.16/bin/go run cmd/fastgallery/main.go /home/toni/gallerytest/temp1/ /tmp/gallerytest

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

@ -0,0 +1 @@
/home/toni/go/src/github.com/tonimelisma/fastgallery/testing/source/2020-05-22 16.41.02.heic

@ -0,0 +1 @@
/home/toni/go/src/github.com/tonimelisma/fastgallery/testing/source/2021-01-01 17.11.35.heic

@ -0,0 +1 @@
/home/toni/go/src/github.com/tonimelisma/fastgallery/testing/source/2021-01-02 23.51.34.mp4

@ -0,0 +1 @@
/home/toni/go/src/github.com/tonimelisma/fastgallery/testing/source/2021-02-22 15.36.43-1.heic

@ -0,0 +1 @@
/home/toni/go/src/github.com/tonimelisma/fastgallery/testing/source/2021-02-25 15.40.44.png

@ -0,0 +1 @@
/home/toni/go/src/github.com/tonimelisma/fastgallery/testing/source/_DSC9363_DxO.jpg

@ -0,0 +1 @@
/home/toni/go/src/github.com/tonimelisma/fastgallery/testing/source/_DSC9439.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

@ -0,0 +1,33 @@
.box {
cursor: pointer;
}
#modalMedia {
max-width: 100%;
max-height: calc(100% - 74px);
}
.modalImage {
object-fit: scale-down;
max-width: 100%;
}
video {
max-width: 100%;
max-height: 100%;
}
#modalDownload {
color: inherit;
}
#modalHeader,
#modalFooter {
height: 37px;
width: 360px;
}
.modalControl:hover,
.modalControl:focus {
background-color: rgba(0, 0, 0, 0.2);
}

@ -0,0 +1,150 @@
// check that the HTML page including us has set the pictures array
if (typeof pictures == 'undefined') {
throw new Error("pictures array not defined")
}
// Hard-coded configuration
const videoExtension = "mp4"
const videoMIMEType = "video/mp4"
// global variable maintains currently shown picture number (pictures[] array)
var currentPicture
// create hover effect shadow for all box elements
const hoverOnBox = (event) => {
event.target.classList.remove("box-shadow")
event.target.classList.remove("border-gray")
event.target.classList.add("box-shadow-large")
event.target.classList.add("border-gray-dark")
}
const hoverOffBox = (event) => {
event.target.classList.remove("border-gray-dark")
event.target.classList.remove("box-shadow-large")
event.target.classList.add("box-shadow")
event.target.classList.add("border-gray")
}
const registerBoxEventHandlers = (element) => {
element.addEventListener("mouseenter", hoverOnBox)
element.addEventListener("mouseleave", hoverOffBox)
}
var boxes = document.getElementsByClassName("box")
for (let box of boxes) {
registerBoxEventHandlers(box)
}
// create hover effect for modal navigation elements
// const hoverOnNav = (event) => {}
// logic to show and hide picture modal
const displayModal = (display) => {
if (display) {
document.getElementById("modal").hidden = false
document.getElementById("thumbnails").hidden = true
} else {
document.getElementById("thumbnails").hidden = false
document.getElementById("modal").hidden = true
document.getElementById("modalMedia").innerHTML = ""
window.location.hash = ""
}
}
// TODO add swipe support https://stackoverflow.com/questions/2264072/detect-a-finger-swipe-through-javascript-on-the-iphone-and-android
// modal previous and next picture button logic
const preload = (number) => {
var preloadLink = document.createElement("link")
preloadLink.rel = "prefetch"
preloadLink.href = encodeURI(pictures[number].fullsize)
const fileExtension = preloadLink.href.split("\.").pop()
if (fileExtension == videoExtension) {
preloadLink.as = "video"
} else {
preloadLink.as = "image"
}
document.head.appendChild(preloadLink)
}
const prevPicture = () => {
changePicture(getPrevPicture())
preload(getPrevPicture())
}
const getPrevPicture = () => {
if (!isNaN(currentPicture)) {
if (currentPicture === 0) {
return (pictures.length - 1)
} else if (currentPicture < 0 || currentPicture >= pictures.length) {
console.error("Invalid currentPicture, 0.." + pictures.length - 1 + ": " + currentPicture)
} else {
return (currentPicture - 1)
}
} else {
console.error("Invalid currentPicture, NaN: " + currentPicture)
}
}
const nextPicture = () => {
changePicture(getNextPicture())
preload(getNextPicture())
}
const getNextPicture = () => {
if (!isNaN(currentPicture)) {
if (currentPicture === pictures.length - 1) {
return (0)
} else if (currentPicture < 0 || currentPicture >= pictures.length) {
console.error("Invalid currentPicture, 0.." + pictures.length - 1 + ": " + currentPicture)
} else {
return (currentPicture + 1)
}
} else {
console.error("Invalid currentPicture, NaN: " + currentPicture)
}
}
// function to change picture in modal, used by hashNavigate, and next/prevPicture
const changePicture = (number) => {
thumbnailFilename = pictures[number].thumbnail
window.location.hash = pictures[number].filename
const fileExtension = pictures[number].fullsize.split("\.").pop()
if (fileExtension == videoExtension) {
document.getElementById("modalMedia").innerHTML = "<video controls><source src=\"" + encodeURI(pictures[number].fullsize) + "\" type=\"" + videoMIMEType + "\"></video>"
} else {
document.getElementById("modalMedia").innerHTML = "<img src=\"" + encodeURI(pictures[number].fullsize) + "\" alt=\"" + pictures[number].filename + "\" class=\"modalImage\">"
}
document.getElementById("modalDescription").innerHTML = pictures[number].filename
document.getElementById("modalDownload").href = pictures[number].original
currentPicture = number
}
// if URL links directly to thumbnail via hash link, open modal for that pic on page load
const hashNavigate = () => {
if (window.location.hash) {
const filename = decodeURI(window.location.hash.substring(1))
id = pictures.findIndex(item => item.filename == filename)
if (id != -1 && id >= 0 && id < pictures.length) {
changePicture(id)
displayModal(true)
} else {
displayModal(false)
console.error("Invalid thumbnail link provided after # in URI")
}
} else {
displayModal(false)
}
}
const checkKey = (event) => {
if (event.key === "ArrowLeft") {
prevPicture()
} else if (event.key === "ArrowRight") {
nextPicture()
} else if (event.key === "Escape") {
displayModal(false)
}
}
document.onkeydown = checkKey
window.onpopstate = hashNavigate

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

@ -0,0 +1,171 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>source</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<link href="fastgallery.css" rel="stylesheet">
<link href="primer.css" rel="stylesheet">
</head>
<body class="bg-gray">
<div id="thumbnails">
<h1 class="px-2 pb-2 my-0 m-md-3 m-lg-4">source</h1>
<!-- Thumbnail view. First subfolders. -->
<div class="container-xl m-0 m-md-2 m-lg-3">
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<a href="subdir">
<img class="box border border-gray box-shadow width-fit" src="folder.png" alt="subdir">
</a>
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">subdir</span>
</div>
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<img class="box border border-gray box-shadow width-fit" src="_thumbnail/2020-05-22 16.41.02.jpg" alt="2020-05-22 16.41.02.heic" onclick="changePicture(0);displayModal(true);">
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">2020-05-22 16.41.02.heic</span>
</div>
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<img class="box border border-gray box-shadow width-fit" src="_thumbnail/2021-01-01 17.11.35.jpg" alt="2021-01-01 17.11.35.heic" onclick="changePicture(1);displayModal(true);">
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">2021-01-01 17.11.35.heic</span>
</div>
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<img class="box border border-gray box-shadow width-fit" src="_thumbnail/2021-01-02 23.51.34.jpg" alt="2021-01-02 23.51.34.mp4" onclick="changePicture(2);displayModal(true);">
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">2021-01-02 23.51.34.mp4</span>
</div>
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<img class="box border border-gray box-shadow width-fit" src="_thumbnail/2021-02-22 15.36.43-1.jpg" alt="2021-02-22 15.36.43-1.heic" onclick="changePicture(3);displayModal(true);">
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">2021-02-22 15.36.43-1.heic</span>
</div>
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<img class="box border border-gray box-shadow width-fit" src="_thumbnail/2021-02-25 15.40.44.jpg" alt="2021-02-25 15.40.44.png" onclick="changePicture(4);displayModal(true);">
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">2021-02-25 15.40.44.png</span>
</div>
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<img class="box border border-gray box-shadow width-fit" src="_thumbnail/_DSC9363_DxO.jpg" alt="_DSC9363_DxO.jpg" onclick="changePicture(5);displayModal(true);">
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">_DSC9363_DxO.jpg</span>
</div>
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<img class="box border border-gray box-shadow width-fit" src="_thumbnail/_DSC9439.jpg" alt="_DSC9439.jpg" onclick="changePicture(6);displayModal(true);">
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">_DSC9439.jpg</span>
</div>
</div>
</div>
<!-- Modal which shows individual pictures full-screen.
Covers thumbnail view. Hidden by default, unless URL contains
hashtag and thumbnail name. -->
<div class="position-fixed top-0 left-0 width-full height-full d-flex flex-column flex-justify-center flex-items-center box border border-gray box-shadow bg-gray" id="modal" hidden>
<div class="bg-gray clearfix position-absolute top-0 p-1" id="modalHeader">
<div class="float-right modalControl float-left" onclick="displayModal(false);">
<i data-feather="x"></i>
</div>
<div class="float-right modalControl float-left">
<a href="#" id="modalDownload" download>
<i data-feather="download"></i>
</a>
</div>
</div>
<div id="modalMedia" class="d-flex flex-justify-center"></div>
<div class="bg-gray position-absolute bottom-0 d-flex flex-justify-center p-1" id="modalFooter">
<div class="float-left modalControl float-left" onclick="prevPicture();">
<i data-feather="chevron-left"></i>
</div>
<div class="mx-auto float-left width-fit css-truncate css-truncate-target" id="modalDescription"></div>
<div class="float-right modalControl float-left" onclick="nextPicture();">
<i data-feather="chevron-right"></i>
</div>
</div>
</div>
<!-- Statically generated javascript array of pictures on this page -->
<script>
const pictures = [
{
thumbnail: "_thumbnail/2020-05-22 16.41.02.jpg",
fullsize: "_fullsize/2020-05-22 16.41.02.jpg",
original: "_original/2020-05-22 16.41.02.heic",
filename: "2020-05-22 16.41.02.heic"
}
,
{
thumbnail: "_thumbnail/2021-01-01 17.11.35.jpg",
fullsize: "_fullsize/2021-01-01 17.11.35.jpg",
original: "_original/2021-01-01 17.11.35.heic",
filename: "2021-01-01 17.11.35.heic"
}
,
{
thumbnail: "_thumbnail/2021-01-02 23.51.34.jpg",
fullsize: "_fullsize/2021-01-02 23.51.34.mp4",
original: "_original/2021-01-02 23.51.34.mp4",
filename: "2021-01-02 23.51.34.mp4"
}
,
{
thumbnail: "_thumbnail/2021-02-22 15.36.43-1.jpg",
fullsize: "_fullsize/2021-02-22 15.36.43-1.jpg",
original: "_original/2021-02-22 15.36.43-1.heic",
filename: "2021-02-22 15.36.43-1.heic"
}
,
{
thumbnail: "_thumbnail/2021-02-25 15.40.44.jpg",
fullsize: "_fullsize/2021-02-25 15.40.44.jpg",
original: "_original/2021-02-25 15.40.44.png",
filename: "2021-02-25 15.40.44.png"
}
,
{
thumbnail: "_thumbnail/_DSC9363_DxO.jpg",
fullsize: "_fullsize/_DSC9363_DxO.jpg",
original: "_original/_DSC9363_DxO.jpg",
filename: "_DSC9363_DxO.jpg"
}
,
{
thumbnail: "_thumbnail/_DSC9439.jpg",
fullsize: "_fullsize/_DSC9439.jpg",
original: "_original/_DSC9439.jpg",
filename: "_DSC9439.jpg"
}
]
</script>
<script src="fastgallery.js"></script>
<script src="feather.min.js"></script>
<script>
feather.replace()
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

@ -0,0 +1 @@
/home/toni/go/src/github.com/tonimelisma/fastgallery/testing/source/subdir/2021-01-13 18.19.20.heic

@ -0,0 +1 @@
/home/toni/go/src/github.com/tonimelisma/fastgallery/testing/source/subdir/2021-02-04 13.46.20.heic

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>subdir</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<link href="../fastgallery.css" rel="stylesheet">
<link href="../primer.css" rel="stylesheet">
</head>
<body class="bg-gray">
<div id="thumbnails">
<h1 class="px-2 pb-2 my-0 m-md-3 m-lg-4">subdir</h1>
<!-- Thumbnail view. First subfolders. -->
<div class="container-xl m-0 m-md-2 m-lg-3">
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<a href="../">
<img class="box border border-gray box-shadow width-fit" src="../back.png" alt="Back">
</a>
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">Back</span>
</div>
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<a href="subsubdir">
<img class="box border border-gray box-shadow width-fit" src="../folder.png" alt="subsubdir">
</a>
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">subsubdir</span>
</div>
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<img class="box border border-gray box-shadow width-fit" src="_thumbnail/2021-01-13 18.19.20.jpg" alt="2021-01-13 18.19.20.heic" onclick="changePicture(0);displayModal(true);">
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">2021-01-13 18.19.20.heic</span>
</div>
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<img class="box border border-gray box-shadow width-fit" src="_thumbnail/2021-02-04 13.46.20.jpg" alt="2021-02-04 13.46.20.heic" onclick="changePicture(1);displayModal(true);">
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">2021-02-04 13.46.20.heic</span>
</div>
</div>
</div>
<!-- Modal which shows individual pictures full-screen.
Covers thumbnail view. Hidden by default, unless URL contains
hashtag and thumbnail name. -->
<div class="position-fixed top-0 left-0 width-full height-full d-flex flex-column flex-justify-center flex-items-center box border border-gray box-shadow bg-gray" id="modal" hidden>
<div class="bg-gray clearfix position-absolute top-0 p-1" id="modalHeader">
<div class="float-right modalControl float-left" onclick="displayModal(false);">
<i data-feather="x"></i>
</div>
<div class="float-right modalControl float-left">
<a href="#" id="modalDownload" download>
<i data-feather="download"></i>
</a>
</div>
</div>
<div id="modalMedia" class="d-flex flex-justify-center"></div>
<div class="bg-gray position-absolute bottom-0 d-flex flex-justify-center p-1" id="modalFooter">
<div class="float-left modalControl float-left" onclick="prevPicture();">
<i data-feather="chevron-left"></i>
</div>
<div class="mx-auto float-left width-fit css-truncate css-truncate-target" id="modalDescription"></div>
<div class="float-right modalControl float-left" onclick="nextPicture();">
<i data-feather="chevron-right"></i>
</div>
</div>
</div>
<!-- Statically generated javascript array of pictures on this page -->
<script>
const pictures = [
{
thumbnail: "_thumbnail/2021-01-13 18.19.20.jpg",
fullsize: "_fullsize/2021-01-13 18.19.20.jpg",
original: "_original/2021-01-13 18.19.20.heic",
filename: "2021-01-13 18.19.20.heic"
}
,
{
thumbnail: "_thumbnail/2021-02-04 13.46.20.jpg",
fullsize: "_fullsize/2021-02-04 13.46.20.jpg",
original: "_original/2021-02-04 13.46.20.heic",
filename: "2021-02-04 13.46.20.heic"
}
]
</script>
<script src="../fastgallery.js"></script>
<script src="../feather.min.js"></script>
<script>
feather.replace()
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

@ -0,0 +1 @@
/home/toni/go/src/github.com/tonimelisma/fastgallery/testing/source/subdir/subsubdir/2021-02-17 18.59.30.heic

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>subsubdir</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<link href="../../fastgallery.css" rel="stylesheet">
<link href="../../primer.css" rel="stylesheet">
</head>
<body class="bg-gray">
<div id="thumbnails">
<h1 class="px-2 pb-2 my-0 m-md-3 m-lg-4">subsubdir</h1>
<!-- Thumbnail view. First subfolders. -->
<div class="container-xl m-0 m-md-2 m-lg-3">
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<a href="../">
<img class="box border border-gray box-shadow width-fit" src="../../back.png" alt="Back">
</a>
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">Back</span>
</div>
<div class="col-4 col-md-3 col-lg-2 float-left p-md-2 p-lg-3">
<img class="box border border-gray box-shadow width-fit" src="_thumbnail/2021-02-17 18.59.30.jpg" alt="2021-02-17 18.59.30.heic" onclick="changePicture(0);displayModal(true);">
<span class="px-2 pb-2 width-fit css-truncate css-truncate-target">2021-02-17 18.59.30.heic</span>
</div>
</div>
</div>
<!-- Modal which shows individual pictures full-screen.
Covers thumbnail view. Hidden by default, unless URL contains
hashtag and thumbnail name. -->
<div class="position-fixed top-0 left-0 width-full height-full d-flex flex-column flex-justify-center flex-items-center box border border-gray box-shadow bg-gray" id="modal" hidden>
<div class="bg-gray clearfix position-absolute top-0 p-1" id="modalHeader">
<div class="float-right modalControl float-left" onclick="displayModal(false);">
<i data-feather="x"></i>
</div>
<div class="float-right modalControl float-left">
<a href="#" id="modalDownload" download>
<i data-feather="download"></i>
</a>
</div>
</div>
<div id="modalMedia" class="d-flex flex-justify-center"></div>
<div class="bg-gray position-absolute bottom-0 d-flex flex-justify-center p-1" id="modalFooter">
<div class="float-left modalControl float-left" onclick="prevPicture();">
<i data-feather="chevron-left"></i>
</div>
<div class="mx-auto float-left width-fit css-truncate css-truncate-target" id="modalDescription"></div>
<div class="float-right modalControl float-left" onclick="nextPicture();">
<i data-feather="chevron-right"></i>
</div>
</div>
</div>
<!-- Statically generated javascript array of pictures on this page -->
<script>
const pictures = [
{
thumbnail: "_thumbnail/2021-02-17 18.59.30.jpg",
fullsize: "_fullsize/2021-02-17 18.59.30.jpg",
original: "_original/2021-02-17 18.59.30.heic",
filename: "2021-02-17 18.59.30.heic"
}
]
</script>
<script src="../../fastgallery.js"></script>
<script src="../../feather.min.js"></script>
<script>
feather.replace()
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Loading…
Cancel
Save