Skip to content

Commit

Permalink
TidalTags - Fix showTags setting & organize modules
Browse files Browse the repository at this point in the history
  • Loading branch information
Inrixia committed Jun 20, 2024
1 parent 2295cda commit b99c1f1
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 163 deletions.
2 changes: 2 additions & 0 deletions plugins/TidalTags/src/Settings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { html } from "@neptune/voby";
// @ts-expect-error Remove this when types are available
import { storage } from "@plugin";
import { updateObserver } from ".";

storage.showTags ??= true;
storage.showAtmosQuality ??= true;
Expand All @@ -27,6 +28,7 @@ export const Settings = () => {

const onChange = (key: string) => (e: { target: { checked: boolean } }) => {
storage[key] = e.target.checked;
updateObserver();
};
return html`<div class="settings-section">
<h3 class="settings-header">Display Tags</h3>
Expand Down
45 changes: 38 additions & 7 deletions plugins/TidalTags/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
import { intercept } from "@neptune";
import { intercept, store } from "@neptune";

import { setFLACInfo } from "./setFLACInfo";

import "./style";
import { updateTrackRow as updateTrackRows } from "./updateTrackElements";
import { setQualityTags } from "./setQualityTags";

export { Settings } from "./Settings";

// @ts-expect-error intercept callback does not have types filled
const unloadIntercept = intercept("playbackControls/MEDIA_PRODUCT_TRANSITION", setFLACInfo);
// @ts-expect-error Remove this when types are available
import { storage } from "@plugin";
import { isElement } from "./lib/isElement";
import { setInfoColumnHeaders, setInfoColumns } from "./setInfoColumns";
import { TrackItemCache } from "./lib/TrackItemCache";
import { PlaybackContext } from "../../../lib/AudioQualityTypes";

export const isElement = (node: Node | undefined): node is Element => node?.nodeType === Node.ELEMENT_NODE;
/**
* Flac Info
*/
// @ts-expect-error Intercept callback does not have types filled
const unloadIntercept = intercept("playbackControls/MEDIA_PRODUCT_TRANSITION", setFLACInfo);
setFLACInfo([{ playbackContext: <PlaybackContext>store.getState().playbackControls.playbackContext }]);

/**
* Tags & Info Columns
*/
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === "childList") {
Expand All @@ -24,8 +36,27 @@ const observer = new MutationObserver((mutationsList) => {
}
}
});
// Start observing the document with the configured parameters
observer.observe(document.body, { childList: true, subtree: true });
const updateTrackRows = async (trackRows: NodeListOf<Element>) => {
if (storage.displayInfoColumns) setInfoColumnHeaders();
for (const trackRow of trackRows) {
const trackId = trackRow.getAttribute("data-track-id");
if (trackId == null) return;

const trackItem = TrackItemCache.get(trackId);
if (trackItem?.contentType !== "track") continue;

if (storage.showTags) setQualityTags(trackRow, trackId, trackItem);
if (storage.displayInfoColumns) setInfoColumns(trackRow, trackId, trackItem);
}
};
export const updateObserver = () => {
observer.disconnect();
if (storage.showTags || storage.displayInfoColumns) {
// Start observing the document with the configured parameters
observer.observe(document.body, { childList: true, subtree: true });
}
};
updateObserver();

export const onUnload = () => {
observer.disconnect();
Expand Down
18 changes: 18 additions & 0 deletions plugins/TidalTags/src/lib/TrackItemCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { store } from "@neptune";
import { TrackItem, MediaItem } from "neptune-types/tidal";

export class TrackItemCache {
private static readonly _cache: Map<string, TrackItem> = new Map<string, TrackItem>();
public static get(trackId: string) {
let mediaItem = TrackItemCache._cache.get(trackId);
if (mediaItem !== undefined) return mediaItem;
const mediaItems: Record<number, MediaItem> = store.getState().content.mediaItems;
for (const itemId in mediaItems) {
const item = mediaItems[itemId]?.item;
if (item?.contentType !== "track") continue;
TrackItemCache._cache.set(itemId, item);
}
mediaItem = TrackItemCache._cache.get(trackId);
if (mediaItem !== undefined) return mediaItem;
}
}
1 change: 1 addition & 0 deletions plugins/TidalTags/src/lib/isElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const isElement = (node: Node | undefined): node is Element => node?.nodeType === Node.ELEMENT_NODE;
3 changes: 0 additions & 3 deletions plugins/TidalTags/src/setFLACInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { AudioQuality, PlaybackContext } from "../../../lib/AudioQualityTypes";
import { storage } from "@plugin";

import { TrackInfoCache } from "./lib/TrackInfoCache";
import { store } from "@neptune";
import { messageError } from "../../../lib/messageLogging";
import { hexToRgba } from "./lib/hexToRgba";

Expand Down Expand Up @@ -127,5 +126,3 @@ export const setFLACInfo = async ([{ playbackContext }]: [{ playbackContext?: Pl

if (flacInfoElem.textContent.length === 0) flacInfoElem.textContent = "Unknown";
};

setFLACInfo([{ playbackContext: <PlaybackContext>store.getState().playbackControls.playbackContext }]);
85 changes: 85 additions & 0 deletions plugins/TidalTags/src/setInfoColumns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { lookupItemQuality, QualityMeta, QualityTag, sortQualityTags } from "../../../lib/AudioQualityTypes";
import { isElement } from "./lib/isElement";

// @ts-expect-error Remove this when types are available
import { storage } from "@plugin";
import { TrackInfoCache } from "./lib/TrackInfoCache";
import { TrackItem } from "neptune-types/tidal";

const setColumn = (trackRow: Element, name: string, sourceSelector: string, content: HTMLElement, beforeSelector?: string | Element) => {
let column = trackRow.querySelector<HTMLElement>(`div[data-test="${name}"]`);
if (column !== null) return;

const sourceColumn = trackRow.querySelector<HTMLElement>(sourceSelector);
if (sourceColumn === null) return;

column = sourceColumn?.cloneNode(true);
if (isElement(column)) {
column.setAttribute("data-test", name);
column.innerHTML = "";
column.appendChild(content);
return sourceColumn.parentElement!.insertBefore(column, beforeSelector instanceof Element ? beforeSelector : beforeSelector ? trackRow.querySelector(beforeSelector) : sourceColumn);
}
};

const ensureColumnHeader = (trackList: Element, name: string, sourceSelector: string, beforeSelector?: string | Element) => {
let columnHeader = trackList.querySelector<HTMLElement>(`span[data-test="${name}"][role="columnheader"]`);
if (columnHeader !== null) return;

const sourceColumn = trackList.querySelector(sourceSelector);
if (!(sourceColumn instanceof HTMLElement)) return;

columnHeader = sourceColumn.cloneNode(true);
if ((columnHeader.firstChild?.childNodes?.length ?? -1) > 1) columnHeader.firstChild?.lastChild?.remove();
columnHeader.setAttribute("data-test", name);
columnHeader.firstChild!.firstChild!.textContent = name;

return sourceColumn.parentElement!.insertBefore(columnHeader, beforeSelector instanceof Element ? beforeSelector : beforeSelector ? trackList.querySelector(beforeSelector) : sourceColumn);
};

export const setInfoColumnHeaders = () => {
for (const trackList of document.querySelectorAll(`div[aria-label="Tracklist"]`)) {
const bitDepthColumn = ensureColumnHeader(trackList, "Depth", `span[class^="timeColumn--"][role="columnheader"]`, `span[class^="timeColumn--"][role="columnheader"]`);
bitDepthColumn?.style.setProperty("min-width", "40px");
const sampleRateColumn = ensureColumnHeader(trackList, "Sample Rate", `span[class^="timeColumn--"][role="columnheader"]`, bitDepthColumn);
sampleRateColumn?.style.setProperty("min-width", "110px");
const bitrateColumn = ensureColumnHeader(trackList, "Bitrate", `span[class^="timeColumn--"][role="columnheader"]`, sampleRateColumn);
bitrateColumn?.style.setProperty("min-width", "100px");
}
};

export const setInfoColumns = (trackRow: Element, trackId: string, trackItem: TrackItem) => {
const qualityTag = sortQualityTags(<QualityTag[]>trackItem.mediaMetadata?.tags)[0] ?? "LOW";

const audioQuality = lookupItemQuality(qualityTag, trackItem.audioQuality);
if (audioQuality === undefined) return;

const bitDepthContent = document.createElement("span");

const bitDepthColumn = setColumn(trackRow, "Depth", `div[data-test="duration"]`, bitDepthContent, `div[data-test="duration"]`);
bitDepthColumn?.style.setProperty("min-width", "40px");

const sampleRateContent = document.createElement("span");

const sampleRateColumn = setColumn(trackRow, "Sample Rate", `div[data-test="duration"]`, sampleRateContent, bitDepthColumn);
sampleRateColumn?.style.setProperty("min-width", "110px");

const bitrateContent = document.createElement("span");

const bitrateColumn = setColumn(trackRow, "Bitrate", `div[data-test="duration"]`, bitrateContent, sampleRateColumn);
bitrateColumn?.style.setProperty("min-width", "100px");

if (storage.infoColumnColors) {
const qualityColor = QualityMeta[qualityTag]?.color ?? "";
bitDepthContent.style.color = qualityColor;
sampleRateContent.style.color = qualityColor;
bitrateContent.style.color = qualityColor;
}

TrackInfoCache.register(trackId, audioQuality, async (trackInfoP) => {
const trackInfo = await trackInfoP;
if (!!trackInfo?.sampleRate) sampleRateContent.textContent = `${trackInfo.sampleRate / 1000}kHz`;
if (!!trackInfo?.bitDepth) bitDepthContent.textContent = `${trackInfo.bitDepth}bit`;
if (!!trackInfo?.bitrate) bitrateContent.textContent = `${Math.floor(trackInfo.bitrate / 1000)}kbps`;
});
};
46 changes: 46 additions & 0 deletions plugins/TidalTags/src/setQualityTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// @ts-expect-error Remove this when types are available
import { storage } from "@plugin";

import { AudioQuality, QualityMeta, QualityTag } from "../../../lib/AudioQualityTypes";
import type { TrackItem } from "neptune-types/tidal";

export const setQualityTags = (trackRow: Element, trackId: string, mediaItem: TrackItem) => {
let trackTags = mediaItem.mediaMetadata?.tags;
if (trackTags === undefined) return;

const isLowQuality = mediaItem.audioQuality === AudioQuality.Low || mediaItem.audioQuality === AudioQuality.Lowest;
if (trackTags.length === 1 && trackTags[0] === QualityTag.High && !isLowQuality) return;

const trackTitle = trackRow.querySelector<HTMLElement>(`[data-test="table-row-title"]`);
if (trackTitle === null) return;

const span = trackTitle.querySelector(".quality-tag-container") ?? document.createElement("span");
if (span.getAttribute("track-id") === trackId) return;

span.className = "quality-tag-container";
span.setAttribute("track-id", trackId);

if (isLowQuality) {
const tagElement = document.createElement("span");
tagElement.className = "quality-tag";
tagElement.textContent = QualityMeta["LOW"].textContent;
tagElement.style.color = QualityMeta["LOW"].color;
span.appendChild(tagElement);
}

for (const tag of trackTags) {
if (tag === QualityTag.High) continue;
if (!storage.showAtmosQuality && tag === QualityTag.DolbyAtmos) continue;

const data = QualityMeta[tag];
if (data === undefined) continue;

const tagElement = document.createElement("span");
tagElement.className = "quality-tag";
tagElement.textContent = data.textContent;
tagElement.style.color = data.color;
span.appendChild(tagElement);
}

trackTitle.appendChild(span);
};
Loading

0 comments on commit b99c1f1

Please sign in to comment.