import React, { useEffect, useState, useContext } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import {
Alert,
Button,
Card,
Container,
Form,
InputGroup,
} from "react-bootstrap";
import * as GQL from "src/core/generated-graphql";
import {
mutateSetup,
useConfigureUI,
useSystemStatus,
} from "src/core/StashService";
import { Link, useHistory } from "react-router-dom";
import { ConfigurationContext } from "src/hooks/Config";
import StashConfiguration from "../Settings/StashConfiguration";
import { Icon } from "../Shared/Icon";
import { LoadingIndicator } from "../Shared/LoadingIndicator";
import { ModalComponent } from "../Shared/Modal";
import { FolderSelectDialog } from "../Shared/FolderSelect/FolderSelectDialog";
import {
faEllipsisH,
faExclamationTriangle,
faQuestionCircle,
} from "@fortawesome/free-solid-svg-icons";
import { releaseNotes } from "src/docs/en/ReleaseNotes";
export const Setup: React.FC = () => {
const { configuration, loading: configLoading } =
useContext(ConfigurationContext);
const [saveUI] = useConfigureUI();
const [step, setStep] = useState(0);
const [setupInWorkDir, setSetupInWorkDir] = useState(false);
const [stashes, setStashes] = useState([]);
const [showStashAlert, setShowStashAlert] = useState(false);
const [databaseFile, setDatabaseFile] = useState("");
const [generatedLocation, setGeneratedLocation] = useState("");
const [cacheLocation, setCacheLocation] = useState("");
const [storeBlobsInDatabase, setStoreBlobsInDatabase] = useState(false);
const [blobsLocation, setBlobsLocation] = useState("");
const [loading, setLoading] = useState(false);
const [setupError, setSetupError] = useState();
const intl = useIntl();
const history = useHistory();
const [showGeneratedSelectDialog, setShowGeneratedSelectDialog] =
useState(false);
const [showCacheSelectDialog, setShowCacheSelectDialog] = useState(false);
const [showBlobsDialog, setShowBlobsDialog] = useState(false);
const { data: systemStatus, loading: statusLoading } = useSystemStatus();
const status = systemStatus?.systemStatus;
const windows = status?.os === "windows";
const pathSep = windows ? "\\" : "/";
const homeDir = windows ? "%USERPROFILE%" : "$HOME";
const pwd = windows ? "%CD%" : "$PWD";
function pathJoin(...paths: string[]) {
return paths.join(pathSep);
}
// simply returns everything preceding the last path separator
function pathDir(path: string) {
const lastSep = path.lastIndexOf(pathSep);
if (lastSep === -1) return "";
return path.slice(0, lastSep);
}
const workingDir = status?.workingDir ?? ".";
// When running Stash.app, the working directory is (usually) set to /.
// Assume that the user doesn't want to set up in / (it's usually mounted read-only anyway),
// so in this situation disallow setting up in the working directory.
const macApp = status?.os === "darwin" && workingDir === "/";
const fallbackStashDir = pathJoin(homeDir, ".stash");
const fallbackConfigPath = pathJoin(fallbackStashDir, "config.yml");
const overrideConfig = status?.configPath;
const overrideGenerated = configuration?.general.generatedPath;
const overrideCache = configuration?.general.cachePath;
const overrideBlobs = configuration?.general.blobsPath;
const overrideDatabase = configuration?.general.databasePath;
useEffect(() => {
if (configuration) {
const configStashes = configuration.general.stashes;
if (configStashes.length > 0) {
setStashes(
configStashes.map((s) => {
const { __typename, ...withoutTypename } = s;
return withoutTypename;
})
);
}
}
}, [configuration]);
const discordLink = (
Discord
);
const githubLink = (
);
function onConfigLocationChosen(inWorkDir: boolean) {
setSetupInWorkDir(inWorkDir);
next();
}
function goBack(n?: number) {
let dec = n;
if (!dec) {
dec = 1;
}
setStep(Math.max(0, step - dec));
}
function next() {
setStep(step + 1);
}
function confirmPaths() {
if (stashes.length > 0) {
next();
return;
}
setShowStashAlert(true);
}
function maybeRenderStashAlert() {
if (!showStashAlert) {
return;
}
return (
{
setShowStashAlert(false);
next();
},
}}
cancel={{ onClick: () => setShowStashAlert(false) }}
>
);
}
function renderWelcomeSpecificConfig() {
return (
<>
>
);
}
function renderWelcome() {
const homeDirPath = pathJoin(status?.homeDir ?? homeDir, ".stash");
return (
<>
{chunks},
fallback_path: fallbackConfigPath,
}}
/>
{chunks},
}}
/>
>
);
}
function onGeneratedSelectClosed(d?: string) {
if (d) {
setGeneratedLocation(d);
}
setShowGeneratedSelectDialog(false);
}
function maybeRenderGeneratedSelectDialog() {
if (!showGeneratedSelectDialog) {
return;
}
return ;
}
function onBlobsClosed(d?: string) {
if (d) {
setBlobsLocation(d);
}
setShowBlobsDialog(false);
}
function maybeRenderBlobsSelectDialog() {
if (!showBlobsDialog) {
return;
}
return ;
}
function maybeRenderDatabase() {
if (overrideDatabase) return;
return (
{chunks},
}}
/>
{chunks},
}}
/>
setDatabaseFile(e.currentTarget.value)}
/>
);
}
function maybeRenderGenerated() {
if (overrideGenerated) return;
return (
{chunks},
}}
/>
setGeneratedLocation(e.currentTarget.value)}
/>
);
}
function onCacheSelectClosed(d?: string) {
if (d) {
setCacheLocation(d);
}
setShowCacheSelectDialog(false);
}
function maybeRenderCacheSelectDialog() {
if (!showCacheSelectDialog) {
return;
}
return ;
}
function maybeRenderCache() {
if (overrideCache) return;
return (
{chunks},
}}
/>
setCacheLocation(e.currentTarget.value)}
/>
);
}
function maybeRenderBlobs() {
if (overrideBlobs) return;
return (
{chunks},
}}
/>
{chunks},
strong: (chunks: string) => {chunks},
}}
/>
setStoreBlobsInDatabase(!storeBlobsInDatabase)}
/>
{!storeBlobsInDatabase && (
setBlobsLocation(e.currentTarget.value)}
disabled={storeBlobsInDatabase}
/>
)}
);
}
function renderSetPaths() {
return (
<>
{maybeRenderStashAlert()}
setStashes(s)}
/>
{maybeRenderDatabase()}
{maybeRenderGenerated()}
{maybeRenderCache()}
{maybeRenderBlobs()}
>
);
}
function maybeRenderExclusions(s: GQL.StashConfig) {
if (!s.excludeImage && !s.excludeVideo) {
return;
}
const excludes = [];
if (s.excludeVideo) {
excludes.push("videos");
}
if (s.excludeImage) {
excludes.push("images");
}
return `(excludes ${excludes.join(" and ")})`;
}
async function onSave() {
let configLocation = overrideConfig;
if (!configLocation) {
configLocation = setupInWorkDir ? "config.yml" : "";
}
try {
setLoading(true);
await mutateSetup({
configLocation,
databaseFile,
generatedLocation,
cacheLocation,
storeBlobsInDatabase,
blobsLocation,
stashes,
});
// Set lastNoteSeen to hide release notes dialog
await saveUI({
variables: {
input: {
...configuration?.ui,
lastNoteSeen: releaseNotes[0].date,
},
},
});
} catch (e) {
if (e instanceof Error && e.message) {
setSetupError(e.message);
} else {
setSetupError(String(e));
}
} finally {
setLoading(false);
next();
}
}
function renderConfirm() {
let cfgDir: string;
let config: string;
if (overrideConfig) {
cfgDir = pathDir(overrideConfig);
config = overrideConfig;
} else {
cfgDir = setupInWorkDir ? pwd : fallbackStashDir;
config = pathJoin(cfgDir, "config.yml");
}
function joinCfgDir(path: string) {
if (cfgDir) {
return pathJoin(cfgDir, path);
} else {
return path;
}
}
return (
<>
-
-
{config}
-
-
{stashes.map((s) => (
-
{s.path}
{maybeRenderExclusions(s)}
))}
{!overrideDatabase && (
-
-
{databaseFile || joinCfgDir("stash-go.sqlite")}
)}
{!overrideGenerated && (
-
-
{generatedLocation || joinCfgDir("generated")}
)}
{!overrideCache && (
-
-
{cacheLocation || joinCfgDir("cache")}
)}
{!overrideBlobs && (
-
-
{storeBlobsInDatabase ? (
) : (
blobsLocation || joinCfgDir("blobs")
)}
)}
>
);
}
function renderError() {
function onBackClick() {
setSetupError(undefined);
goBack(2);
}
return (
<>
>
);
}
function renderSuccess() {
return (
<>
{chunks},
localized_task: intl.formatMessage({
id: "config.categories.tasks",
}),
localized_scan: intl.formatMessage({ id: "actions.scan" }),
}}
/>
{" "}
OpenCollective{" "}
),
}}
/>
>
);
}
function renderFinish() {
if (setupError !== undefined) {
return renderError();
}
return renderSuccess();
}
// only display setup wizard if system is not setup
if (statusLoading || configLoading) {
return ;
}
if (step === 0 && status && status.status !== GQL.SystemStatusEnum.Setup) {
// redirect to main page
history.push("/");
return ;
}
const welcomeStep = overrideConfig
? renderWelcomeSpecificConfig
: renderWelcome;
const steps = [welcomeStep, renderSetPaths, renderConfirm, renderFinish];
function renderCreating() {
return (
{chunks},
}}
/>
);
}
return (
{maybeRenderGeneratedSelectDialog()}
{maybeRenderCacheSelectDialog()}
{maybeRenderBlobsSelectDialog()}
{loading ? renderCreating() : {steps[step]()}}
);
};
export default Setup;