and
14 | favicon: false # or string with a path to custom favicon (in PNG format)
15 | subtitles:
16 | convertSrtToVtt: false
17 | ssl: false
18 | #certificate: '' # required if ssl is set
19 | #certificateKey: '' # required if ssl is set
20 | #redirectHttpToHttps: true # bool - yes/no
21 | #httpPort: 80 # HTTP port to redirect to HTTPS or for Let's encrypt (if Let's encrypt is used, must be 80)
22 | #letsEncrypt: false # or string with a path to directory to store ACME challenge
23 | httpAuth: false # or string with htpasswd auth file
24 |
--------------------------------------------------------------------------------
/docs/gallery-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/docs/gallery-image.jpg
--------------------------------------------------------------------------------
/docs/gallery-list.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/docs/gallery-list.jpg
--------------------------------------------------------------------------------
/docs/gallery-video.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/docs/gallery-video.jpg
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | New BSD License
2 | ---------------
3 |
4 | Copyright 2023 Jakub Trmota (https://forrest79.dev)
5 |
6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
11 |
12 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "@babel/plugin-transform-runtime": "^7.23",
4 | "@babel/preset-env": "^7.23",
5 | "@rollup/plugin-babel": "^6.0",
6 | "@rollup/plugin-commonjs": "^25.0",
7 | "@rollup/plugin-node-resolve": "^15.2",
8 | "@rollup/plugin-replace": "^5.0",
9 | "@rollup/plugin-terser": "^0.4",
10 | "commander": "^11.0",
11 | "core-js": "^3.33",
12 | "ejs": "^3.1",
13 | "eslint": "^8.51",
14 | "eslint-config-airbnb-base": "^15.0",
15 | "exifr": "^7.1",
16 | "glightbox": "^3.2",
17 | "node-sass": "^9.0",
18 | "photo-sphere-viewer": "^4.8",
19 | "rollup": "^4.0",
20 | "shareon": "^2.3",
21 | "tippy.js": "^6.3",
22 | "yaml": "^2.3"
23 | },
24 | "scripts": {
25 | "build": "node ./src/cli.mjs",
26 | "eslint": "npx eslint ./src --ext js,mjs"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # StaticNginxGallery
2 |
3 | Static gallery (images, videos with subtitles, other files) on the top of the nginx web server.
4 |
5 | **Based on https://stackoverflow.com/questions/39575873/is-there-a-way-to-create-a-simple-static-image-gallery-in-nginx-without-any-thir.**
6 |
7 | Just put your files in the folder and get it instantly in the gallery - no background service is running, files are read directly from your file system. With the XSTL is directory listing converted to an HTML page with some controlling JavaScript and CSS.
8 |
9 | 
10 |
11 | 
12 |
13 | 
14 |
15 | ## About gallery
16 |
17 | ### Features:
18 |
19 | - images...
20 | - reading EXIF (via [exifr](https://github.com/MikeKovarik/exifr))
21 | - show GPS on the Google Maps
22 | - view photo sphere panoramas (via [Photo Sphere Viewer](https://photo-sphere-viewer.js.org/))
23 | - an optional automatic image thumbnails with an optional cache
24 | - videos (via [Plyr](https://plyr.io/))
25 | - an optional thumbnail generation (`ffmpeg` installed on system needed)
26 | - with optional auto conversion SRT subtitles to supported VTT
27 | - other files (just view and download)
28 | - sharing link to the gallery
29 | - sharing on social networks (via [shareon](https://shareon.js.org/))
30 | - as lightbox is used [GLightbox](https://biati-digital.github.io/glightbox/)
31 | - HTTP or HTTPS (Let's encrypt support - via external tools - [dehyhrated](https://dehydrated.io/) for example)
32 | - custom gallery title/favicon and many more settings
33 | - password access (via HTTP Basic Auth)
34 |
35 | ### What do I need?
36 |
37 | [nginx](https://www.nginx.com/) to run gallery and [node.js](https://nodejs.org) version 16 or greater with `npm` to generate configuration. If you want to generate also video thumbnails, `ffmpeg` must be installed on your system. Nothing more, really. This tool generates nginx configuration and one XSLT, JS and CSS file to control whole gallery and all supported tools.
38 |
39 | You need nginx with the [XSTL module](http://nginx.org/en/docs/http/ngx_http_xslt_module.html). In Debian/Ubuntu world this is the `nginx-full` package.
40 |
41 | If you want an automatic image thumbnail (with a cache) you need also the [images filter module](http://nginx.org/en/docs/http/ngx_http_image_filter_module.html). Still the `nginx-full` in Debian/Ubuntu.
42 |
43 | For an automatic SRT to VTT conversion you need the [lua module](https://github.com/openresty/lua-nginx-module). This is the `nginx-extras` package.
44 |
45 | And for video thumbnails generation you also need the `lua module` and [ffmpeg](https://ffmpeg.org/) installed on your system and binaries must be set in `PATH` (you can run `ffmpeg` command from everywhere). Thumbnail generation was tested on `ffmpeg` version `4.3.0`.
46 |
47 | > This configuration generator was tested on the linux only.
48 |
49 | ## How to use it
50 |
51 | Clone this repository or download source files from GitHub and install npm modules with `npm install`.
52 |
53 | Then you need configuration file in YAML:
54 |
55 | ```yaml
56 | nginxConfDir: /etc/nginx/sites-available/
57 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/
58 | serverName: gallery.test
59 | galleryDir: /var/www/gallery
60 | ```
61 |
62 | These are the only 4 required items you always must specify:
63 | - `nginxConfDir` - where nginx configuration file will be generated (file will have `static-nginx-gallery` name) - don't forget to enable configuration in nginx, when it's not generated into included files - in Debian `sudo ln -s /etc/nginx/sites-available/static-nginx-gallery /etc/nginx/sites-enabled/static-nginx-gallery` and `sudo service nginx reload`
64 | - `nginxAppDir` - where the other (`XSLT`, `JS` and `CSS`) files will be stored
65 | - `serverName` - domain where the gallery will be accessible
66 | - `galleryDir` - root directory with all your files (you can access also all subdirectories)
67 |
68 | There are some more optional options:
69 |
70 | ```yaml
71 | path: null # /path/
72 | port: null # null = use default - 80 for HTTP, 443 for HTTPS
73 | thumbnails: false # bool - yes/no or string with cache directory
74 | videoThumbnails: false # or string with cache directory
75 | lightTheme: false # bool - yes/no
76 | title: 'Gallery' #
and
77 | favicon: false # or string with a path to custom favicon (in PNG format)
78 | subtitles:
79 | convertSrtToVtt: false
80 | ssl: false # explain later
81 | httpAuth: false # or string with htpasswd auth file
82 | ```
83 |
84 | - `path` - by default, gallery is accessible at serverName host `/`, this can change the default path to `path` (for example `http://gallery.test/some-path/` it you set `serverName: gallery.test` and `path: 'some-path'`)
85 | - `port` - `null` is used for default value - `80` for HTTP or `443` for HTTPS version, but you can use your own, if you need
86 | - `thumbnails` - by default, thumbnail images in the list are original images, this could cause to download a lot of data for one page. When you set this to `true`, nginx will generate for all images smaller thumbnails on the fly, so data usage will be less, but more processor time will be used to generate thumbnails on every request. The last option is to provide a `string` with a directory, where this thumbnails will be cached, so every thumbnail will be generated just once. This has just one disadvantage: when a thumbnail is generated, it's never regenerated, even if the original image is changed. You must remove cached thumbnail to regenerate it. *`images` module is needed in nginx for this*
87 | - `videoThumbnails` - by default, videos has no thumbnail images. When you set directory for cache, nginx will generate via `ffmpeg` thumbnails and these thumbnails will be cached, so every thumbnail will be generated just once. This has the same disadvantage as image thumbnails: when a thumbnail is generated, it's never regenerated, even if the original video is changed. You must remove cached thumbnail to regenerate it. *`lua` module is needed in nginx for this and `ffmpeg` must be installed on the system*
88 | - `lightTheme` - gallery is by default in dark theme, set this to `true` and light theme will be used
89 | - `title` - can change default `` and `
` which is `Gallery`
90 | - `favicon` - can change default favicon file, use path to a PNG image
91 | - `subtitels` -> `convertSrtToVtt` - if set to `true` try to automatically convert existing SRT subtitles to VTT (only VTT subtitles are supported in video player) - *`LUA` module is needed in nginx*
92 | - `httpAuth` - use can specify a `string` with a path to htpasswd file for enabling user/password access to the gallery
93 |
94 | > How to generate `htpasswd` file? In linux with installed `openssl` you can use `printf "USER:$(openssl passwd -crypt PASSWORD)\n" >> .htpasswd` - just change `USER` with the correct username and `PASSWORD` with the correct password. `.htpasswd` is path to the generated file.
95 |
96 | In most cases, you want to run your gallery on HTTPS protocol. You can also use own HTTPS (or other, for example for access restriction I can recommend [oauth2-proxy](https://github.com/oauth2-proxy/oauth2-proxy)) proxy and keep running gallery on HTTP:
97 |
98 | ```yaml
99 | ssl:
100 | certificate: '/etc/letsencrypt/certs/gallery.test/fullchain.pem' # required if ssl is set
101 | certificateKey: '/etc/letsencrypt/certs/gallery.test/privkey.pem' # required if ssl is set
102 | redirectHttpToHttps: true # bool - yes/no
103 | httpPort: 80 # HTTP port to redirect to HTTPS or for Let's encrypt (if Let's encrypt is used, must be 80)
104 | letsEncrypt: false # or string with a path to directory to store ACME challenge
105 | ```
106 |
107 | - `certificate` - path to SSL certificate, required
108 | - `certificateKey` - path to SSL certificate key, required
109 | - `redirectHttpToHttps` - by default, for HTTPS is also generated a HTTP block, that just redirect HTTP to HTTPS. If you don't need this, set config to `false`
110 | - `httpPort` - belongs to the previous config, set port for HTTP block for redirect, default is `80`. If you use Let's encrypt config, you must keep it to `80`.
111 | - `letsEncrypt` - when `string` path is provided, to HTTP block is generated snippet to enable auto generating Let's encrypt certificates. You also need some tool to generate certificates, for example [dehyhrated](https://dehydrated.io/). Before first certificate is generated, you must comment SSL certificate and SSL certificate key lines.
112 |
113 | ### Build configuration
114 |
115 | So, we have `config.yml` with our configuration, now just run:
116 |
117 | ```bash
118 | bin/build config.yml
119 | ```
120 |
121 | or:
122 |
123 | ```bash
124 | npm run build config.yml
125 | ```
126 |
127 | You will probably need `root` rights to proper save configuration, so use:
128 |
129 | ```bash
130 | sudo bin/build config.yml
131 | ```
132 |
133 | And after that don't forget to reload your nginx configuration. In Debian/Ubuntu like this:
134 |
135 | ```bash
136 | sudo service nginx reload
137 | ```
138 |
139 | You can also add `-d` or `--debug` option to disable JS and CSS minimization.
140 |
141 | ### Preferable nginx configuration
142 |
143 | It's recommended to add this option to main nginx configuration, the `http` section:
144 |
145 | ```
146 | variables_hash_max_size 4096
147 | ```
148 |
149 | In Debian/Ubuntu in file `/etc/nginx/nginx.conf`.
150 |
151 | ## Development
152 |
153 | You can make your own changes in CSS, JavaScript and HTML structure.
154 |
155 | CSS is in the [src/sass/main.scss](/src/sass/main.scss) file and is compiled with the [SASS preprocessor](https://sass-lang.com/).
156 |
157 | JavaScript is in the [src/js/main.js](/src/js/main.js) file and is compiled with the [Rollup](https://rollupjs.org/).
158 |
159 | HTML can be changed in the [XSLT template](/src/templates/xslt.ejs).
160 |
161 | And the generated nginx configurations can be updated in the [configuration](/src/templates/conf.ejs).
162 |
163 | XSLT and nginx configurations are generated with the [EJS templates](https://ejs.co/).
164 |
165 | To check your JS coding standard, run [eslint](https://eslint.org/).
166 |
167 | ```bash
168 | npm run eslint
169 | ```
170 |
171 | ### Tests
172 |
173 | In this repository, there are some tests configurations. They expected Debian/Ubuntu linux with installed `nginx-extras` package, symlinked directory [tests](/tests) to the `/var/www/static-nginx-gallery/` and pointed host `gallery.test` to the nginx. Self-signed certificate is used for SSL configuration.
174 |
175 | You can run `sudo tests/run-tests` that will try all test configurations one by one.
176 |
--------------------------------------------------------------------------------
/src/cli.mjs:
--------------------------------------------------------------------------------
1 | import { Command } from 'commander/esm.mjs';
2 | import fs from 'fs';
3 | import crypto from 'crypto';
4 | import yaml from 'yaml';
5 | import ejs from 'ejs';
6 | import * as rollup from 'rollup';
7 | import rollupNodeResolve from '@rollup/plugin-node-resolve';
8 | import rollupReplace from '@rollup/plugin-replace';
9 | import rollupCommonJS from '@rollup/plugin-commonjs';
10 | import { babel } from '@rollup/plugin-babel';
11 | import terser from '@rollup/plugin-terser';
12 | import sass from 'node-sass';
13 | import { fileURLToPath } from 'url';
14 | import * as path from 'path';
15 | import defaultConfig from './defaultConfig.mjs';
16 |
17 | // Helpers
18 |
19 | const srcDir = path.dirname(fileURLToPath(import.meta.url));
20 | const sha1 = (data) => crypto.createHash('sha1').update(data).digest('hex');
21 |
22 | // Prepare build options
23 |
24 | const program = new Command();
25 |
26 | program.option('-d, --debug', 'build debug version (don\'t minimize CSS and JS)');
27 | program.parse(process.argv);
28 |
29 | if (program.args.length === 0) {
30 | console.error('You must specify YAML file with the build configuration.');
31 | process.exit(1);
32 | } else if (program.args.length > 1) {
33 | console.error('You must specify only one YAML file with the build configuration.');
34 | process.exit(1);
35 | }
36 |
37 | const configYamlFile = program.args[0];
38 | const debugMode = program.opts().debug === true;
39 |
40 | // Prepare configuration
41 |
42 | const config = defaultConfig;
43 | let configYaml = null;
44 |
45 | try {
46 | configYaml = yaml.parse(fs.readFileSync(configYamlFile, 'utf8'));
47 |
48 | if (configYaml.nginxConfDir !== undefined) {
49 | config.nginxConfDir = configYaml.nginxConfDir.replace(/(\/)$/, '');
50 | }
51 |
52 | if (configYaml.nginxAppDir !== undefined) {
53 | config.nginxAppDir = configYaml.nginxAppDir.replace(/(\/)$/, '');
54 | }
55 |
56 | if (configYaml.serverName !== undefined) {
57 | config.serverName = configYaml.serverName;
58 | }
59 |
60 | if (configYaml.galleryDir !== undefined) {
61 | config.galleryDir = configYaml.galleryDir;
62 | }
63 |
64 | if (configYaml.path !== undefined) {
65 | config.path = configYaml.path;
66 | }
67 |
68 | if (configYaml.port !== undefined) {
69 | config.port = configYaml.port;
70 | }
71 |
72 | if (configYaml.thumbnails !== undefined) {
73 | config.thumbnails = configYaml.thumbnails;
74 | }
75 |
76 | if (configYaml.videoThumbnails !== undefined) {
77 | config.videoThumbnails = configYaml.videoThumbnails;
78 | }
79 |
80 | if (configYaml.lightTheme !== undefined) {
81 | config.lightTheme = configYaml.lightTheme;
82 | }
83 |
84 | if (configYaml.title !== undefined) {
85 | config.title = configYaml.title;
86 | }
87 |
88 | if (configYaml.favicon !== undefined) {
89 | config.favicon = configYaml.favicon;
90 | }
91 |
92 | if (typeof configYaml.subtitles === 'object') {
93 | if (configYaml.subtitles.convertSrtToVtt !== undefined) {
94 | config.subtitlesConvertSrtToVtt = configYaml.subtitles.convertSrtToVtt;
95 | }
96 | }
97 |
98 | if (typeof configYaml.ssl === 'object') {
99 | config.ssl = true;
100 |
101 | if (configYaml.ssl.certificate !== undefined) {
102 | config.sslCertificate = configYaml.ssl.certificate;
103 | }
104 |
105 | if (configYaml.ssl.certificateKey !== undefined) {
106 | config.sslCertificateKey = configYaml.ssl.certificateKey;
107 | }
108 |
109 | if (configYaml.ssl.redirectHttpToHttps !== undefined) {
110 | config.sslRedirectHttpToHttps = configYaml.ssl.redirectHttpToHttps;
111 | }
112 |
113 | if (configYaml.ssl.httpPort !== undefined) {
114 | config.sslHttpPort = configYaml.ssl.httpPort;
115 | }
116 |
117 | if (configYaml.ssl.letsEncrypt !== undefined) {
118 | config.sslLetsEncrypt = configYaml.ssl.letsEncrypt;
119 | }
120 | }
121 |
122 | if (configYaml.httpAuth !== undefined) {
123 | config.httpAuth = configYaml.httpAuth;
124 | }
125 |
126 | let configError = false;
127 |
128 | if (config.nginxConfDir === null) {
129 | console.error('You must specify "nginxConfDir" in your configuration.');
130 | configError = true;
131 | }
132 |
133 | if (config.nginxAppDir === null) {
134 | console.error('You must specify "nginxAppDir" in your configuration.');
135 | configError = true;
136 | }
137 |
138 | if (config.serverName === null) {
139 | console.error('You must specify "serverName" in your configuration.');
140 | configError = true;
141 | }
142 |
143 | if (config.galleryDir === null) {
144 | console.error('You must specify "galleryDir" in your configuration.');
145 | configError = true;
146 | }
147 |
148 | if (config.ssl) {
149 | if (config.sslCertificate === null) {
150 | console.error('You must specify "ssl.certificate" in your configuration.');
151 | configError = true;
152 | }
153 |
154 | if (config.sslCertificateKey === null) {
155 | console.error('You must specify "ssl.certificateKey" in your configuration.');
156 | configError = true;
157 | }
158 |
159 | if (config.sslLetsEncrypt && (config.sslHttpPort !== 80)) {
160 | console.error('When using "ssl.letsEncrypt" in your configuration, then "ssl.httpPort" must be 80.');
161 | configError = true;
162 | }
163 | }
164 |
165 | if (configError) {
166 | process.exit(2);
167 | }
168 |
169 | if (config.port === null) {
170 | config.port = config.ssl === false ? 80 : 443;
171 | }
172 |
173 | if (config.path !== null) {
174 | config.path = '/' + config.path.replace(/^(\/)/, '');
175 | } else {
176 | config.path = '';
177 | }
178 | } catch (e) {
179 | console.error(`Can't read YAML configuration file "${configYamlFile}".`, e);
180 | process.exit(1);
181 | }
182 |
183 | // Compile CSS
184 | const renderedCss = sass.renderSync({
185 | file: path.join(srcDir, 'sass/main.scss'),
186 | outputStyle: debugMode ? 'expanded' : 'compressed',
187 | }).css.toString().trim();
188 |
189 | // Compile JS
190 |
191 | const bundle = await rollup.rollup({
192 | input: path.join(srcDir, 'js/main.js'),
193 | plugins: [
194 | rollupReplace({
195 | 'process.env.NODE_ENV': JSON.stringify(debugMode ? 'development' : 'production'),
196 | preventAssignment: true,
197 | }),
198 | rollupNodeResolve(),
199 | rollupCommonJS(),
200 | babel({
201 | babelHelpers: 'runtime',
202 | presets: [
203 | [
204 | '@babel/preset-env',
205 | {
206 | bugfixes: true,
207 | corejs: '3.9',
208 | targets: '>0.25%',
209 | useBuiltIns: 'usage',
210 | },
211 | ],
212 | ],
213 | compact: false,
214 | plugins: ['@babel/plugin-transform-runtime'],
215 | exclude: 'node_modules/core-js/**',
216 | }),
217 | ].concat(debugMode ? [] : [terser()]),
218 | });
219 | const { output } = await bundle.generate({
220 | format: 'iife',
221 | name: 'gallery',
222 | });
223 | const renderedJs = output[0].code.trim();
224 |
225 | // Build application
226 |
227 | config.cssSha1 = sha1(renderedCss);
228 | config.jsSha1 = sha1(renderedJs);
229 |
230 | const nginxTemplate = fs.readFileSync(path.join(srcDir, 'templates/conf.ejs'), 'utf8');
231 | const renderedNginxTemplate = ejs.render(nginxTemplate, config);
232 |
233 | const xsltTemplate = fs.readFileSync(path.join(srcDir, 'templates/xslt.ejs'), 'utf8');
234 | const renderedXsltTemplate = ejs.render(xsltTemplate, config);
235 |
236 | fs.writeFileSync(config.nginxConfDir + '/static-nginx-gallery', renderedNginxTemplate);
237 | fs.writeFileSync(config.nginxAppDir + '/static-nginx-gallery.xslt', renderedXsltTemplate);
238 | fs.writeFileSync(config.nginxAppDir + '/static-nginx-gallery.css', renderedCss);
239 | fs.writeFileSync(config.nginxAppDir + '/static-nginx-gallery.js', renderedJs);
240 |
--------------------------------------------------------------------------------
/src/defaultConfig.mjs:
--------------------------------------------------------------------------------
1 | const defaultConfig = {
2 | // required
3 | nginxConfDir: null,
4 | nginxAppDir: null,
5 | serverName: null,
6 | galleryDir: null,
7 | // optional
8 | path: null,
9 | port: null,
10 | thumbnails: false,
11 | videoThumbnails: false,
12 | lightTheme: false,
13 | title: 'Gallery',
14 | favicon: false,
15 | subtitlesConvertSrtToVtt: false,
16 | ssl: false,
17 | sslCertificate: null,
18 | sslCertificateKey: null,
19 | sslRedirectHttpToHttps: true,
20 | sslHttpPort: 80,
21 | sslLetsEncrypt: false,
22 | httpAuth: false,
23 | // can't be rewritten in user configuration
24 | thumbnailsResizeWidth: 800,
25 | thumbnailsResizeHeight: 800,
26 | imageFilterBuffer: '100M',
27 | imageFilterJpegQuality: 85,
28 | imageFilterWebpQuality: 80,
29 | imageFilterSharpen: 50,
30 | };
31 |
32 | export default defaultConfig;
33 |
--------------------------------------------------------------------------------
/src/js/main.js:
--------------------------------------------------------------------------------
1 | import GLightbox from 'glightbox';
2 | import { Viewer } from 'photo-sphere-viewer';
3 | import tippy from 'tippy.js';
4 | import { init as shareon } from '../../node_modules/shareon/dist/shareon.es';
5 | import exifr from '../../node_modules/exifr/dist/lite.esm';
6 |
7 | const exifSvg = ``;
11 |
12 | const mapSvg = ``;
16 |
17 | const photo360Svg = ``;
20 |
21 | const shareSvg = ``;
24 |
25 | const openSvg = ``;
28 |
29 | const downloadSvg = ``;
33 |
34 | const copySvg = ``;
38 |
39 | const copySvgDone = ``;
44 |
45 | const closeSvg = ``;
49 |
50 | const customLightboxHTML = `
51 |