diff --git a/content/src/scripts/components/Pane/Pane.jsx b/content/src/scripts/components/Pane/Pane.jsx index 050b233..244c031 100644 --- a/content/src/scripts/components/Pane/Pane.jsx +++ b/content/src/scripts/components/Pane/Pane.jsx @@ -3,6 +3,7 @@ import { TabIdentifierClient } from "chrome-tab-identifier"; import SVG from "../SVG"; import TreeList from "../../containers/TreeList/TreeList"; +import Filter from "../../containers/Filter/Filter"; import Resizer from "../../containers/Resizer"; import { OptionsContext } from "../../contexts/OptionsContext"; import { fetchURLDetails } from "../../utils/url"; @@ -109,10 +110,11 @@ function Pane({ tabId={tabId} /> ) : null} + - + - + ); } diff --git a/content/src/scripts/components/Toggler/styles.css b/content/src/scripts/components/Toggler/styles.css index 3a49fce..1c4fba8 100644 --- a/content/src/scripts/components/Toggler/styles.css +++ b/content/src/scripts/components/Toggler/styles.css @@ -6,7 +6,7 @@ transform-origin: top left; border-radius: 0px 0px 4px 4px; cursor: pointer; - z-index: 10; + z-index: 1000; box-shadow: 0px 0px 5px #000000d9; color: white; background: rgb(77, 61, 146); diff --git a/content/src/scripts/components/TreeItem/TreeItem.jsx b/content/src/scripts/components/TreeItem/TreeItem.jsx index 10c91f3..35db51a 100644 --- a/content/src/scripts/components/TreeItem/TreeItem.jsx +++ b/content/src/scripts/components/TreeItem/TreeItem.jsx @@ -5,6 +5,8 @@ import { fetchURLDetails } from "../../utils/url"; import fileIcons from "../../utils/file-icons"; import "./styles.css"; +import { isMergeRequestShown, isRepositoryShown, grabMergeRequestIdFromCurrentUrl } from "../../../../../event/src/actions/API"; +import { sha1 } from "."; function TreeItem({ width, @@ -34,14 +36,23 @@ function TreeItem({ } else { setClicked(true); const URLDetails = fetchURLDetails(); - if ("compatibility-mode" in options && options["compatibility-mode"]) { - window.location.href = `${window.location.origin}/${ - URLDetails.dirFormatted - }/blob/${URLDetails.branchName}/${path.join("/")}`; - } else { - window.location.href = `${window.location.origin}/${ - URLDetails.dirFormatted - }/-/blob/${URLDetails.branchName}/${path.join("/")}`; + if (isRepositoryShown()) { + if ("compatibility-mode" in options && options["compatibility-mode"]) { + window.location.href = `${window.location.origin}/${URLDetails.dirFormatted + }/blob/${URLDetails.branchName}/${path.join("/")}`; + } else { + window.location.href = `${window.location.origin}/${URLDetails.dirFormatted + }/-/blob/${URLDetails.branchName}/${path.join("/")}`; + } + } else if (isMergeRequestShown()) { + let hash = sha1(path); + let mergeRequestId = grabMergeRequestIdFromCurrentUrl(); + if (!window.location.href.includes('diffs')) { + window.location.href = (window.location.origin + "/" + URLDetails.dirFormatted + "/-/merge_requests/" + mergeRequestId + "/diffs#" + hash); + } else { + window.location.hash = hash; + window.location.reload(); + } } } }; diff --git a/content/src/scripts/components/TreeItem/index.js b/content/src/scripts/components/TreeItem/index.js index 4fdcf6d..c579521 100644 --- a/content/src/scripts/components/TreeItem/index.js +++ b/content/src/scripts/components/TreeItem/index.js @@ -1 +1,142 @@ export { default } from "./TreeItem"; + + +function rotate_left(n, s) { + var t4 = (n << s) | (n >>> (32 - s)); + return t4; +}; +function lsb_hex(val) { + var str = ''; + var i; + var vh; + var vl; + for (i = 0; i <= 6; i += 2) { + vh = (val >>> (i * 4 + 4)) & 0x0f; + vl = (val >>> (i * 4)) & 0x0f; + str += vh.toString(16) + vl.toString(16); + } + return str; +}; +function cvt_hex(val) { + var str = ''; + var i; + var v; + for (i = 7; i >= 0; i--) { + v = (val >>> (i * 4)) & 0x0f; + str += v.toString(16); + } + return str; +}; +function Utf8Encode(string) { + string = string.replace(/\r\n/g, '\n'); + var utftext = ''; + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); + if (c < 128) { + utftext += String.fromCharCode(c); + } + else if ((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } + else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + } + return utftext; +}; + +/** +* Secure Hash Algorithm (SHA1) +* http://www.webtoolkit.info/ +**/ +export const sha1 = (msg) => { + + var blockstart; + var i, j; + var W = new Array(80); + var H0 = 0x67452301; + var H1 = 0xEFCDAB89; + var H2 = 0x98BADCFE; + var H3 = 0x10325476; + var H4 = 0xC3D2E1F0; + var A, B, C, D, E; + var temp; + msg = Utf8Encode(msg); + var msg_len = msg.length; + var word_array = new Array(); + for (i = 0; i < msg_len - 3; i += 4) { + j = msg.charCodeAt(i) << 24 | msg.charCodeAt(i + 1) << 16 | + msg.charCodeAt(i + 2) << 8 | msg.charCodeAt(i + 3); + word_array.push(j); + } + switch (msg_len % 4) { + case 0: + i = 0x080000000; + break; + case 1: + i = msg.charCodeAt(msg_len - 1) << 24 | 0x0800000; + break; + case 2: + i = msg.charCodeAt(msg_len - 2) << 24 | msg.charCodeAt(msg_len - 1) << 16 | 0x08000; + break; + case 3: + i = msg.charCodeAt(msg_len - 3) << 24 | msg.charCodeAt(msg_len - 2) << 16 | msg.charCodeAt(msg_len - 1) << 8 | 0x80; + break; + } + word_array.push(i); + while ((word_array.length % 16) != 14) word_array.push(0); + word_array.push(msg_len >>> 29); + word_array.push((msg_len << 3) & 0x0ffffffff); + for (blockstart = 0; blockstart < word_array.length; blockstart += 16) { + for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i]; + for (i = 16; i <= 79; i++) W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1); + A = H0; + B = H1; + C = H2; + D = H3; + E = H4; + for (i = 0; i <= 19; i++) { + temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + for (i = 20; i <= 39; i++) { + temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + for (i = 40; i <= 59; i++) { + temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + for (i = 60; i <= 79; i++) { + temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + H0 = (H0 + A) & 0x0ffffffff; + H1 = (H1 + B) & 0x0ffffffff; + H2 = (H2 + C) & 0x0ffffffff; + H3 = (H3 + D) & 0x0ffffffff; + H4 = (H4 + E) & 0x0ffffffff; + } + var temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4); + + return temp.toLowerCase(); +} \ No newline at end of file diff --git a/content/src/scripts/containers/Filter/Filter.jsx b/content/src/scripts/containers/Filter/Filter.jsx new file mode 100644 index 0000000..bbadaf5 --- /dev/null +++ b/content/src/scripts/containers/Filter/Filter.jsx @@ -0,0 +1,49 @@ +import React, { useState, useEffect } from "react"; +import { + isMergeRequestShown +} from "../../../../../event/src/actions/API"; + +import "./styles.css"; + + +function Filter({ + +}) { + + // const [checkedTests, setCheckedTest] = useState(); + // useEffect(() => { + // handleChange = () => { + // setCheckedTest(!checkedTests, () => { + // window.location.reload(); + // }); + // }; + // }, []); + + return ( +
+ {isMergeRequestShown() ? ( +
+ + {/* */} + + + + + + + + +
+ + + + + + +
+ ) : null} +
+ ); +} + +export default Filter; diff --git a/content/src/scripts/containers/Filter/index.js b/content/src/scripts/containers/Filter/index.js new file mode 100644 index 0000000..84ae2e8 --- /dev/null +++ b/content/src/scripts/containers/Filter/index.js @@ -0,0 +1 @@ +export { default } from "./Filter"; diff --git a/content/src/scripts/containers/Filter/styles.css b/content/src/scripts/containers/Filter/styles.css new file mode 100644 index 0000000..09390b5 --- /dev/null +++ b/content/src/scripts/containers/Filter/styles.css @@ -0,0 +1,7 @@ +.spantree-filter-header { + height: 50px; + background-color: rgb(77, 61, 146); + border-bottom: 1px #404040 solid; + color: white; + padding: 5px; +} diff --git a/content/src/scripts/containers/TreeList/TreeList.jsx b/content/src/scripts/containers/TreeList/TreeList.jsx index 746eaab..009e706 100644 --- a/content/src/scripts/containers/TreeList/TreeList.jsx +++ b/content/src/scripts/containers/TreeList/TreeList.jsx @@ -62,6 +62,7 @@ function TreeList({ setClicked, getInitialTree, closeDir, + filtersEnabled, }) { const [loading, setLoading] = useState(true); const [rendering, setRendering] = useState(false); @@ -80,6 +81,16 @@ function TreeList({ return firstPageLoad; }; + const buildFilterMap = () => { + let map = new Map(); + map['test'] = document.getElementById('filterTests').checked; + map['removed'] = document.getElementById('filterRemoved').checked; + map['renamed'] = document.getElementById('filterRenamed').checked; + map['imports'] = document.getElementById('filteredImports').checked; + map['newFile'] = document.getElementById('filteredNewFiles').checked; + return map; + } + useEffect(() => { if (URLDetails.baseRemovedURL.length === 0) { setRendering(false); @@ -99,6 +110,7 @@ function TreeList({ branchName: URLDetails.branchName, tabId, }, + buildFilterMap(), ); } setFirstPageLoad(false); diff --git a/content/src/scripts/containers/TreeList/styles.css b/content/src/scripts/containers/TreeList/styles.css index e8b918a..38f8b19 100644 --- a/content/src/scripts/containers/TreeList/styles.css +++ b/content/src/scripts/containers/TreeList/styles.css @@ -9,7 +9,7 @@ .spantree-tree-list { scroll-behavior: smooth; overflow-y: auto; - height: calc(100vh - 40px); + height: calc(100vh - 90px); } .spantree-tree-list::-webkit-scrollbar-track { diff --git a/content/src/scripts/containers/app/App.jsx b/content/src/scripts/containers/app/App.jsx index 2230eaa..2cb4d3c 100644 --- a/content/src/scripts/containers/app/App.jsx +++ b/content/src/scripts/containers/app/App.jsx @@ -15,6 +15,11 @@ import { browserKey } from "../../utils/browser"; import searchBarWorkerJS from "../../utils/searchBarWorker"; import WebWorker from "./WebWorker"; +import { + isRepositoryShown, + isMergeRequestShown +} from "../../../../../event/src/actions/API"; + import "./App.css"; const importFileIconCSS = `${browserKey()}-extension://${chrome.i18n.getMessage( @@ -45,7 +50,7 @@ class App extends Component { }; this.shouldShowSpanTree = () => { return ( - document.querySelector(".qa-branches-select") !== null && + (isRepositoryShown() || isMergeRequestShown()) && document.querySelector(".nav-sidebar") !== null ); }; @@ -96,24 +101,24 @@ class App extends Component { {this.props.opened[tabId] ? ReactDOM.createPortal( - this.setShowSearchbar(true)} - />, - parentDiv, - ) + this.setShowSearchbar(true)} + />, + parentDiv, + ) : ReactDOM.createPortal( - , - document.getElementById("rcr-anchor"), - )} + , + document.getElementById("rcr-anchor"), + )} { - let url = `${id}/repository/tree`; - url += "?per_page=10000"; +export const isRepositoryShown = () => { + return document.querySelector(".qa-branches-select") !== null; +}; +export const isMergeRequestShown = () => { + return document.querySelector(".diffs-tab") !== null; +}; + +export const grabMergeRequestIdFromCurrentUrl = () => { + const pathName = window.location.pathname; + const mergeRequest = '/merge_requests/'; + let start = pathName.indexOf(mergeRequest); + if (start == -1) { + return null; + } + let path = pathName.substring(start + mergeRequest.length); + if (path.includes('/')) { + return path.substring(0, path.indexOf('/')); + } + return path; +}; + +export const getUrl = (id) => { + if (isRepositoryShown()) { + return `${id}/repository/tree?per_page=10000`; + } else if (isMergeRequestShown()) { + let mergeRequestId = grabMergeRequestIdFromCurrentUrl(); + return `${id}/merge_requests/${mergeRequestId}/changes?access_raw_diffs=false`; + } +}; + +export const getInitialTree = (id, params, reducerDetails, filtersEnabled) => { + let url = getUrl(id); for (let param in params) { url += `&${param}=${params[param]}`; } @@ -15,11 +44,13 @@ export const getInitialTree = (id, params, reducerDetails) => { .then((res) => { store.dispatch({ type: types.FETCH_TREE, + dataUrl: res.request.responseURL, payload: res.data, reducerDetails, + filtersEnabled, }); }) - .catch((_err) => {}); + .catch((_err) => { }); }; export const openDir = (id, path, params, reducerDetails) => { @@ -42,7 +73,7 @@ export const openDir = (id, path, params, reducerDetails) => { reducerDetails, }); }) - .catch((_err) => {}); + .catch((_err) => { }); }; export const closeDir = (path, reducerDetails) => { @@ -69,5 +100,5 @@ export const getSearchTerms = (reducerDetails) => { reducerDetails, }); }) - .catch((_err) => {}); + .catch((_err) => { }); }; diff --git a/event/src/reducers/API/tree.js b/event/src/reducers/API/tree.js index 79976a7..045c038 100644 --- a/event/src/reducers/API/tree.js +++ b/event/src/reducers/API/tree.js @@ -9,26 +9,7 @@ export default (state = initialState, action) => { case FETCH_TREE: return { ...state, - [action.reducerDetails.tabId]: action.payload - .map((node) => { - return { - name: node.name, - path: node.path - .split("/") - .filter((pathSub) => pathSub.length !== 0), - isTree: - node.type === "tree" - ? { - isOpen: false, - } - : false, - children: node.type === "tree" ? {} : undefined, - }; - }) - .reduce((map, obj) => { - map[obj.name] = obj; - return map; - }, {}), + [action.reducerDetails.tabId]: mapNodesFromResult(action), }; case OPEN_DIR: let objectPath = [action.reducerDetails.tabId]; @@ -67,8 +48,8 @@ export default (state = initialState, action) => { isTree: node.type === "tree" ? { - isOpen: false, - } + isOpen: false, + } : false, children: node.type === "tree" ? {} : undefined, }; @@ -106,3 +87,72 @@ export default (state = initialState, action) => { return state; } }; +function mapNodesFromResult(action) { + if (action.dataUrl.toString().includes('/merge_requests/')) { + return filterData(action) + .map((node) => { + return { + name: node.new_path, + path: node.new_path, + isTree: false, + children: false, + }; + }) + .reduce((map, obj) => { + map[obj.name] = obj; + return map; + }, {}); + } else { + return action.payload + .map((node) => { + return { + name: node.name, + path: node.path + .split("/") + .filter((pathSub) => pathSub.length !== 0), + isTree: node.type === "tree" + ? { + isOpen: false, + } + : false, + children: node.type === "tree" ? {} : undefined, + }; + }) + .reduce((map, obj) => { + map[obj.name] = obj; + return map; + }, {}); + } + +}; + +function filterData(action) { + let data = action.payload['changes']; + if (action.filtersEnabled['test']) { + data = data.filter((node) => { + return !node.new_path.includes('src/test/'); + }) + } + if (action.filtersEnabled['renamed']) { + data = data.filter((node) => { + return !node.renamed_file; + }) + } + if (action.filtersEnabled['removed']) { + data = data.filter((node) => { + return !node.deleted_file; + }) + } + if (action.filtersEnabled['newFile']) { + data = data.filter((node) => { + return !node.new_file; + }) + } + if (action.filtersEnabled['imports']) { + data = data.filter((node) => { + return !node.new_file; + }) + } + return data; +}; + diff --git a/event/src/reducers/UI/width.js b/event/src/reducers/UI/width.js index 96b3267..c42cd83 100644 --- a/event/src/reducers/UI/width.js +++ b/event/src/reducers/UI/width.js @@ -1,6 +1,6 @@ import { SET_WIDTH } from "../../types/UI"; -const initialWidth = 250; +const initialWidth = 300; export default (state = initialWidth, action) => { switch (action.type) { diff --git a/package-lock.json b/package-lock.json index aba01a9..f8a90fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "span-tree", "version": "0.0.1", "dependencies": { "axios": "^0.21.1",