Skip to content

Commit

Permalink
feat(ui): Added directory selector for snapshots and filesystem repos…
Browse files Browse the repository at this point in the history
…itory (#229)

- Added a modified directory selector
- Fixes kopia/kopia#3453
  • Loading branch information
lupusA committed Jan 25, 2024
1 parent d3822cf commit ac057d3
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 48 deletions.
7 changes: 2 additions & 5 deletions src/components/SetupRepositoryFilesystem.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { Component } from 'react';
import Row from 'react-bootstrap/Row';
import { handleChange, validateRequiredFields } from '../forms';
import { RequiredField } from '../forms/RequiredField';
import { RequiredDirectory } from '../forms/RequiredDirectory';

export class SetupRepositoryFilesystem extends Component {
constructor(props) {
Expand All @@ -19,9 +18,7 @@ export class SetupRepositoryFilesystem extends Component {

render() {
return <>
<Row>
{RequiredField(this, "Directory Path", "path", { autoFocus: true, placeholder: "enter directory path where you want to store repository files" })}
</Row>
{RequiredDirectory(this, "Directory Path", "path", { autoFocus: true, placeholder: "enter directory path where you want to store repository files"})}
</>;
}
}
2 changes: 0 additions & 2 deletions src/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

code {
Expand Down
50 changes: 50 additions & 0 deletions src/forms/OptionalDirectory.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import { Col, FormGroup, FormControl, InputGroup } from 'react-bootstrap';
import { faFolderOpen } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { stateProperty } from '.';
import { setDeepStateProperty } from '../utils/deepstate';

/**
* This functions returns a directory selector that allows the user to select a directory.
* The selections is invoked using a button that calls a functions within the electron app.
* If the electron app is not present, the button is not visible.
*
* @param {*} component
* The component that this function is called from
* @param {string} label
* Label, that is added before the input field
* @param {string} name
* Name of the variable in which the directory path is stored
* @param {*} props
* Additional properties of the component
* @returns The form group with the components
*/
export function OptionalDirectory(component, label, name, props = {}) {
/**
* Saves the selected path as a deepstate variable within the component
* @param {The path that has been selected} path
*/
function onDirectorySelected(path) {
setDeepStateProperty(component, name, path)
}

return <FormGroup>
{label && <Form.Label htmlFor='directoryInput' className="required">{label}</Form.Label>}
<InputGroup as={Col}>
<FormControl
id='directoryInput'
size="sm"
name={name}
value={stateProperty(component, name)}
data-testid={'control-' + name}
onChange={component.handleChange}{...props}></FormControl>
{window.kopiaUI &&
<Button size="sm" onClick={() => window.kopiaUI.selectDirectory(onDirectorySelected)}>
<FontAwesomeIcon icon={faFolderOpen} />
</Button>}
</InputGroup>
</FormGroup>
}
3 changes: 1 addition & 2 deletions src/forms/OptionalField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Form from 'react-bootstrap/Form';
import Col from 'react-bootstrap/Col';
import { stateProperty } from '.';

export function OptionalField(component, label, name, props = {}, helpText = null, invalidFeedback = null) {
export function OptionalField(component, label, name, props = {}, helpText = null) {
return <Form.Group as={Col}>
<Form.Label>{label}</Form.Label>
<Form.Control
Expand All @@ -14,6 +14,5 @@ export function OptionalField(component, label, name, props = {}, helpText = nul
onChange={component.handleChange}
{...props} />
{helpText && <Form.Text className="text-muted">{helpText}</Form.Text>}
{invalidFeedback && <Form.Control.Feedback type="invalid">{invalidFeedback}</Form.Control.Feedback>}
</Form.Group>;
}
52 changes: 52 additions & 0 deletions src/forms/RequiredDirectory.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import { Col, FormGroup, FormControl, InputGroup } from 'react-bootstrap';
import { faFolderOpen } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { stateProperty } from '.';
import { setDeepStateProperty } from '../utils/deepstate';

/**
* This functions returns a directory selector that allows the user to select a directory.
* The selections is invoked using a button that calls a functions within the electron app.
* If the electron app is not present, the button is not visible. The path is required.
*
* @param {*} component
* The component that this function is called from
* @param {string} label
* Label, that is added before the input field
* @param {string} name
* Name of the variable in which the directory path is stored
* @param {*} props
* Additional properties of the component
* @returns The form group with the components
*/
export function RequiredDirectory(component, label, name, props = {}) {
/**
* Saves the selected path as a deepstate variable within the component
* @param {The path that has been selected} path
*/
function onDirectorySelected(path) {
setDeepStateProperty(component, name, path)
}

return <FormGroup>
{label && <Form.Label className="required">{label}</Form.Label>}
<InputGroup as={Col}>
<FormControl
id='directoryInput'
size="sm"
name={name}
isInvalid={stateProperty(component, name, null) === ''}
value={stateProperty(component, name)}
data-testid={'control-' + name}
onChange={component.handleChange}{...props}></FormControl>
{window.kopiaUI &&
<Button size="sm" onClick={() => window.kopiaUI.selectDirectory(onDirectorySelected)}>
<FontAwesomeIcon icon={faFolderOpen} />
</Button>}
<Form.Control.Feedback type="invalid">Required field</Form.Control.Feedback>
</InputGroup>
</FormGroup>
}
9 changes: 4 additions & 5 deletions src/pages/Policies.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import Form from 'react-bootstrap/Form';
import Row from 'react-bootstrap/Row';
import { Link } from 'react-router-dom';
import { handleChange } from '../forms';
import { OptionalDirectory } from '../forms/OptionalDirectory'
import KopiaTable from '../utils/KopiaTable';
import { CLIEquivalent, compare, DirectorySelector, isAbsolutePath, ownerName, policyEditorURL, redirect } from '../utils/uiutil';
import { CLIEquivalent, compare, isAbsolutePath, ownerName, policyEditorURL, redirect } from '../utils/uiutil';

const applicablePolicies = "Applicable Policies"
const localPolicies = "Local Path Policies"
Expand Down Expand Up @@ -274,9 +275,7 @@ export class Policies extends Component {
</Col>
{(this.state.selectedOwner === localPolicies || this.state.selectedOwner === this.state.localSourceName || this.state.selectedOwner === applicablePolicies) ? <>
<Col>
<DirectorySelector autoFocus onDirectorySelected={p => this.setState({ policyPath: p })}
placeholder="enter directory to find or set policy"
name="policyPath" value={this.state.policyPath} onChange={this.handleChange} />
{OptionalDirectory(this, null, "policyPath", { autoFocus: true, placeholder: "enter directory to find or set policy" })}
</Col>
<Col xs="auto">
<Button disabled={!this.state.policyPath} size="sm" type="submit" onClick={this.editPolicyForPath}>Set Policy</Button>
Expand All @@ -295,4 +294,4 @@ export class Policies extends Component {
<CLIEquivalent command="policy list" />
</>;
}
}
}
27 changes: 10 additions & 17 deletions src/pages/SnapshotCreate.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import Row from 'react-bootstrap/Row';
import { handleChange } from '../forms';
import { PolicyEditor } from '../components/policy-editor/PolicyEditor';
import { SnapshotEstimation } from '../components/SnapshotEstimation';
import { CLIEquivalent, DirectorySelector, errorAlert, GoBackButton, redirect } from '../utils/uiutil';
import { RequiredDirectory } from '../forms/RequiredDirectory';
import { CLIEquivalent, errorAlert, GoBackButton, redirect } from '../utils/uiutil';

export class SnapshotCreate extends Component {
constructor() {
Expand Down Expand Up @@ -59,7 +60,7 @@ export class SnapshotCreate extends Component {
} else {
this.setState({
lastResolvedPath: currentPath,
resolvedSource: null,
resolvedSource: "",
});

this.maybeResolveCurrentPath(currentPath);
Expand Down Expand Up @@ -147,18 +148,15 @@ export class SnapshotCreate extends Component {

render() {
return <>
<Row>
<Form.Group>
<GoBackButton onClick={this.props.history.goBack} />
</Form.Group>
&nbsp;&nbsp;&nbsp;<h4>New Snapshot</h4>
</Row>
<Form.Group>
<GoBackButton onClick={this.props.history.goBack} />
</Form.Group>
<br />
<h4>New Snapshot</h4>
<br />
<Row>
<Col>
<Form.Group>
<DirectorySelector onDirectorySelected={p => this.setState({ path: p })} autoFocus placeholder="enter path to snapshot" name="path" value={this.state.path} onChange={this.handleChange} />
</Form.Group>
{RequiredDirectory(this, null, "path", { autoFocus: true, placeholder: "enter path to snapshot" })}
</Col>
<Col xs="auto">
<Button
Expand All @@ -168,7 +166,6 @@ export class SnapshotCreate extends Component {
title="Estimate"
variant="secondary"
onClick={this.estimate}>Estimate</Button>
&nbsp;
<Button
data-testid='snapshot-now'
size="sm"
Expand All @@ -182,21 +179,17 @@ export class SnapshotCreate extends Component {
<SnapshotEstimation taskID={this.state.estimateTaskID} hideDescription={true} showZeroCounters={true} />
}
<br />

{this.state.resolvedSource && <Row><Col xs={12}>
<Form.Text>
{this.state.resolvedSource ? this.state.resolvedSource.path : this.state.path}
</Form.Text>

<PolicyEditor ref={this.policyEditorRef}
embedded
host={this.state.resolvedSource.host}
userName={this.state.resolvedSource.userName}
path={this.state.resolvedSource.path} />
</Col></Row>}

<Row><Col>&nbsp;</Col></Row>

<br />
<CLIEquivalent command={`snapshot create ${this.state.resolvedSource ? this.state.resolvedSource.path : this.state.path}`} />
</>;
}
Expand Down
18 changes: 1 addition & 17 deletions src/utils/uiutil.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { faBan, faCheck, faChevronLeft, faCopy, faExclamationCircle, faExclamationTriangle, faFolderOpen, faTerminal, faXmark } from '@fortawesome/free-solid-svg-icons';
import { faBan, faCheck, faChevronLeft, faCopy, faExclamationCircle, faExclamationTriangle, faTerminal, faXmark } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import axios from 'axios';
import React, { useState } from 'react';
import Button from 'react-bootstrap/Button';
import Form from 'react-bootstrap/Form';
import FormControl from 'react-bootstrap/FormControl';
import InputGroup from 'react-bootstrap/InputGroup';
import Spinner from 'react-bootstrap/Spinner';
Expand Down Expand Up @@ -367,21 +366,6 @@ export function errorAlert(err, prefix) {
}
}

export function DirectorySelector(props) {
let { onDirectorySelected, ...inputProps } = props;

if (!window.kopiaUI) {
return <Form.Control size="sm" {...inputProps} />
}

return <InputGroup>
<FormControl size="sm" {...inputProps} />
<Button size="sm" onClick={() => window.kopiaUI.selectDirectory(onDirectorySelected)}>
<FontAwesomeIcon icon={faFolderOpen} />
</Button>
</InputGroup>;
}

export function CLIEquivalent(props) {
let [visible, setVisible] = useState(false);
let [cliInfo, setCLIInfo] = useState({});
Expand Down

0 comments on commit ac057d3

Please sign in to comment.