mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Selective clean (#2125)
* Add backend support for selective clean * Add selective clean button and dialog
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { Button, Col, Form, Row } from "react-bootstrap";
|
||||
import {
|
||||
mutateMigrateHashNaming,
|
||||
mutateMetadataExport,
|
||||
@@ -17,6 +17,104 @@ import { SettingSection } from "../SettingSection";
|
||||
import { BooleanSetting, Setting } from "../Inputs";
|
||||
import { ManualLink } from "src/components/Help/Manual";
|
||||
import { Icon } from "src/components/Shared";
|
||||
import { ConfigurationContext } from "src/hooks/Config";
|
||||
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
||||
|
||||
interface ICleanDialog {
|
||||
pathSelection?: boolean;
|
||||
dryRun: boolean;
|
||||
onClose: (paths?: string[]) => void;
|
||||
}
|
||||
|
||||
const CleanDialog: React.FC<ICleanDialog> = ({
|
||||
pathSelection = false,
|
||||
dryRun,
|
||||
onClose,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { configuration } = React.useContext(ConfigurationContext);
|
||||
|
||||
const libraryPaths = configuration?.general.stashes.map((s) => s.path);
|
||||
|
||||
const [paths, setPaths] = useState<string[]>([]);
|
||||
const [currentDirectory, setCurrentDirectory] = useState<string>("");
|
||||
|
||||
function removePath(p: string) {
|
||||
setPaths(paths.filter((path) => path !== p));
|
||||
}
|
||||
|
||||
function addPath(p: string) {
|
||||
if (p && !paths.includes(p)) {
|
||||
setPaths(paths.concat(p));
|
||||
}
|
||||
}
|
||||
|
||||
let msg;
|
||||
if (dryRun) {
|
||||
msg = (
|
||||
<p>{intl.formatMessage({ id: "actions.tasks.dry_mode_selected" })}</p>
|
||||
);
|
||||
} else {
|
||||
msg = (
|
||||
<p>{intl.formatMessage({ id: "actions.tasks.clean_confirm_message" })}</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
show
|
||||
icon="trash-alt"
|
||||
disabled={pathSelection && paths.length === 0}
|
||||
accept={{
|
||||
text: intl.formatMessage({ id: "actions.clean" }),
|
||||
variant: "danger",
|
||||
onClick: () => onClose(paths),
|
||||
}}
|
||||
cancel={{ onClick: () => onClose() }}
|
||||
>
|
||||
<div className="dialog-container">
|
||||
<div className="mb-3">
|
||||
{paths.map((p) => (
|
||||
<Row className="align-items-center mb-1" key={p}>
|
||||
<Form.Label column xs={10}>
|
||||
{p}
|
||||
</Form.Label>
|
||||
<Col xs={2} className="d-flex justify-content-end">
|
||||
<Button
|
||||
className="ml-auto"
|
||||
size="sm"
|
||||
variant="danger"
|
||||
title={intl.formatMessage({ id: "actions.delete" })}
|
||||
onClick={() => removePath(p)}
|
||||
>
|
||||
<Icon icon="minus" />
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
|
||||
{pathSelection ? (
|
||||
<FolderSelect
|
||||
currentDirectory={currentDirectory}
|
||||
setCurrentDirectory={(v) => setCurrentDirectory(v)}
|
||||
defaultDirectories={libraryPaths}
|
||||
appendButton={
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => addPath(currentDirectory)}
|
||||
>
|
||||
<Icon icon="plus" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
) : undefined}
|
||||
</div>
|
||||
|
||||
{msg}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
interface ICleanOptions {
|
||||
options: GQL.CleanMetadataInput;
|
||||
@@ -56,6 +154,7 @@ export const DataManagementTasks: React.FC<IDataManagementTasks> = ({
|
||||
importAlert: false,
|
||||
import: false,
|
||||
clean: false,
|
||||
cleanAlert: false,
|
||||
});
|
||||
|
||||
const [cleanOptions, setCleanOptions] = useState<GQL.CleanMetadataInput>({
|
||||
@@ -110,39 +209,12 @@ export const DataManagementTasks: React.FC<IDataManagementTasks> = ({
|
||||
return <ImportDialog onClose={() => setDialogOpen({ import: false })} />;
|
||||
}
|
||||
|
||||
function renderCleanDialog() {
|
||||
let msg;
|
||||
if (cleanOptions.dryRun) {
|
||||
msg = (
|
||||
<p>{intl.formatMessage({ id: "actions.tasks.dry_mode_selected" })}</p>
|
||||
);
|
||||
} else {
|
||||
msg = (
|
||||
<p>
|
||||
{intl.formatMessage({ id: "actions.tasks.clean_confirm_message" })}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
show={dialogOpen.clean}
|
||||
icon="trash-alt"
|
||||
accept={{
|
||||
text: intl.formatMessage({ id: "actions.clean" }),
|
||||
variant: "danger",
|
||||
onClick: onClean,
|
||||
}}
|
||||
cancel={{ onClick: () => setDialogOpen({ clean: false }) }}
|
||||
>
|
||||
{msg}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
async function onClean() {
|
||||
async function onClean(paths?: string[]) {
|
||||
try {
|
||||
await mutateMetadataClean(cleanOptions);
|
||||
await mutateMetadataClean({
|
||||
...cleanOptions,
|
||||
paths,
|
||||
});
|
||||
|
||||
Toast.success({
|
||||
content: intl.formatMessage(
|
||||
@@ -212,7 +284,30 @@ export const DataManagementTasks: React.FC<IDataManagementTasks> = ({
|
||||
<Form.Group>
|
||||
{renderImportAlert()}
|
||||
{renderImportDialog()}
|
||||
{renderCleanDialog()}
|
||||
{dialogOpen.cleanAlert || dialogOpen.clean ? (
|
||||
<CleanDialog
|
||||
dryRun={cleanOptions.dryRun}
|
||||
pathSelection={dialogOpen.clean}
|
||||
onClose={(p) => {
|
||||
// undefined means cancelled
|
||||
if (p !== undefined) {
|
||||
if (dialogOpen.cleanAlert) {
|
||||
// don't provide paths
|
||||
onClean();
|
||||
} else {
|
||||
onClean(p);
|
||||
}
|
||||
}
|
||||
|
||||
setDialogOpen({
|
||||
clean: false,
|
||||
cleanAlert: false,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
dialogOpen.clean
|
||||
)}
|
||||
|
||||
<SettingSection headingID="config.tasks.maintenance">
|
||||
<div className="setting-group">
|
||||
@@ -230,10 +325,17 @@ export const DataManagementTasks: React.FC<IDataManagementTasks> = ({
|
||||
<Button
|
||||
variant="danger"
|
||||
type="submit"
|
||||
onClick={() => setDialogOpen({ clean: true })}
|
||||
onClick={() => setDialogOpen({ cleanAlert: true })}
|
||||
>
|
||||
<FormattedMessage id="actions.clean" />…
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger"
|
||||
type="submit"
|
||||
onClick={() => setDialogOpen({ clean: true })}
|
||||
>
|
||||
<FormattedMessage id="actions.selective_clean" />…
|
||||
</Button>
|
||||
</Setting>
|
||||
<CleanOptions
|
||||
options={cleanOptions}
|
||||
|
||||
Reference in New Issue
Block a user