Skip to content

Commit

Permalink
Merge pull request #69 from rars/add-side-by-side-diff
Browse files Browse the repository at this point in the history
feat(ngx-diff): add side-by-side diff component
  • Loading branch information
rars authored Mar 14, 2024
2 parents 6bc1062 + f7b7c1d commit 4409d7d
Show file tree
Hide file tree
Showing 12 changed files with 739 additions and 32 deletions.
2 changes: 1 addition & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
"projectType": "library",
"root": "projects/ngx-diff",
"sourceRoot": "projects/ngx-diff/src",
"prefix": "lib",
"prefix": "ngx",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
Expand Down
26 changes: 7 additions & 19 deletions projects/ngx-diff/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
{
"extends": "../../.eslintrc.json",
"ignorePatterns": [
"!**/*"
],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": [
"*.ts"
],
"files": ["*.ts"],
"parserOptions": {
"project": [
"projects/ngx-diff/tsconfig.lib.json",
"projects/ngx-diff/tsconfig.spec.json"
],
"project": ["projects/ngx-diff/tsconfig.lib.json", "projects/ngx-diff/tsconfig.spec.json"],
"createDefaultProgram": true
},
"rules": {
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "lib",
"prefix": "ngx",
"style": "kebab-case"
}
],
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "lib",
"prefix": "ngx",
"style": "camelCase"
}
],
Expand All @@ -38,17 +31,12 @@
"accessibility": "explicit"
}
],
"arrow-parens": [
"off",
"always"
],
"arrow-parens": ["off", "always"],
"import/order": "off"
}
},
{
"files": [
"*.html"
],
"files": ["*.html"],
"rules": {}
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
--ngx-diff-delete-color: #ffd6d6;
--ngx-diff-equal-color: #ffffff;
--ngx-diff-mix-color: #000;
--ngx-diff-light-mix-percentage: 2%;
--ngx-diff-light-mix-percentage: 4%;
--ngx-diff-heavy-mix-percentage: 10%;

--ngx-diff-inserted-background-color: var(--ngx-diff-insert-color);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export class InlineDiffComponent implements OnInit, OnChanges {

if (type === LineDiffType.Placeholder) {
this.expandPlaceholder(index, lineDiff);
this.selectedLine = undefined;
}

this.selectedLineChange.emit({
Expand Down Expand Up @@ -108,7 +109,7 @@ export class InlineDiffComponent implements OnInit, OnChanges {
type: LineDiffType.Placeholder,
lineNumberInOldText: null,
lineNumberInNewText: null,
line: '...',
line: `... ${remainingSkippedLines.length} hidden lines ...`,
args: {
skippedLines: remainingSkippedLines,
lineInOldText: lineInOldText + prefix.length,
Expand Down Expand Up @@ -265,17 +266,19 @@ export class InlineDiffComponent implements OnInit, OnChanges {
// Take the first 'lineContextSize' lines from this diff to provide context for the last diff
this.outputEqualDiffLines(diffLines.slice(0, this.lineContextSize), diffCalculation);

const skippedLines = diffLines.slice(
this.lineContextSize,
diffLines.length - this.lineContextSize,
);

// Output a special line indicating that some content is equal and has been skipped
diffCalculation.lines.push({
type: LineDiffType.Placeholder,
lineNumberInOldText: null,
lineNumberInNewText: null,
line: '...',
line: `... ${skippedLines.length} hidden lines ...`,
args: {
skippedLines: diffLines.slice(
this.lineContextSize,
diffLines.length - this.lineContextSize,
),
skippedLines,
lineInOldText: diffCalculation.lineInOldText,
lineInNewText: diffCalculation.lineInNewText,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<div class="sbs-diff-title-bar">
@if (title) {
<span>{{ title }}</span
>&nbsp;
}
<span class="sbs-diff-summary-lines-added">+++ {{ diffSummary.numLinesAdded }}</span
>&nbsp;
<span class="sbs-diff-summary-lines-removed">--- {{ diffSummary.numLinesRemoved }}</span>
</div>
@if (isContentEqual) {
<div class="sbs-diff-no-changes-text">There are no changes to display.</div>
}
@if (!isContentEqual) {
<div class="sbs-diff">
<!-- before -->
<div class="sbs-diff-margin">
@for (lineDiff of beforeLines; track lineDiff; let idx = $index) {
<div
class="line-selector"
[ngClass]="
idx === selectedLineIndex ? [lineDiff.cssClass, 'selected'] : [lineDiff.cssClass]
"
(click)="selectLine(idx)"
>
<div class="sbs-diff-before">{{ lineDiff.lineNumber | lineNumber }}</div>
</div>
}
<div class="dmp-margin-bottom-spacer"></div>
</div>
<div class="sbs-diff-content">
<div class="sbs-diff-content-wrapper">
@for (lineDiff of beforeLines; track lineDiff; let idx = $index) {
<div
class="line-content"
[ngClass]="
idx === selectedLineIndex ? [lineDiff.cssClass, 'selected'] : [lineDiff.cssClass]
"
>
<div class="sbs-diff-text">{{ lineDiff.line }}</div>
</div>
}
<div class="dmp-margin-bottom-spacer line-content"></div>
</div>
</div>
<!-- after -->
<div class="sbs-diff-margin">
@for (lineDiff of afterLines; track lineDiff; let idx = $index) {
<div
class="line-selector"
[ngClass]="
idx === selectedLineIndex ? [lineDiff.cssClass, 'selected'] : [lineDiff.cssClass]
"
(click)="selectLine(idx)"
>
<div class="sbs-diff-after">{{ lineDiff.lineNumber | lineNumber }}</div>
</div>
}
<div class="dmp-margin-bottom-spacer"></div>
</div>
<div class="sbs-diff-content">
<div class="sbs-diff-content-wrapper">
@for (lineDiff of afterLines; track lineDiff; let idx = $index) {
<div
class="line-content"
[ngClass]="
idx === selectedLineIndex ? [lineDiff.cssClass, 'selected'] : [lineDiff.cssClass]
"
>
<div class="sbs-diff-text">{{ lineDiff.line }}</div>
</div>
}
<div class="dmp-margin-bottom-spacer line-content"></div>
</div>
</div>
</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
:host {
--ngx-diff-border-color: #888888;
--ngx-diff-font-size: 0.9rem;
--ngx-diff-font-family: Consolas, Courier, monospace;
--ngx-diff-font-color: #000000;
--ngx-diff-line-number-font-color: #484848;
--ngx-diff-line-number-hover-font-color: #ffffff;
--ngx-diff-selected-border-color: #ff8000;

--ngx-diff-line-number-width: 2rem;
--ngx-diff-border-width: 1px;
--ngx-diff-line-left-padding: 1rem;
--ngx-diff-bottom-spacer-height: 1rem;
--ngx-diff-title-bar-padding: 0.5rem;
--ngx-diff-title-font-weight: 600;

--ngx-diff-insert-color: #d6ffd6;
--ngx-diff-delete-color: #ffd6d6;
--ngx-diff-equal-color: #ffffff;
--ngx-diff-mix-color: #000;
--ngx-diff-light-mix-percentage: 4%;
--ngx-diff-heavy-mix-percentage: 10%;

--ngx-diff-inserted-background-color: var(--ngx-diff-insert-color);
--ngx-diff-deleted-background-color: var(--ngx-diff-delete-color);
--ngx-diff-equal-background-color: var(--ngx-diff-equal-color);
--ngx-diff-margin-background-color: color-mix(in srgb, var(--ngx-diff-equal-color), var(--ngx-diff-mix-color) var(--ngx-diff-light-mix-percentage));

--ngx-diff-insert-color-darker: color-mix(in srgb, var(--ngx-diff-insert-color), var(--ngx-diff-mix-color) var(--ngx-diff-light-mix-percentage));
--ngx-diff-insert-color-darkest: color-mix(in srgb, var(--ngx-diff-insert-color), var(--ngx-diff-mix-color) var(--ngx-diff-heavy-mix-percentage));

--ngx-diff-delete-color-darker: color-mix(in srgb, var(--ngx-diff-delete-color), var(--ngx-diff-mix-color) var(--ngx-diff-light-mix-percentage));
--ngx-diff-delete-color-darkest: color-mix(in srgb, var(--ngx-diff-delete-color), var(--ngx-diff-mix-color) var(--ngx-diff-heavy-mix-percentage));
}

div.sbs-diff-title-bar {
background-color: var(--ngx-diff-margin-background-color);
font-family: var(--ngx-diff-font-family);
font-size: var(--ngx-diff-font-size);
font-weight: var(--ngx-diff-title-font-weight);
padding: var(--ngx-diff-title-bar-padding);
}

div.sbs-diff-no-changes-text {
font-family: var(--ngx-diff-font-family);
font-size: var(--ngx-diff-font-size);
font-weight: var(--ngx-diff-title-font-weight);
padding: var(--ngx-diff-title-bar-padding);
background-color: var(--ngx-diff-equal-background-color);
color: var(--ngx-diff-font-color);
}

.sbs-diff-summary-lines-added {
color: var(--ngx-diff-insert-color-darkest);
}

.sbs-diff-summary-lines-removed {
color: var(--ngx-diff-delete-color-darkest);
}

div.sbs-diff {
display: flex;
flex-direction: row;
border: var(--ngx-diff-border-width) solid var(--ngx-diff-border-color);
font-family: var(--ngx-diff-font-family);

div.sbs-diff-margin:last-of-type {
border-left: var(--ngx-diff-border-width) solid var(--ngx-diff-border-color);
}
}

div.sbs-diff-content {
position: relative;
top: 0px;
left: 0px;
flex-grow: 1;
overflow-x: auto;
overflow-y: hidden;
}

div.sbs-diff-content-wrapper {
position: absolute;
top: 0px;
left: 0px;
display: flex;
flex-direction: column;
align-items: stretch;
width: 100%;
}

div.sbs-diff-old {
width: var(--ngx-diff-line-number-width);
text-align: center;
font-size: var(--ngx-diff-font-size);
}

div.sbs-diff-new {
width: var(--ngx-diff-line-number-width);
text-align: center;
border-right: var(--ngx-diff-border-width) solid var(--border-color);
font-size: var(--ngx-diff-font-size);
}

div.sbs-diff-text {
white-space: pre;
padding-left: var(--ngx-diff-line-left-padding);
font-size: var(--ngx-diff-font-size);
color: var(--ngx-diff-font-color);
}

.sbs-diff-equal {
background-color: var(--ngx-diff-margin-background-color);

&.line-content {
background-color: var(--ngx-diff-equal-background-color);
}
}

.sbs-diff-delete {
background-color: var(--ngx-diff-delete-color-darker);

&.line-content {
background-color: var(--ngx-diff-deleted-background-color);
}
}

.sbs-diff-insert {
background-color: var(--ngx-diff-insert-color-darker);

&.line-content {
background-color: var(--ngx-diff-inserted-background-color);
}
}

.sbs-diff-delete > div {
display: inline-block;
}

.sbs-diff-insert > div {
display: inline-block;
}

.sbs-diff-equal > div {
display: inline-block;
}

.dmp-margin-bottom-spacer {
height: var(--ngx-diff-bottom-spacer-height);
background-color: var(--ngx-diff-margin-background-color);
border-right: var(--ngx-diff-border-width) solid var(--border-color);

&.line-content {
background-color: var(--ngx-diff-equal-background-color);
}
}

.line-selector {
color: var(--ngx-diff-line-number-font-color);

.sbs-diff-before, .sbs-diff-after {
width: var(--ngx-diff-line-number-width);
text-align: center;
}

&:hover {
cursor: pointer;
color: var(--ngx-diff-line-number-hover-font-color);
}

&.selected {
border-top: var(--ngx-diff-border-width) solid var(--ngx-diff-selected-border-color);
border-left: var(--ngx-diff-border-width) solid var(--ngx-diff-selected-border-color);
border-bottom: var(--ngx-diff-border-width) solid var(--ngx-diff-selected-border-color);
}
}

.line-content.selected {
border-top: var(--ngx-diff-border-width) solid var(--ngx-diff-selected-border-color);
border-right: var(--ngx-diff-border-width) solid var(--ngx-diff-selected-border-color);
border-bottom: var(--ngx-diff-border-width) solid var(--ngx-diff-selected-border-color);
}
Loading

0 comments on commit 4409d7d

Please sign in to comment.