summaryrefslogtreecommitdiffstats
path: root/web_src
diff options
context:
space:
mode:
authorzeripath <art27@cantab.net>2020-08-06 09:04:08 +0100
committerGitHub <noreply@github.com>2020-08-06 09:04:08 +0100
commit2c1ae6c82d0b3fa62dda7e6a30fb91e27aba6e04 (patch)
treebe14ac1376125be2482e6ca7de3eedc276203304 /web_src
parentf1a42f5d5ee0279ddec7973a1ba9236c70bd5b5e (diff)
downloadgitea-2c1ae6c82d0b3fa62dda7e6a30fb91e27aba6e04.tar.gz
gitea-2c1ae6c82d0b3fa62dda7e6a30fb91e27aba6e04.zip
Render the git graph on the server (#12333)
Rendering the git graph on the server means that we can properly track flows and switch from the Canvas implementation to a SVG implementation. * This implementation provides a 16 limited color selection * The uniqued color numbers are also provided * And there is also a monochrome version *In addition is a hover highlight that allows users to highlight commits on the same flow. Closes #12209 Signed-off-by: Andrew Thornton art27@cantab.net Co-authored-by: silverwind <me@silverwind.io>
Diffstat (limited to 'web_src')
-rw-r--r--web_src/js/features/gitgraph.js641
-rw-r--r--web_src/less/_repository.less8
-rw-r--r--web_src/less/features/gitgraph.less256
-rw-r--r--web_src/less/index.less3
-rw-r--r--web_src/less/themes/theme-arc-green.less47
-rw-r--r--web_src/less/vendor/gitGraph.css15
-rw-r--r--web_src/svg/material-invert-colors.svg1
-rw-r--r--web_src/svg/material-palette.svg1
8 files changed, 383 insertions, 589 deletions
diff --git a/web_src/js/features/gitgraph.js b/web_src/js/features/gitgraph.js
index 3e6b27436d..655cfb77c2 100644
--- a/web_src/js/features/gitgraph.js
+++ b/web_src/js/features/gitgraph.js
@@ -1,568 +1,81 @@
-// Although inspired by the https://github.com/bluef/gitgraph.js/blob/master/gitgraph.js
-// this has been completely rewritten with almost no remaining code
-
-// GitGraphCanvas is a canvas for drawing gitgraphs on to
-class GitGraphCanvas {
- constructor(canvas, widthUnits, heightUnits, config) {
- this.ctx = canvas.getContext('2d');
-
- const width = widthUnits * config.unitSize;
- this.height = heightUnits * config.unitSize;
-
- const ratio = window.devicePixelRatio || 1;
-
- canvas.width = width * ratio;
- canvas.height = this.height * ratio;
-
- canvas.style.width = `${width}px`;
- canvas.style.height = `${this.height}px`;
-
- this.ctx.lineWidth = config.lineWidth;
- this.ctx.lineJoin = 'round';
- this.ctx.lineCap = 'round';
-
- this.ctx.scale(ratio, ratio);
- this.config = config;
- }
- drawLine(moveX, moveY, lineX, lineY, color) {
- this.ctx.strokeStyle = color;
- this.ctx.beginPath();
- this.ctx.moveTo(moveX, moveY);
- this.ctx.lineTo(lineX, lineY);
- this.ctx.stroke();
- }
- drawLineRight(x, y, color) {
- this.drawLine(
- x - 0.5 * this.config.unitSize,
- y + this.config.unitSize / 2,
- x + 0.5 * this.config.unitSize,
- y + this.config.unitSize / 2,
- color
- );
- }
- drawLineUp(x, y, color) {
- this.drawLine(
- x,
- y + this.config.unitSize / 2,
- x,
- y - this.config.unitSize / 2,
- color
- );
- }
- drawNode(x, y, color) {
- this.ctx.strokeStyle = color;
-
- this.drawLineUp(x, y, color);
-
- this.ctx.beginPath();
- this.ctx.arc(x, y, this.config.nodeRadius, 0, Math.PI * 2, true);
- this.ctx.fillStyle = color;
- this.ctx.fill();
- }
- drawLineIn(x, y, color) {
- this.drawLine(
- x + 0.5 * this.config.unitSize,
- y + this.config.unitSize / 2,
- x - 0.5 * this.config.unitSize,
- y - this.config.unitSize / 2,
- color
- );
- }
- drawLineOut(x, y, color) {
- this.drawLine(
- x - 0.5 * this.config.unitSize,
- y + this.config.unitSize / 2,
- x + 0.5 * this.config.unitSize,
- y - this.config.unitSize / 2,
- color
- );
- }
- drawSymbol(symbol, columnNumber, rowNumber, color) {
- const y = this.height - this.config.unitSize * (rowNumber + 0.5);
- const x = this.config.unitSize * 0.5 * (columnNumber + 1);
- switch (symbol) {
- case '-':
- if (columnNumber % 2 === 1) {
- this.drawLineRight(x, y, color);
- }
- break;
- case '_':
- this.drawLineRight(x, y, color);
- break;
- case '*':
- this.drawNode(x, y, color);
- break;
- case '|':
- this.drawLineUp(x, y, color);
- break;
- case '/':
- this.drawLineOut(x, y, color);
- break;
- case '\\':
- this.drawLineIn(x, y, color);
- break;
- case '.':
- case ' ':
- break;
- default:
- console.error('Unknown symbol', symbol, color);
- }
- }
-}
-
-class GitGraph {
- constructor(canvas, rawRows, config) {
- this.rows = [];
- let maxWidth = 0;
-
- for (let i = 0; i < rawRows.length; i++) {
- const rowStr = rawRows[i];
- maxWidth = Math.max(rowStr.replace(/([_\s.-])/g, '').length, maxWidth);
-
- const rowArray = rowStr.split('');
-
- this.rows.unshift(rowArray);
- }
-
- this.currentFlows = [];
- this.previousFlows = [];
-
- this.gitGraphCanvas = new GitGraphCanvas(
- canvas,
- maxWidth,
- this.rows.length,
- config
- );
- }
-
- generateNewFlow(column) {
- let newId;
-
- do {
- newId = generateRandomColorString();
- } while (this.hasFlow(newId, column));
-
- return {id: newId, color: `#${newId}`};
- }
-
- hasFlow(id, column) {
- // We want to find the flow with the current ID
- // Possible flows are those in the currentFlows
- // Or flows in previousFlows[column-2:...]
- for (
- let idx = column - 2 < 0 ? 0 : column - 2;
- idx < this.previousFlows.length;
- idx++
- ) {
- if (this.previousFlows[idx] && this.previousFlows[idx].id === id) {
- return true;
- }
- }
- for (let idx = 0; idx < this.currentFlows.length; idx++) {
- if (this.currentFlows[idx] && this.currentFlows[idx].id === id) {
- return true;
- }
- }
- return false;
- }
-
- takePreviousFlow(column) {
- if (column < this.previousFlows.length && this.previousFlows[column]) {
- const flow = this.previousFlows[column];
- this.previousFlows[column] = null;
- return flow;
- }
- return this.generateNewFlow(column);
- }
-
- draw() {
- if (this.rows.length === 0) {
- return;
- }
-
- this.currentFlows = new Array(this.rows[0].length);
-
- // Generate flows for the first row - I do not believe that this can contain '_', '-', '.'
- for (let column = 0; column < this.rows[0].length; column++) {
- if (this.rows[0][column] === ' ') {
- continue;
- }
- this.currentFlows[column] = this.generateNewFlow(column);
- }
-
- // Draw the first row
- for (let column = 0; column < this.rows[0].length; column++) {
- const symbol = this.rows[0][column];
- const color = this.currentFlows[column] ? this.currentFlows[column].color : '';
- this.gitGraphCanvas.drawSymbol(symbol, column, 0, color);
- }
-
- for (let row = 1; row < this.rows.length; row++) {
- // Done previous row - step up the row
- const currentRow = this.rows[row];
- const previousRow = this.rows[row - 1];
-
- this.previousFlows = this.currentFlows;
- this.currentFlows = new Array(currentRow.length);
-
- // Set flows for this row
- for (let column = 0; column < currentRow.length; column++) {
- column = this.setFlowFor(column, currentRow, previousRow);
- }
-
- // Draw this row
- for (let column = 0; column < currentRow.length; column++) {
- const symbol = currentRow[column];
- const color = this.currentFlows[column] ? this.currentFlows[column].color : '';
- this.gitGraphCanvas.drawSymbol(symbol, column, row, color);
- }
- }
- }
-
- setFlowFor(column, currentRow, previousRow) {
- const symbol = currentRow[column];
- switch (symbol) {
- case '|':
- case '*':
- return this.setUpFlow(column, currentRow, previousRow);
- case '/':
- return this.setOutFlow(column, currentRow, previousRow);
- case '\\':
- return this.setInFlow(column, currentRow, previousRow);
- case '_':
- return this.setRightFlow(column, currentRow, previousRow);
- case '-':
- return this.setLeftFlow(column, currentRow, previousRow);
- case ' ':
- // In space no one can hear you flow ... (?)
- return column;
- default:
- // Unexpected so let's generate a new flow and wait for bug-reports
- this.currentFlows[column] = this.generateNewFlow(column);
- return column;
- }
- }
-
- // setUpFlow handles '|' or '*' - returns the last column that was set
- // generally we prefer to take the left most flow from the previous row
- setUpFlow(column, currentRow, previousRow) {
- // If ' |/' or ' |_'
- // '/|' '/|' -> Take the '|' flow directly beneath us
- if (
- column + 1 < currentRow.length &&
- (currentRow[column + 1] === '/' || currentRow[column + 1] === '_') &&
- column < previousRow.length &&
- (previousRow[column] === '|' || previousRow[column] === '*') &&
- previousRow[column - 1] === '/'
- ) {
- this.currentFlows[column] = this.takePreviousFlow(column);
- return column;
- }
-
- // If ' |/' or ' |_'
- // '/ ' '/ ' -> Take the '/' flow from the preceding column
- if (
- column + 1 < currentRow.length &&
- (currentRow[column + 1] === '/' || currentRow[column + 1] === '_') &&
- column - 1 < previousRow.length &&
- previousRow[column - 1] === '/'
- ) {
- this.currentFlows[column] = this.takePreviousFlow(column - 1);
- return column;
- }
-
- // If ' |'
- // '/' -> Take the '/' flow - (we always prefer the left-most flow)
- if (
- column > 0 &&
- column - 1 < previousRow.length &&
- previousRow[column - 1] === '/'
- ) {
- this.currentFlows[column] = this.takePreviousFlow(column - 1);
- return column;
- }
-
- // If '|' OR '|' take the '|' flow
- // '|' '*'
- if (
- column < previousRow.length &&
- (previousRow[column] === '|' || previousRow[column] === '*')
- ) {
- this.currentFlows[column] = this.takePreviousFlow(column);
- return column;
- }
-
- // If '| ' keep the '\' flow
- // ' \'
- if (column + 1 < previousRow.length && previousRow[column + 1] === '\\') {
- this.currentFlows[column] = this.takePreviousFlow(column + 1);
- return column;
- }
-
- // Otherwise just create a new flow - probably this is an error...
- this.currentFlows[column] = this.generateNewFlow(column);
- return column;
- }
-
- // setOutFlow handles '/' - returns the last column that was set
- // generally we prefer to take the left most flow from the previous row
- setOutFlow(column, currentRow, previousRow) {
- // If '_/' -> keep the '_' flow
- if (column > 0 && currentRow[column - 1] === '_') {
- this.currentFlows[column] = this.currentFlows[column - 1];
- return column;
- }
-
- // If '_|/' -> keep the '_' flow
- if (
- column > 1 &&
- (currentRow[column - 1] === '|' || currentRow[column - 1] === '*') &&
- currentRow[column - 2] === '_'
- ) {
- this.currentFlows[column] = this.currentFlows[column - 2];
- return column;
- }
-
- // If '|/'
- // '/' -> take the '/' flow (if it is still available)
- if (
- column > 1 &&
- currentRow[column - 1] === '|' &&
- column - 2 < previousRow.length &&
- previousRow[column - 2] === '/'
- ) {
- this.currentFlows[column] = this.takePreviousFlow(column - 2);
- return column;
- }
-
- // If ' /'
- // '/' -> take the '/' flow, but transform the symbol to '|' due to our spacing
- // This should only happen if there are 3 '/' - in a row so we don't need to be cleverer here
- if (
- column > 0 &&
- currentRow[column - 1] === ' ' &&
- column - 1 < previousRow.length &&
- previousRow[column - 1] === '/'
- ) {
- this.currentFlows[column] = this.takePreviousFlow(column - 1);
- currentRow[column] = '|';
- return column;
- }
-
- // If ' /'
- // '|' -> take the '|' flow
- if (
- column > 0 &&
- currentRow[column - 1] === ' ' &&
- column - 1 < previousRow.length &&
- (previousRow[column - 1] === '|' || previousRow[column - 1] === '*')
- ) {
- this.currentFlows[column] = this.takePreviousFlow(column - 1);
- return column;
- }
-
- // If '/' <- Not sure this ever happens... but take the '\' flow
- // '\'
- if (column < previousRow.length && previousRow[column] === '\\') {
- this.currentFlows[column] = this.takePreviousFlow(column);
- return column;
- }
-
- // Otherwise just generate a new flow and wait for bug-reports...
- this.currentFlows[column] = this.generateNewFlow(column);
- return column;
- }
-
- // setInFlow handles '\' - returns the last column that was set
- // generally we prefer to take the left most flow from the previous row
- setInFlow(column, currentRow, previousRow) {
- // If '\?'
- // '/?' -> take the '/' flow
- if (column < previousRow.length && previousRow[column] === '/') {
- this.currentFlows[column] = this.takePreviousFlow(column);
- return column;
- }
-
- // If '\?'
- // ' \' -> take the '\' flow and reassign to '|'
- // This should only happen if there are 3 '\' - in a row so we don't need to be cleverer here
- if (column + 1 < previousRow.length && previousRow[column + 1] === '\\') {
- this.currentFlows[column] = this.takePreviousFlow(column + 1);
- currentRow[column] = '|';
- return column;
- }
-
- // If '\?'
- // ' |' -> take the '|' flow
- if (
- column + 1 < previousRow.length &&
- (previousRow[column + 1] === '|' || previousRow[column + 1] === '*')
- ) {
- this.currentFlows[column] = this.takePreviousFlow(column + 1);
- return column;
- }
-
- // Otherwise just generate a new flow and wait for bug-reports if we're wrong...
- this.currentFlows[column] = this.generateNewFlow(column);
- return column;
- }
-
- // setRightFlow handles '_' - returns the last column that was set
- // generally we prefer to take the left most flow from the previous row
- setRightFlow(column, currentRow, previousRow) {
- // if '__' keep the '_' flow
- if (column > 0 && currentRow[column - 1] === '_') {
- this.currentFlows[column] = this.currentFlows[column - 1];
- return column;
- }
-
- // if '_|_' -> keep the '_' flow
- if (
- column > 1 &&
- currentRow[column - 1] === '|' &&
- currentRow[column - 2] === '_'
- ) {
- this.currentFlows[column] = this.currentFlows[column - 2];
- return column;
- }
-
- // if ' _' -> take the '/' flow
- // '/ '
- if (
- column > 0 &&
- column - 1 < previousRow.length &&
- previousRow[column - 1] === '/'
- ) {
- this.currentFlows[column] = this.takePreviousFlow(column - 1);
- return column;
- }
-
- // if ' |_'
- // '/? ' -> take the '/' flow (this may cause generation...)
- // we can do this because we know that git graph
- // doesn't create compact graphs like: ' |_'
- // '//'
- if (
- column > 1 &&
- column - 2 < previousRow.length &&
- previousRow[column - 2] === '/'
- ) {
- this.currentFlows[column] = this.takePreviousFlow(column - 2);
- return column;
- }
-
- // There really shouldn't be another way of doing this - generate and wait for bug-reports...
-
- this.currentFlows[column] = this.generateNewFlow(column);
- return column;
- }
-
- // setLeftFlow handles '----.' - returns the last column that was set
- // generally we prefer to take the left most flow from the previous row that terminates this left recursion
- setLeftFlow(column, currentRow, previousRow) {
- // This is: '----------.' or the like
- // ' \ \ /|\'
-
- // Find the end of the '-' or nearest '/|\' in the previousRow :
- let originalColumn = column;
- let flow;
- for (; column < currentRow.length && currentRow[column] === '-'; column++) {
- if (column > 0 && column - 1 < previousRow.length && previousRow[column - 1] === '/') {
- flow = this.takePreviousFlow(column - 1);
- break;
- } else if (column < previousRow.length && previousRow[column] === '|') {
- flow = this.takePreviousFlow(column);
- break;
- } else if (
- column + 1 < previousRow.length &&
- previousRow[column + 1] === '\\'
- ) {
- flow = this.takePreviousFlow(column + 1);
- break;
- }
- }
-
- // if we have a flow then we found a '/|\' in the previousRow
- if (flow) {
- for (; originalColumn < column + 1; originalColumn++) {
- this.currentFlows[originalColumn] = flow;
- }
- return column;
- }
-
- // If the symbol in the column is not a '.' then there's likely an error
- if (currentRow[column] !== '.') {
- // It really should end in a '.' but this one doesn't...
- // 1. Step back - we don't want to eat this column
- column--;
- // 2. Generate a new flow and await bug-reports...
- this.currentFlows[column] = this.generateNewFlow(column);
-
- // 3. Assign all of the '-' to the same flow.
- for (; originalColumn < column; originalColumn++) {
- this.currentFlows[originalColumn] = this.currentFlows[column];
- }
- return column;
- }
-
- // We have a terminal '.' eg. the current row looks like '----.'
- // the previous row should look like one of '/|\' eg. ' \'
- if (column > 0 && column - 1 < previousRow.length && previousRow[column - 1] === '/') {
- flow = this.takePreviousFlow(column - 1);
- } else if (column < previousRow.length && previousRow[column] === '|') {
- flow = this.takePreviousFlow(column);
- } else if (
- column + 1 < previousRow.length &&
- previousRow[column + 1] === '\\'
- ) {
- flow = this.takePreviousFlow(column + 1);
+export default async function initGitGraph() {
+ const graphContainer = document.getElementById('git-graph-container');
+ if (!graphContainer) return;
+
+ $('#flow-color-monochrome').on('click', () => {
+ $('#flow-color-monochrome').addClass('active');
+ $('#flow-color-colored').removeClass('active');
+ $('#git-graph-container').removeClass('colored').addClass('monochrome');
+ const params = new URLSearchParams(window.location.search);
+ params.set('mode', 'monochrome');
+ const queryString = params.toString();
+ if (queryString) {
+ window.history.replaceState({}, '', `?${queryString}`);
} else {
- // Again unexpected so let's generate and wait the bug-report
- flow = this.generateNewFlow(column);
- }
-
- // Assign all of the rest of the ----. to this flow.
- for (; originalColumn < column + 1; originalColumn++) {
- this.currentFlows[originalColumn] = flow;
+ window.history.replaceState({}, '', window.location.pathname);
+ }
+ $('.pagination a').each((_, that) => {
+ const href = $(that).attr('href');
+ if (!href) return;
+ const url = new URL(href, window.location);
+ const params = url.searchParams;
+ params.set('mode', 'monochrome');
+ url.search = `?${params.toString()}`;
+ $(that).attr('href', url.href);
+ });
+ });
+ $('#flow-color-colored').on('click', () => {
+ $('#flow-color-colored').addClass('active');
+ $('#flow-color-monochrome').removeClass('active');
+ $('#git-graph-container').addClass('colored').removeClass('monochrome');
+ $('.pagination a').each((_, that) => {
+ const href = $(that).attr('href');
+ if (!href) return;
+ const url = new URL(href, window.location);
+ const params = url.searchParams;
+ params.delete('mode');
+ url.search = `?${params.toString()}`;
+ $(that).attr('href', url.href);
+ });
+ const params = new URLSearchParams(window.location.search);
+ params.delete('mode');
+ const queryString = params.toString();
+ if (queryString) {
+ window.history.replaceState({}, '', `?${queryString}`);
+ } else {
+ window.history.replaceState({}, '', window.location.pathname);
}
-
- return column;
- }
-}
-
-function generateRandomColorString() {
- const chars = '0123456789ABCDEF';
- const stringLength = 6;
- let randomString = '',
- rnum,
- i;
- for (i = 0; i < stringLength; i++) {
- rnum = Math.floor(Math.random() * chars.length);
- randomString += chars.substring(rnum, rnum + 1);
- }
-
- return randomString;
-}
-
-export default async function initGitGraph() {
- const graphCanvas = document.getElementById('graph-canvas');
- if (!graphCanvas || !graphCanvas.getContext) return;
-
- // Grab the raw graphList
- const graphList = [];
- $('#graph-raw-list li span.node-relation').each(function () {
- graphList.push($(this).text());
});
-
- // Define some drawing parameters
- const config = {
- unitSize: 20,
- lineWidth: 3,
- nodeRadius: 4
- };
-
-
- const gitGraph = new GitGraph(graphCanvas, graphList, config);
- gitGraph.draw();
- graphCanvas.closest('#git-graph-container').classList.add('in');
+ $('#git-graph-container').on('mouseenter', '#rev-list li', (e) => {
+ const flow = $(e.currentTarget).data('flow');
+ if (flow === 0) return;
+ $(`#flow-${flow}`).addClass('highlight');
+ $(e.currentTarget).addClass('hover');
+ $(`#rev-list li[data-flow='${flow}']`).addClass('highlight');
+ });
+ $('#git-graph-container').on('mouseleave', '#rev-list li', (e) => {
+ const flow = $(e.currentTarget).data('flow');
+ if (flow === 0) return;
+ $(`#flow-${flow}`).removeClass('highlight');
+ $(e.currentTarget).removeClass('hover');
+ $(`#rev-list li[data-flow='${flow}']`).removeClass('highlight');
+ });
+ $('#git-graph-container').on('mouseenter', '#rel-container .flow-group', (e) => {
+ $(e.currentTarget).addClass('highlight');
+ const flow = $(e.currentTarget).data('flow');
+ $(`#rev-list li[data-flow='${flow}']`).addClass('highlight');
+ });
+ $('#git-graph-container').on('mouseleave', '#rel-container .flow-group', (e) => {
+ $(e.currentTarget).removeClass('highlight');
+ const flow = $(e.currentTarget).data('flow');
+ $(`#rev-list li[data-flow='${flow}']`).removeClass('highlight');
+ });
+ $('#git-graph-container').on('mouseenter', '#rel-container .flow-commit', (e) => {
+ const rev = $(e.currentTarget).data('rev');
+ $(`#rev-list li#commit-${rev}`).addClass('hover');
+ });
+ $('#git-graph-container').on('mouseleave', '#rel-container .flow-commit', (e) => {
+ const rev = $(e.currentTarget).data('rev');
+ $(`#rev-list li#commit-${rev}`).removeClass('hover');
+ });
}
diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less
index 94b1d72a0b..81a3282ee4 100644
--- a/web_src/less/_repository.less
+++ b/web_src/less/_repository.less
@@ -3082,11 +3082,3 @@ tbody.commit-list {
}
}
}
-
-#git-graph-container {
- display: none;
-}
-
-#git-graph-container.in {
- display: block;
-}
diff --git a/web_src/less/features/gitgraph.less b/web_src/less/features/gitgraph.less
new file mode 100644
index 0000000000..8a9c4239a7
--- /dev/null
+++ b/web_src/less/features/gitgraph.less
@@ -0,0 +1,256 @@
+#git-graph-container {
+ float: left;
+ display: block;
+ overflow-x: auto;
+ width: 100%;
+
+ .color-buttons {
+ margin-right: 0;
+ }
+
+ .ui.header.dividing {
+ padding-bottom: 10px;
+ }
+
+ li {
+ list-style-type: none;
+ height: 20px;
+ line-height: 20px;
+ white-space: nowrap;
+
+ .node-relation {
+ font-family: "Bitstream Vera Sans Mono", "Courier", monospace;
+ }
+
+ .author {
+ color: #666666;
+ }
+
+ .time {
+ color: #999999;
+ font-size: 80%;
+ }
+
+ a {
+ color: #000000;
+ }
+
+ a:hover {
+ text-decoration: underline;
+ }
+
+ a em {
+ color: #bb0000;
+ border-bottom: 1px dotted #bbbbbb;
+ text-decoration: none;
+ font-style: normal;
+ }
+ }
+
+ #rel-container {
+ max-width: 30%;
+ overflow-x: auto;
+ float: left;
+ }
+
+ #rev-container {
+ width: 100%;
+ }
+
+ #rev-list {
+ margin: 0;
+ padding: 0 5px;
+ min-width: 95%;
+
+ li.highlight,
+ li.hover {
+ background-color: rgba(0, 0, 0, .05);
+ }
+
+ li.highlight.hover {
+ background-color: rgba(0, 0, 0, .1);
+ }
+ }
+
+ #graph-raw-list {
+ margin: 0;
+ }
+
+ &.monochrome #rel-container {
+ .flow-group {
+ stroke: grey;
+ fill: grey;
+ }
+
+ .flow-group.highlight {
+ stroke: black;
+ fill: black;
+ }
+ }
+
+ &:not(.monochrome) #rel-container {
+ .flow-group {
+ &.flow-color-16-1 {
+ stroke: #499a37;
+ fill: #499a37;
+ }
+
+ &.flow-color-16-2 {
+ stroke: hsl(356, 58%, 54%);
+ fill: #ce4751;
+ }
+
+ &.flow-color-16-3 {
+ stroke: #8f9121;
+ fill: #8f9121;
+ }
+
+ &.flow-color-16-4 {
+ stroke: #ac32a6;
+ fill: #ac32a6;
+ }
+
+ &.flow-color-16-5 {
+ stroke: #3d27aa;
+ fill: #3d27aa;
+ }
+
+ &.flow-color-16-6 {
+ stroke: #c67d28;
+ fill: #c67d28;
+ }
+
+ &.flow-color-16-7 {
+ stroke: #4db392;
+ fill: #4db392;
+ }
+
+ &.flow-color-16-8 {
+ stroke: #aa4d30;
+ fill: #aa4d30;
+ }
+
+ &.flow-color-16-9 {
+ stroke: #2a6f84;
+ fill: #2a6f84;
+ }
+
+ &.flow-color-16-10 {
+ stroke: #c45327;
+ fill: #c45327;
+ }
+
+ &.flow-color-16-11 {
+ stroke: #3d965c;
+ fill: #3d965c;
+ }
+
+ &.flow-color-16-12 {
+ stroke: #792a93;
+ fill: #792a93;
+ }
+
+ &.flow-color-16-13 {
+ stroke: #439d73;
+ fill: #439d73;
+ }
+
+ &.flow-color-16-14 {
+ stroke: #103aad;
+ fill: #103aad;
+ }
+
+ &.flow-color-16-15 {
+ stroke: #982e85;
+ fill: #982e85;
+ }
+
+ &.flow-color-16-0 {
+ stroke: #7db233;
+ fill: #7db233;
+ }
+ }
+
+ .flow-group.highlight {
+ &.flow-color-16-1 {
+ stroke: #5ac144;
+ fill: #5ac144;
+ }
+
+ &.flow-color-16-2 {
+ stroke: #ed5a8b;
+ fill: #ed5a8b;
+ }
+
+ &.flow-color-16-3 {
+ stroke: #ced049;
+ fill: #ced048;
+ }
+
+ &.flow-color-16-4 {
+ stroke: #db61d7;
+ fill: #db62d6;
+ }
+
+ &.flow-color-16-5 {
+ stroke: #4e33d1;
+ fill: #4f35d1;
+ }
+
+ &.flow-color-16-6 {
+ stroke: #e6a151;
+ fill: #e6a151;
+ }
+
+ &.flow-color-16-7 {
+ stroke: #44daaa;
+ fill: #44daaa;
+ }
+
+ &.flow-color-16-8 {
+ stroke: #dd7a5c;
+ fill: #dd7a5c;
+ }
+
+ &.flow-color-16-9 {
+ stroke: #38859c;
+ fill: #38859c;
+ }
+
+ &.flow-color-16-10 {
+ stroke: #d95520;
+ fill: #d95520;
+ }
+
+ &.flow-color-16-11 {
+ stroke: #42ae68;
+ fill: #42ae68;
+ }
+
+ &.flow-color-16-12 {
+ stroke: #9126b5;
+ fill: #9126b5;
+ }
+
+ &.flow-color-16-13 {
+ stroke: #4ab080;
+ fill: #4ab080;
+ }
+
+ &.flow-color-16-14 {
+ stroke: #284fb8;
+ fill: #284fb8;
+ }
+
+ &.flow-color-16-15 {
+ stroke: #971c80;
+ fill: #971c80;
+ }
+
+ &.flow-color-16-0 {
+ stroke: #87ca28;
+ fill: #87ca28;
+ }
+ }
+ }
+}
diff --git a/web_src/less/index.less b/web_src/less/index.less
index ef38f863cd..92b25e1db1 100644
--- a/web_src/less/index.less
+++ b/web_src/less/index.less
@@ -1,5 +1,6 @@
@import "~font-awesome/css/font-awesome.css";
-@import "./vendor/gitGraph.css";
+
+@import "./features/gitgraph.less";
@import "./features/animations.less";
@import "./markdown/mermaid.less";
diff --git a/web_src/less/themes/theme-arc-green.less b/web_src/less/themes/theme-arc-green.less
index 8de66fd251..1e8c118675 100644
--- a/web_src/less/themes/theme-arc-green.less
+++ b/web_src/less/themes/theme-arc-green.less
@@ -919,11 +919,17 @@ a.ui.basic.green.label:hover {
.ui.active.button:active,
.ui.button:active,
-.ui.button:focus {
+.ui.button:focus,
+.ui.active.button {
background-color: #2e3e4e;
color: #dbdbdb;
}
+.ui.active.button:hover {
+ background-color: #475e75;
+ color: #dbdbdb;
+}
+
.ui.dropdown .menu .selected.item,
.ui.dropdown.selected {
color: #dbdbdb;
@@ -1921,6 +1927,45 @@ footer .container .links > * {
color: #2a2e3a;
}
+#git-graph-container.monochrome #rel-container .flow-group {
+ stroke: dimgrey;
+ fill: dimgrey;
+}
+
+#git-graph-container.monochrome #rel-container .flow-group.highlight {
+ stroke: darkgrey;
+ fill: darkgrey;
+}
+
+#git-graph-container:not(.monochrome) #rel-container .flow-group {
+ &.flow-color-16-5 {
+ stroke: #5543b1;
+ fill: #5543b1;
+ }
+}
+
+#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight {
+ &.flow-color-16-5 {
+ stroke: #7058e6;
+ fill: #7058e6;
+ }
+}
+
+#git-graph-container #rev-list li.highlight,
+#git-graph-container #rev-list li.hover {
+ background-color: rgba(255, 255, 255, .05);
+}
+
+#git-graph-container #rev-list li.highlight.hover {
+ background-color: rgba(255, 255, 255, .1);
+}
+
+#git-graph-container .ui.buttons button#flow-color-monochrome.ui.button {
+ border-left-color: rgb(76, 80, 92);
+ border-left-style: solid;
+ border-left-width: 1px;
+}
+
.mermaid-chart {
filter: invert(84%) hue-rotate(180deg);
}
diff --git a/web_src/less/vendor/gitGraph.css b/web_src/less/vendor/gitGraph.css
deleted file mode 100644
index bb7e708101..0000000000
--- a/web_src/less/vendor/gitGraph.css
+++ /dev/null
@@ -1,15 +0,0 @@
-/* This is a customized version of https://github.com/bluef/gitgraph.js/blob/master/gitgraph.css
- Changes include the removal of `body` and `em` styles */
-#git-graph-container, #rel-container {float:left;}
-#rel-container {max-width:30%; overflow-x:auto;}
-#git-graph-container {overflow-x:auto; width:100%}
-#git-graph-container li {list-style-type:none;height:20px;line-height:20px; white-space:nowrap;}
-#git-graph-container li .node-relation {font-family:'Bitstream Vera Sans Mono', 'Courier', monospace;}
-#git-graph-container li .author {color:#666666;}
-#git-graph-container li .time {color:#999999;font-size:80%}
-#git-graph-container li a {color:#000000;}
-#git-graph-container li a:hover {text-decoration:underline;}
-#git-graph-container li a em {color:#BB0000;border-bottom:1px dotted #BBBBBB;text-decoration:none;font-style:normal;}
-#rev-container {width:100%}
-#rev-list {margin:0;padding:0 5px 0 5px;min-width:95%}
-#graph-raw-list {margin:0px;}
diff --git a/web_src/svg/material-invert-colors.svg b/web_src/svg/material-invert-colors.svg
new file mode 100644
index 0000000000..e6445ab230
--- /dev/null
+++ b/web_src/svg/material-invert-colors.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 768 768"><path d="M384 627V163.5l-135 135c-36 36-57 85.5-57 136.5 0 103.19 88.8 192 192 192zm181.5-373.5C666 354 666 514.5 565.5 615 516 664.5 450 690 384 690s-132-25.5-181.5-75C102 514.5 102 354 202.5 253.5L384 72z"/></svg> \ No newline at end of file
diff --git a/web_src/svg/material-palette.svg b/web_src/svg/material-palette.svg
new file mode 100644
index 0000000000..df0e1756ff
--- /dev/null
+++ b/web_src/svg/material-palette.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 768 768"><path d="M559.5 384c27 0 48-21 48-48s-21-48-48-48-48 21-48 48 21 48 48 48zm-96-127.5c27 0 48-21 48-48s-21-48-48-48-48 21-48 48 21 48 48 48zm-159 0c27 0 48-21 48-48s-21-48-48-48-48 21-48 48 21 48 48 48zm-96 127.5c27 0 48-21 48-48s-21-48-48-48-48 21-48 48 21 48 48 48zM384 96c159 0 288 115.5 288 256.5 0 88.5-72 159-160.5 159H456c-27 0-48 21-48 48 0 12 4.5 22.5 12 31.5s12 21 12 33c0 27-21 48-48 48-159 0-288-129-288-288S225 96 384 96z"/></svg> \ No newline at end of file