Bug 888161 - Adds download file option to the context menu of source tree items r=davidwalsh

This patch addresses bug 888161 by adding a ‘Download File’ option to the context menu of individual source tree item files.

Differential Revision: https://phabricator.services.mozilla.com/D32828

--HG--
extra : moz-landing-system : lando
This commit is contained in:
janelledement 2019-06-06 14:42:03 +00:00
Родитель 5da02d9c69
Коммит 0e820e6a25
3 изменённых файлов: 114 добавлений и 3 удалений

Просмотреть файл

@ -19,6 +19,7 @@ import {
hasPrettySource as checkHasPrettySource, hasPrettySource as checkHasPrettySource,
getContext, getContext,
getMainThread, getMainThread,
getSourceContent,
} from "../../selectors"; } from "../../selectors";
import actions from "../../actions"; import actions from "../../actions";
@ -31,9 +32,16 @@ import {
import { isDirectory } from "../../utils/sources-tree"; import { isDirectory } from "../../utils/sources-tree";
import { copyToTheClipboard } from "../../utils/clipboard"; import { copyToTheClipboard } from "../../utils/clipboard";
import { features } from "../../utils/prefs"; import { features } from "../../utils/prefs";
import { downloadFile } from "../../utils/utils";
import type { TreeNode } from "../../utils/sources-tree/types"; import type { TreeNode } from "../../utils/sources-tree/types";
import type { Source, Context, MainThread, Thread } from "../../types"; import type {
Source,
Context,
MainThread,
Thread,
SourceContent,
} from "../../types";
type Props = { type Props = {
autoExpand: ?boolean, autoExpand: ?boolean,
@ -42,6 +50,7 @@ type Props = {
projectRoot: string, projectRoot: string,
source: ?Source, source: ?Source,
item: TreeNode, item: TreeNode,
sourceContent: SourceContent,
depth: number, depth: number,
focused: boolean, focused: boolean,
expanded: boolean, expanded: boolean,
@ -56,6 +65,7 @@ type Props = {
clearProjectDirectoryRoot: typeof actions.clearProjectDirectoryRoot, clearProjectDirectoryRoot: typeof actions.clearProjectDirectoryRoot,
setProjectDirectoryRoot: typeof actions.setProjectDirectoryRoot, setProjectDirectoryRoot: typeof actions.setProjectDirectoryRoot,
toggleBlackBox: typeof actions.toggleBlackBox, toggleBlackBox: typeof actions.toggleBlackBox,
loadSourceText: typeof actions.loadSourceText,
}; };
type State = {}; type State = {};
@ -122,7 +132,14 @@ class SourceTreeItem extends Component<Props, State> {
disabled: !shouldBlackbox(source), disabled: !shouldBlackbox(source),
click: () => this.props.toggleBlackBox(cx, source), click: () => this.props.toggleBlackBox(cx, source),
}; };
menuOptions.push(copySourceUri2, blackBoxMenuItem); const downloadFileItem = {
id: "node-menu-download-file",
label: L10N.getStr("downloadFile.label"),
accesskey: L10N.getStr("downloadFile.accesskey"),
disabled: false,
click: () => this.handleDownloadFile(cx, source, item),
};
menuOptions.push(copySourceUri2, blackBoxMenuItem, downloadFileItem);
} }
} }
} }
@ -156,6 +173,15 @@ class SourceTreeItem extends Component<Props, State> {
showMenu(event, menuOptions); showMenu(event, menuOptions);
}; };
handleDownloadFile = async (cx: Context, source: ?Source, item: TreeNode) => {
const name = item.name;
if (!this.props.sourceContent) {
await this.props.loadSourceText({ cx, source });
}
const data = this.props.sourceContent;
downloadFile(data, name);
};
addCollapseExpandAllOptions = (menuOptions: ContextMenu, item: TreeNode) => { addCollapseExpandAllOptions = (menuOptions: ContextMenu, item: TreeNode) => {
const { setExpanded } = this.props; const { setExpanded } = this.props;
@ -310,6 +336,11 @@ function getHasMatchingGeneratedSource(state, source: ?Source) {
return !!getGeneratedSourceByURL(state, source.url); return !!getGeneratedSourceByURL(state, source.url);
} }
function getSourceContentValue(state, source: Source) {
const content = getSourceContent(state, source.id);
return content !== null ? content.value : false;
}
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
const { source } = props; const { source } = props;
return { return {
@ -318,6 +349,7 @@ const mapStateToProps = (state, props) => {
hasMatchingGeneratedSource: getHasMatchingGeneratedSource(state, source), hasMatchingGeneratedSource: getHasMatchingGeneratedSource(state, source),
hasSiblingOfSameName: getHasSiblingOfSameName(state, source), hasSiblingOfSameName: getHasSiblingOfSameName(state, source),
hasPrettySource: source ? checkHasPrettySource(state, source.id) : false, hasPrettySource: source ? checkHasPrettySource(state, source.id) : false,
sourceContent: source ? getSourceContentValue(state, source) : false,
}; };
}; };
@ -327,5 +359,6 @@ export default connect(
setProjectDirectoryRoot: actions.setProjectDirectoryRoot, setProjectDirectoryRoot: actions.setProjectDirectoryRoot,
clearProjectDirectoryRoot: actions.clearProjectDirectoryRoot, clearProjectDirectoryRoot: actions.clearProjectDirectoryRoot,
toggleBlackBox: actions.toggleBlackBox, toggleBlackBox: actions.toggleBlackBox,
loadSourceText: actions.loadSourceText,
} }
)(SourceTreeItem); )(SourceTreeItem);

Просмотреть файл

@ -91,6 +91,13 @@ describe("SourceTreeItem", () => {
id: "node-menu-blackbox", id: "node-menu-blackbox",
label: "Blackbox source", label: "Blackbox source",
}, },
{
accesskey: "d",
click: expect.any(Function),
disabled: false,
id: "node-menu-download-file",
label: "Download file",
},
]; ];
const mockEvent = { const mockEvent = {
preventDefault: jest.fn(), preventDefault: jest.fn(),
@ -104,7 +111,6 @@ describe("SourceTreeItem", () => {
await instance.onContextMenu(mockEvent, item, source); await instance.onContextMenu(mockEvent, item, source);
expect(showMenu).toHaveBeenCalledWith(mockEvent, menuOptions); expect(showMenu).toHaveBeenCalledWith(mockEvent, menuOptions);
expect(mockEvent.preventDefault).toHaveBeenCalled(); expect(mockEvent.preventDefault).toHaveBeenCalled();
expect(mockEvent.stopPropagation).toHaveBeenCalled(); expect(mockEvent.stopPropagation).toHaveBeenCalled();
@ -130,6 +136,13 @@ describe("SourceTreeItem", () => {
id: "node-menu-blackbox", id: "node-menu-blackbox",
label: "Blackbox source", label: "Blackbox source",
}, },
{
accesskey: "d",
click: expect.any(Function),
disabled: false,
id: "node-menu-download-file",
label: "Download file",
},
]; ];
const mockEvent = { const mockEvent = {
preventDefault: jest.fn(), preventDefault: jest.fn(),
@ -153,6 +166,57 @@ describe("SourceTreeItem", () => {
expect(props.toggleBlackBox).toHaveBeenCalled(); expect(props.toggleBlackBox).toHaveBeenCalled();
}); });
it("shows context menu on file to download source file", async () => {
const menuOptions = [
{
accesskey: "u",
click: expect.any(Function),
disabled: false,
id: "node-menu-copy-source",
label: "Copy source URI",
},
{
accesskey: "B",
click: expect.any(Function),
disabled: false,
id: "node-menu-blackbox",
label: "Blackbox source",
},
{
accesskey: "d",
click: expect.any(Function),
disabled: false,
id: "node-menu-download-file",
label: "Download file",
},
];
const mockEvent = {
preventDefault: jest.fn(),
stopPropagation: jest.fn(),
};
const { props, instance } = render({
projectRoot: "root/",
});
const { item, source } = instance.props;
instance.handleDownloadFile = jest.fn(() => {});
await instance.onContextMenu(mockEvent, item, source);
expect(showMenu).toHaveBeenCalledWith(mockEvent, menuOptions);
expect(mockEvent.preventDefault).toHaveBeenCalled();
expect(mockEvent.stopPropagation).toHaveBeenCalled();
showMenu.mock.calls[0][1][2].click();
expect(props.setProjectDirectoryRoot).not.toHaveBeenCalled();
expect(props.clearProjectDirectoryRoot).not.toHaveBeenCalled();
expect(props.toggleBlackBox).not.toHaveBeenCalled();
expect(copyToTheClipboard).not.toHaveBeenCalled();
expect(instance.handleDownloadFile).toHaveBeenCalled();
});
it("shows context menu on root to remove directory root", async () => { it("shows context menu on root to remove directory root", async () => {
const menuOptions = [ const menuOptions = [
{ {

Просмотреть файл

@ -38,6 +38,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -263,6 +264,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -494,6 +496,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -708,6 +711,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -880,6 +884,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -1055,6 +1060,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -1198,6 +1204,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -1367,6 +1374,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -1504,6 +1512,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -1647,6 +1656,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -1819,6 +1829,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -2000,6 +2011,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -2223,6 +2235,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {
@ -2449,6 +2462,7 @@ Object {
"instance": SourceTreeItem { "instance": SourceTreeItem {
"addCollapseExpandAllOptions": [Function], "addCollapseExpandAllOptions": [Function],
"context": Object {}, "context": Object {},
"handleDownloadFile": [Function],
"onClick": [Function], "onClick": [Function],
"onContextMenu": [Function], "onContextMenu": [Function],
"props": Object { "props": Object {