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 ( <>

{chunks}, }} />

); } 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 ( <>

{setupError} }} />

); } 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;