Skip to content

Commit

Permalink
Added directory breadcrumb buttons to see path and navigate back (#141)
Browse files Browse the repository at this point in the history
* Added directory breadcrumbs to
- be able to view the current path one came to the current directory
- be able to quickly navigate upwards

* layout fixes

* use breadcrumps for history in DirectoryObject.jsx / DirectoryBreadcrumbs.jsx

* - get path of snapshot
- no tooltip if no OID
- disable / improve wrapping of tooltip
- disable clicking on current breadcrumb item

* enable copying of OID to clipboard

* fix warning about className and readOnly form

---------

Co-authored-by: Peter Tandler <[email protected]>
  • Loading branch information
ptandler and Peter Tandler authored Sep 5, 2023
1 parent a4a2fa1 commit db097d2
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 32 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"license": "Apache-2.0",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-regular-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"@fortawesome/react-fontawesome": "^0.2.0",
Expand Down
40 changes: 40 additions & 0 deletions src/DirectoryBreadcrumbs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import Breadcrumb from 'react-bootstrap/Breadcrumb';
import { useHistory, useLocation } from 'react-router-dom';
import { OverlayTrigger, Tooltip } from "react-bootstrap";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";

export function DirectoryBreadcrumbs() {
const location = useLocation();
const history = useHistory();

const breadcrumbs = []
for (let state = location.state; state; state = state.prevState) {
breadcrumbs.unshift(state)
}

return (
<Breadcrumb>
{
breadcrumbs.map((state, i) => {
const index = breadcrumbs.length - i - 1 // revert index
return <Breadcrumb.Item key={index} size="sm" variant="outline-secondary"
onClick={() => {
if (index) history.go(-index);
}}
active={!index}>
{state.label}
{state.oid && !index && <>&nbsp;<OverlayTrigger placement="top"
trigger="click"
overlay={<Tooltip
className={"wide-tooltip"}>OID: {state.oid}</Tooltip>}
>
<FontAwesomeIcon icon={faInfoCircle} />
</OverlayTrigger></>}
</Breadcrumb.Item>;
})
}
</Breadcrumb>
)
}
6 changes: 3 additions & 3 deletions src/DirectoryItems.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ function sizeInfo(item) {
return 0;
}

function directoryLinkOrDownload(x) {
function directoryLinkOrDownload(x, state) {
if (x.obj.startsWith("k")) {
return <Link to={objectLink(x.obj)}>{objectName(x.name, x.type)}</Link>;
return <Link to={objectLink(x.obj, x.name, state)}>{objectName(x.name, x.type)}</Link>;
}

return <a href={"/api/v1/objects/" + x.obj + "?fname=" + encodeURIComponent(x.name)}>{x.name}</a>;
Expand All @@ -39,7 +39,7 @@ export class DirectoryItems extends Component {
id: "name",
Header: 'Name',
width: "",
accessor: x => directoryLinkOrDownload(x),
accessor: x => directoryLinkOrDownload(x, this.props.historyState),
}, {
id: "mtime",
accessor: "mtime",
Expand Down
32 changes: 18 additions & 14 deletions src/DirectoryObject.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Spinner from 'react-bootstrap/Spinner';
import { DirectoryItems } from "./DirectoryItems";
import { CLIEquivalent, GoBackButton } from './uiutil';
import { CLIEquivalent } from './uiutil';
import { DirectoryBreadcrumbs } from "./DirectoryBreadcrumbs";

export class DirectoryObject extends Component {
constructor() {
Expand Down Expand Up @@ -118,33 +119,36 @@ export class DirectoryObject extends Component {
}

return <>
<DirectoryBreadcrumbs />
<Row>
<Col xs={12}>
<GoBackButton onClick={this.props.history.goBack} />
&nbsp;
<Col xs="auto">
{this.state.mountInfo.path ? <>
<Button size="sm" variant="info" onClick={this.unmount} >Unmount</Button>
<Button size="sm" variant="info" onClick={this.unmount}>Unmount</Button>
{window.kopiaUI && <>
&nbsp;
<Button size="sm" variant="info" onClick={this.browseMounted} >Browse</Button>
<Button size="sm" variant="info" onClick={this.browseMounted}>Browse</Button>
</>}
&nbsp;<input id="mountedPath" value={this.state.mountInfo.path} />
<Button size="sm" variant="primary" onClick={this.copyPath} ><FontAwesomeIcon icon={faCopy} /></Button>
<Button size="sm" variant="primary" onClick={this.copyPath}><FontAwesomeIcon
icon={faCopy} /></Button>
</> : <>
<Button size="sm" variant="primary" onClick={this.mount} >Mount as Local Filesystem</Button>
<Button size="sm" variant="primary" onClick={this.mount}>Mount as Local Filesystem</Button>
</>}
&nbsp;
<Button size="sm" variant="info" href={"/snapshots/dir/" + this.props.match.params.oid + "/restore"}>Restore Files & Directories</Button>
<Button size="sm" variant="info"
href={"/snapshots/dir/" + this.props.match.params.oid + "/restore"}>Restore
Files & Directories</Button>
&nbsp;
</Col>
<Col xs={12} md={6}>
You can mount/restore all the files & directories that you see below or restore files
individually.
</Col>
</Row>
<Row><Col>&nbsp;</Col>
</Row>
<Row><Col xs={12}>You can mount/restore all the files & directories that you see below or restore files individually.</Col>
</Row>
<Row><Col>&nbsp;</Col>
</Row>
<Row>
<Col xs={12}><DirectoryItems items={items} /></Col>
<Col xs={12}><DirectoryItems items={items} historyState={this.props.location.state} /></Col>
</Row>
<CLIEquivalent command={`snapshot list ${this.state.oid}`} />
</>
Expand Down
18 changes: 12 additions & 6 deletions src/SnapshotsTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@ export class SnapshotsTable extends Component {
originalSnapshotDescription: x.description,
})
}}
title={x.description + " - Click to update snapshot description."}
className={x.description ? "text-warning" : "text-muted"}><b><FontAwesomeIcon icon={faFileAlt} /></b></a>;
title={x.description + " - Click to update snapshot description."}
className={x.description ? "text-warning" : "text-muted"}><b><FontAwesomeIcon icon={faFileAlt} /></b></a>;
}

newPinFor(x) {
Expand Down Expand Up @@ -323,6 +323,9 @@ export class SnapshotsTable extends Component {
if (isLoading && !snapshots) {
return <Spinner animation="border" variant="primary" />;
}
const searchParams = new URLSearchParams(window.location.search);
const path = searchParams.get("path");


snapshots.sort((a, b) => -compare(a.startTime, b.startTime));

Expand All @@ -336,7 +339,10 @@ export class SnapshotsTable extends Component {
id: 'startTime',
Header: 'Start time',
width: 200,
accessor: x => <Link to={objectLink(x.rootID)}>{rfc3339TimestampForDisplay(x.startTime)}</Link>,
accessor: x => {
let timestamp = rfc3339TimestampForDisplay(x.startTime);
return <Link to={objectLink(x.rootID, timestamp, { label: path })}>{timestamp}</Link>;
},
}, {
id: 'description',
Header: '',
Expand Down Expand Up @@ -465,9 +471,9 @@ export class SnapshotsTable extends Component {
<Form.Group>
<Form.Label>Enter new description</Form.Label>
<Form.Control as="textarea"
size="sm"
value={this.state.updatedSnapshotDescription}
onChange={e => this.setState({ updatedSnapshotDescription: e.target.value })} />
size="sm"
value={this.state.updatedSnapshotDescription}
onChange={e => this.setState({ updatedSnapshotDescription: e.target.value })} />
</Form.Group>
</Modal.Body>

Expand Down
18 changes: 9 additions & 9 deletions src/uiutil.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ export function rfc3339TimestampForDisplay(n) {
return t.toLocaleString();
}

export function objectLink(n) {
export function objectLink(n, label, prevState) {
if (n.startsWith("k") || n.startsWith("Ik")) {
return "/snapshots/dir/" + n;
return { pathname: "/snapshots/dir/" + n, state: { label, oid: n, prevState } };
}
return "/api/v1/objects/" + n;
}
Expand All @@ -120,7 +120,7 @@ export function redirect(e) {

/**
* Convert a number of milliseconds into a string containing multiple units.
*
*
* e.g. 90000 --> "1m 30s" or "1 minute 30 seconds"
*
* @param {number} ms - A duration (as a number of milliseconds).
Expand All @@ -136,9 +136,9 @@ export function formatMillisecondsUsingMultipleUnits(ms) {
* Separate a duration into integer magnitudes of multiple units which,
* when combined together, equal the original duration (minus any partial
* milliseconds, if the original duration included any partial milliseconds).
*
*
* e.g. 100000123.999 --> 1 day 3 hours 46 minutes 40 seconds 123 milliseconds
*
*
* @param {number} ms - A duration (as a number of milliseconds).
* @returns {object} An object having numeric properties named `days`, `hours`,
* `minutes`, `seconds`, and `milliseconds`; whose values,
Expand All @@ -161,16 +161,16 @@ export function separateMillisecondsIntoMagnitudes(ms) {
* Format a duration in terms of the largest unit having a non-zero magnitude,
* together with the next largest unit (e.g. hours --> hours and minutes),
* disregarding all smaller units (i.e. truncate, as opposed to round).
*
*
* There are some exceptions, which are listed below.
*
*
* Exceptions:
* 1. If the largest unit having a non-zero magnitude is `seconds` and the
* magnitude is at least 10, format it as an integer number of seconds.
* 2. If the largest unit having a non-zero magnitude is `seconds` and the
* magnitude is less than 10, or if the largest unit having a non-zero
* magnitude is `milliseconds`, format it as a fractional number of seconds.
*
*
* @param {object} magnitudes - An object having numeric properties named
* `days`, `hours`, `minutes`, `seconds`, and
* `milliseconds`; whose values, when combined
Expand Down Expand Up @@ -234,7 +234,7 @@ export function formatMagnitudesUsingMultipleUnits(magnitudes, abbreviateUnits =
/**
* Convert a number of milliseconds into a formatted string, either
* using multiple units (e.g. "1m 5s") or using seconds (e.g. "65.0s").
*
*
* @param {number} ms - The number of milliseconds (i.e. some duration).
* @param {boolean} useMultipleUnits - Whether you want to use multiple units.
* @returns {string} The formatted string.
Expand Down

0 comments on commit db097d2

Please sign in to comment.