Some progress towards recent page

This commit is contained in:
FoxxMD
2023-08-15 14:28:56 -04:00
parent 64c46ac87f
commit e2b472cf44
28 changed files with 278 additions and 120 deletions
+2 -3
View File
@@ -1,10 +1,8 @@
import dayjs, {Dayjs} from "dayjs";
import {
buildTrackString,
capitalize, isPlayTemporallyClose,
mergeArr,
playObjDataMatch, setIntersection,
truncateStringToLength
playObjDataMatch, setIntersection
} from "../utils.js";
import {
ClientType, FormatPlayObjectOptions,
@@ -19,6 +17,7 @@ import {CommonClientConfig} from "../common/infrastructure/config/client/index.j
import {ClientConfig} from "../common/infrastructure/config/client/clients.js";
import {Notifiers} from "../notifier/Notifiers.js";
import {FixedSizeList} from 'fixed-size-list';
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
export default abstract class AbstractScrobbleClient {
+3 -2
View File
@@ -2,11 +2,11 @@ import AbstractScrobbleClient from "./AbstractScrobbleClient.js";
import dayjs from 'dayjs';
import {
buildTrackString, capitalize,
capitalize,
playObjDataMatch, removeUndefinedKeys,
setIntersection, sleep,
sortByOldestPlayDate,
truncateStringToLength,
} from "../utils.js";
import LastfmApiClient from "../apis/LastfmApiClient.js";
import {
@@ -19,6 +19,7 @@ import {LastfmClientConfig} from "../common/infrastructure/config/client/lastfm.
import {TrackScrobbleResponse, UserGetRecentTracksResponse} from "lastfm-node-client";
import {Notifiers} from "../notifier/Notifiers.js";
import {Logger} from '@foxxmd/winston';
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
export default class LastfmScrobbler extends AbstractScrobbleClient {
+3 -2
View File
@@ -2,11 +2,11 @@ import dayjs from 'dayjs';
import AbstractScrobbleClient from "./AbstractScrobbleClient.js";
import {
buildTrackString, capitalize,
capitalize,
playObjDataMatch, removeUndefinedKeys,
setIntersection, sleep,
sortByOldestPlayDate,
truncateStringToLength,
} from "../utils.js";
import LastfmApiClient from "../apis/LastfmApiClient.js";
import {
@@ -19,6 +19,7 @@ import {Notifiers} from "../notifier/Notifiers.js";
import {Logger} from '@foxxmd/winston';
import {ListenBrainzClientConfig} from "../common/infrastructure/config/client/listenbrainz.js";
import {ListenbrainzApiClient} from "../apis/ListenbrainzApiClient.js";
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
export default class ListenbrainzScrobbler extends AbstractScrobbleClient {
+1 -2
View File
@@ -3,12 +3,10 @@ import request from 'superagent';
import dayjs from 'dayjs';
import compareVersions from 'compare-versions';
import {
buildTrackString,
playObjDataMatch,
setIntersection,
sleep,
sortByOldestPlayDate,
truncateStringToLength,
parseRetryAfterSecsFromObj, capitalize, isPlayTemporallyClose
} from "../utils.js";
import {
@@ -26,6 +24,7 @@ import {
MalojaScrobbleV2RequestData,
MalojaScrobbleV3RequestData, MalojaScrobbleV3ResponseData, MalojaV2ScrobbleData, MalojaV3ScrobbleData
} from "../apis/maloja/interfaces";
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
const feat = ["ft.", "ft", "feat.", "feat", "featuring", "Ft.", "Ft", "Feat.", "Feat", "Featuring"];
+1 -1
View File
@@ -1,6 +1,5 @@
import dayjs, {Dayjs} from "dayjs";
import {
buildTrackString,
createAjvFactory,
mergeArr,
playObjDataMatch,
@@ -23,6 +22,7 @@ import winston from '@foxxmd/winston';
import ListenbrainzScrobbler from "./ListenbrainzScrobbler.js";
import {ListenBrainzClientConfig} from "../common/infrastructure/config/client/listenbrainz.js";
import {ErrorWithCause} from "pony-cause";
import {buildTrackString} from "../utils/StringUtils.js";
type groupedNamedConfigs = {[key: string]: ParsedConfig[]};
+2 -2
View File
@@ -33,9 +33,9 @@ class TupleMap<X,Y,Z> {
return this.map.values();
}
forEach(callbackfn: (value: Z, key: [X, Y], map: Map<[X, Y], Z>) => void, thisArg?: any): void {
forEach(callbackfn: (value: Z, key: [X, Y], map: Map<string, Z>) => void, thisArg?: any): void {
this.map.forEach((value, key) => {
callbackfn.call(thisArg, value, JSON.parse(key), this);
callbackfn.call(thisArg, value, JSON.parse(key), this.map);
});
}
}
+6 -5
View File
@@ -173,13 +173,14 @@ export const NO_USER = 'SingleUser';
export const SINGLE_USER_PLATFORM_ID: PlayPlatformId = [NO_DEVICE, NO_USER];
export interface TrackStringOptions {
export interface TrackStringOptions<T = string> {
include?: ('time' | 'artist' | 'track' | 'timeFromNow' | 'trackId')[]
transformers?: {
artists?: (a: string[]) => string
track?: (t: string) => string
time?: (t: Dayjs) => string
timeFromNow?: (t: Dayjs) => string
artists?: (a: string[]) => T | string
track?: (t: string, hasExistingParts?: boolean) => T | string
time?: (t: Dayjs) => T | string
timeFromNow?: (t: Dayjs) => T | string
reducer?: (arr: (T|string)[]) => T //(acc: T, curr: T | string) => T
}
}
+2 -1
View File
@@ -4,7 +4,7 @@ import winston, {format, Logger} from '@foxxmd/winston';
import {DuplexTransport} from "winston-duplex";
import {asLogOptions, LogConfig, LogInfo, LogLevel, LogOptions} from "./infrastructure/Atomic.js";
import process from "process";
import {fileOrDirectoryIsWriteable, parseBool, truncateStringToLength} from "../utils.js";
import {fileOrDirectoryIsWriteable, parseBool} from "../utils.js";
import {ErrorWithCause, stackWithCauses} from "pony-cause";
import {NullTransport} from 'winston-null';
import DailyRotateFile from 'winston-daily-rotate-file';
@@ -12,6 +12,7 @@ import dayjs from "dayjs";
import stringify from 'safe-stable-stringify';
import {SPLAT, LEVEL, MESSAGE} from 'triple-beam';
import {Symbol} from "typescript-json-schema";
import {truncateStringToLength} from "../utils/StringUtils.js";
const {combine, printf, timestamp, label, splat, errors} = format;
+3 -4
View File
@@ -13,13 +13,11 @@ import session from 'express-session';
import {createSession} from 'better-sse';
import {
buildTrackString,
capitalize,
longestString, mergeArr, parseBool,
mergeArr, parseBool,
readJson,
remoteHostIdentifiers,
sleep,
truncateStringToLength
sleep
} from "./utils.js";
import {makeClientCheckMiddle, makeSourceCheckMiddle} from "./server/middleware.js";
import TautulliSource from "./sources/TautulliSource.js";
@@ -49,6 +47,7 @@ import {formatLogToHtml, getLogger, isLogLineMinLevel} from "./common/logging.js
import {MESSAGE} from "triple-beam";
import {setupApi} from "./server/api.js";
import {Transform} from "stream";
import {buildTrackString, longestString, truncateStringToLength} from "./utils/StringUtils.js";
dayjs.extend(utc)
+2 -1
View File
@@ -1,9 +1,10 @@
import {ExpressWithAsync} from "@awaitjs/express";
import {getRoot} from "../ioc.js";
import {buildTrackString, capitalize, longestString, truncateStringToLength} from "../utils.js";
import {capitalize} from "../utils.js";
import {makeClientCheckMiddle, makeSourceCheckMiddle} from "./middleware.js";
import AbstractSource from "../sources/AbstractSource.js";
import {ClientStatusData, PlayObject, SourceStatusData, TrackStringOptions} from "../common/infrastructure/Atomic.js";
import {buildTrackString, longestString, truncateStringToLength} from "../utils/StringUtils.js";
+1 -1
View File
@@ -1,6 +1,5 @@
import dayjs, {Dayjs} from "dayjs";
import {
buildTrackString,
capitalize, isPlayTemporallyClose, genGroupId,
genGroupIdStrFromPlay, mergeArr,
playObjDataMatch, pollingBackoff,
@@ -20,6 +19,7 @@ import {SourceConfig} from "../common/infrastructure/config/source/sources.js";
import {EventEmitter} from "events";
import {FixedSizeList} from "fixed-size-list";
import TupleMap from "../common/TupleMap.js";
import {buildTrackString} from "../utils/StringUtils.js";
export interface RecentlyPlayedOptions {
limit?: number
+3 -2
View File
@@ -1,11 +1,11 @@
import MemorySource from "./MemorySource.js";
import dayjs, {Dayjs} from "dayjs";
import {
buildTrackString, isPlayTemporallyClose,
isPlayTemporallyClose,
combinePartsToString, parseBool,
parseDurationFromTimestamp,
playObjDataMatch,
truncateStringToLength, comparePlayTemporally, temporalPlayComparisonSummary, doubleReturnNewline
comparePlayTemporally, temporalPlayComparisonSummary, doubleReturnNewline
} from "../utils.js";
import {JellySourceConfig} from "../common/infrastructure/config/source/jellyfin.js";
import {FormatPlayObjectOptions, InternalConfig, PlayObject, PlayPlatformId} from "../common/infrastructure/Atomic.js";
@@ -13,6 +13,7 @@ import EventEmitter from "events";
import {PlayerStateOptions} from "./PlayerState/AbstractPlayerState.js";
import {Logger} from "@foxxmd/winston";
import {JellyfinPlayerState} from "./PlayerState/JellyfinPlayerState.js";
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
const shortDeviceId = truncateStringToLength(10, '');
+1 -1
View File
@@ -2,7 +2,6 @@ import AbstractSource from "./AbstractSource.js";
import {
playObjDataMatch,
sortByOldestPlayDate,
buildTrackString,
toProgressAwarePlayObject,
getProgress,
genGroupIdStrFromPlay,
@@ -25,6 +24,7 @@ import TupleMap from "../common/TupleMap.js";
import {AbstractPlayerState, PlayerStateOptions} from "./PlayerState/AbstractPlayerState.js";
import {GenericPlayerState} from "./PlayerState/GenericPlayerState.js";
import {Logger} from "@foxxmd/winston";
import {buildTrackString} from "../utils/StringUtils.js";
export default class MemorySource extends AbstractSource {
/*
+2 -1
View File
@@ -16,8 +16,9 @@ import pEvent from 'p-event';
import winston from '@foxxmd/winston';
import {RecentlyPlayedOptions} from "./AbstractSource.js";
import request from "superagent";
import {buildTrackString, removeDuplicates} from "../utils.js";
import {removeDuplicates} from "../utils.js";
import {SubsonicSource} from "./SubsonicSource.js";
import {buildTrackString} from "../utils/StringUtils.js";
export class MopidySource extends MemorySource {
declare config: MopidySourceConfig;
@@ -8,9 +8,10 @@ import {
ReportedPlayerStatus
} from "../../common/infrastructure/Atomic.js";
import dayjs, {Dayjs} from "dayjs";
import {buildTrackString, formatNumber, genGroupIdStr, playObjDataMatch, progressBar} from "../../utils.js";
import {formatNumber, genGroupIdStr, playObjDataMatch, progressBar} from "../../utils.js";
import {Logger} from "@foxxmd/winston";
import {ListenProgress} from "./ListenProgress.js";
import {buildTrackString} from "../../utils/StringUtils.js";
export interface PlayerStateIntervals {
staleInterval?: number
+2 -3
View File
@@ -1,9 +1,7 @@
import dayjs from "dayjs";
import {
buildTrackString,
combinePartsToString,
mergeArr,
truncateStringToLength
mergeArr
} from "../utils.js";
import AbstractSource from "./AbstractSource.js";
import formidable from 'formidable';
@@ -12,6 +10,7 @@ import {PlexSourceConfig} from "../common/infrastructure/config/source/plex.js";
import {FormatPlayObjectOptions, InternalConfig, PlayObject, SourceType} from "../common/infrastructure/Atomic.js";
import EventEmitter from "events";
import winston from '@foxxmd/winston';
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
const shortDeviceId = truncateStringToLength(10, '');
+2 -1
View File
@@ -2,7 +2,7 @@ import dayjs from "dayjs";
import {
readJson,
writeFile,
sortByOldestPlayDate, sleep, parseRetryAfterSecsFromObj, buildTrackString, truncateStringToLength, combinePartsToString,
sortByOldestPlayDate, sleep, parseRetryAfterSecsFromObj, combinePartsToString,
} from "../utils.js";
import SpotifyWebApi from "spotify-web-api-node";
import AbstractSource, {RecentlyPlayedOptions} from "./AbstractSource.js";
@@ -23,6 +23,7 @@ import AlbumObjectSimplified = SpotifyApi.AlbumObjectSimplified;
import UserDevice = SpotifyApi.UserDevice;
import MemorySource from "./MemorySource.js";
import {ErrorWithCause} from "pony-cause";
import {buildTrackString, truncateStringToLength} from "../utils/StringUtils.js";
const scopes = ['user-read-recently-played', 'user-read-currently-playing', 'user-read-playback-state', 'user-read-playback-position'];
const state = 'random';
+2 -1
View File
@@ -2,12 +2,13 @@ import request from 'superagent';
import * as crypto from 'crypto';
import dayjs from "dayjs";
import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
import {buildTrackString, parseRetryAfterSecsFromObj, removeDuplicates, sleep} from "../utils.js";
import {parseRetryAfterSecsFromObj, removeDuplicates, sleep} from "../utils.js";
import MemorySource from "./MemorySource.js";
import {SubSonicSourceConfig} from "../common/infrastructure/config/source/subsonic.js";
import {FormatPlayObjectOptions, InternalConfig, PlayObject} from "../common/infrastructure/Atomic.js";
import {RecentlyPlayedOptions} from "./AbstractSource.js";
import EventEmitter from "events";
import {buildTrackString} from "../utils/StringUtils.js";
dayjs.extend(isSameOrAfter);
+2 -1
View File
@@ -2,9 +2,10 @@ import dayjs from "dayjs";
import PlexSource from "./PlexSource.js";
import {TautulliSourceConfig} from "../common/infrastructure/config/source/tautulli.js";
import {FormatPlayObjectOptions, InternalConfig, PlayObject} from "../common/infrastructure/Atomic.js";
import {combinePartsToString, truncateStringToLength} from "../utils.js";
import {combinePartsToString} from "../utils.js";
import EventEmitter from "events";
import {Request} from "express";
import {truncateStringToLength} from "../utils/StringUtils.js";
const shortDeviceId = truncateStringToLength(10, '');
+17 -80
View File
@@ -1,32 +1,33 @@
import {promises, constants, accessSync} from "fs";
import dayjs, {Dayjs} from 'dayjs';
import {accessSync, constants, promises} from "fs";
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc.js';
import {Logger} from '@foxxmd/winston';
import JSON5 from 'json5';
import { TimeoutError, WebapiError } from "spotify-web-api-node/src/response-error.js";
import {TimeoutError, WebapiError} from "spotify-web-api-node/src/response-error.js";
import Ajv, {Schema} from 'ajv';
import {
asPlayerStateData,
DEFAULT_SCROBBLE_DURATION_THRESHOLD, ListenRange,
lowGranularitySources, NO_DEVICE, NO_USER, numberFormatOptions, PlayerStateData,
PlayObject, PlayPlatformId, ProgressAwarePlayObject, RegExResult,
RemoteIdentityParts, ScrobbleThresholdResult,
TrackStringOptions
DEFAULT_SCROBBLE_DURATION_THRESHOLD,
ListenRange,
lowGranularitySources,
NO_DEVICE,
NO_USER,
numberFormatOptions,
PlayerStateData,
PlayObject,
PlayPlatformId,
ProgressAwarePlayObject,
RegExResult,
RemoteIdentityParts,
ScrobbleThresholdResult
} from "./common/infrastructure/Atomic.js";
import {Request} from "express";
import pathUtil from "path";
import {ErrorWithCause} from "pony-cause";
import backoffStrategies from '@kenyip/backoff-strategies';
import {ScrobbleThresholds} from "./common/infrastructure/config/source/index.js";
import {
oneLineTrim,
replaceResultTransformer,
stripIndentTransformer,
TemplateTag,
trimResultTransformer
} from 'common-tags';
import {replaceResultTransformer, stripIndentTransformer, TemplateTag, trimResultTransformer} from 'common-tags';
import is from "@sindresorhus/is";
import date = is.date;
import {Duration} from "dayjs/plugin/duration.js";
dayjs.extend(utc);
@@ -83,70 +84,6 @@ export function sleep(ms: any) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export const longestString = (strings: any) => strings.reduce((acc: any, curr: any) => curr.length > acc ? curr.length : acc, 0);
export const truncateStringArrToLength = (length: any, truncStr = '...') => {
const truncater = truncateStringToLength(length, truncStr);
return (strings: any) => strings.map(truncater);
}
export const truncateStringToLength = (length: any, truncStr = '...') => (val: any = '') => {
if(val === null) {
return '';
}
const str = typeof val !== 'string' ? val.toString() : val;
return str.length > length ? `${str.slice(0, length)}${truncStr}` : str;
}
const defaultTransformer = (input: any) => input;
export const buildTrackString = (playObj: PlayObject, options: TrackStringOptions = {}) => {
const {
include = ['time', 'artist', 'track'],
transformers: {
artists: artistsFunc = (a: string[]) => a.join(' / '),
track: trackFunc = defaultTransformer,
time: timeFunc = (t: Dayjs | undefined) => t === undefined ? 'N/A' : t.local().format(),
timeFromNow = (t: Dayjs | undefined) => t === undefined ? undefined : t.local().fromNow(),
} = {}
} = options;
const {
data: {
artists,
album,
track,
playDate
} = {},
meta: {
trackId
} = {},
} = playObj;
const strParts = [];
if(include.includes('trackId') && trackId !== undefined) {
strParts.push(`(${trackId})`);
}
if(include.includes('artist')) {
strParts.push(`${artistsFunc(artists)}`)
}
if(include.includes('track')) {
if(strParts.length > 0) {
strParts.push(`- ${trackFunc(track)}`);
} else {
strParts.push(`${trackFunc(track)}`);
}
}
if (include.includes('time')) {
strParts.push(`@ ${timeFunc(playDate)}`);
}
if (include.includes('timeFromNow')) {
const tfn = timeFromNow(playDate);
if(tfn !== undefined) {
strParts.push(`(${tfn})`)
}
}
return strParts.join(' ');
}
// sorts playObj formatted objects by playDate in ascending (oldest first) order
export const sortByOldestPlayDate = (a: PlayObject, b: PlayObject) => {
const {
+93
View File
@@ -0,0 +1,93 @@
import dayjs, {Dayjs} from "dayjs";
import {PlayObject, TrackStringOptions} from "../common/infrastructure/Atomic.js";
import React, {ReactElement} from "react";
import utc from "dayjs/plugin/utc.js";
import isBetween from "dayjs/plugin/isBetween.js";
import relativeTime from "dayjs/plugin/relativeTime.js";
import duration from "dayjs/plugin/duration.js";
import timezone from "dayjs/plugin/timezone.js";
dayjs.extend(utc)
dayjs.extend(isBetween);
dayjs.extend(relativeTime);
dayjs.extend(duration);
dayjs.extend(timezone);
export const longestString = (strings: any) => strings.reduce((acc: any, curr: any) => curr.length > acc ? curr.length : acc, 0);
export const truncateStringArrToLength = (length: any, truncStr = '...') => {
const truncater = truncateStringToLength(length, truncStr);
return (strings: any) => strings.map(truncater);
}
export const truncateStringToLength = (length: any, truncStr = '...') => (val: any = '') => {
if (val === null) {
return '';
}
const str = typeof val !== 'string' ? val.toString() : val;
return str.length > length ? `${str.slice(0, length)}${truncStr}` : str;
}
export const defaultTrackTransformer = (input: any, hasExistingParts: boolean = false) => hasExistingParts ? `- ${input}` : input;
export const defaultReducer = (acc, curr) => `${acc} ${curr}`;
export const defaultArtistFunc = (a: string[]) => a.join(' / ');
export const defaultTimeFunc = (t: Dayjs | undefined) => t === undefined ? '@ N/A' : `@ ${t.local().format()}`;
export const defaultTimeFromNowFunc = (t: Dayjs | undefined) => t === undefined ? undefined : `(${t.local().fromNow()})`;
export const defaultBuildTrackStringTransformers = {
artists: defaultArtistFunc,
track: defaultTrackTransformer,
time: defaultTimeFunc,
timeFromNow: defaultTimeFromNowFunc,
}
export const buildTrackString = <T = string>(playObj: PlayObject, options: TrackStringOptions<T> = {}): T => {
const {
include = ['time', 'artist', 'track'],
transformers: {
artists: artistsFunc = defaultBuildTrackStringTransformers.artists,
track: trackFunc = defaultBuildTrackStringTransformers.track,
time: timeFunc = defaultBuildTrackStringTransformers.time,
timeFromNow = defaultBuildTrackStringTransformers.timeFromNow,
reducer = arr => arr.join(' ') // (acc, curr) => `${acc} ${curr}`
} = {},
} = options;
const {
data: {
artists,
album,
track,
playDate
} = {},
meta: {
trackId
} = {},
} = playObj;
const strParts: (T | string)[] = [];
if (include.includes('trackId') && trackId !== undefined) {
strParts.push(`(${trackId})`);
}
if (include.includes('artist')) {
strParts.push(artistsFunc(artists))
}
if (include.includes('track')) {
strParts.push(trackFunc(track, strParts.length > 0));
}
if (include.includes('time')) {
strParts.push(timeFunc(playDate));
}
if (include.includes('timeFromNow')) {
const tfn = timeFromNow(playDate);
if (tfn !== undefined) {
strParts.push(tfn)
}
}
// @ts-ignore
return reducer(strParts); //strParts.join(' ');
}
export const buildTrackStringReactOptions: TrackStringOptions<ReactElement> = {
transformers: {
...defaultBuildTrackStringTransformers,
reducer: arr => {
const allFrags = arr.map(x => typeof x === 'string' ? React.createElement("span", null, x) : x);
return React.createElement("span", null, allFrags);
}
}
}
+1
View File
@@ -15,6 +15,7 @@
"ky": "^0.33.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.15.0",
"react-scripts": "5.0.1",
"tailwindcss": "^3.3.3",
"typescript": "^4.9.5",
+19 -4
View File
@@ -1,11 +1,27 @@
import React from 'react';
import logo from './logo.svg';
import * as ReactDOM from "react-dom/client";
import {
createBrowserRouter,
RouterProvider,
} from "react-router-dom";
import './App.css';
import StatusSection from "./status/StatusSection";
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import LogsSection from "./logs/LogsSection";
const queryClient = new QueryClient()
import Dashboard from "./dashboard/dashboard";
import RecentPage from "./recent/RecentPage";
const router = createBrowserRouter([
{
path: "/",
element: <Dashboard />,
},
{
path: "/recent",
element: <RecentPage />,
},
]);
function App() {
return (
@@ -24,8 +40,7 @@ function App() {
</div>
</div>
<div className="container mx-auto">
<StatusSection/>
<LogsSection/>
<RouterProvider router={router}/>
</div>
</div>
</QueryClientProvider>
+41
View File
@@ -0,0 +1,41 @@
import React, {Fragment, PropsWithChildren, ReactElement} from 'react';
import SkeletonTitle from "./skeleton/SkeletonTitle.js";
import SkeletonParagraph from "./skeleton/SkeletonParagraph.js";
import StatusCardSkeleton, {StatusCardSkeletonData} from "./statusCard/StatusCardSkeleton.js";
import {PlayData, PlayObject, TrackStringOptions} from "../../../src/common/infrastructure/Atomic.js";
import {buildTrackString, buildTrackStringReactOptions} from "../../../src/utils/StringUtils.js";
interface PlayObjectProps {
data: PlayObject
artistLength?: number
trackLength?: number
buildOptions?: TrackStringOptions<ReactElement> & {includeWeb?: boolean}
}
const PlayDisplay = (props: PlayObjectProps) => {
const {
data,
artistLength,
trackLength,
buildOptions = {},
} = props || {};
const {
transformers,
includeWeb,
...restBuild
} = buildOptions;
const bOpts: TrackStringOptions<ReactElement> = {
...restBuild,
// @ts-ignore
transformers: {
...buildTrackStringReactOptions.transformers,
...transformers,
track: (t, hasExistingParts) => includeWeb ? <>{hasExistingParts ? '- ' : ''}<a>${t}</a></> : <>{hasExistingParts ? '- ' : ''}t</>
}
}
return buildTrackString<ReactElement>(data, bOpts);
}
export default PlayDisplay;
+13
View File
@@ -0,0 +1,13 @@
import React from 'react';
import StatusSection from "../status/StatusSection";
import LogsSection from "../logs/LogsSection";
const dashboard = () => {
return (
<>
<StatusSection/>
<LogsSection/>
</>
)
}
export default dashboard;
+31
View File
@@ -0,0 +1,31 @@
import React, {Fragment, useState} from 'react';
import {
useQuery,
} from '@tanstack/react-query'
import ky from "ky";
import {PlayObject} from "../../../src/common/infrastructure/Atomic";
import PlayDisplay from "../components/PlayDisplay";
const recent = () => {
const {isLoading, isSuccess, isError, data, error} = useQuery({
queryKey: ['recent?name=default&type=spotify'], queryFn: async () => {
return await ky.get('/api/recent?name=default&type=spotify').json() as PlayObject[]
}
});
return (
<div className="grid ">
<div className="shadow-md rounded my-6 bg-gray-500 text-white">
<div className="space-x-4 p-6 md:px-10 md:py-6 leading-6 font-semibold bg-gray-700 text-white">
<h2>Recently Played
</h2>
</div>
<div className="p-6 md:px-10 md:py-6">
<ul>{data.map(x => <li key={x.data.track}><PlayDisplay data={x}/></li>)}</ul>
</div>
</div>
</div>
);
}
export default recent;
+1 -1
View File
@@ -10,7 +10,7 @@
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
+20
View File
@@ -1738,6 +1738,11 @@
resolved "https://registry.yarnpkg.com/@react-nano/use-event-source/-/use-event-source-0.13.0.tgz#38469b006e093c10e3cbfa0039cde59b404ae30b"
integrity sha512-RzeoLepE/EnZTUkdIuZc1Odoswcfsu9bcTaoSqFhWkJ7j41T8COxcHatUHytJMiKKr+J7hpuAFoDGATshU98nQ==
"@remix-run/router@1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.8.0.tgz#e848d2f669f601544df15ce2a313955e4bf0bafc"
integrity sha512-mrfKqIHnSZRyIzBcanNJmVQELTnX+qagEDlcKO90RgRBVOZGSGvZKeDihTRfWcqoDn5N/NkUcwWTccnpN18Tfg==
"@rollup/plugin-babel@^5.2.0":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
@@ -9091,6 +9096,21 @@ react-refresh@^0.11.0:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
react-router-dom@^6.15.0:
version "6.15.0"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.15.0.tgz#6da7db61e56797266fbbef0d5e324d6ac443ee40"
integrity sha512-aR42t0fs7brintwBGAv2+mGlCtgtFQeOzK0BM1/OiqEzRejOZtpMZepvgkscpMUnKb8YO84G7s3LsHnnDNonbQ==
dependencies:
"@remix-run/router" "1.8.0"
react-router "6.15.0"
react-router@6.15.0:
version "6.15.0"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.15.0.tgz#bf2cb5a4a7ed57f074d4ea88db0d95033f39cac8"
integrity sha512-NIytlzvzLwJkCQj2HLefmeakxxWHWAP+02EGqWEZy+DgfHHKQMUoBBjUQLOtFInBMhWtb3hiUy6MfFgwLjXhqg==
dependencies:
"@remix-run/router" "1.8.0"
react-scripts@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-5.0.1.tgz#6285dbd65a8ba6e49ca8d651ce30645a6d980003"