Fix various console errors and graphql loading issues (#760)

* Refactor listhook to resolve loading issues
* Fix graphql loading race conditions
* Various console spam
* Fix scene card overlay hierarchy
* Fix modal and manual borders
This commit is contained in:
InfiniteTF
2020-08-28 08:33:19 +02:00
committed by GitHub
parent 9a84726128
commit fef16d7e09
16 changed files with 345 additions and 325 deletions

View File

@@ -38,6 +38,24 @@ import {
import { ListFilterModel } from "src/models/list-filter/filter";
import { FilterMode } from "src/models/list-filter/types";
const getSelectedData = <I extends IDataItem>(
result: I[],
selectedIds: Set<string>
) => {
// find the selected items from the ids
const selectedResults: I[] = [];
selectedIds.forEach((id) => {
const item = result.find((s) => s.id === id);
if (item) {
selectedResults.push(item);
}
});
return selectedResults;
};
interface IListHookData {
filter: ListFilterModel;
template: JSX.Element;
@@ -99,34 +117,45 @@ interface IQuery<T extends IQueryResult, T2 extends IDataItem> {
filterMode: FilterMode;
useData: (filter: ListFilterModel) => T;
getData: (data: T) => T2[];
getSelectedData: (data: T, selectedIds: Set<string>) => T2[];
getCount: (data: T) => number;
}
const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
options: IListHookOptions<QueryResult, QueryData> &
IQuery<QueryResult, QueryData>
): IListHookData => {
const [interfaceState, setInterfaceState] = useInterfaceLocalForage();
const [forageInitialised, setForageInitialised] = useState(false);
const history = useHistory();
const location = useLocation();
const [filter, setFilter] = useState<ListFilterModel>(
new ListFilterModel(options.filterMode, queryString.parse(location.search))
);
interface IRenderListProps {
filter: ListFilterModel;
onChangePage: (page: number) => void;
updateQueryParams: (filter: ListFilterModel) => void;
}
const RenderList = <
QueryResult extends IQueryResult,
QueryData extends IDataItem
>({
defaultZoomIndex,
filter,
onChangePage,
addKeybinds,
useData,
getCount,
getData,
otherOperations,
renderContent,
zoomable,
selectable,
renderEditDialog,
renderDeleteDialog,
updateQueryParams,
}: IListHookOptions<QueryResult, QueryData> &
IQuery<QueryResult, QueryData> &
IRenderListProps) => {
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
const [lastClickedId, setLastClickedId] = useState<string | undefined>();
const [zoomIndex, setZoomIndex] = useState<number>(
options.defaultZoomIndex ?? 1
);
// Store initial pathname to prevent hooks from operating outside this page
const originalPathName = useRef(location.pathname);
const [zoomIndex, setZoomIndex] = useState<number>(defaultZoomIndex ?? 1);
const result = options.useData(getFilter());
const totalCount = options.getCount(result);
const items = options.getData(result);
const result = useData(filter);
const totalCount = getCount(result);
const items = getData(result);
useEffect(() => {
Mousetrap.bind("right", () => {
@@ -140,7 +169,6 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
onChangePage(filter.currentPage - 1);
}
});
Mousetrap.bind("shift+right", () => {
const maxPage = totalCount / filter.itemsPerPage + 1;
onChangePage(Math.min(maxPage, filter.currentPage + 10));
@@ -157,8 +185,8 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
});
let unbindExtras: () => void;
if (options.addKeybinds) {
unbindExtras = options.addKeybinds(result, filter, selectedIds);
if (addKeybinds) {
unbindExtras = addKeybinds(result, filter, selectedIds);
}
return () => {
@@ -175,102 +203,6 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
};
});
const updateInterfaceConfig = useCallback(
(updatedFilter: ListFilterModel) => {
setInterfaceState((config) => {
const data = { ...config } as IInterfaceConfig;
data.queries = {
[options.filterMode]: {
filter: updatedFilter.makeQueryParameters(),
itemsPerPage: updatedFilter.itemsPerPage,
currentPage: updatedFilter.currentPage,
},
};
return data;
});
},
[options.filterMode, setInterfaceState]
);
useEffect(() => {
if (
interfaceState.loading ||
// Only update query params on page the hook was mounted on
history.location.pathname !== originalPathName.current
)
return;
if (!forageInitialised) setForageInitialised(true);
if (!options.persistState) return;
const storedQuery = interfaceState.data?.queries?.[options.filterMode];
if (!storedQuery) return;
const queryFilter = queryString.parse(history.location.search);
const storedFilter = queryString.parse(storedQuery.filter);
const query = history.location.search
? {
sortby: storedFilter.sortby,
sortdir: storedFilter.sortdir,
disp: storedFilter.disp,
perPage: storedFilter.perPage,
...queryFilter,
}
: storedFilter;
const newFilter = new ListFilterModel(options.filterMode, query);
// Compare constructed filter with current filter.
// If different it's the result of navigation, and we update the filter.
const newLocation = { ...history.location };
newLocation.search = newFilter.makeQueryParameters();
if (newLocation.search !== filter.makeQueryParameters()) {
setFilter(newFilter);
updateInterfaceConfig(newFilter);
}
// If constructed search is different from current, update it as well
if (newLocation.search !== location.search) {
newLocation.search = newFilter.makeQueryParameters();
history.replace(newLocation);
}
}, [
filter,
interfaceState.data,
interfaceState.loading,
history,
location.search,
options.filterMode,
forageInitialised,
updateInterfaceConfig,
options.persistState,
]);
function getFilter() {
if (!options.filterHook) {
return filter;
}
// make a copy of the filter and call the hook
const newFilter = _.cloneDeep(filter);
return options.filterHook(newFilter);
}
function updateQueryParams(listFilter: ListFilterModel) {
setFilter(listFilter);
const newLocation = { ...location };
newLocation.search = listFilter.makeQueryParameters();
history.replace(newLocation);
if (options.persistState) {
updateInterfaceConfig(listFilter);
}
}
function onChangePage(page: number) {
const newFilter = _.cloneDeep(filter);
newFilter.currentPage = page;
updateQueryParams(newFilter);
}
function singleSelect(id: string, selected: boolean) {
setLastClickedId(id);
@@ -348,54 +280,21 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
setZoomIndex(newZoomIndex);
}
const otherOperations = options.otherOperations
? options.otherOperations.map((o) => {
return {
text: o.text,
onClick: () => {
o.onClick(result, filter, selectedIds);
},
isDisplayed: () => {
if (o.isDisplayed) {
return o.isDisplayed(result, filter, selectedIds);
}
const operations =
otherOperations &&
otherOperations.map((o) => ({
text: o.text,
onClick: () => {
o.onClick(result, filter, selectedIds);
},
isDisplayed: () => {
if (o.isDisplayed) {
return o.isDisplayed(result, filter, selectedIds);
}
return true;
},
};
})
: undefined;
function maybeRenderContent() {
if (!result.loading && !result.error) {
return options.renderContent(result, filter, selectedIds, zoomIndex);
}
}
function maybeRenderPaginationIndex() {
if (!result.loading && !result.error) {
return (
<PaginationIndex
itemsPerPage={filter.itemsPerPage}
currentPage={filter.currentPage}
totalItems={totalCount}
/>
);
}
}
function maybeRenderPagination() {
if (!result.loading && !result.error) {
return (
<Pagination
itemsPerPage={filter.itemsPerPage}
currentPage={filter.currentPage}
totalItems={totalCount}
onChangePage={onChangePage}
/>
);
}
}
return true;
},
}));
function onEdit() {
setIsEditDialogOpen(true);
@@ -425,60 +324,189 @@ const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
result.refetch();
}
const template = (
<div>
<ListFilter
onFilterUpdate={updateQueryParams}
onSelectAll={options.selectable ? onSelectAll : undefined}
onSelectNone={options.selectable ? onSelectNone : undefined}
zoomIndex={options.zoomable ? zoomIndex : undefined}
onChangeZoom={options.zoomable ? onChangeZoom : undefined}
otherOperations={otherOperations}
itemsSelected={selectedIds.size > 0}
onEdit={options.renderEditDialog ? onEdit : undefined}
onDelete={options.renderDeleteDialog ? onDelete : undefined}
filter={filter}
/>
{isEditDialogOpen && options.renderEditDialog
? options.renderEditDialog(
options.getSelectedData(result, selectedIds),
(applied) => onEditDialogClosed(applied)
)
: undefined}
{isDeleteDialogOpen && options.renderDeleteDialog
? options.renderDeleteDialog(
options.getSelectedData(result, selectedIds),
(deleted) => onDeleteDialogClosed(deleted)
)
: undefined}
{(result.loading || !forageInitialised) && <LoadingIndicator />}
{result.error && <h1>{result.error.message}</h1>}
{maybeRenderPagination()}
{maybeRenderContent()}
{maybeRenderPaginationIndex()}
{maybeRenderPagination()}
</div>
const renderPagination = () => (
<Pagination
itemsPerPage={filter.itemsPerPage}
currentPage={filter.currentPage}
totalItems={totalCount}
onChangePage={onChangePage}
/>
);
return { filter, template, onSelectChange };
let content;
if (result.loading) {
content = <LoadingIndicator />;
} else if (result.error) {
content = <h1>{result.error.message}</h1>;
} else {
content = (
<div>
<ListFilter
onFilterUpdate={updateQueryParams}
onSelectAll={selectable ? onSelectAll : undefined}
onSelectNone={selectable ? onSelectNone : undefined}
zoomIndex={zoomable ? zoomIndex : undefined}
onChangeZoom={zoomable ? onChangeZoom : undefined}
otherOperations={operations}
itemsSelected={selectedIds.size > 0}
onEdit={renderEditDialog ? onEdit : undefined}
onDelete={renderDeleteDialog ? onDelete : undefined}
filter={filter}
/>
{isEditDialogOpen &&
renderEditDialog &&
renderEditDialog(
getSelectedData(getData(result), selectedIds),
(applied) => onEditDialogClosed(applied)
)}
{isDeleteDialogOpen &&
renderDeleteDialog &&
renderDeleteDialog(
getSelectedData(getData(result), selectedIds),
(deleted) => onDeleteDialogClosed(deleted)
)}
{renderPagination()}
{renderContent(result, filter, selectedIds, zoomIndex)}
<PaginationIndex
itemsPerPage={filter.itemsPerPage}
currentPage={filter.currentPage}
totalItems={totalCount}
/>
{renderPagination()}
</div>
);
}
return { contentTemplate: content, onSelectChange };
};
const getSelectedData = <I extends IDataItem>(
result: I[],
selectedIds: Set<string>
) => {
// find the selected items from the ids
const selectedResults: I[] = [];
const useList = <QueryResult extends IQueryResult, QueryData extends IDataItem>(
options: IListHookOptions<QueryResult, QueryData> &
IQuery<QueryResult, QueryData>
): IListHookData => {
const history = useHistory();
const location = useLocation();
const [interfaceState, setInterfaceState] = useInterfaceLocalForage();
// If persistState is false we don't care about forage and consider it initialised
const [forageInitialised, setForageInitialised] = useState(
!options.persistState
);
// Store initial pathname to prevent hooks from operating outside this page
const originalPathName = useRef(location.pathname);
selectedIds.forEach((id) => {
const item = result.find((s) => s.id === id);
const [filter, setFilter] = useState<ListFilterModel>(
new ListFilterModel(options.filterMode, queryString.parse(location.search))
);
if (item) {
selectedResults.push(item);
const updateInterfaceConfig = useCallback(
(updatedFilter: ListFilterModel) => {
setInterfaceState((config) => {
const data = { ...config } as IInterfaceConfig;
data.queries = {
[options.filterMode]: {
filter: updatedFilter.makeQueryParameters(),
itemsPerPage: updatedFilter.itemsPerPage,
currentPage: updatedFilter.currentPage,
},
};
return data;
});
},
[options.filterMode, setInterfaceState]
);
useEffect(() => {
if (
interfaceState.loading ||
// Only update query params on page the hook was mounted on
history.location.pathname !== originalPathName.current
)
return;
if (!forageInitialised) setForageInitialised(true);
if (!options.persistState) return;
const storedQuery = interfaceState.data?.queries?.[options.filterMode];
if (!storedQuery) return;
const queryFilter = queryString.parse(history.location.search);
const storedFilter = queryString.parse(storedQuery.filter);
const query = history.location.search
? {
sortby: storedFilter.sortby,
sortdir: storedFilter.sortdir,
disp: storedFilter.disp,
perPage: storedFilter.perPage,
...queryFilter,
}
: storedFilter;
const newFilter = new ListFilterModel(options.filterMode, query);
// Compare constructed filter with current filter.
// If different it's the result of navigation, and we update the filter.
const newLocation = { ...history.location };
newLocation.search = newFilter.makeQueryParameters();
if (newLocation.search !== filter.makeQueryParameters()) {
setFilter(newFilter);
updateInterfaceConfig(newFilter);
}
// If constructed search is different from current, update it as well
if (newLocation.search !== location.search) {
newLocation.search = newFilter.makeQueryParameters();
history.replace(newLocation);
}
}, [
filter,
interfaceState.data,
interfaceState.loading,
history,
location.search,
options.filterMode,
forageInitialised,
updateInterfaceConfig,
options.persistState,
]);
function updateQueryParams(listFilter: ListFilterModel) {
setFilter(listFilter);
const newLocation = { ...location };
newLocation.search = listFilter.makeQueryParameters();
history.replace(newLocation);
if (options.persistState) {
updateInterfaceConfig(listFilter);
}
}
const onChangePage = (page: number) => {
const newFilter = _.cloneDeep(filter);
newFilter.currentPage = page;
updateQueryParams(newFilter);
};
const renderFilter = !options.filterHook
? filter
: options.filterHook(_.cloneDeep(filter));
const { contentTemplate, onSelectChange } = RenderList({
...options,
filter: renderFilter,
onChangePage,
updateQueryParams,
});
return selectedResults;
const template = !forageInitialised ? (
<LoadingIndicator />
) : (
<>{contentTemplate}</>
);
return {
filter,
template,
onSelectChange,
};
};
export const useScenesList = (
@@ -492,10 +520,6 @@ export const useScenesList = (
result?.data?.findScenes?.scenes ?? [],
getCount: (result: FindScenesQueryResult) =>
result?.data?.findScenes?.count ?? 0,
getSelectedData: (
result: FindScenesQueryResult,
selectedIds: Set<string>
) => getSelectedData(result?.data?.findScenes?.scenes ?? [], selectedIds),
});
export const useSceneMarkersList = (
@@ -509,14 +533,6 @@ export const useSceneMarkersList = (
result?.data?.findSceneMarkers?.scene_markers ?? [],
getCount: (result: FindSceneMarkersQueryResult) =>
result?.data?.findSceneMarkers?.count ?? 0,
getSelectedData: (
result: FindSceneMarkersQueryResult,
selectedIds: Set<string>
) =>
getSelectedData(
result?.data?.findSceneMarkers?.scene_markers ?? [],
selectedIds
),
});
export const useGalleriesList = (
@@ -530,14 +546,6 @@ export const useGalleriesList = (
result?.data?.findGalleries?.galleries ?? [],
getCount: (result: FindGalleriesQueryResult) =>
result?.data?.findGalleries?.count ?? 0,
getSelectedData: (
result: FindGalleriesQueryResult,
selectedIds: Set<string>
) =>
getSelectedData(
result?.data?.findGalleries?.galleries ?? [],
selectedIds
),
});
export const useStudiosList = (
@@ -551,10 +559,6 @@ export const useStudiosList = (
result?.data?.findStudios?.studios ?? [],
getCount: (result: FindStudiosQueryResult) =>
result?.data?.findStudios?.count ?? 0,
getSelectedData: (
result: FindStudiosQueryResult,
selectedIds: Set<string>
) => getSelectedData(result?.data?.findStudios?.studios ?? [], selectedIds),
});
export const usePerformersList = (
@@ -568,14 +572,6 @@ export const usePerformersList = (
result?.data?.findPerformers?.performers ?? [],
getCount: (result: FindPerformersQueryResult) =>
result?.data?.findPerformers?.count ?? 0,
getSelectedData: (
result: FindPerformersQueryResult,
selectedIds: Set<string>
) =>
getSelectedData(
result?.data?.findPerformers?.performers ?? [],
selectedIds
),
});
export const useMoviesList = (
@@ -589,10 +585,6 @@ export const useMoviesList = (
result?.data?.findMovies?.movies ?? [],
getCount: (result: FindMoviesQueryResult) =>
result?.data?.findMovies?.count ?? 0,
getSelectedData: (
result: FindMoviesQueryResult,
selectedIds: Set<string>
) => getSelectedData(result?.data?.findMovies?.movies ?? [], selectedIds),
});
export const useTagsList = (
@@ -606,13 +598,11 @@ export const useTagsList = (
result?.data?.findTags?.tags ?? [],
getCount: (result: FindTagsQueryResult) =>
result?.data?.findTags?.count ?? 0,
getSelectedData: (result: FindTagsQueryResult, selectedIds: Set<string>) =>
getSelectedData(result?.data?.findTags?.tags ?? [], selectedIds),
});
export const showWhenSelected = (
result: FindScenesQueryResult,
filter: ListFilterModel,
_result: FindScenesQueryResult,
_filter: ListFilterModel,
selectedIds: Set<string>
) => {
return selectedIds.size > 0;