« get me outta code hell

search: query -> select, factor out backend parts of searchSpec - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src/search-select.js
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2025-10-08 20:52:28 -0300
committer(quasar) nebula <qznebula@protonmail.com>2025-10-08 20:52:28 -0300
commit9be32e448e65efeef59fa1ed6c2f4190c86d83d4 (patch)
tree017b01a752ad658073cd8b6770ead0de01612184 /src/search-select.js
parentc60c6b04114efa65da26ded995fb5793c893d066 (diff)
search: query -> select, factor out backend parts of searchSpec
Diffstat (limited to 'src/search-select.js')
-rw-r--r--src/search-select.js213
1 files changed, 213 insertions, 0 deletions
diff --git a/src/search-select.js b/src/search-select.js
new file mode 100644
index 00000000..e7372ad4
--- /dev/null
+++ b/src/search-select.js
@@ -0,0 +1,213 @@
+// Complements the specs in search-shape.js with the functions that actually
+// process live wiki data into records that are appropriate for storage.
+// These files totally go together, so read them side by side, okay?
+
+import baseSearchSpec from '#search-shape';
+
+function prepareArtwork(artwork, thing, {
+  checkIfImagePathHasCachedThumbnails,
+  getThumbnailEqualOrSmaller,
+  urls,
+}) {
+  if (!artwork) {
+    return undefined;
+  }
+
+  const hasWarnings =
+    artwork.artTags?.some(artTag => artTag.isContentWarning);
+
+  const artworkPath =
+    artwork.path;
+
+  if (!artworkPath) {
+    return undefined;
+  }
+
+  const mediaSrc =
+    urls
+      .from('media.root')
+      .to(...artworkPath);
+
+  if (!checkIfImagePathHasCachedThumbnails(mediaSrc)) {
+    return undefined;
+  }
+
+  const selectedSize =
+    getThumbnailEqualOrSmaller(
+      (hasWarnings ? 'mini' : 'adorb'),
+      mediaSrc);
+
+  const mediaSrcJpeg =
+    mediaSrc.replace(/\.(png|jpg)$/, `.${selectedSize}.jpg`);
+
+  const displaySrc =
+    urls
+      .from('thumb.root')
+      .to('thumb.path', mediaSrcJpeg);
+
+  const serializeSrc =
+    displaySrc.replace(thing.directory, '<>');
+
+  return serializeSrc;
+}
+
+function baselineProcess(thing, opts) {
+  const fields = {};
+
+  fields.primaryName =
+    thing.name;
+
+  fields.artwork =
+    null;
+
+  fields.color =
+    thing.color;
+
+  fields.disambiguator =
+    null;
+
+  return fields;
+}
+
+function genericSelect(wikiData) {
+  const groupOrder =
+    wikiData.wikiInfo.divideTrackListsByGroups;
+
+  const getGroupRank = thing => {
+    const relevantRanks =
+      Array.from(groupOrder.entries())
+        .filter(({1: group}) => thing.groups.includes(group))
+        .map(({0: index}) => index);
+
+    if (relevantRanks.length === 0) {
+      return Infinity;
+    } else if (relevantRanks.length === 1) {
+      return relevantRanks[0];
+    } else {
+      return relevantRanks[0] + 0.5;
+    }
+  }
+
+  const sortByGroupRank = things =>
+    things.sort((a, b) => getGroupRank(a) - getGroupRank(b));
+
+  return [
+    sortByGroupRank(wikiData.albumData.slice()),
+
+    wikiData.artTagData,
+
+    wikiData.artistData
+      .filter(artist => !artist.isAlias),
+
+    wikiData.flashData,
+
+    wikiData.groupData,
+
+    sortByGroupRank(
+      wikiData.trackData
+        .filter(track => track.isMainRelease)),
+  ].flat();
+}
+
+function genericProcess(thing, opts) {
+  const fields = baselineProcess(thing, opts);
+
+  const kind =
+    thing.constructor[Symbol.for('Thing.referenceType')];
+
+  const boundPrepareArtwork = artwork =>
+    prepareArtwork(artwork, thing, opts);
+
+  fields.artwork =
+    (kind === 'track' && thing.hasUniqueCoverArt
+      ? boundPrepareArtwork(thing.trackArtworks[0])
+   : kind === 'track'
+      ? boundPrepareArtwork(thing.album.coverArtworks[0])
+   : kind === 'album'
+      ? boundPrepareArtwork(thing.coverArtworks[0])
+   : kind === 'flash'
+      ? boundPrepareArtwork(thing.coverArtwork)
+      : null);
+
+  fields.parentName =
+    (kind === 'track'
+      ? thing.album.name
+   : kind === 'group'
+      ? thing.category.name
+   : kind === 'flash'
+      ? thing.act.name
+      : null);
+
+  fields.disambiguator =
+    fields.parentName;
+
+  fields.artTags =
+    (Array.from(new Set(
+      (kind === 'track'
+        ? thing.trackArtworks.flatMap(artwork => artwork.artTags)
+     : kind === 'album'
+        ? thing.coverArtworks.flatMap(artwork => artwork.artTags)
+        : []))))
+
+      .map(artTag => artTag.nameShort);
+
+  fields.additionalNames =
+    (thing.constructor.hasPropertyDescriptor('additionalNames')
+      ? thing.additionalNames.map(entry => entry.name)
+   : thing.constructor.hasPropertyDescriptor('aliasNames')
+      ? thing.aliasNames
+      : []);
+
+  const contribKeys = [
+    'artistContribs',
+    'contributorContribs',
+  ];
+
+  const contributions =
+    contribKeys
+      .filter(key => Object.hasOwn(thing, key))
+      .flatMap(key => thing[key]);
+
+  fields.contributors =
+    contributions
+      .flatMap(({artist}) => [
+        artist.name,
+        ...artist.aliasNames,
+      ]);
+
+  const groups =
+     (Object.hasOwn(thing, 'groups')
+       ? thing.groups
+    : Object.hasOwn(thing, 'album')
+       ? thing.album.groups
+       : []);
+
+  const mainContributorNames =
+    contributions
+      .map(({artist}) => artist.name);
+
+  fields.groups =
+    groups
+      .filter(group => !mainContributorNames.includes(group.name))
+      .map(group => group.name);
+
+  return fields;
+}
+
+const spiffySearchSpec = {
+  generic: {
+    ...baseSearchSpec.generic,
+
+    select: genericSelect,
+    process: genericProcess,
+  },
+
+  verbatim: {
+    ...baseSearchSpec.verbatim,
+
+    select: genericSelect,
+    process: genericProcess,
+  },
+};
+
+export default spiffySearchSpec;