diff --git a/.dockerignore b/.dockerignore index 40afce8..4c78b80 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ .git/ Dockerfile -.vscode/ \ No newline at end of file +.vscode/ +dist/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 233d426..ae0ce9c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,8 @@ server/music/* #except for the readme !server/music/readme.txt #and the sample songs -!server/music/Catch the Sun.mp3 -!server/music/Crisp day.mp3 -!server/music/Rolling Clouds.mp3 -!server/music/Strong Breeze.mp3 \ No newline at end of file +!server/music/default + +#dist folder +dist/* +!dist/readme.txt \ No newline at end of file diff --git a/README.md b/README.md index f5f28b1..a5f3839 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,13 @@ As time allows I will be working on the following enhancements. * Better error reporting when api.weather.gov is down (happens more often than you would think) +## Serving static files +The app can be served as a static set of files on any web server. Run the provided gulp task to create a set of static distribution files: +``` +npm run buildDist +``` +The resulting files will be in the /dist folder in the root of the project. These can then be uploaded to a web server for hosting, no server-side scripting is required. + ## Community Notes Thanks to the WeatherStar community for providing these discussions to further extend your retro forecasts! diff --git a/dist/readme.txt b/dist/readme.txt new file mode 100644 index 0000000..10f6b13 --- /dev/null +++ b/dist/readme.txt @@ -0,0 +1 @@ +This folder is a placeholder for static files generated by the gulp task buildDist \ No newline at end of file diff --git a/gulp/publish-frontend.mjs b/gulp/publish-frontend.mjs index a91b340..a8d8520 100644 --- a/gulp/publish-frontend.mjs +++ b/gulp/publish-frontend.mjs @@ -12,11 +12,13 @@ import s3Upload from 'gulp-s3-upload'; import webpack from 'webpack-stream'; import TerserPlugin from 'terser-webpack-plugin'; import { readFile } from 'fs/promises'; +import reader from '../src/playlist-reader.mjs'; +import file from "gulp-file"; // get cloudfront import { CloudFrontClient, CreateInvalidationCommand } from '@aws-sdk/client-cloudfront'; -const clean = () => deleteAsync(['./dist**']); +const clean = () => deleteAsync(['./dist/**/*', '!./dist/readme.txt']); const cloudfront = new CloudFrontClient({ region: 'us-east-1' }); @@ -122,8 +124,9 @@ const compressHtml = async () => { const otherFiles = [ 'server/robots.txt', 'server/manifest.json', + 'server/music/**/*.mp3' ]; -const copyOtherFiles = () => src(otherFiles, { base: 'server/' }) +const copyOtherFiles = () => src(otherFiles, { base: 'server/', encoding: false }) .pipe(dest('./dist')); const s3 = s3Upload({ @@ -170,10 +173,20 @@ const invalidate = () => cloudfront.send(new CreateInvalidationCommand({ }, })); -const buildDist = series(clean, parallel(buildJs, compressJsData, compressJsVendor, copyCss, compressHtml, copyOtherFiles)); +const buildPlaylist = async () => { + const availableFiles = await reader(); + const playlist = { availableFiles }; + return file('playlist.json', JSON.stringify(playlist)).pipe(dest('./dist')) +} + +const buildDist = series(clean, parallel(buildJs, compressJsData, compressJsVendor, copyCss, compressHtml, copyOtherFiles, buildPlaylist)); // upload_images could be in parallel with upload, but _images logs a lot and has little changes // by running upload last the majority of the changes will be at the bottom of the log for easy viewing const publishFrontend = series(buildDist, uploadImages, upload, invalidate); export default publishFrontend; + +export { + buildDist, +} diff --git a/gulpfile.mjs b/gulpfile.mjs index 09fb16a..a7149ac 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -1,7 +1,8 @@ import updateVendor from './gulp/update-vendor.mjs'; -import publishFrontend from './gulp/publish-frontend.mjs'; +import publishFrontend, { buildDist } from './gulp/publish-frontend.mjs' export { updateVendor, publishFrontend, + buildDist, }; diff --git a/package-lock.json b/package-lock.json index ace7284..8a6b023 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "gulp-awspublish": "^8.0.0", "gulp-concat": "^2.6.1", "gulp-ejs": "^5.1.0", + "gulp-file": "^0.4.0", "gulp-htmlmin": "^5.0.1", "gulp-rename": "^2.0.0", "gulp-s3-upload": "^1.7.3", @@ -5473,6 +5474,102 @@ "readable-stream": "2 || 3" } }, + "node_modules/gulp-file": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/gulp-file/-/gulp-file-0.4.0.tgz", + "integrity": "sha512-3NPCJpAPpbNoV2aml8T96OK3Aof4pm4PMOIa1jSQbMNSNUUXdZ5QjVgLXLStjv0gg9URcETc7kvYnzXdYXUWug==", + "dev": true, + "license": "BSD", + "dependencies": { + "through2": "^0.4.1", + "vinyl": "^2.1.0" + } + }, + "node_modules/gulp-file/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-file/node_modules/object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-file/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/gulp-file/node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-file/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-file/node_modules/through2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha512-45Llu+EwHKtAZYTPPVn3XZHBgakWMN3rokhEv5hu596XP+cNgplMg+Gj+1nmAvj+L0K7+N49zBKx5rah5u0QIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~1.0.17", + "xtend": "~2.1.1" + } + }, + "node_modules/gulp-file/node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/gulp-file/node_modules/xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==", + "dev": true, + "dependencies": { + "object-keys": "~0.4.0" + }, + "engines": { + "node": ">=0.4" + } + }, "node_modules/gulp-htmlmin": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/gulp-htmlmin/-/gulp-htmlmin-5.0.1.tgz", diff --git a/package.json b/package.json index 43a0870..5055fa7 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build:css": "sass --style=compressed ./server/styles/scss/main.scss ./server/styles/main.css", + "build": "gulp buildDist", "lint": "eslint ./server/scripts/**/*.mjs", "lint:fix": "eslint --fix ./server/scripts/**/*.mjs" }, @@ -21,32 +22,33 @@ }, "homepage": "https://github.com/netbymatt/ws4kp#readme", "devDependencies": { - "del": "^8.0.0", - "jquery": "^3.6.0", - "jquery-touchswipe": "^1.6.19", - "luxon": "^3.0.0", - "nosleep.js": "^0.12.0", - "suncalc": "^1.8.0", - "swiped-events": "^1.1.4", "@aws-sdk/client-cloudfront": "^3.609.0", - "gulp-awspublish": "^8.0.0", - "gulp-s3-upload": "^1.7.3", + "del": "^8.0.0", "eslint": "^8.2.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.10.0", "gulp": "^5.0.0", + "gulp-awspublish": "^8.0.0", "gulp-concat": "^2.6.1", "gulp-ejs": "^5.1.0", + "gulp-file": "^0.4.0", "gulp-htmlmin": "^5.0.1", "gulp-rename": "^2.0.0", + "gulp-s3-upload": "^1.7.3", "gulp-sass": "^6.0.0", "gulp-terser": "^2.0.0", + "jquery": "^3.6.0", + "jquery-touchswipe": "^1.6.19", + "luxon": "^3.0.0", + "nosleep.js": "^0.12.0", + "sass": "^1.54.0", + "suncalc": "^1.8.0", + "swiped-events": "^1.1.4", "terser-webpack-plugin": "^5.3.6", - "webpack-stream": "^7.0.0", - "sass": "^1.54.0" + "webpack-stream": "^7.0.0" }, "dependencies": { - "express": "^4.17.1", - "ejs": "^3.1.5" + "ejs": "^3.1.5", + "express": "^4.17.1" } } \ No newline at end of file diff --git a/server/music/default/Catch the Sun.mp3 b/server/music/default/Catch the Sun.mp3 new file mode 100644 index 0000000..c2dbdad Binary files /dev/null and b/server/music/default/Catch the Sun.mp3 differ diff --git a/server/music/default/Crisp day.mp3 b/server/music/default/Crisp day.mp3 new file mode 100644 index 0000000..957184e Binary files /dev/null and b/server/music/default/Crisp day.mp3 differ diff --git a/server/music/default/Rolling Clouds.mp3 b/server/music/default/Rolling Clouds.mp3 new file mode 100644 index 0000000..41430cd Binary files /dev/null and b/server/music/default/Rolling Clouds.mp3 differ diff --git a/server/music/default/Strong Breeze.mp3 b/server/music/default/Strong Breeze.mp3 new file mode 100644 index 0000000..06e0c4f Binary files /dev/null and b/server/music/default/Strong Breeze.mp3 differ diff --git a/server/music/readme.txt b/server/music/readme.txt index 867b12a..49f563b 100644 --- a/server/music/readme.txt +++ b/server/music/readme.txt @@ -1,2 +1,3 @@ .mp3 files placed in this folder will be available via the un-mute button in the application. -No subdirectories will be scanned, and music will be played in a random order. \ No newline at end of file +No subdirectories will be scanned, and music will be played in a random order. +The default folder will be used only if no .mp3 files are found in this /server/music folder \ No newline at end of file diff --git a/server/scripts/modules/media.mjs b/server/scripts/modules/media.mjs index 934ac42..6186873 100644 --- a/server/scripts/modules/media.mjs +++ b/server/scripts/modules/media.mjs @@ -63,12 +63,17 @@ const toggleMedia = (forcedState) => { stateChanged(); }; -const startMedia = () => { +const startMedia = async () => { // if there's not media player yet, enable it if (!player) { initializePlayer(); } else { - player.play(); + try { + await player.play(); + } catch (e) { + console.error('Couldn\'t play music'); + console.error(e); + } } }; @@ -128,11 +133,12 @@ const initializePlayer = () => { console.log('player initialized'); }; -const playerCanPlay = () => { +const playerCanPlay = async () => { // check to make sure they user still wants music (protect against slow loading music) if (!mediaPlaying.value) return; // start playing - player.play(); + startMedia(); + }; const playerEnded = () => { diff --git a/src/playlist-reader.mjs b/src/playlist-reader.mjs index 401f23d..ae96bf6 100644 --- a/src/playlist-reader.mjs +++ b/src/playlist-reader.mjs @@ -1,12 +1,21 @@ import fs from 'fs/promises'; +const mp3Filter = (file) => file.match(/\.mp3$/); + const reader = async () => { // get the listing of files in the folder const rawFiles = await fs.readdir('./server/music'); // filter for mp3 files - const files = rawFiles.filter((file) => file.match(/\.mp3$/)); - console.log(files); - return files; + const files = rawFiles.filter(mp3Filter); + // if files were found return them + if (files.length > 0) { + return files; + } + + // fall back to the default folder + const defaultFiles = await fs.readdir('./server/music/default'); + return defaultFiles.map(file => `default/${file}`).filter(mp3Filter); + }; export default reader;