« get me outta code hell

data steps: experimental live JS reload infrastructure - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/content
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-03-18 20:15:37 -0300
committer(quasar) nebula <qznebula@protonmail.com>2023-03-18 20:15:37 -0300
commitc6e1a0b6fb9314186a46cf1352a8685e8aa5fe8d (patch)
tree54d9ba89a99882d635c5504262eccf31fc8f2147 /src/content
parent4f0d935f1dec0cece23ac661b02486f095b5ee94 (diff)
data steps: experimental live JS reload infrastructure
Diffstat (limited to 'src/content')
-rw-r--r--src/content/dependencies/generateAlbumSocialEmbed.js71
-rw-r--r--src/content/dependencies/generateAlbumSocialEmbedDescription.js48
-rw-r--r--src/content/dependencies/generateAlbumStylesheet.js59
-rw-r--r--src/content/dependencies/index.js108
4 files changed, 286 insertions, 0 deletions
diff --git a/src/content/dependencies/generateAlbumSocialEmbed.js b/src/content/dependencies/generateAlbumSocialEmbed.js
new file mode 100644
index 00000000..699b3d26
--- /dev/null
+++ b/src/content/dependencies/generateAlbumSocialEmbed.js
@@ -0,0 +1,71 @@
+export default {
+  contentDependencies: [
+    'generateSocialEmbedDescription',
+  ],
+
+  extraDependencies: [
+    'absoluteTo',
+    'language',
+    'to',
+    'urls',
+  ],
+
+  data(album, {
+    generateSocialEmbedDescription,
+  }) {
+    const data = {};
+
+    data.descriptionData = generateSocialEmbedDescription.data(album);
+
+    data.hasHeading = !empty(album.groups);
+
+    if (data.hasHeading) {
+      const firstGroup = album.groups[0];
+      data.headingGroupName = firstGroup.directory;
+      data.headingGroupDirectory = firstGroup.directory;
+    }
+
+    data.albumName = album.name;
+    data.albumColor = album.color;
+
+    return data;
+  },
+
+  generate(data, {
+    generateSocialEmbedDescription,
+
+    absoluteTo,
+    language,
+    to,
+    urls,
+  }) {
+    const socialEmbed = {};
+
+    if (data.hasHeading) {
+      socialEmbed.heading =
+        language.$('albumPage.socialEmbed.heading', {
+          group: data.headingGroupName,
+        });
+
+      socialEmbed.headingLink =
+        absoluteTo('localized.album', data.headingGroupDirectory);
+    } else {
+      socialEmbed.heading = '';
+      socialEmbed.headingLink = null;
+    }
+
+    socialEmbed.title =
+      language.$('albumPage.socialEmbed.title', {
+        album: data.albumName,
+      });
+
+    socialEmbed.description = generateSocialEmbedDescription(data.descriptionData);
+
+    socialEmbed.image =
+      '/' + getAlbumCover(album, {to: urls.from('shared.root').to});
+
+    socialEmbed.color = data.albumColor;
+
+    return socialEmbed;
+  },
+};
diff --git a/src/content/dependencies/generateAlbumSocialEmbedDescription.js b/src/content/dependencies/generateAlbumSocialEmbedDescription.js
new file mode 100644
index 00000000..2bb62596
--- /dev/null
+++ b/src/content/dependencies/generateAlbumSocialEmbedDescription.js
@@ -0,0 +1,48 @@
+export default {
+  extraDependencies: ['language'],
+
+  data(album) {
+    const data = {};
+
+    const duration = getTotalDuration(album);
+
+    data.hasDuration = duration > 0;
+    data.hasTracks = album.tracks.length > 0;
+    data.hasDate = !!album.date;
+    data.hasAny = (data.hasDuration || data.hasTracks || data.hasDuration);
+
+    if (!data.hasAny)
+      return data;
+
+    if (data.hasDuration)
+      data.duration = duration;
+
+    if (data.hasTracks)
+      data.tracks = album.tracks.length;
+
+    if (data.hasDate)
+      data.date = album.date;
+
+    return data;
+  },
+
+  generate(data, {
+    language,
+  }) {
+    return language.formatString(
+      'albumPage.socialEmbed.body' + [
+        data.hasDuration && '.withDuration',
+        data.hasTracks && '.withTracks',
+        data.hasDate && '.withReleaseDate',
+      ].filter(Boolean).join(''),
+
+      Object.fromEntries([
+        data.hasDuration &&
+          ['duration', language.formatDuration(data.duration)],
+        data.hasTracks &&
+          ['tracks', language.countTracks(data.tracks, {unit: true})],
+        data.hasDate &&
+          ['date', language.formatDate(data.date)],
+      ].filter(Boolean)));
+  },
+};
diff --git a/src/content/dependencies/generateAlbumStylesheet.js b/src/content/dependencies/generateAlbumStylesheet.js
new file mode 100644
index 00000000..575f7d59
--- /dev/null
+++ b/src/content/dependencies/generateAlbumStylesheet.js
@@ -0,0 +1,59 @@
+export default {
+  extraDependencies: [
+    'to',
+  ],
+
+  data: function(album) {
+    const data = {};
+
+    data.hasWallpaper = !empty(album.wallpaperArtistContribs);
+    data.hasBanner = !empty(album.bannerArtistContribs);
+
+    if (data.hasWallpaper) {
+      data.hasWallpaperStyle = !!album.wallpaperStyle;
+      data.wallpaperPath = ['media.albumWallpaper', album.directory, album.wallpaperFileExtension];
+      data.wallpaperStyle = album.wallpaperStyle;
+    }
+
+    if (data.hasBanner) {
+      data.hasBannerStyle = !!album.bannerStyle;
+      data.bannerStyle = album.bannerStyle;
+    }
+
+    return data;
+  },
+
+  generate(data, {to}) {
+    const wallpaperPart =
+      (data.hasWallpaper
+        ? [
+            `body::before {`,
+            `    background-image: url("${to(...data.wallpaperPath)}");`,
+            ...(data.hasWallpaperStyle
+              ? data.wallpaperStyle
+                  .split('\n')
+                  .map(line => `    ${line}`)
+              : []),
+            `}`,
+          ]
+        : []);
+
+    const bannerPart =
+      (data.hasBannerStyle
+        ? [
+            `#banner img {`,
+            ...data.bannerStyle
+              .split('\n')
+              .map(line => `    ${line}`),
+            `}`,
+          ]
+        : []);
+
+    return [
+      ...wallpaperPart,
+      ...bannerPart,
+    ]
+      .filter(Boolean)
+      .join('\n');
+  },
+};
diff --git a/src/content/dependencies/index.js b/src/content/dependencies/index.js
new file mode 100644
index 00000000..5cd116d4
--- /dev/null
+++ b/src/content/dependencies/index.js
@@ -0,0 +1,108 @@
+import chokidar from 'chokidar';
+import EventEmitter from 'events';
+import * as path from 'path';
+import {fileURLToPath} from 'url';
+
+import contentFunction from '../../content-function.js';
+import {color, logWarn} from '../../util/cli.js';
+import {annotateFunction} from '../../util/sugar.js';
+
+export function watchContentDependencies() {
+  const events = new EventEmitter();
+  const contentDependencies = {};
+
+  Object.assign(events, {
+    contentDependencies,
+  });
+
+  // Watch adjacent files
+  const metaPath = fileURLToPath(import.meta.url);
+  const metaDirname = path.dirname(metaPath);
+  const watcher = chokidar.watch(metaDirname);
+
+  watcher.on('all', (event, filePath) => {
+    if (!['add', 'change'].includes(event)) return;
+    if (filePath === metaPath) return;
+    handlePathUpdated(filePath);
+  });
+
+  watcher.on('unlink', (filePath) => {
+    if (filePath === metaPath) {
+      console.error(`Yeowzers content dependencies just got nuked.`);
+      return;
+    }
+    handlePathRemoved(filePath);
+  })
+
+  return events;
+
+  function getFunctionName(filePath) {
+    const shortPath = path.basename(filePath);
+    const functionName = shortPath.slice(0, -path.extname(shortPath).length);
+    return functionName;
+  }
+
+  async function handlePathRemoved(filePath) {
+    const functionName = getFunctionName(filePath);
+    delete contentDependencies[functionName];
+  }
+
+  async function handlePathUpdated(filePath) {
+    const functionName = getFunctionName(filePath);
+    let error = null;
+
+    main: {
+      let spec;
+      try {
+        spec = (await import(`${filePath}?${Date.now()}`)).default;
+      } catch (caughtError) {
+        error = caughtError;
+        error.message = `Error importing: ${error.message}`;
+        break main;
+      }
+
+      try {
+        if (typeof spec.data === 'function') {
+          annotateFunction(spec.data, {name: functionName, description: 'data'});
+        }
+
+        if (typeof spec.generate === 'function') {
+          annotateFunction(spec.generate, {name: functionName});
+        }
+      } catch (caughtError) {
+        error = caughtError;
+        error.message = `Error annotating functions: ${error.message}`;
+        break main;
+      }
+
+      let fn;
+      try {
+        fn = contentFunction(spec);
+      } catch (caughtError) {
+        error = caughtError;
+        error.message = `Error loading spec: ${error.message}`;
+        break main;
+      }
+
+      contentDependencies[functionName] = fn;
+    }
+
+    if (!error) {
+      return true;
+    }
+
+    if (contentDependencies[functionName]) {
+      logWarn`Failed to import ${functionName} - using existing version`;
+    } else {
+      logWarn`Failed to import ${functionName} - no prior version loaded`;
+    }
+
+    if (typeof error === 'string') {
+      console.error(color.yellow(error));
+    } else {
+      console.error(error);
+    }
+
+    return false;
+  }
+}