diff --git a/src/js/components/tree.jsx b/src/js/components/tree.jsx index 6525c08..032d701 100644 --- a/src/js/components/tree.jsx +++ b/src/js/components/tree.jsx @@ -2,12 +2,29 @@ import React from 'react' import Branch from './branch.jsx' import { isElementVisible } from '../lib' +const MIN_RESIZE_WIDTH = 55 +const MAX_RESIZE_WIDTH = 700 + +const widthLocalStorageKey = '__better_github_pr_tree_width' + class Tree extends React.Component { constructor (props) { super(props) this.onClose = this.onClose.bind(this) this.onScroll = this.onScroll.bind(this) + this.onResizerMouseDown = this.onResizerMouseDown.bind(this) + this.onMouseMove = this.onMouseMove.bind(this) + this.onMouseUp = this.onMouseUp.bind(this) + this.toggleDocumentFullWidth = this.toggleDocumentFullWidth.bind(this) + + this.isResizing = false + this.resizeDelta = 0 + + this.treeContainer = document.querySelector('.__better_github_pr') + this.reviewContainers = document.querySelectorAll('.enable_better_github_pr .diff-view, .enable_better_github_pr .commit.full-commit.prh-commit') + + this.setInitialWidth() this.state = { show: true, @@ -20,6 +37,10 @@ class Tree extends React.Component { window.addEventListener('load', this.onScroll, false) window.addEventListener('scroll', this.onScroll, false) window.addEventListener('resize', this.onScroll, false) + + this.resizer.addEventListener('mousedown', this.onResizerMouseDown, false) + document.addEventListener('mousemove', this.onMouseMove, false) + document.addEventListener('mouseup', this.onMouseUp, false) } componentWillUnmount () { @@ -27,6 +48,33 @@ class Tree extends React.Component { window.removeEventListener('load', this.onScroll, false) window.removeEventListener('scroll', this.onScroll, false) window.removeEventListener('resize', this.onScroll, false) + + this.resizer.removeEventListener('mousedown', this.onResizerMouseDown, false) + document.removeEventListener('mousemove', this.onMouseMove, false) + document.removeEventListener('mouseup', this.onMouseUp, false) + } + + onResizerMouseDown () { + this.isResizing = true + this.treeContainer.classList.add('__better_github_pr_noselect') + this.prevWidth = this.treeContainer.offsetWidth + this.startResizeX = this.resizer.getBoundingClientRect().x + } + + onMouseMove (e) { + if (!this.isResizing) return + + this.resizeDelta = e.clientX - this.startResizeX + let newWidth = this.prevWidth + this.resizeDelta + setTimeout(() => this.setWidth(newWidth), 0) + } + + onMouseUp () { + if (!this.isResizing) return + + this.isResizing = false + this.treeContainer.classList.remove('__better_github_pr_noselect') + window.localStorage.setItem(widthLocalStorageKey, this.treeContainer.offsetWidth) } onScroll () { @@ -45,6 +93,30 @@ class Tree extends React.Component { const show = false this.setState({ show }) document.body.classList.toggle('enable_better_github_pr', show) + this.setWidth(0, false) + } + + setInitialWidth () { + const savedWitdh = window.localStorage.getItem(widthLocalStorageKey) + if (savedWitdh) { + this.setWidth(parseInt(savedWitdh, 10)) + } + } + + setWidth (width, withConstraints = true) { + if (withConstraints) { + if (width <= MIN_RESIZE_WIDTH) width = MIN_RESIZE_WIDTH + if (width >= MAX_RESIZE_WIDTH) width = MAX_RESIZE_WIDTH + } + + this.treeContainer.style.width = `${width}px` + this.reviewContainers.forEach((element) => { + element.style['margin-left'] = `${width + 10}px` + }) + } + + toggleDocumentFullWidth () { + document.querySelector('body').classList.toggle('__better_github_pr_wide') } render () { @@ -56,13 +128,17 @@ class Tree extends React.Component { } return ( -
+
+ - {root.list.map(node => ( - - - - ))} +
+
{ this.resizer = node }} /> + {root.list.map(node => ( + + + + ))} +
) } diff --git a/src/js/lib.js b/src/js/lib.js index 82a6380..5cd7080 100644 --- a/src/js/lib.js +++ b/src/js/lib.js @@ -34,6 +34,28 @@ const hasCommentsForFileIndex = (fileIndex) => { return diffTable.querySelectorAll('.inline-comments').length } +export const folderConcat = (node) => { + const isFileOrEmpty = (node.list === undefined || node.list.length === 0 || (node.href !== null && node.href !== undefined)) + if (isFileOrEmpty) { + return node + } + + const hasSingleChild = (node.list.length === 1) + if (hasSingleChild) { + const collapsed = folderConcat(node.list[0]) + const isLastCollapsedIsFolder = node.nodeLabel !== '/' && (collapsed.href === null || collapsed.href === undefined) + if (isLastCollapsedIsFolder) { + node.nodeLabel = node.nodeLabel + '/' + collapsed.nodeLabel + node.hasComments = collapsed.hasComments || node.hasComments + node.list = collapsed.list + } + return node + } + + node.list.map(x => folderConcat(x)) + return node +} + export const createFileTree = () => { const fileInfo = [...document.querySelectorAll('.file-info > a')] const files = fileInfo.map(({ title, href }) => { @@ -68,7 +90,7 @@ export const createFileTree = () => { }) }) return { - tree, + tree: folderConcat(tree), count: fileInfo.length } } diff --git a/src/js/style.css b/src/js/style.css index f790752..f7399eb 100644 --- a/src/js/style.css +++ b/src/js/style.css @@ -7,13 +7,29 @@ display: none; } +._better_github_pr_resizer { + position: absolute; + width: 5px; + height: 100%; + background: lightgray; + right: 0; + top: 0; + cursor: col-resize; + opacity: .4; + transition: opacity .2s ease; + z-index: 1; +} + +._better_github_pr_resizer:hover { + opacity: 1; +} + .enable_better_github_pr .__better_github_pr { display: block; position: relative; margin-top: 20px; - padding: 10px; - width: 240px; - overflow: auto; + padding: 30px 10px 0; + width: 330px; height: calc(100vh - 61px); background-color: #fafbfc; border: 1px solid #e1e4e8; @@ -22,23 +38,65 @@ } .__better_github_pr > div { - display: inline-block; + overflow: auto; + height: 100%; + padding-bottom: 10px; +} + +.__better_github_pr > div > div { + width: fit-content; } .__better_github_pr .close_button { background: none; border: none; position: absolute; - top: 0; - right: 0; + top: 1px; + right: 10px; padding: 5px 2px; } +.__better_github_pr_full_width { + width: 16px; + height: 16px; + background-size: cover; + position: absolute; + top: 6px; + right: 40px; + cursor: pointer; + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDMyIDMyIiBoZWlnaHQ9IjMycHgiIGlkPSLQodC70L7QuV8xIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjMycHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxnIGlkPSJGdWxsc2NyZWVuIj48cGF0aCBkPSJNMzIsMWMwLTAuNTU4LTAuNDQyLTEtMS0xbC04Ljk4NSwwYy0wLjU2OCwwLTAuOTkxLDAuNDQ4LTAuOTkyLDEuMDE2QzIxLjAyMywxLjU4MywyMS40NDcsMiwyMi4wMTUsMkwzMCwyICAgbC0wLjAxNiw4LjAyM2MwLDAuNTY4LDAuNDMyLDEsMSwxYzAuNTY4LTAuMDAxLDEtMC40MzIsMS0xTDMyLDEuMDE1YzAtMC4wMDMtMC4wMDEtMC4wMDUtMC4wMDEtMC4wMDdDMzEuOTk5LDEuMDA1LDMyLDEuMDAzLDMyLDF6ICAgIiBmaWxsPSIjMTIxMzEzIi8+PHBhdGggZD0iTTEwLjAxNiwwSDEuMDMxQzEuMDI4LDAsMS4wMjYsMC4wMDEsMS4wMjMsMC4wMDFDMS4wMjEsMC4wMDEsMS4wMTgsMCwxLjAxNiwwYy0wLjU1OCwwLTEsMC40NDItMSwxICAgTDAsMTAuMDA4QzAsMTAuNTc2LDAuNDQ4LDExLDEuMDE2LDExQzEuNTgzLDExLDIsMTAuNTc2LDIsMTAuMDA4TDIuMDE2LDJoOGMwLjU2OCwwLDEtMC40MzIsMS0xQzExLjAxNSwwLjQzMiwxMC41ODMsMCwxMC4wMTYsMHoiIGZpbGw9IiMxMjEzMTMiLz48cGF0aCBkPSJNOS45ODUsMzBIMnYtOGMwLTAuNTY4LTAuNDMyLTEtMS0xYy0wLjU2OCwwLTEsMC40MzItMSwxdjguOTg1YzAsMC4wMDMsMC4wMDEsMC4wMDUsMC4wMDEsMC4wMDcgICBDMC4wMDEsMzAuOTk1LDAsMzAuOTk3LDAsMzFjMCwwLjU1OCwwLjQ0MiwxLDEsMWg4Ljk4NWMwLjU2OCwwLDAuOTkxLTAuNDQ4LDAuOTkyLTEuMDE2QzEwLjk3NywzMC40MTcsMTAuNTUzLDMwLDkuOTg1LDMweiIgZmlsbD0iIzEyMTMxMyIvPjxwYXRoIGQ9Ik0zMC45ODQsMjEuMDIzYy0wLjU2OCwwLTAuOTg1LDAuNDI0LTAuOTg0LDAuOTkyVjMwbC04LDBjLTAuNTY4LDAtMSwwLjQzMi0xLDFjMCwwLjU2OCwwLjQzMiwxLDEsMSAgIGw4Ljk4NSwwYzAuMDAzLDAsMC4wMDUtMC4wMDEsMC4wMDctMC4wMDFDMzAuOTk1LDMxLjk5OCwzMC45OTcsMzIsMzEsMzJjMC41NTgsMCwxLTAuNDQyLDEtMXYtOC45ODUgICBDMzIsMjEuNDQ3LDMxLjU1MiwyMS4wMjMsMzAuOTg0LDIxLjAyM3oiIGZpbGw9IiMxMjEzMTMiLz48L2c+PGcvPjxnLz48Zy8+PGcvPjxnLz48Zy8+PC9zdmc+'); + background-color: transparent; + border: none; +} + +.__better_github_pr_noselect { + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + supported by Chrome and Opera */ +} + +.__better_github_pr_wide .container { + min-width: 980px; + width: 96% !important; +} + +.__better_github_pr_wide .repository-content .discussion-timeline { + width: calc(100% - 220px) !important; +} + +.__better_github_pr_wide .repository-content .discussion-timeline .timeline-new-comment { + max-width: 100% !important; +} + /* GitHub page changes */ .enable_better_github_pr .diff-view, .enable_better_github_pr .commit.full-commit.prh-commit { - margin-left: 250px; + margin-left: 340px; } .enable_better_github_pr .diff-view .file-header { @@ -51,6 +109,10 @@ max-width: 708px; } +.enable_better_github_pr .container { + transition: width .2s ease; +} + /* react-treeview */ .tree-view_item {