From 84ea8a2498ab083807d70ad9d14e3b9b354b570b Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Sun, 7 Jul 2024 16:52:10 +0200 Subject: [PATCH 01/10] Fixed a ton of eslint errors (we are down to like 10). Might have broken some functionality, haven't tested rigorously. --- frontend/.eslintrc.cjs | 7 + frontend/src/components/common/buttons.tsx | 11 +- .../src/components/common/contextMenu.tsx | 149 +++++++------- .../components/common/itemDetailsTable.tsx | 6 +- frontend/src/components/common/list.tsx | 14 +- .../components/common/navigation/index.tsx | 0 .../src/components/common/tagGroupView.tsx | 5 +- frontend/src/components/common/tagView.tsx | 192 +++++++++--------- .../src/components/context/useSelection.tsx | 4 +- .../src/components/context/useSiblings.tsx | 64 +++--- frontend/src/components/terminal.tsx | 2 +- frontend/src/lib/library.ts | 11 +- frontend/src/routes/inbox.tsx | 6 +- .../library/browse.$artist.$albumId.tsx | 16 +- .../src/routes/library/browse.$artist.tsx | 11 +- frontend/src/routes/library/browse.tsx | 8 +- frontend/src/routes/tags/index.tsx | 11 +- 17 files changed, 266 insertions(+), 251 deletions(-) delete mode 100644 frontend/src/components/common/navigation/index.tsx diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index e18bf8c..4cab843 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -13,10 +13,17 @@ module.exports = { ignorePatterns: ['dist', '.eslintrc.cjs', 'tailwind.config.js', 'postcss.config.js', 'vite.config.ts', '*.md'], parser: '@typescript-eslint/parser', plugins: ['react-refresh'], + rules: { 'react-refresh/only-export-components': [ 0, ], + "@typescript-eslint/no-empty-function": "off", + }, + settings: { + react: { + version: 'detect', + }, }, parserOptions: { ecmaVersion: "latest", diff --git a/frontend/src/components/common/buttons.tsx b/frontend/src/components/common/buttons.tsx index ac8155c..ea22d84 100644 --- a/frontend/src/components/common/buttons.tsx +++ b/frontend/src/components/common/buttons.tsx @@ -7,9 +7,6 @@ import { forwardRef, useState } from "react"; - - - /** * Renders an icon button with a mutation option. * @@ -30,8 +27,8 @@ export const IconButtonWithMutation = forwardRef(function IconButtonWithMutation children, ...props }: { - mutationOption?: UseMutationOptions - mutateArgs?: any; + mutationOption?: UseMutationOptions + mutateArgs?: unknown; } & ButtonProps, ref?: React.Ref ) { @@ -96,8 +93,8 @@ export const IconButtonWithMutationAndFeedback = forwardRef(function IconButtonW mutateArgs, ...props }: { - mutationOption: UseMutationOptions - mutateArgs: any, + mutationOption: UseMutationOptions + mutateArgs: unknown, confirmTitle: string; } & ButtonProps, ref?: React.Ref diff --git a/frontend/src/components/common/contextMenu.tsx b/frontend/src/components/common/contextMenu.tsx index a4e000b..798d11e 100644 --- a/frontend/src/components/common/contextMenu.tsx +++ b/frontend/src/components/common/contextMenu.tsx @@ -1,5 +1,5 @@ import Menu from "@mui/material/Menu"; -import MenuItem from "@mui/material/MenuItem"; +import MenuItem, { MenuItemOwnProps } from "@mui/material/MenuItem"; import { UseMutationOptions, @@ -14,7 +14,6 @@ import { useContext, useRef, useEffect, - cloneElement, forwardRef, } from "react"; import { queryClient } from "@/main"; @@ -42,6 +41,7 @@ import styles from "./contextMenu.module.scss"; import { useSiblings } from "@/components/context/useSiblings"; import { ErrorDialog } from "./dialogs"; import { Typography } from "@mui/material"; +import { ExpandableSib } from "./tagView"; interface ContextMenuContextI { closeMenu: () => void; @@ -54,11 +54,11 @@ interface ContextMenuContextI { } const ContextMenuContext = createContext({ - closeMenu: () => {}, - openMenuMouse: () => {}, - startLongPressTimer: () => {}, - handleTouchMove: () => {}, - cancelLongPressTimer: () => {}, + closeMenu: () => { }, + openMenuMouse: () => { }, + startLongPressTimer: () => { }, + handleTouchMove: () => { }, + cancelLongPressTimer: () => { }, open: false, position: undefined, }); @@ -77,22 +77,24 @@ const ContextMenuContext = createContext({ * */ + interface ContextMenuProps extends Omit, "onContextMenu"> { children: React.ReactNode; - actions?: React.ReactNode[]; + actions?: JSX.Element[]; identifier?: string; } + export const defaultActions = [ - , - , - , - , - , - , - , - , + , + , + , + , + , + , + , + , ]; export function ContextMenu({ @@ -119,9 +121,9 @@ export function ContextMenu({ setPosition((prev?: { left: number; top: number }) => prev === undefined ? { - left: event.clientX + 2, - top: event.clientY - 6, - } + left: event.clientX + 2, + top: event.clientY - 6, + } : undefined ); }; @@ -217,11 +219,7 @@ export function ContextMenu({ anchorPosition={position} className={styles.ContextMenu} > - {actions.map((action, index) => - cloneElement(action as React.ReactElement, { - key: (action as React.ReactElement).key || `action-${index}`, - }) - )} + {actions} ); @@ -257,14 +255,14 @@ function Trigger({ children, ...props }: React.HTMLAttributes) { /* Simple Actions */ /* ---------------------------------------------------------------------------------- */ -export function SelectionSummary({ ...props }: { [key: string]: any }) { +export function SelectionSummary({ ...props }: Partial) { const { numSelected } = useSelection(); const N = useRef(numSelected()); // on close, we reset N. prevent the menu from displaying 0 for a split second useEffect(() => { N.current = numSelected(); - }, []); + }, [numSelected]); return ( // make this a non-clickable heading @@ -272,7 +270,7 @@ export function SelectionSummary({ ...props }: { [key: string]: any }) { ); } -export function SelectAllAction({ ...props }: { [key: string]: any }) { +export function SelectAllAction({ ...props }: Partial) { const { selectAll } = useSelection(); const { closeMenu } = useContextMenu(); return ( @@ -288,7 +286,7 @@ export function SelectAllAction({ ...props }: { [key: string]: any }) { ); } -export function DeselectAllAction({ ...props }: { [key: string]: any }) { +export function DeselectAllAction({ ...props }: Partial) { const { deselectAll } = useSelection(); const { closeMenu } = useContextMenu(); return ( @@ -305,16 +303,16 @@ export function DeselectAllAction({ ...props }: { [key: string]: any }) { } // for accordions that can be expanded, like in the tags view -export function ExpandAllAction({ ...props }: { [key: string]: any }) { - const { siblings } = useSiblings(); +export function ExpandAllAction({ ...props }: Partial) { + const { callOnSiblings } = useSiblings(); const { closeMenu } = useContextMenu(); return ( { - siblings.forEach((sibling: React.RefObject) => { - sibling.current.setExpanded(true); - }); + callOnSiblings((sib) => { + sib.setExpanded(true); + }) closeMenu(); }} text={"Expand All"} @@ -323,16 +321,16 @@ export function ExpandAllAction({ ...props }: { [key: string]: any }) { ); } -export function CollapseAllAction({ ...props }: { [key: string]: any }) { - const { siblings } = useSiblings(); +export function CollapseAllAction({ ...props }: Partial) { + const { callOnSiblings } = useSiblings(); const { closeMenu } = useContextMenu(); return ( { - siblings.forEach((sibling: React.RefObject) => { - sibling.current.setExpanded(false); - }); + callOnSiblings((sib) => { + sib.setExpanded(false); + }) closeMenu(); }} text={"Collapse All"} @@ -341,14 +339,14 @@ export function CollapseAllAction({ ...props }: { [key: string]: any }) { ); } -export function CopyPathAction({ ...props }: { [key: string]: any }) { +export function CopyPathAction(props: Partial) { const { closeMenu } = useContextMenu(); const { getSelected } = useSelection(); const text = useRef(""); useEffect(() => { text.current = "'" + getSelected().join("' '") + "'"; - }, [text]); + }, [getSelected, text]); return ( ) { const { closeMenu } = useContextMenu(); const { setOpen, inputText, clearInput } = useTerminalContext(); const { getSelected } = useSelection(); @@ -371,7 +369,7 @@ export function TerminalImportAction({ ...props }: { [key: string]: any }) { useEffect(() => { text.current = "'" + getSelected().join("' '") + "'"; - }, [text]); + }, [getSelected, text]); return ( ) { +export function UndoImportAction(props: Partial) { const { closeMenu } = useContextMenu(); const { setOpen, inputText, clearInput } = useTerminalContext(); const { getSelected } = useSelection(); @@ -440,7 +438,7 @@ export function UndoImportAction(props: Record) { /** * Refresh or tag for the first time. */ -export function RetagAction({ ...props }: { [key: string]: any }) { +export function RetagAction(props: Partial) { const { closeMenu } = useContextMenu(); const { getSelected } = useSelection(); const retagOptions: UseMutationOptions = { @@ -457,11 +455,12 @@ export function RetagAction({ ...props }: { [key: string]: any }) { }); }, onSuccess: async () => { - getSelected().forEach(async (tagPath: string) => { + const selected = getSelected(); + for (const tagPath of selected) { await queryClient.setQueryData(["tag", tagPath], (old: TagI) => { return { ...old, status: "pending" }; }); - }); + } closeMenu(); }, onError: (error: Error) => { @@ -479,7 +478,7 @@ export function RetagAction({ ...props }: { [key: string]: any }) { ); } -export function ImportAction({ ...props }: { [key: string]: any }) { +export function ImportAction(props: Partial) { const { closeMenu } = useContextMenu(); const { getSelected } = useSelection(); const importOptions: UseMutationOptions = { @@ -497,11 +496,12 @@ export function ImportAction({ ...props }: { [key: string]: any }) { }, onSuccess: async () => { closeMenu(); - getSelected().forEach(async (tagPath: string) => { + const selected = getSelected(); + for (const tagPath of selected) { await queryClient.setQueryData(["tag", tagPath], (old: TagI) => { return { ...old, status: "pending" }; }); - }); + } }, onError: (error: Error) => { console.error(error); @@ -518,7 +518,7 @@ export function ImportAction({ ...props }: { [key: string]: any }) { ); } -export function DeleteAction({ ...props }: { [key: string]: any }) { +export function DeleteAction(props: Partial) { const { closeMenu } = useContextMenu(); const { getSelected } = useSelection(); const selection = getSelected(); @@ -558,29 +558,39 @@ export function DeleteAction({ ...props }: { [key: string]: any }) { } /* ---------------------------------------------------------------------------------- */ -/* Action Helpers */ +/* Base action definitions */ /* ---------------------------------------------------------------------------------- */ + +interface ActionWithMutationProps extends ActionProps { + mutationOption: UseMutationOptions; +} + + +interface ActionProps extends MenuItemOwnProps { + onClick?: () => void; + icon?: React.ReactNode; + text: React.ReactNode; + className?: string; +} + + + + function Action({ onClick, icon, text, className, ...props -}: { - onClick?: () => void; - icon?: React.ReactNode; - text: React.ReactNode; - className?: string; - [key: string]: any; -}) { +}: ActionProps) { const { closeMenu } = useContextMenu(); return ( {icon ? (
{icon}
@@ -601,13 +611,7 @@ const ActionWithMutation = forwardRef(function ActionWithMutation( text, className, ...props - }: { - mutationOption: UseMutationOptions; - icon: React.ReactNode; - text: React.ReactNode; - className?: string; - [key: string]: any; - }, + }: ActionWithMutationProps, ref?: React.Ref ) { const { isSuccess, isPending, mutate, isError, error, reset } = @@ -615,7 +619,6 @@ const ActionWithMutation = forwardRef(function ActionWithMutation( return ( { event.stopPropagation(); @@ -625,6 +628,7 @@ const ActionWithMutation = forwardRef(function ActionWithMutation( mutate(); } }} + {...props} > {icon ? (
@@ -635,7 +639,7 @@ const ActionWithMutation = forwardRef(function ActionWithMutation(
)} -
{isPending ? text + " ..." : text}
+
{isPending ? <>{text}... : text}
{isError && }
@@ -643,20 +647,17 @@ const ActionWithMutation = forwardRef(function ActionWithMutation( }); function Heading({ - onClick, icon, text, className, ...props }: { - onClick?: () => void; icon?: React.ReactNode; text: React.ReactNode; className?: string; - [key: string]: any; -}) { +} & MenuItemOwnProps) { return ( - +
{text}
{icon &&
{icon}
}
diff --git a/frontend/src/components/common/itemDetailsTable.tsx b/frontend/src/components/common/itemDetailsTable.tsx index 7f17102..3163617 100644 --- a/frontend/src/components/common/itemDetailsTable.tsx +++ b/frontend/src/components/common/itemDetailsTable.tsx @@ -7,6 +7,7 @@ import TableBody from "@mui/material/TableBody"; import TableCell from "@mui/material/TableCell"; import TableContainer from "@mui/material/TableContainer"; import TableRow from "@mui/material/TableRow"; +import { ReactNode } from "react"; export default function ItemDetailsTableView({ item, @@ -69,7 +70,8 @@ export default function ItemDetailsTableView({ } -function parse(key: string, value: any) { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function parse(key: string, value: any): ReactNode { // format n/a if (value === null || value === undefined || value === "") { return n/a; @@ -107,5 +109,5 @@ function parse(key: string, value: any) { return n/a; } - return value; + return value as string; } diff --git a/frontend/src/components/common/list.tsx b/frontend/src/components/common/list.tsx index 43d69d9..a51e27c 100644 --- a/frontend/src/components/common/list.tsx +++ b/frontend/src/components/common/list.tsx @@ -1,16 +1,17 @@ import { Link } from "@tanstack/react-router"; import { ListItemText } from "@mui/material"; -import ListItem, { ListItemProps } from "@mui/material/ListItem"; +import ListItem, { ListItemOwnProps, ListItemProps } from "@mui/material/ListItem"; import { ReactNode, ComponentType } from "react"; import { FixedSizeList, ListChildComponentProps } from "react-window"; import AutoSizer from "react-virtualized-auto-sizer"; +// eslint-disable-next-line @typescript-eslint/no-explicit-any interface WrapperProps { - children: ComponentType>>; + children: ComponentType>; className?: string; - data: Array; + data: D[]; } function List({ children, data, ...props }: WrapperProps) { @@ -33,13 +34,12 @@ function List({ children, data, ...props }: WrapperProps) { ); } -type ListItemData = { +interface ListItemData extends ListItemOwnProps { label: string; to?: string; - params?: { [key: string]: any }; + params?: Record; icon?: ReactNode; - [key: string]: any; -}; +} function Item({ index, data, style }: ListChildComponentProps) { const { label, to, params, icon, ...props } = data[index]; diff --git a/frontend/src/components/common/navigation/index.tsx b/frontend/src/components/common/navigation/index.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/components/common/tagGroupView.tsx b/frontend/src/components/common/tagGroupView.tsx index e103ba0..2788856 100644 --- a/frontend/src/components/common/tagGroupView.tsx +++ b/frontend/src/components/common/tagGroupView.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { ComponentProps } from "react"; import Accordion from "@mui/material/Accordion"; import AccordionSummary from "@mui/material/AccordionSummary"; import AccordionDetails from "@mui/material/AccordionDetails"; @@ -44,8 +44,7 @@ export default function TagGroupView({ title: React.ReactNode | string; subtitle?: string; children: React.ReactNode; - [key: string]: unknown; -}) { +} & ComponentProps) { return ( ({ }, })); -export const TagView = forwardRef( - ({ tagId, tagPath }: { tagId?: string; tagPath?: string }, ref) => { - if (!tagId && !tagPath) { - throw new Error("TagView requires either a tagId or tagPath"); + +export interface ExpandableSib { + setExpanded: (state: boolean) => void +} + +export function TagView({ tagId, tagPath }: { tagId?: string; tagPath?: string }) { + if (!tagId && !tagPath) { + throw new Error("TagView requires either a tagId or tagPath"); + } + const identifier: string = tagId ?? tagPath!; + const { data, isLoading, isPending, isError } = useQuery( + tagQueryOptions(tagId, tagPath) + ); + const { isSelected, toggleSelection, markSelectable } = useSelection(); + const { register: registerSibling, unregister: unregisterSibling } = useSiblings(); + const config = useConfig(); + const [expanded, setExpanded] = useState(config.gui.tags.expand_tags); + const handleSelect = (event: React.MouseEvent) => { + if (event.metaKey || event.ctrlKey) { + if (data) toggleSelection(data.album_folder); + event.stopPropagation(); + event.preventDefault(); } - const identifier: string = tagId ?? tagPath!; - const { data, isLoading, isPending, isError } = useQuery( - tagQueryOptions(tagId, tagPath) - ); - const { isSelected, toggleSelection, markSelectable } = useSelection(); - const { registerSibling } = useSiblings(); - const config = useConfig(); - const [expanded, setExpanded] = useState(config.gui.tags.expand_tags); - const handleSelect = (event: React.MouseEvent) => { - if (event.metaKey || event.ctrlKey) { - if (data) toggleSelection(data.album_folder); - event.stopPropagation(); - event.preventDefault(); - } - }; - const handleExpand = (event: React.MouseEvent) => { - if (event.metaKey || event.ctrlKey) { - return; - } else { - setExpanded(!expanded); - } - }; - - useEffect(() => { - registerSibling(ref as React.RefObject); - }, [registerSibling, ref]); - - useEffect(() => { - if (data?.album_folder) markSelectable(data?.album_folder); - }, [markSelectable, data?.album_folder]); - - // expose setExpanded to the outside - useImperativeHandle(ref, () => ({ - setExpanded: (state: boolean) => { - setExpanded(state); - }, - })); - - if (isLoading || isPending || isError) { - let inner = ""; - if (isLoading) inner = "Loading..."; - if (isPending) inner = "Pending..."; - if (isError) inner = "Error..."; - return ( - - {inner} - - ); + }; + const handleExpand = (event: React.MouseEvent) => { + if (event.metaKey || event.ctrlKey) { + return; + } else { + setExpanded(!expanded); } + }; + + + // Self register as sibling + useEffect(() => { + registerSibling(identifier, { + setExpanded + }); + return () => { + unregisterSibling(identifier); + } + }, [identifier, registerSibling, unregisterSibling]); + + useEffect(() => { + if (data?.album_folder) markSelectable(data?.album_folder); + }, [markSelectable, data?.album_folder]); + + + if (isLoading || isPending || isError) { + let inner = ""; + if (isLoading) inner = "Loading..."; + if (isPending) inner = "Pending..."; + if (isError) inner = "Error..."; return ( - , - , - ...defaultActions, - ]} - > - - } - onClick={handleExpand} - className={styles.header} - > -
- - -
- - {data.album_folder_basename} - -
- - - -
-
+ + {inner} + ); } -); + + return ( + , + , + ...defaultActions, + ]} + > + + } + onClick={handleExpand} + className={styles.header} + > +
+ + +
+ + {data.album_folder_basename} + +
+ + + +
+
+ ); +} + export const TagPreview = ({ tagId, diff --git a/frontend/src/components/context/useSelection.tsx b/frontend/src/components/context/useSelection.tsx index 76f2869..923ed63 100644 --- a/frontend/src/components/context/useSelection.tsx +++ b/frontend/src/components/context/useSelection.tsx @@ -5,7 +5,7 @@ import { TagI } from "@/lib/tag"; import { useQueries } from "@tanstack/react-query"; import { createContext, useCallback, useContext, useState } from "react"; -type SelectionContextType = { +interface SelectionContextType { selection: Map; setSelection: React.Dispatch>>; addToSelection: (item: string) => void; @@ -18,7 +18,7 @@ type SelectionContextType = { markSelectable: (item: string) => void; selectAll: () => void; deselectAll: () => void; -}; +} const SelectionContext = createContext({ selection: new Map(), diff --git a/frontend/src/components/context/useSiblings.tsx b/frontend/src/components/context/useSiblings.tsx index ae307ee..e494aea 100644 --- a/frontend/src/components/context/useSiblings.tsx +++ b/frontend/src/components/context/useSiblings.tsx @@ -1,35 +1,41 @@ // we want to be able to modify a componenents siblings. // e.g. rightclicking on a tag in the tag list should allow us to expand all tags in the same group. -import React, { createContext, useCallback, useContext, useRef } from "react"; - -type SiblingRefsContextType = { - siblings: React.RefObject[]; - registerSibling: (ref: React.RefObject) => void; - callOnSiblings: (callable: (ref: React.RefObject) => void) => void; -}; - -const SiblingRefsContext = createContext({ - siblings: [], - registerSibling: () => {}, - callOnSiblings: () => {}, +import React, { Context, MutableRefObject, createContext, useCallback, useContext, useRef } from "react"; + + +interface SiblingRefsContextType { + siblingsRef: MutableRefObject>; + register: (id: string, sib: SibType) => void; + unregister: (id: string) => void; + callOnSiblings: (callable: (sib: SibType) => void) => void; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const SiblingRefsContext = createContext>({ + siblingsRef: { + current: new Map() + }, + register: () => { }, + unregister: () => { }, + callOnSiblings: () => { }, }); -const SiblingRefsProvider = ({ children }: { children: React.ReactNode }) => { - const refs = useRef[]>([]); +function SiblingRefsProvider({ children }: { children: React.ReactNode }) { + const siblingsRef = useRef>(new Map()); - const registerSibling = useCallback((ref: any) => { - if (ref && !refs.current.includes(ref)) { - refs.current.push(ref); - } + const register = useCallback((id: string, sib: SibType) => { + siblingsRef.current.set(id, sib); + }, []); + + const unregister = useCallback((id: string) => { + siblingsRef.current.delete(id); }, []); const callOnSiblings = useCallback( - (callable: (ref: React.RefObject) => void) => { - refs.current.forEach((childRef) => { - if (childRef.current) { - callable(childRef.current); - } + (callable: (sib: SibType, id?: string) => void) => { + siblingsRef.current.forEach((child, key) => { + callable(child, key); }); }, [] @@ -37,20 +43,20 @@ const SiblingRefsProvider = ({ children }: { children: React.ReactNode }) => { return ( {children} ); -}; +} -const useSiblings = () => { - const context = useContext(SiblingRefsContext); +function useSiblings() { + const context = useContext>(SiblingRefsContext as Context>); if (!context) { throw new Error("useSiblings must be used within a SiblingRefsProvider"); } - return context; -}; + return context +} export { SiblingRefsProvider, useSiblings }; export type { SiblingRefsContextType }; diff --git a/frontend/src/components/terminal.tsx b/frontend/src/components/terminal.tsx index b386088..5b98de9 100644 --- a/frontend/src/components/terminal.tsx +++ b/frontend/src/components/terminal.tsx @@ -218,7 +218,7 @@ export function TerminalContextProvider({ children }: { children: React.ReactNod socket.emit("ptyInput", { input: data }); }); - function onOutput(data: { output: Array }) { + function onOutput(data: { output: string[] }) { // term!.clear(); seems to be preferred from the documentation, // but it leaves the prompt on the first line in place - which we here do not want // ideally we would directly access the buffer. diff --git a/frontend/src/lib/library.ts b/frontend/src/lib/library.ts index 8eed7ea..4e84754 100644 --- a/frontend/src/lib/library.ts +++ b/frontend/src/lib/library.ts @@ -20,7 +20,7 @@ export interface MinimalItem { } export interface Item extends MinimalItem { - [key: string]: any; // enable indexing item[key] + [key: string]: unknown; // enable indexing item[key] album?: string; // "In the City / Wasted" album_id?: number; // 1 @@ -165,7 +165,10 @@ export async function fetchArtist({ expand?: boolean; minimal?: boolean; }): Promise { - let url = _url_parse_minimal_expand(`/library/artist/${name}`, { expand, minimal }); + const url = _url_parse_minimal_expand(`/library/artist/${name}`, { + expand, + minimal, + }); const response = await fetch(url); return (await response.json()) as MinimalArtist; } @@ -193,7 +196,7 @@ export async function fetchAlbum({ expand?: boolean; minimal?: boolean; }): Promise { - let url = _url_parse_minimal_expand(`/library/album/${id}`, { expand, minimal }); + const url = _url_parse_minimal_expand(`/library/album/${id}`, { expand, minimal }); console.log(url); const response = await fetch(url); return (await response.json()) as MinimalAlbum; @@ -222,7 +225,7 @@ export async function fetchItem({ expand?: boolean; minimal?: boolean; }): Promise { - let url = _url_parse_minimal_expand(`/library/item/${id}`, { expand, minimal }); + const url = _url_parse_minimal_expand(`/library/item/${id}`, { expand, minimal }); const response = await fetch(url); return (await response.json()) as Item; } diff --git a/frontend/src/routes/inbox.tsx b/frontend/src/routes/inbox.tsx index 1dcaa8f..54e58f1 100644 --- a/frontend/src/routes/inbox.tsx +++ b/frontend/src/routes/inbox.tsx @@ -97,7 +97,7 @@ function FolderTreeView({ useEffect(() => { const savedState = localStorage.getItem(uid); if (savedState !== null) { - setExpanded(JSON.parse(savedState)); + setExpanded(JSON.parse(savedState) as boolean); } }, [uid]); @@ -116,11 +116,11 @@ function FolderTreeView({ , ...defaultActions]} + actions={[, ...defaultActions]} > diff --git a/frontend/src/routes/library/browse.$artist.$albumId.tsx b/frontend/src/routes/library/browse.$artist.$albumId.tsx index ecad768..a799db4 100644 --- a/frontend/src/routes/library/browse.$artist.$albumId.tsx +++ b/frontend/src/routes/library/browse.$artist.$albumId.tsx @@ -1,7 +1,7 @@ import List from "@/components/common/list"; import { Album, albumQueryOptions } from "@/lib/library"; import Box from "@mui/material/Box"; -import { Outlet, createFileRoute } from "@tanstack/react-router"; +import { Outlet, createFileRoute, useParams } from "@tanstack/react-router"; import z from "zod"; import styles from "./browse.module.scss"; import { BASE_ROUTE } from "./browse"; @@ -23,20 +23,18 @@ export const Route = createFileRoute(`${BASE_ROUTE}/$artist/$albumId`)({ }); function AlbumOverview() { - const album = Route.useLoaderData() as Album; - const params = Route.useParams() as { - artist: string; - albumId: number; - itemId?: number; - }; + const album = Route.useLoaderData(); + const params = useParams({ + from: '/library/browse/$artist/$albumId/$itemId', + }); const data = useMemo(() => { - return album.items.map((item) => ({ + return (album as Album).items.map((item) => ({ to: `${BASE_ROUTE}/$artist/$albumId/$itemId`, params: { artist: params.artist, albumId: params.albumId, itemId: item.id }, label: item.name, className: styles.listItem, - "data-selected": item.id === params.itemId, + "data-selected": params.itemId && params.itemId == item.id })); }, [album, params]); diff --git a/frontend/src/routes/library/browse.$artist.tsx b/frontend/src/routes/library/browse.$artist.tsx index af7e5b9..c97b438 100644 --- a/frontend/src/routes/library/browse.$artist.tsx +++ b/frontend/src/routes/library/browse.$artist.tsx @@ -1,7 +1,7 @@ import List from "@/components/common/list"; import { artistQueryOptions } from "@/lib/library"; import Box from "@mui/material/Box"; -import { Outlet, createFileRoute } from "@tanstack/react-router"; +import { Outlet, createFileRoute, useParams } from "@tanstack/react-router"; import z from "zod"; import styles from "./browse.module.scss"; import { BASE_ROUTE } from "./browse"; @@ -24,10 +24,9 @@ export const Route = createFileRoute(`${BASE_ROUTE}/$artist`)({ function ArtistOverview() { const artist = Route.useLoaderData(); - const params = Route.useParams() as { - artist: string; - albumId?: number; - }; + const params = useParams({ + from: '/library/browse/$artist/$albumId', + }); const data = useMemo(() => { return artist.albums.map((album) => ({ @@ -35,7 +34,7 @@ function ArtistOverview() { params: { artist: params.artist, albumId: album.id }, label: album.name, className: styles.listItem, - "data-selected": params.albumId == album.id, + "data-selected": params.albumId && params.albumId == album.id, })); }, [artist, params]); diff --git a/frontend/src/routes/library/browse.tsx b/frontend/src/routes/library/browse.tsx index d0f39e0..97061ad 100644 --- a/frontend/src/routes/library/browse.tsx +++ b/frontend/src/routes/library/browse.tsx @@ -1,5 +1,5 @@ import { artistsQueryOptions } from "@/lib/library"; -import { Outlet, createFileRoute } from "@tanstack/react-router"; +import { Outlet, createFileRoute, useParams } from "@tanstack/react-router"; import List from "@/components/common/list"; import Box from "@mui/material/Box"; @@ -14,7 +14,9 @@ export const Route = createFileRoute(BASE_ROUTE)({ function AllArtists() { const artists = Route.useLoaderData(); - const params = Route.useParams() as { artist?: string }; + const params = useParams({ + from: '/library/browse/$artist', + }); const data = useMemo(() => { return artists.map((artist) => ({ @@ -22,7 +24,7 @@ function AllArtists() { params: { artist: artist.name }, label: artist.name, className: styles.listItem, - "data-selected": params.artist == artist.name, + "data-selected": params.artist && params.artist == artist.name, })); }, [artists, params]); diff --git a/frontend/src/routes/tags/index.tsx b/frontend/src/routes/tags/index.tsx index 39d2093..ac725f3 100644 --- a/frontend/src/routes/tags/index.tsx +++ b/frontend/src/routes/tags/index.tsx @@ -1,12 +1,11 @@ import { createFileRoute } from "@tanstack/react-router"; - import { useSuspenseQuery } from "@tanstack/react-query"; import { tagGroupAllQueryOptions, tagGroupIdQueryOptions } from "@/lib/tag"; import TagGroupView from "@/components/common/tagGroupView"; import { TagView } from "@/components/common/tagView"; import { SiblingRefsProvider } from "@/components/context/useSiblings"; -import { createRef, useMemo } from "react"; +import { ComponentProps } from "react"; export const Route = createFileRoute("/tags/")({ loader: (opts) => @@ -58,9 +57,7 @@ function TagGroup({ tag_ids: string[]; title?: string; subtitle?: string; - [key: string]: any; -}) { - const tagRefs = useMemo(() => tag_ids.map(() => createRef()), [tag_ids]); +} & Omit, "children">) { return ( {tag_ids.map((tagId, i) => ( - + ))} ); } -function PredefinedTagGroup({ id, ...props }: { id: string; [key: string]: any }) { +function PredefinedTagGroup({ id, ...props }: { id: string; } & Partial>) { const query = useSuspenseQuery(tagGroupIdQueryOptions(id)); const group = query.data; const tag_ids = group.tag_ids; From 174a32d5cc661eedbfb66183ae6a72e2e59ea22e Mon Sep 17 00:00:00 2001 From: pSpitzner Date: Sun, 7 Jul 2024 23:14:33 +0200 Subject: [PATCH 02/10] fixed config handling. now, we do not need every key in the user config (by setting defaults) --- backend/beets_flask/config.py | 11 +++- configs/beets_default.yaml | 62 -------------------- configs/default.yaml | 29 +++++++++ configs/{beets_example.yaml => example.yaml} | 2 +- entrypoint.sh | 4 +- entrypoint_dev.sh | 4 +- entrypoint_test.sh | 7 ++- 7 files changed, 50 insertions(+), 69 deletions(-) delete mode 100644 configs/beets_default.yaml create mode 100644 configs/default.yaml rename configs/{beets_example.yaml => example.yaml} (100%) diff --git a/backend/beets_flask/config.py b/backend/beets_flask/config.py index 0bf968e..bc02958 100644 --- a/backend/beets_flask/config.py +++ b/backend/beets_flask/config.py @@ -18,7 +18,16 @@ ``` """ +import confuse from beets_flask.utility import log from beets import config -config.set_env(prefix='BF') +config.set_env(prefix="BF") + +try: + # docker container + default_source = confuse.YamlSource("/repo/configs/default.yaml", default=True) +except confuse.ConfigReadError: + # running as module. but we should place the default config where confuse looks for it. + default_source = confuse.YamlSource("./configs/default.yaml", default=True) +config.add(default_source) # .add inserts with lowest priority diff --git a/configs/beets_default.yaml b/configs/beets_default.yaml deleted file mode 100644 index 9622cd6..0000000 --- a/configs/beets_default.yaml +++ /dev/null @@ -1,62 +0,0 @@ -# minimal config that assumes that your music sits in `/music/imported` - -gui: - num_workers_preview: 4 # how many previews to generate in parallel - - library: - readonly: no - include_paths: yes - - tags: - expand_tags: yes # for tag groups, on page load, show tag details? - recent_days: 14 # Number of days to consider for the "recent" tag group - - terminal: - start_path: "/music/inbox" # the directory where to start new terminal sessions - - inbox: - concat_nested_folders: yes # show multiple folders in one line if they only have one child - expand_files: no # on page load, show files in (album) folders, or collapse them - order_by: "name" # how to sort tags within the trag groups: "name" (the album folder basename) | "date_created" | "date_modified" - - folders: # keep in mind to volume-map these folders in your docker-compose.yml - Inbox: - name: "Inbox" - path: "/music/inbox" - autotag: no # no | "preview" | "import" - -directory: /music/imported -# library: /home/user/.config/beets/library.db # default location - -import: - # for our gui it makes sense to keep files around (copy) and - # only clear occasionally by hand (we have a button for that) - move: no - copy: yes - write: yes - log: /music/last_beets_imports.log - quiet_fallback: skip - detail: yes # `yes` is needed for our previews to work. - duplicate_action: ask # ask|skip|merge|keep|remove - -ui: - color: yes - -replace: - '[\\]': "" - "[_]": "-" - "[/]": "-" - '^\.+': "" - '[\x00-\x1f]': "" - '[<>:"\?\*\|]': "" - '\.$': "" - '\s+$': "" - '^\s+': "" - "^-": "" - "’": "" - "′": "" - "″": "" - "‐": "-" - -threaded: yes -asciify_paths: yes diff --git a/configs/default.yaml b/configs/default.yaml new file mode 100644 index 0000000..745e755 --- /dev/null +++ b/configs/default.yaml @@ -0,0 +1,29 @@ +# ------------------------------------------------------------------------------------ # +# DO NOT EDIT THIS FILE # +# ------------------------------------------------------------------------------------ # +# these are the defaults for the gui. +# you must provide your own config and map it to /home/beetle/.config/beets/config.yaml +# to get started, see the example.yaml + +gui: + num_workers_preview: 4 + + library: + readonly: no + include_paths: yes + + tags: + expand_tags: yes + recent_days: 14 + order_by: "name" + + terminal: + start_path: "/repo" + + inbox: + concat_nested_folders: yes + expand_files: no + +# set this in you beets config! (we only keep it around so the container can launch when nothing is configured) +directory: /music/imported + diff --git a/configs/beets_example.yaml b/configs/example.yaml similarity index 100% rename from configs/beets_example.yaml rename to configs/example.yaml index 0899f8a..6939efe 100644 --- a/configs/beets_example.yaml +++ b/configs/example.yaml @@ -10,6 +10,7 @@ gui: tags: expand_tags: yes # for tag groups, on page load, show tag details? recent_days: 14 # Number of days to consider for the "recent" tag group + order_by: "name" # how to sort tags within the trag groups: "name" (the album folder basename) | "date_created" | "date_modified" terminal: start_path: "/music/inbox" # the directory where to start new terminal sessions @@ -17,7 +18,6 @@ gui: inbox: concat_nested_folders: yes # show multiple folders in one line if they only have one child expand_files: no # on page load, show files in (album) folders, or collapse them - order_by: "name" # how to sort tags within the trag groups: "name" (the album folder basename) | "date_created" | "date_modified" folders: # keep in mind to volume-map these folders in your docker-compose.yml Lorem: diff --git a/entrypoint.sh b/entrypoint.sh index 0104f30..52f2f59 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -8,12 +8,12 @@ cd /repo # Check if configs exist and copy if they dont if [ ! -f /home/beetle/.config/beets/config.yaml ]; then mkdir -p /home/beetle/.config/beets - cp /repo/configs/beets_default.yaml /home/beetle/.config/beets/config.yaml + cp /repo/configs/default.yaml /home/beetle/.config/beets/config.yaml fi NUM_WORKERS_PREVIEW=$(yq e '.gui.num_workers_preview' /home/beetle/.config/beets/config.yaml) if ! [[ "$NUM_WORKERS_PREVIEW" =~ ^[0-9]+$ ]]; then - NUM_WORKERS_PREVIEW=1 + NUM_WORKERS_PREVIEW=4 fi mkdir -p /repo/log diff --git a/entrypoint_dev.sh b/entrypoint_dev.sh index cd662dd..3139f16 100755 --- a/entrypoint_dev.sh +++ b/entrypoint_dev.sh @@ -15,12 +15,12 @@ cd /repo # Check if configs exist and copy if they dont if [ ! -f /home/beetle/.config/beets/config.yaml ]; then mkdir -p /home/beetle/.config/beets - cp /repo/configs/beets_default.yaml /home/beetle/.config/beets/config.yaml + cp /repo/configs/default.yaml /home/beetle/.config/beets/config.yaml fi NUM_WORKERS_PREVIEW=$(yq e '.gui.num_workers_preview' /home/beetle/.config/beets/config.yaml) if ! [[ "$NUM_WORKERS_PREVIEW" =~ ^[0-9]+$ ]]; then - NUM_WORKERS_PREVIEW=1 + NUM_WORKERS_PREVIEW=4 fi mkdir -p /repo/log diff --git a/entrypoint_test.sh b/entrypoint_test.sh index 16dd3e9..ed13931 100755 --- a/entrypoint_test.sh +++ b/entrypoint_test.sh @@ -11,9 +11,13 @@ redis-server --daemonize yes # Check if configs exist and copy if they dont if [ ! -f /home/beetle/.config/beets/config.yaml ]; then mkdir -p /home/beetle/.config/beets - cp /repo/configs/beets_default.yaml /home/beetle/.config/beets/config.yaml + cp /repo/configs/default.yaml /home/beetle/.config/beets/config.yaml fi +NUM_WORKERS_PREVIEW=$(yq e '.gui.num_workers_preview' /home/beetle/.config/beets/config.yaml) +if ! [[ "$NUM_WORKERS_PREVIEW" =~ ^[0-9]+$ ]]; then + NUM_WORKERS_PREVIEW=4 +fi for i in $(seq 1 $NUM_WORKERS_PREVIEW) do @@ -21,6 +25,7 @@ do rq worker preview --log-format "Preview worker $i: %(message)s" > /dev/null & done +NUM_WORKERS_IMPORT=1 for i in $(seq 1 $NUM_WORKERS_IMPORT) do rq worker import --log-format "Import worker $i: %(message)s" > /dev/null & From 44f4376df08017879940d525d1ff24b6769fce59 Mon Sep 17 00:00:00 2001 From: pSpitzner Date: Mon, 8 Jul 2024 19:01:03 +0200 Subject: [PATCH 03/10] fixed broken library view --- .../src/routes/library/browse.$artist.$albumId.tsx | 12 ++++++++---- frontend/src/routes/library/browse.$artist.tsx | 11 +++++++---- frontend/src/routes/library/browse.tsx | 10 ++++++---- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/frontend/src/routes/library/browse.$artist.$albumId.tsx b/frontend/src/routes/library/browse.$artist.$albumId.tsx index a799db4..6df26c4 100644 --- a/frontend/src/routes/library/browse.$artist.$albumId.tsx +++ b/frontend/src/routes/library/browse.$artist.$albumId.tsx @@ -1,7 +1,7 @@ import List from "@/components/common/list"; import { Album, albumQueryOptions } from "@/lib/library"; import Box from "@mui/material/Box"; -import { Outlet, createFileRoute, useParams } from "@tanstack/react-router"; +import { Outlet, createFileRoute } from "@tanstack/react-router"; import z from "zod"; import styles from "./browse.module.scss"; import { BASE_ROUTE } from "./browse"; @@ -22,11 +22,15 @@ export const Route = createFileRoute(`${BASE_ROUTE}/$artist/$albumId`)({ component: AlbumOverview, }); +interface RouteParams { + artist: string; + albumId: number; + itemId?: number; +} + function AlbumOverview() { const album = Route.useLoaderData(); - const params = useParams({ - from: '/library/browse/$artist/$albumId/$itemId', - }); + const params = Route.useParams(); const data = useMemo(() => { return (album as Album).items.map((item) => ({ diff --git a/frontend/src/routes/library/browse.$artist.tsx b/frontend/src/routes/library/browse.$artist.tsx index c97b438..9cc7d99 100644 --- a/frontend/src/routes/library/browse.$artist.tsx +++ b/frontend/src/routes/library/browse.$artist.tsx @@ -1,7 +1,7 @@ import List from "@/components/common/list"; import { artistQueryOptions } from "@/lib/library"; import Box from "@mui/material/Box"; -import { Outlet, createFileRoute, useParams } from "@tanstack/react-router"; +import { Outlet, createFileRoute } from "@tanstack/react-router"; import z from "zod"; import styles from "./browse.module.scss"; import { BASE_ROUTE } from "./browse"; @@ -22,11 +22,14 @@ export const Route = createFileRoute(`${BASE_ROUTE}/$artist`)({ component: ArtistOverview, }); +interface RouteParams { + artist: string; + albumId?: number; +} + function ArtistOverview() { const artist = Route.useLoaderData(); - const params = useParams({ - from: '/library/browse/$artist/$albumId', - }); + const params = Route.useParams(); const data = useMemo(() => { return artist.albums.map((album) => ({ diff --git a/frontend/src/routes/library/browse.tsx b/frontend/src/routes/library/browse.tsx index 97061ad..b3a3afe 100644 --- a/frontend/src/routes/library/browse.tsx +++ b/frontend/src/routes/library/browse.tsx @@ -1,5 +1,5 @@ import { artistsQueryOptions } from "@/lib/library"; -import { Outlet, createFileRoute, useParams } from "@tanstack/react-router"; +import { Outlet, createFileRoute } from "@tanstack/react-router"; import List from "@/components/common/list"; import Box from "@mui/material/Box"; @@ -12,11 +12,13 @@ export const Route = createFileRoute(BASE_ROUTE)({ component: () => , }); +interface RouteParams { + artist?: string; +} + function AllArtists() { const artists = Route.useLoaderData(); - const params = useParams({ - from: '/library/browse/$artist', - }); + const params = Route.useParams < RouteParams >(); const data = useMemo(() => { return artists.map((artist) => ({ From 4fdb07cd42d302013df3515e3998d417c1430e6c Mon Sep 17 00:00:00 2001 From: pSpitzner Date: Mon, 8 Jul 2024 20:10:38 +0200 Subject: [PATCH 04/10] fixed column browser x-overflow (and truncated list item text) --- .../src/components/common/list.module.scss | 34 +++++++++++++++++++ frontend/src/components/common/list.tsx | 31 +++++++++++++++-- .../src/routes/library/browse.module.scss | 1 + 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/common/list.module.scss diff --git a/frontend/src/components/common/list.module.scss b/frontend/src/components/common/list.module.scss new file mode 100644 index 0000000..536a0f5 --- /dev/null +++ b/frontend/src/components/common/list.module.scss @@ -0,0 +1,34 @@ +.ListItemText { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: block; + &.overflowing { + &:hover { + animation: MoveText 3s ease-in-out infinite; + } + } +} + +@keyframes MoveText { + 0% { + transform: translateX(0); + } + 5% { + transform: translateX(0); + } + 40% { + transform: translateX(var(--translate-distance, 0)); + + } + 60% { + transform: translateX(var(--translate-distance, 0)); + + } + 95% { + transform: translateX(0); + } + 100% { + transform: translateX(0); + } +} diff --git a/frontend/src/components/common/list.tsx b/frontend/src/components/common/list.tsx index a51e27c..3ae8737 100644 --- a/frontend/src/components/common/list.tsx +++ b/frontend/src/components/common/list.tsx @@ -1,11 +1,12 @@ import { Link } from "@tanstack/react-router"; -import { ListItemText } from "@mui/material"; +import { Typography } from "@mui/material"; import ListItem, { ListItemOwnProps, ListItemProps } from "@mui/material/ListItem"; -import { ReactNode, ComponentType } from "react"; +import { ReactNode, ComponentType, useRef, useState, useEffect } from "react"; import { FixedSizeList, ListChildComponentProps } from "react-window"; import AutoSizer from "react-virtualized-auto-sizer"; +import styles from "./list.module.scss"; // eslint-disable-next-line @typescript-eslint/no-explicit-any interface WrapperProps { @@ -43,11 +44,35 @@ interface ListItemData extends ListItemOwnProps { function Item({ index, data, style }: ListChildComponentProps) { const { label, to, params, icon, ...props } = data[index]; + const textRef = useRef(null); + const [isOverflowing, setIsOverflowing] = useState(false); + + useEffect(() => { + const textEl = textRef.current; + if (textEl) { + const parent = textEl.parentElement?.parentElement; + if (parent && textEl.scrollWidth > parent.clientWidth) { + setIsOverflowing(true); + const d = parent.clientWidth - textEl.scrollWidth - 15; + textEl.style.setProperty("--translate-distance", `${d}px`); + } else { + setIsOverflowing(false); + textEl.style.removeProperty("--translate-distance"); + } + } + }, [label]); const it = ( {icon} - +
+ + {label} + +
); diff --git a/frontend/src/routes/library/browse.module.scss b/frontend/src/routes/library/browse.module.scss index 754bfaa..e1badd3 100644 --- a/frontend/src/routes/library/browse.module.scss +++ b/frontend/src/routes/library/browse.module.scss @@ -15,6 +15,7 @@ border: 1px solid #495057; border-radius: 0.3rem; overflow-y: auto; + overflow-x: hidden; // mui adds uls & > ul { padding: 0; From d53a04bcb90ab0f1992c387f30011199fd0ef702 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Wed, 10 Jul 2024 14:20:07 +0200 Subject: [PATCH 05/10] Fixed the last 17 eslint errors/warnings --- .../src/components/frontpage/libraryStats.tsx | 2 +- frontend/src/components/json.tsx | 1 + frontend/src/components/terminal.tsx | 20 +++++++++---------- frontend/src/lib/inbox.ts | 12 +++++------ frontend/src/lib/socket.tsx | 10 +++++----- frontend/src/routes/_frontpage/_modal.tsx | 3 ++- frontend/src/routes/inbox.tsx | 2 +- 7 files changed, 26 insertions(+), 24 deletions(-) diff --git a/frontend/src/components/frontpage/libraryStats.tsx b/frontend/src/components/frontpage/libraryStats.tsx index d5d5422..fb7fda9 100644 --- a/frontend/src/components/frontpage/libraryStats.tsx +++ b/frontend/src/components/frontpage/libraryStats.tsx @@ -29,7 +29,7 @@ export function LibraryStats() { marginBottom: "0.875em", }} > - {data?.libraryPath || "Loading..."} + {data?.libraryPath ?? "Loading..."}
diff --git a/frontend/src/components/json.tsx b/frontend/src/components/json.tsx index 40d17a9..efc2021 100644 --- a/frontend/src/components/json.tsx +++ b/frontend/src/components/json.tsx @@ -1,3 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function JSONPretty(props: any) { return (
diff --git a/frontend/src/components/terminal.tsx b/frontend/src/components/terminal.tsx
index 5b98de9..fcd1c6d 100644
--- a/frontend/src/components/terminal.tsx
+++ b/frontend/src/components/terminal.tsx
@@ -111,7 +111,7 @@ function XTermBinding() {
 
     useEffect(() => {
         if (!ref.current || !term) return;
-
+        const ele = ref.current;
         function copyPasteHandler(e: KeyboardEvent) {
             if (!term) return false;
 
@@ -123,7 +123,7 @@ function XTermBinding() {
                     // ctrl+shift+v: paste whatever is in the clipboard
                     navigator.clipboard.readText().then((toPaste) => {
                         term.write(toPaste);
-                    });
+                    }).catch(console.error);
                     return false;
                 } else if (key === "c" || key === "x") {
                     // ctrl+shift+x: copy whatever is highlighted to clipboard
@@ -134,7 +134,7 @@ function XTermBinding() {
                     // I'm not aware of ctrl+shift+x being used by anything in the terminal
                     // or browser
                     const toCopy = term.getSelection();
-                    navigator.clipboard.writeText(toCopy);
+                    navigator.clipboard.writeText(toCopy).catch(console.error);
                     term.focus();
                     return false;
                 }
@@ -146,18 +146,18 @@ function XTermBinding() {
 
         const fitAddon = new xTermFitAddon();
         term.loadAddon(fitAddon);
-        term.open(ref.current);
+        term.open(ele);
         fitAddon.fit();
 
         const resizeObserver = new ResizeObserver(() => {
             fitAddon.fit();
         });
 
-        resizeObserver.observe(ref.current);
+        resizeObserver.observe(ele);
 
         return () => {
             term.dispose();
-            if (ref.current) resizeObserver.unobserve(ref.current);
+            if (ele) resizeObserver.unobserve(ele);
         };
     }, [term]);
 
@@ -176,10 +176,10 @@ export interface TerminalContextI {
 
 const TerminalContext = createContext({
     open: false,
-    toggle: () => {},
-    setOpen: () => {},
-    inputText: () => {},
-    clearInput: () => {},
+    toggle: () => { },
+    setOpen: () => { },
+    inputText: () => { },
+    clearInput: () => { },
 });
 
 export function TerminalContextProvider({ children }: { children: React.ReactNode }) {
diff --git a/frontend/src/lib/inbox.ts b/frontend/src/lib/inbox.ts
index 9d674e9..258b3b2 100644
--- a/frontend/src/lib/inbox.ts
+++ b/frontend/src/lib/inbox.ts
@@ -71,8 +71,8 @@ export const deleteInboxMutation: UseMutationOptions = {
             }),
         });
     },
-    onSuccess: () => {
-        queryClient.invalidateQueries({ queryKey: ["inbox"] });
+    onSuccess: async () => {
+        await queryClient.invalidateQueries({ queryKey: ["inbox"] });
     },
 };
 
@@ -88,8 +88,8 @@ export const deleteInboxImportedMutation: UseMutationOptions {
-        queryClient.invalidateQueries({ queryKey: ["inbox", "path", variables] });
+    onSuccess: async (_data, variables) => {
+        await queryClient.invalidateQueries({ queryKey: ["inbox", "path", variables] });
     },
 };
 
@@ -123,7 +123,7 @@ export const retagInboxAllMutation: UseMutationOptions =
             }),
         });
     },
-    onSuccess: () => {
-        queryClient.invalidateQueries({ queryKey: ["inbox"] });
+    onSuccess: async () => {
+        await queryClient.invalidateQueries({ queryKey: ["inbox"] });
     },
 };
diff --git a/frontend/src/lib/socket.tsx b/frontend/src/lib/socket.tsx
index a2d4265..e3aa28c 100644
--- a/frontend/src/lib/socket.tsx
+++ b/frontend/src/lib/socket.tsx
@@ -29,7 +29,7 @@ export const useTerminalSocket = () => {
         return () => {
             termSocket.disconnect();
         };
-    }, [termSocket]);
+    }, []);
 
     useEffect(() => {
         function handleConnect() {
@@ -48,7 +48,7 @@ export const useTerminalSocket = () => {
             termSocket.off("connect", handleConnect);
             termSocket.off("disconnect", handleDisconnect);
         };
-    }, [termSocket]);
+    }, []);
 
     return { socket: termSocket, isConnected };
 };
@@ -118,9 +118,9 @@ const StatusContextProvider = ({
 
             if (data.attributes === "all") {
                 if (data.tagId)
-                    client.invalidateQueries({ queryKey: ["tag", data.tagId] });
+                    client.invalidateQueries({ queryKey: ["tag", data.tagId] }).catch(console.error);
                 if (data.tagPath)
-                    client.invalidateQueries({ queryKey: ["tag", data.tagPath] });
+                    client.invalidateQueries({ queryKey: ["tag", data.tagPath] }).catch(console.error);
             } else {
                 const attrs = data.attributes;
                 if (data.tagId)
@@ -138,7 +138,7 @@ const StatusContextProvider = ({
             if (data.attributes === "all") {
                 client.invalidateQueries({
                     queryKey: ["inbox"],
-                });
+                }).catch(console.error);
             } else {
                 throw new Error(
                     "Inbox update with partial attributes is not supported"
diff --git a/frontend/src/routes/_frontpage/_modal.tsx b/frontend/src/routes/_frontpage/_modal.tsx
index c701bf4..7674887 100644
--- a/frontend/src/routes/_frontpage/_modal.tsx
+++ b/frontend/src/routes/_frontpage/_modal.tsx
@@ -12,6 +12,7 @@ export const Route = createFileRoute("/_frontpage/_modal")({
 
 const Transition = forwardRef(function Transition(
     props: TransitionProps & {
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
         children: React.ReactElement;
     },
     ref: React.Ref
@@ -33,7 +34,7 @@ function DialogWrapper() {
             onClose={handleClose}
             onTransitionExited={() => {
                 if (!open) {
-                    navigate({ to: ".." });
+                    navigate({ to: ".." }).catch(console.error);
                 }
             }}
             TransitionComponent={Transition}
diff --git a/frontend/src/routes/inbox.tsx b/frontend/src/routes/inbox.tsx
index 54e58f1..3658b38 100644
--- a/frontend/src/routes/inbox.tsx
+++ b/frontend/src/routes/inbox.tsx
@@ -171,7 +171,7 @@ function Folder({ fp, label }: { fp: FsPath; label: string }) {
         if (fp.is_album && numChildren > 0) {
             markSelectable(fp.full_path);
         }
-    }, []);
+    }, [fp.full_path, fp.is_album, markSelectable, numChildren]);
 
     return (
         
Date: Wed, 10 Jul 2024 14:30:08 +0200 Subject: [PATCH 06/10] Added a folders to the default inbox config. --- configs/default.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/configs/default.yaml b/configs/default.yaml index 745e755..5756cba 100644 --- a/configs/default.yaml +++ b/configs/default.yaml @@ -24,6 +24,11 @@ gui: concat_nested_folders: yes expand_files: no + folders: # keep in mind to volume-map these folders in your docker-compose.yml + Lorem: + name: "Please configure your inbox!" + path: "/music/inbox" + autotag: no # no | "preview" | "import" + # set this in you beets config! (we only keep it around so the container can launch when nothing is configured) directory: /music/imported - From 02383476d868b0f146cc7edd98b137151c538d3a Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Thu, 11 Jul 2024 12:29:55 +0200 Subject: [PATCH 07/10] If not all types are available eslint will show problems that do not exist... Therefore we sadly need to install all dependencies to run eslint --- .github/workflows/eslint.yml | 53 +++++++++++++++--------------------- frontend/.eslintrc.cjs | 3 +- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 30dd154..174d2a7 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -10,38 +10,29 @@ name: ESLint on: - push: - branches: [ "main" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "main" ] + push: + branches: ["main"] + pull_request: + # The branches below must be a subset of the branches above + branches: ["main"] jobs: - eslint: - name: Run eslint scanning - runs-on: ubuntu-latest - permissions: - contents: read - security-events: write - actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status - steps: - - name: Checkout code - uses: actions/checkout@v4 + eslint: + name: Run eslint scanning + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + steps: + - name: Checkout code + uses: actions/checkout@v4 - - name: Install ESLint - run: | - npm install \ - eslint@^8.57.0 \ - eslint-plugin-react@^7.34.1 \ - eslint-plugin-react-hooks@^4.6.2 \ - eslint-plugin-react-refresh@^0.4.6 \ - @typescript-eslint/eslint-plugin@^7.2.0 \ - @typescript-eslint/parser@^7.2.0 \ - @tanstack/eslint-plugin-query@^5.35.6 + - name: Install ESLint + run: | + npm install - - name: Run ESLint - run: | - cd ./frontend - npx eslint . - --config .eslintrc.js - --ext .js,.jsx,.ts,.tsx + - name: Run ESLint + run: | + cd ./frontend + npm run lint diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 4cab843..2d92dba 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -13,7 +13,6 @@ module.exports = { ignorePatterns: ['dist', '.eslintrc.cjs', 'tailwind.config.js', 'postcss.config.js', 'vite.config.ts', '*.md'], parser: '@typescript-eslint/parser', plugins: ['react-refresh'], - rules: { 'react-refresh/only-export-components': [ 0, @@ -22,7 +21,7 @@ module.exports = { }, settings: { react: { - version: 'detect', + version: '18.2', }, }, parserOptions: { From 423c62404b6f660a8673fe94d7d06d138a75c847 Mon Sep 17 00:00:00 2001 From: Sebastian Mohr Date: Thu, 11 Jul 2024 12:32:43 +0200 Subject: [PATCH 08/10] Forgot to cd before install --- .github/workflows/eslint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index 174d2a7..5f7496d 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -30,6 +30,7 @@ jobs: - name: Install ESLint run: | + cd ./frontend npm install - name: Run ESLint From 80866dd458c791cc9c9b7c1b1737f11c639705c4 Mon Sep 17 00:00:00 2001 From: pSpitzner Date: Tue, 16 Jul 2024 09:58:19 +0200 Subject: [PATCH 09/10] since we now read the default config, do not copy it into the user directory! --- configs/default.yaml | 8 +++++--- entrypoint.sh | 7 ------- entrypoint_dev.sh | 6 ------ entrypoint_test.sh | 6 ------ 4 files changed, 5 insertions(+), 22 deletions(-) diff --git a/configs/default.yaml b/configs/default.yaml index 5756cba..c580904 100644 --- a/configs/default.yaml +++ b/configs/default.yaml @@ -24,11 +24,13 @@ gui: concat_nested_folders: yes expand_files: no - folders: # keep in mind to volume-map these folders in your docker-compose.yml - Lorem: + # you must set inbox folders and the music directory in *your* beets config! + # (we only keep it around so the container can launch when nothing is configured) + + folders: + Placeholder: name: "Please configure your inbox!" path: "/music/inbox" autotag: no # no | "preview" | "import" -# set this in you beets config! (we only keep it around so the container can launch when nothing is configured) directory: /music/imported diff --git a/entrypoint.sh b/entrypoint.sh index 52f2f59..57778b7 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -4,13 +4,6 @@ id cd /repo - -# Check if configs exist and copy if they dont -if [ ! -f /home/beetle/.config/beets/config.yaml ]; then - mkdir -p /home/beetle/.config/beets - cp /repo/configs/default.yaml /home/beetle/.config/beets/config.yaml -fi - NUM_WORKERS_PREVIEW=$(yq e '.gui.num_workers_preview' /home/beetle/.config/beets/config.yaml) if ! [[ "$NUM_WORKERS_PREVIEW" =~ ^[0-9]+$ ]]; then NUM_WORKERS_PREVIEW=4 diff --git a/entrypoint_dev.sh b/entrypoint_dev.sh index 3139f16..ffcb1b7 100755 --- a/entrypoint_dev.sh +++ b/entrypoint_dev.sh @@ -12,12 +12,6 @@ npm run dev & cd /repo -# Check if configs exist and copy if they dont -if [ ! -f /home/beetle/.config/beets/config.yaml ]; then - mkdir -p /home/beetle/.config/beets - cp /repo/configs/default.yaml /home/beetle/.config/beets/config.yaml -fi - NUM_WORKERS_PREVIEW=$(yq e '.gui.num_workers_preview' /home/beetle/.config/beets/config.yaml) if ! [[ "$NUM_WORKERS_PREVIEW" =~ ^[0-9]+$ ]]; then NUM_WORKERS_PREVIEW=4 diff --git a/entrypoint_test.sh b/entrypoint_test.sh index ed13931..ed824ef 100755 --- a/entrypoint_test.sh +++ b/entrypoint_test.sh @@ -8,12 +8,6 @@ cd /repo redis-server --daemonize yes -# Check if configs exist and copy if they dont -if [ ! -f /home/beetle/.config/beets/config.yaml ]; then - mkdir -p /home/beetle/.config/beets - cp /repo/configs/default.yaml /home/beetle/.config/beets/config.yaml -fi - NUM_WORKERS_PREVIEW=$(yq e '.gui.num_workers_preview' /home/beetle/.config/beets/config.yaml) if ! [[ "$NUM_WORKERS_PREVIEW" =~ ^[0-9]+$ ]]; then NUM_WORKERS_PREVIEW=4 From 762dc92c1d785a058ddbc025aee7c5413222e787 Mon Sep 17 00:00:00 2001 From: pSpitzner Date: Tue, 16 Jul 2024 12:09:57 +0200 Subject: [PATCH 10/10] version bump, updated changelog --- CHANGELOG.md | 18 +++++++++++++++++- backend/beets_flask/_version.py | 2 +- frontend/package.json | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 704112c..245a29d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,18 @@ -# 24-05-22 version 0.0.1 +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [0.0.2] - 24-07-16 + +### Fixed +- ESLint errors and Github action +- Now loading the default config + +## 0.0.1 - 24-05-22 - initial commit + +[0.0.2]: https://github.com/pSpitzner/beets-flask/compare/v0.0.1...v0.0.2 diff --git a/backend/beets_flask/_version.py b/backend/beets_flask/_version.py index b8023d8..d18f409 100644 --- a/backend/beets_flask/_version.py +++ b/backend/beets_flask/_version.py @@ -1 +1 @@ -__version__ = '0.0.1' +__version__ = '0.0.2' diff --git a/frontend/package.json b/frontend/package.json index 753910d..1efca93 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "0.0.1", + "version": "0.0.2", "type": "module", "scripts": { "dev": "vite --host --cors",