Skip to content

Commit

Permalink
feat: Create playlist from results
Browse files Browse the repository at this point in the history
fix: Playlist ordering
  • Loading branch information
colin969 committed Jul 30, 2024
1 parent d46fdb9 commit cd03350
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 31 deletions.
6 changes: 6 additions & 0 deletions src/back/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,12 @@ export function registerRequestCallbacks(state: BackState, init: () => Promise<v
};
});

state.socketServer.register(BackIn.BROWSE_ALL_RESULTS, async (event, search) => {
search.limit = 99999999999;

return await fpDatabase.searchGames(search);
});

state.socketServer.register(BackIn.BROWSE_VIEW_PAGE, async (event, search) => {
search.slim = true;

Expand Down
2 changes: 1 addition & 1 deletion src/renderer/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1312,7 +1312,7 @@ export class App extends React.Component<AppProps> {
if (this.props.currentView.selectedPlaylist && this.props.currentView.advancedFilter.playlistOrder && (sourceGameId !== destGameId)) {
// Send swap to backend, reflect on frontend immediately
const library = getViewName(this.props.location.pathname);
this.props.searchActions.swapPlaylistGame({
this.props.searchActions.movePlaylistGame({
view: library,
sourceGameId,
destGameId,
Expand Down
83 changes: 63 additions & 20 deletions src/renderer/components/pages/BrowsePage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as remote from '@electron/remote';
import { WithTagCategoriesProps } from '@renderer/containers/withTagCategories';
import { BackIn } from '@shared/back/types';
import { BackIn, BackOut } from '@shared/back/types';
import { BrowsePageLayout } from '@shared/BrowsePageLayout';
import { ExtensionContribution } from '@shared/extensions/interfaces';
import { LangContainer } from '@shared/lang';
Expand All @@ -24,8 +24,8 @@ import { RequestState } from '@renderer/store/search/slice';
import { WithSearchProps } from '@renderer/containers/withSearch';
import { WithViewProps } from '@renderer/containers/withView';
import { SearchBar } from '@renderer/components/SearchBar';
import path = require('path');
import { delayedThrottle } from '@shared/utils/throttle';
import path = require('path');

type Pick<T, K extends keyof T> = { [P in K]: T[P]; };

Expand Down Expand Up @@ -388,6 +388,10 @@ export class BrowsePage extends React.Component<BrowsePageProps, BrowsePageState
if (this.state.currentPlaylist) {
window.Shared.back.request(BackIn.SAVE_PLAYLIST, this.state.currentPlaylist)
.then((data) => {
this.props.searchActions.selectPlaylist({
view: this.props.currentView.id,
playlist: data
});
this.props.onUpdatePlaylist(data);
});
this.setState({
Expand All @@ -412,24 +416,63 @@ export class BrowsePage extends React.Component<BrowsePageProps, BrowsePageState
};

onCreatePlaylistClick = (): void => {
this.setState({
currentPlaylist: {
filePath: '',
id: uuid(),
games: [],
title: '',
description: '',
author: '',
icon: '',
library: this.props.currentView.id,
extreme: false
},
isEditingPlaylist: true,
isNewPlaylist: true,
});
if (this.props.currentView.selectedPlaylist) {
this.onSelectPlaylist(null);
}
const contextButtons: MenuItemConstructorOptions[] = [{
label: 'Create Empty Playlist',
click: () => {
this.setState({
currentPlaylist: {
filePath: '',
id: uuid(),
games: [],
title: '',
description: '',
author: '',
icon: '',
library: this.props.currentView.id,
extreme: false
},
isEditingPlaylist: true,
isNewPlaylist: true,
});
if (this.props.currentView.selectedPlaylist) {
this.onSelectPlaylist(null);
}
}
},
{
label: 'Create From Search Results',
click: () => {
window.Shared.back.request(BackIn.BROWSE_ALL_RESULTS, {
...this.props.currentView.searchFilter,
slim: true,
})
.then((games) => {
this.setState({
currentPlaylist: {
filePath: '',
id: uuid(),
games: games.map(g => ({
gameId: g.id,
notes: '',
})),
title: '',
description: '',
author: '',
icon: '',
library: this.props.currentView.id,
extreme: false
},
isEditingPlaylist: true,
isNewPlaylist: true,
});
if (this.props.currentView.selectedPlaylist) {
this.onSelectPlaylist(null);
}
});
}
}];
const menu = remote.Menu.buildFromTemplate(contextButtons);
menu.popup({ window: remote.getCurrentWindow() });
};

onDiscardPlaylistClick = (): void => {
Expand Down
47 changes: 37 additions & 10 deletions src/renderer/store/search/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export enum RequestState {
RECEIVED,
}

export type SwapPlaylistGameData = {
export type MovePlaylistGameData = {
view: string;
sourceGameId: string;
destGameId: string;
Expand Down Expand Up @@ -419,26 +419,52 @@ const searchSlice = createSlice({
}
}
},
swapPlaylistGame(state: SearchState, { payload }: PayloadAction<SwapPlaylistGameData>) {
movePlaylistGame(state: SearchState, { payload }: PayloadAction<MovePlaylistGameData>) {
const view = state.views[payload.view];
const { sourceGameId, destGameId } = payload;

if (view && view.selectedPlaylist) {
const sourceIdx = view.selectedPlaylist.games.findIndex(g => g.gameId === sourceGameId);
const destIdx = view.selectedPlaylist.games.findIndex(g => g.gameId === destGameId);
if (sourceIdx > -1 && destIdx > -1) {
const replacedGame = view.selectedPlaylist.games[destIdx];
view.selectedPlaylist.games[destIdx] = view.selectedPlaylist.games[sourceIdx];
view.selectedPlaylist.games[sourceIdx] = replacedGame;
// Remove existing source game
const sourceGame = view.selectedPlaylist.games.splice(sourceIdx, 1)[0];
if (sourceIdx < destIdx) {
const destIdx = view.selectedPlaylist.games.findIndex(g => g.gameId === destGameId);
if (destIdx < view.selectedPlaylist.games.length) {
view.selectedPlaylist.games.splice(destIdx + 1, 0, sourceGame);
} else {
view.selectedPlaylist.games.push(sourceGame);
}
} else {
const destIdx = view.selectedPlaylist.games.findIndex(g => g.gameId === destGameId);
view.selectedPlaylist.games.splice(destIdx, 0, sourceGame);
}

// Try and swap them in the current results
const games = Object.entries(view.data.games);


// Try and move them in the results view
const games = Object.entries(view.data.games).map<GameRecordsArray>(([key, value]) => [Number(key), value]);
const sourceGameEntry = games.find((g) => g[1].id === sourceGameId);
const destGameEntry = games.find((g) => g[1].id === destGameId);
if (sourceGameEntry && destGameEntry) {
view.data.games[num(sourceGameEntry[0])] = destGameEntry[1];
view.data.games[num(destGameEntry[0])] = sourceGameEntry[1];
const sourceIndex = games.indexOf(sourceGameEntry);
const destIndex = games.indexOf(destGameEntry);
if (sourceIdx < destIndex) {
games[sourceIndex][0] = games[destIndex][0];
// Moving down (Shift games between up)
for (let i = sourceIdx + 1; i < destIndex + 1; i++) {
games[i][0]--;
}
} else {
games[sourceIndex][0] = games[destIndex][0];
// Moving up (Shift games between down)
for (let i = destIndex; i < sourceIndex; i++) {
games[i][0]++;
}
}
}
view.data.games = Object.fromEntries(games);

// Update the playlist file
window.Shared.back.send(BackIn.SAVE_PLAYLIST, view.selectedPlaylist);
Expand Down Expand Up @@ -523,6 +549,7 @@ const searchSlice = createSlice({
},
});

type GameRecordsArray = [number, Game];

export const { actions: searchActions } = searchSlice;
export const {
Expand All @@ -537,7 +564,7 @@ export const {
setOrderBy,
setOrderReverse,
setAdvancedFilter,
swapPlaylistGame,
movePlaylistGame,
requestRange,
updateGame,
addData } = searchSlice.actions;
Expand Down
3 changes: 3 additions & 0 deletions src/shared/back/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ export enum BackIn {

/** Returns a query object for the given data */
PARSE_QUERY_DATA,
/** Returns all results for a search */
BROWSE_ALL_RESULTS,
/** Get a page of a browse view. */
BROWSE_VIEW_PAGE,
/** Get the first page of a browse view */
Expand Down Expand Up @@ -362,6 +364,7 @@ export type BackInTemplate = SocketTemplate<BackIn, {
[BackIn.DELETE_TAG_CATEGORY]: (data: number) => boolean;

[BackIn.PARSE_QUERY_DATA]: (query: QueryData) => SearchQuery;
[BackIn.BROWSE_ALL_RESULTS]: (searchQuery: SearchQuery) => Game[];
[BackIn.BROWSE_VIEW_PAGE]: (search: SearchQuery) => void;
[BackIn.BROWSE_VIEW_FIRST_PAGE]: (search: SearchQuery) => BrowseViewFirstPageResponseData;
[BackIn.BROWSE_VIEW_KEYSET]: (search: SearchQuery) => BrowseViewKeysetResponse;
Expand Down

0 comments on commit cd03350

Please sign in to comment.