Fixed some more bugs.

editsong
Sander Vocke 4 years ago
parent b27176ae66
commit 0a9bec1c87
  1. 10
      client/src/components/windows/manage_links/BatchLinkDialog.tsx
  2. 21
      client/src/components/windows/manage_links/LinksStatusWidget.tsx
  3. 103
      client/src/components/windows/manage_tags/ManageTagsWindow.tsx
  4. 2
      client/src/lib/backend/queries.tsx

@ -89,7 +89,6 @@ async function makeTasks(
if (linkTracks) { promises.push(doForType(ResourceType.Track)); } if (linkTracks) { promises.push(doForType(ResourceType.Track)); }
if (linkArtists) { promises.push(doForType(ResourceType.Artist)); } if (linkArtists) { promises.push(doForType(ResourceType.Artist)); }
if (linkAlbums) { promises.push(doForType(ResourceType.Album)); } if (linkAlbums) { promises.push(doForType(ResourceType.Album)); }
console.log("Awaiting answer...")
await Promise.all(promises); await Promise.all(promises);
} }
@ -98,8 +97,6 @@ async function doLinking(
setStatus: any, setStatus: any,
integrations: IntegrationState[], integrations: IntegrationState[],
) { ) {
console.log("Linking start!", toLink);
// Start the collecting phase. // Start the collecting phase.
setStatus((s: any) => { setStatus((s: any) => {
return { return {
@ -109,15 +106,12 @@ async function doLinking(
tasksFailed: 0, tasksFailed: 0,
} }
}); });
console.log("Starting collection");
var tasks: Task[] = []; var tasks: Task[] = [];
let collectionPromises = toLink.map((v: any) => { let collectionPromises = toLink.map((v: any) => {
let { integrationId, tracks, artists, albums } = v; let { integrationId, tracks, artists, albums } = v;
let integration = integrations.find((i: IntegrationState) => i.id === integrationId); let integration = integrations.find((i: IntegrationState) => i.id === integrationId);
if (!integration) { return; } if (!integration) { return; }
console.log('integration collect:', integration)
return makeTasks( return makeTasks(
integration, integration,
tracks, tracks,
@ -126,9 +120,7 @@ async function doLinking(
(t: Task) => { tasks.push(t) } (t: Task) => { tasks.push(t) }
); );
}) })
console.log("Awaiting collection.")
await Promise.all(collectionPromises); await Promise.all(collectionPromises);
console.log("Done collecting.", tasks)
// Start the linking phase. // Start the linking phase.
setStatus((status: BatchJobStatus) => { setStatus((status: BatchJobStatus) => {
return { return {
@ -157,7 +149,6 @@ async function doLinking(
}); });
try { try {
if (integration === undefined) { return; } if (integration === undefined) { return; }
console.log('integration search:', integration)
let _integration = integration as IntegrationState; let _integration = integration as IntegrationState;
let searchFuncs: any = { let searchFuncs: any = {
[ResourceType.Track]: (q: any, l: any) => { return _integration.integration.searchTrack(q, l) }, [ResourceType.Track]: (q: any, l: any) => { return _integration.integration.searchTrack(q, l) },
@ -202,7 +193,6 @@ async function doLinking(
success = true; success = true;
} }
console.log(query, candidates);
if (success) { if (success) {
onSuccess(); onSuccess();
} else { } else {

@ -12,7 +12,7 @@ export default function LinksStatusWidget(props: {
}) { }) {
type Counts = { type Counts = {
songs: number | undefined, tracks: number | undefined,
albums: number | undefined, albums: number | undefined,
artists: number | undefined, artists: number | undefined,
}; };
@ -27,7 +27,7 @@ export default function LinksStatusWidget(props: {
[ResourceType.Album]: QueryLeafBy.AlbumStoreLinks, [ResourceType.Album]: QueryLeafBy.AlbumStoreLinks,
} }
let whichElem: any = { let whichElem: any = {
[ResourceType.Track]: 'songs', [ResourceType.Track]: 'tracks',
[ResourceType.Artist]: 'artists', [ResourceType.Artist]: 'artists',
[ResourceType.Album]: 'albums', [ResourceType.Album]: 'albums',
} }
@ -42,6 +42,7 @@ export default function LinksStatusWidget(props: {
undefined, undefined,
QueryResponseType.Count QueryResponseType.Count
); );
console.log("Result: ", type, store, r);
return r[whichElem[type]]; return r[whichElem[type]];
} }
@ -55,6 +56,7 @@ export default function LinksStatusWidget(props: {
undefined, undefined,
QueryResponseType.Count QueryResponseType.Count
); );
console.log("Got total counts: ", counts)
setTotalCounts(counts); setTotalCounts(counts);
} }
)(); )();
@ -64,16 +66,17 @@ export default function LinksStatusWidget(props: {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
let promises = $enum(IntegrationWith).getValues().map((s: IntegrationWith) => { let promises = $enum(IntegrationWith).getValues().map((s: IntegrationWith) => {
let songsPromise: Promise<number> = queryStoreCount(s, ResourceType.Track); let tracksPromise: Promise<number> = queryStoreCount(s, ResourceType.Track);
let albumsPromise: Promise<number> = queryStoreCount(s, ResourceType.Album); let albumsPromise: Promise<number> = queryStoreCount(s, ResourceType.Album);
let artistsPromise: Promise<number> = queryStoreCount(s, ResourceType.Artist); let artistsPromise: Promise<number> = queryStoreCount(s, ResourceType.Artist);
let updatePromise = Promise.all([songsPromise, albumsPromise, artistsPromise]).then( let updatePromise = Promise.all([tracksPromise, albumsPromise, artistsPromise]).then(
(r: any[]) => { (r: any[]) => {
console.log("Grouped: ", r)
setLinkedCounts((prev: Record<string, Counts>) => { setLinkedCounts((prev: Record<string, Counts>) => {
return { return {
...prev, ...prev,
[s]: { [s]: {
songs: r[0], tracks: r[0],
artists: r[2], artists: r[2],
albums: r[1], albums: r[1],
} }
@ -101,7 +104,7 @@ export default function LinksStatusWidget(props: {
let tot = totalCounts; let tot = totalCounts;
let lin = linkedCounts[s]; let lin = linkedCounts[s];
let perc = { let perc = {
songs: Math.round((lin.songs || 0) / (tot.songs || 1) * 100), tracks: Math.round((lin.tracks || 0) / (tot.tracks || 1) * 100),
artists: Math.round((lin.artists || 0) / (tot.artists || 1) * 100), artists: Math.round((lin.artists || 0) / (tot.artists || 1) * 100),
albums: Math.round((lin.albums || 0) / (tot.albums || 1) * 100), albums: Math.round((lin.albums || 0) / (tot.albums || 1) * 100),
} }
@ -124,9 +127,9 @@ export default function LinksStatusWidget(props: {
<td><Typography>{lin.albums} / {tot.albums}</Typography></td> <td><Typography>{lin.albums} / {tot.albums}</Typography></td>
</tr> </tr>
<tr> <tr>
<td><Typography>Linked songs:</Typography></td> <td><Typography>Linked tracks:</Typography></td>
<td><Box minWidth="200px"><LinearProgress variant="determinate" color="secondary" value={perc.songs} /></Box></td> <td><Box minWidth="200px"><LinearProgress variant="determinate" color="secondary" value={perc.tracks} /></Box></td>
<td><Typography>{lin.songs} / {tot.songs}</Typography></td> <td><Typography>{lin.tracks} / {tot.tracks}</Typography></td>
</tr> </tr>
<tr><td colSpan={4}>&nbsp;</td></tr> <tr><td colSpan={4}>&nbsp;</td></tr>
</> </>

@ -14,13 +14,14 @@ import { useHistory } from 'react-router';
import { NotLoggedInError, handleNotLoggedIn } from '../../../lib/backend/request'; import { NotLoggedInError, handleNotLoggedIn } from '../../../lib/backend/request';
import { useAuth } from '../../../lib/useAuth'; import { useAuth } from '../../../lib/useAuth';
import * as serverApi from '../../../api/api'; import * as serverApi from '../../../api/api';
import { Id, QueryResponseTagDetails, Tag, Name } from '../../../api/api';
var _ = require('lodash'); var _ = require('lodash');
export interface ManageTagsWindowState extends WindowState { export interface ManageTagsWindowState extends WindowState {
// Tags are indexed by a string ID. This can be a stringified MuDBase ID integer, // Tags are indexed by a string ID. This can be a stringified MuDBase ID integer,
// or a UID for tags which only exist in the front-end and haven't been committed // or a UID for tags which only exist in the front-end and haven't been committed
// to the database. // to the database.
fetchedTags: Record<string, any> | null, fetchedTags: Record<string, ManagedTag> | null,
pendingChanges: TagChange[], pendingChanges: TagChange[],
alert: ReactFragment | null, // For notifications such as errors alert: ReactFragment | null, // For notifications such as errors
} }
@ -61,36 +62,50 @@ export function ManageTagsWindowReducer(state: ManageTagsWindowState, action: an
} }
} }
export function organiseTags(allTags: Record<string, any>, fromId: string | null): any[] { export function organiseTags(allTags: Record<string, ManagedTag>, fromId: string | null):
const base = Object.values(allTags).filter((tag: any) => { ManagedTag[] {
var par: any = ("proposedParent" in tag) ? tag.proposedParent : tag.parentId; const base = Object.values(allTags).filter((tag: ManagedTag) => {
var par = ("proposedParent" in tag) ? tag.proposedParent : tag.parentId;
return (fromId === null && !par) || return (fromId === null && !par) ||
(par && par === fromId) (par && par === fromId)
}); });
return base.map((tag: any) => { return base.map((tag: ManagedTag) => {
return { return {
...tag, ...tag,
children: organiseTags(allTags, tag.tagId), children: organiseTags(allTags, tag.strTagId),
} }
}); });
} }
export async function getAllTags() { // Work with strings so we can have temporary randomized IDs.
type ManagedTag = (Tag & Name & {
id?: number,
strTagId: string,
strParentId: string | null,
strChildIds: string[],
proposeDelete?: boolean,
proposedName?: string,
proposedParent?: string | null,
proposedMergeInto?: string,
isNewTag?: boolean,
children?: ManagedTag[],
});
export async function getAllTags(): Promise<Record<string, ManagedTag>> {
return (async () => { return (async () => {
var retval: Record<string, any> = {}; var retval: Record<string, ManagedTag> = {};
const tags: any = await queryTags( const tags: QueryResponseTagDetails[] = await queryTags(
undefined, 0, -1, serverApi.QueryResponseType.Details, undefined, 0, -1, serverApi.QueryResponseType.Details,
); ) as QueryResponseTagDetails[];
// Convert numeric IDs to string IDs because that is tags.forEach((tag: QueryResponseTagDetails) => {
// what we work with within this component. retval[tag.id.toString()] = {
tags.forEach((tag: any) => {
retval[tag.tagId.toString()] = {
...tag, ...tag,
tagId: tag.tagId && tag.tagId.toString(), strTagId: tag.id.toString(),
parentId: tag.parentId && tag.parentId.toString(), strParentId: tag.parentId?.toString() || null,
childIds: tag.childIds && tag.childIds.map((c: number) => c.toString()), strChildIds: tags
.filter((t: QueryResponseTagDetails) => t.parentId == tag.id)
.map((t: QueryResponseTagDetails) => t.id.toString() )
} }
}); });
return retval; return retval;
@ -116,14 +131,14 @@ export function CreateTagButton(props: any) {
} }
export function SingleTag(props: { export function SingleTag(props: {
tag: any, tag: ManagedTag,
prependElems: any[], prependElems: any[],
dispatch: (action: any) => void, dispatch: (action: any) => void,
state: ManageTagsWindowState, state: ManageTagsWindowState,
changedTags: any[], changedTags: ManagedTag[],
}) { }) {
const tag = props.tag; const tag = props.tag;
const hasChildren = 'children' in tag && tag.children.length > 0; const hasChildren = tag.children && tag.children.length > 0;
const [menuPos, setMenuPos] = React.useState<null | number[]>(null); const [menuPos, setMenuPos] = React.useState<null | number[]>(null);
const [expanded, setExpanded] = useState<boolean>(true); const [expanded, setExpanded] = useState<boolean>(true);
@ -163,9 +178,9 @@ export function SingleTag(props: {
{props.prependElems} {props.prependElems}
<TagChip transparent={tag.proposeDelete} label={tagLabel} /> <TagChip transparent={tag.proposeDelete} label={tagLabel} />
</Box> </Box>
{hasChildren && expanded && tag.children {expanded && tag.children && tag.children
.sort((a: any, b: any) => a.name.localeCompare(b.name)) .sort((a: ManagedTag, b: ManagedTag) => a.name.localeCompare(b.name))
.map((child: any) => <SingleTag .map((child: ManagedTag) => <SingleTag
tag={child} tag={child}
prependElems={[...props.prependElems, prependElems={[...props.prependElems,
<TagChip transparent={true} label={tagLabel} />, <TagChip transparent={true} label={tagLabel} />,
@ -179,7 +194,7 @@ export function SingleTag(props: {
open={menuPos !== null} open={menuPos !== null}
onClose={onCloseMenu} onClose={onCloseMenu}
onOpenTag={() => { onOpenTag={() => {
history.push('/tag/' + tag.tagId); history.push('/tag/' + tag.strTagId);
}} }}
onRename={(s: string) => { onRename={(s: string) => {
props.dispatch({ props.dispatch({
@ -189,7 +204,7 @@ export function SingleTag(props: {
{ {
type: TagChangeType.Rename, type: TagChangeType.Rename,
name: s, name: s,
id: tag.tagId, id: tag.strTagId,
} }
] ]
}) })
@ -205,7 +220,7 @@ export function SingleTag(props: {
...props.state.pendingChanges, ...props.state.pendingChanges,
{ {
type: TagChangeType.Delete, type: TagChangeType.Delete,
id: tag.tagId, id: tag.strTagId,
} }
] ]
}) })
@ -221,7 +236,7 @@ export function SingleTag(props: {
...props.state.pendingChanges, ...props.state.pendingChanges,
{ {
type: TagChangeType.MoveTo, type: TagChangeType.MoveTo,
id: tag.tagId, id: tag.strTagId,
parent: to, parent: to,
} }
] ]
@ -238,7 +253,7 @@ export function SingleTag(props: {
...props.state.pendingChanges, ...props.state.pendingChanges,
{ {
type: TagChangeType.MergeTo, type: TagChangeType.MergeTo,
id: tag.tagId, id: tag.strTagId,
into: into, into: into,
} }
] ]
@ -254,13 +269,14 @@ export function SingleTag(props: {
</> </>
} }
function annotateTagsWithChanges(tags: Record<string, any>, changes: TagChange[]) { function annotateTagsWithChanges(tags: Record<string, ManagedTag>, changes: TagChange[])
var retval: Record<string, any> = _.cloneDeep(tags); : Record<string, ManagedTag> {
var retval: Record<string, ManagedTag> = _.cloneDeep(tags);
const applyDelete = (id: string) => { const applyDelete = (id: string) => {
retval[id].proposeDelete = true; retval[id].proposeDelete = true;
Object.values(tags).filter((t: any) => t.parentId === id) Object.values(tags).filter((t: ManagedTag) => t.strParentId === id)
.forEach((child: any) => applyDelete(child.tagId)); .forEach((child: ManagedTag) => applyDelete(child.strTagId));
} }
changes.forEach((change: TagChange) => { changes.forEach((change: TagChange) => {
@ -278,15 +294,20 @@ function annotateTagsWithChanges(tags: Record<string, any>, changes: TagChange[]
retval[change.id].proposedMergeInto = change.into; retval[change.id].proposedMergeInto = change.into;
break; break;
case TagChangeType.Create: case TagChangeType.Create:
if (!change.name) {
throw new Error("Trying to create a tag without a name");
}
retval[change.id] = { retval[change.id] = {
mbApi_typename: 'tag',
isNewTag: true, isNewTag: true,
name: change.name, name: change.name,
parentId: change.parent, strTagId: change.id,
tagId: change.id, strParentId: change.parent || null,
strChildIds: [],
} }
if (change.parent) { if (change.parent) {
retval[change.parent].childIds = retval[change.parent].strChildIds =
[...retval[change.parent].childIds, change.id] [...retval[change.parent].strChildIds, change.id]
} }
break; break;
default: default:
@ -296,12 +317,12 @@ function annotateTagsWithChanges(tags: Record<string, any>, changes: TagChange[]
return retval; return retval;
} }
function applyTagsChanges(tags: Record<string, any>, changes: TagChange[]) { function applyTagsChanges(tags: Record<string, ManagedTag>, changes: TagChange[]) {
var retval = _.cloneDeep(tags); var retval = _.cloneDeep(tags);
const applyDelete = (id: string) => { const applyDelete = (id: string) => {
Object.values(tags).filter((t: any) => t.parentId === id) Object.values(tags).filter((t: ManagedTag) => t.strParentId === id)
.forEach((child: any) => applyDelete(child.tagId)); .forEach((child: ManagedTag) => applyDelete(child.strTagId));
delete retval[id].proposeDelete; delete retval[id].proposeDelete;
} }
@ -447,8 +468,8 @@ export function ManageTagsWindowControlled(props: {
width="80%" width="80%"
> >
{tags && tags.length && tags {tags && tags.length && tags
.sort((a: any, b: any) => a.name.localeCompare(b.name)) .sort((a: ManagedTag, b: ManagedTag) => a.name.localeCompare(b.name))
.map((tag: any) => { .map((tag: ManagedTag) => {
return <SingleTag return <SingleTag
tag={tag} tag={tag}
prependElems={[]} prependElems={[]}

@ -31,8 +31,6 @@ export async function queryItems(
responseType: responseType, responseType: responseType,
}; };
console.log(q);
const requestOpts = { const requestOpts = {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },

Loading…
Cancel
Save