This is an automated email from the ASF dual-hosted git repository.
chanholee pushed a commit to branch branch-0.12
in repository https://gitbox.apache.org/repos/asf/zeppelin.git
The following commit(s) were added to refs/heads/branch-0.12 by this push:
new 108cb6eb03 [ZEPPELIN-6162] Implement revisions comparator for New UI
108cb6eb03 is described below
commit 108cb6eb03ad662ea8e293f15e9b78b08629d079
Author: Prabhjyot Singh <[email protected]>
AuthorDate: Sun Mar 1 08:54:45 2026 -0500
[ZEPPELIN-6162] Implement revisions comparator for New UI
### What is this PR for?
Port the revision comparison feature from the legacy AngularJS UI to the
new Angular 13 frontend. Users can now select two revisions and view
paragraph-by-paragraph diffs with color-coded additions and deletions.
### What type of PR is it?
Improvement
### What is the Jira issue?
* https://issues.apache.org/jira/browse/ZEPPELIN-6162
### How should this be tested?
* Strongly recommended: add automated unit tests for any new or changed
behavior
* Outline any manual steps to test the PR here.
### Screenshots (if appropriate)

### Questions:
* Does the license files need to update? no
* Is there breaking changes for older versions? no
* Does this needs documentation? no
Closes #5155 from prabhjyotsingh/ZEPPELIN-6162.
Signed-off-by: ChanHo Lee <[email protected]>
(cherry picked from commit c3ccd9b4dd40eac2fc7500ae15b71cbee9384cd5)
Signed-off-by: ChanHo Lee <[email protected]>
---
.../interfaces/message-data-type-map.interface.ts | 2 +
.../src/interfaces/message-notebook.interface.ts | 7 +
.../workspace/notebook/notebook.component.html | 6 +-
.../pages/workspace/notebook/notebook.module.ts | 6 +-
.../revisions-comparator.component.html | 111 +++++++++++-
.../revisions-comparator.component.less | 146 +++++++++++++++
.../revisions-comparator.component.ts | 198 ++++++++++++++++++++-
.../src/styles/theme/dark-theme-overrides.css | 15 ++
8 files changed, 479 insertions(+), 12 deletions(-)
diff --git
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-data-type-map.interface.ts
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-data-type-map.interface.ts
index 2578655269..6c6088c73a 100644
---
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-data-type-map.interface.ts
+++
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-data-type-map.interface.ts
@@ -34,6 +34,7 @@ import {
NoteRename,
NoteRevision,
NoteRevisionForCompare,
+ NoteRevisionForCompareReceived,
NoteRunningStatus,
NoteUpdate,
NoteUpdated,
@@ -118,6 +119,7 @@ export interface MessageReceiveDataTypeMap {
[OP.ANGULAR_OBJECT_UPDATE]: AngularObjectUpdate;
[OP.ANGULAR_OBJECT_REMOVE]: AngularObjectRemove;
[OP.PARAS_INFO]: ParasInfo;
+ [OP.NOTE_REVISION_FOR_COMPARE]: NoteRevisionForCompareReceived;
}
export interface MessageSendDataTypeMap {
diff --git
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-notebook.interface.ts
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-notebook.interface.ts
index a07d52d372..986aed0b91 100644
---
a/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-notebook.interface.ts
+++
b/zeppelin-web-angular/projects/zeppelin-sdk/src/interfaces/message-notebook.interface.ts
@@ -125,6 +125,13 @@ export interface NoteRevisionForCompare {
position: string;
}
+export interface NoteRevisionForCompareReceived {
+ noteId: string;
+ revisionId: string;
+ position: string;
+ note: Note['note'];
+}
+
export interface CollaborativeModeStatus {
status: boolean;
users: string[];
diff --git
a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html
b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html
index ed0bddd2f2..8541afc964 100644
---
a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html
+++
b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.component.html
@@ -56,7 +56,11 @@
[(activatedExtension)]="activatedExtension"
[permissions]="permissions"
></zeppelin-notebook-permissions>
- <zeppelin-notebook-revisions-comparator
*ngSwitchCase="'revisions'"></zeppelin-notebook-revisions-comparator>
+ <zeppelin-notebook-revisions-comparator
+ *ngSwitchCase="'revisions'"
+ [noteRevisions]="noteRevisions"
+ [noteId]="note.id"
+ ></zeppelin-notebook-revisions-comparator>
</div>
<div class="paragraph-area">
<zeppelin-note-form-block
diff --git
a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.module.ts
b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.module.ts
index 78581aef7e..62899c39b5 100644
--- a/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.module.ts
+++ b/zeppelin-web-angular/src/app/pages/workspace/notebook/notebook.module.ts
@@ -33,6 +33,8 @@ import { NzRadioModule } from 'ng-zorro-antd/radio';
import { NzResizableModule } from 'ng-zorro-antd/resizable';
import { NzSelectModule } from 'ng-zorro-antd/select';
import { NzSwitchModule } from 'ng-zorro-antd/switch';
+import { NzTableModule } from 'ng-zorro-antd/table';
+import { NzTagModule } from 'ng-zorro-antd/tag';
import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
import { ShareModule } from '@zeppelin/share';
@@ -98,7 +100,9 @@ import { NotebookSidebarComponent } from
'./sidebar/sidebar.component';
DragDropModule,
NzCodeEditorModule,
NzCheckboxModule,
- NzResizableModule
+ NzResizableModule,
+ NzTableModule,
+ NzTagModule
]
})
export class NotebookModule {}
diff --git
a/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.html
b/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.html
index c8099d6f33..17dda5a0ff 100644
---
a/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.html
+++
b/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.html
@@ -10,10 +10,111 @@
~ limitations under the License.
-->
-<div class="revisions-comarator">
- <div>
- <h2>Revisions comparator</h2>
+<div class="revisions-comparator" nz-row [nzGutter]="16">
+ <div nz-col [nzSpan]="8">
+ <div class="commit-tree">
+ <nz-table
+ #revisionTable
+ [nzData]="sortedRevisions"
+ [nzShowPagination]="false"
+ [nzScroll]="{ y: '25vh' }"
+ nzSize="small"
+ >
+ <thead>
+ <tr>
+ <th>Revision name</th>
+ <th>Date</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr
+ *ngFor="let revision of revisionTable.data; let i = index; let
last = last"
+ [class.cursor-hand]="!last"
+ [class.selected-revision]="revision.message ===
currentSecondRevisionLabel"
+ (click)="!last && onRevisionRowClick(i)"
+ >
+ <td>{{ revision.message }}</td>
+ <td>{{ formatRevisionDate(revision.time) }}</td>
+ </tr>
+ </tbody>
+ </nz-table>
+ </div>
+
+ <div class="revisions-comparator-bar">
+ <nz-select
+ *ngIf="noteRevisions.length > 0"
+ class="revision-select"
+ [ngModel]="selectedFirstRevisionId"
+ nzPlaceHolder="Choose..."
+ (ngModelChange)="onFirstRevisionSelect($event)"
+ >
+ <nz-option
+ *ngFor="let revision of sortedRevisions"
+ [nzValue]="revision.id"
+ [nzLabel]="revision.message + (revision.time ? ' - ' +
formatRevisionDate(revision.time) : '')"
+ ></nz-option>
+ </nz-select>
+ <span class="compare-label">compare with</span>
+ <nz-select
+ *ngIf="noteRevisions.length > 0"
+ class="revision-select"
+ [ngModel]="selectedSecondRevisionId"
+ [nzDisabled]="firstNoteRevisionForCompare === null"
+ nzPlaceHolder="Choose..."
+ (ngModelChange)="onSecondRevisionSelect($event)"
+ >
+ <nz-option
+ *ngFor="let revision of sortedRevisions"
+ [nzValue]="revision.id"
+ [nzLabel]="revision.message + (revision.time ? ' - ' +
formatRevisionDate(revision.time) : '')"
+ ></nz-option>
+ </nz-select>
+ </div>
+
+ <div class="diff-panel">
+ <div class="paragraphs-div">
+ <div
+ *ngFor="let p of mergeNoteRevisionsDiff"
+ class="paragraph-item"
+
[class.paragraph-item-selected]="currentParagraphDiffDisplay?.paragraph?.id ===
p.paragraph.id"
+ (click)="changeCurrentParagraphDiffDisplay(p.paragraph.id)"
+ >
+ <div class="paragraph-item-heading">
+ <span class="paragraph-id">{{ p.paragraph.id }}</span>
+ <strong *ngIf="p.paragraph.title" class="paragraph-title">({{
p.paragraph.title }})</strong>
+ <nz-tag *ngIf="p.type === 'added'" nzColor="green">added</nz-tag>
+ <nz-tag *ngIf="p.type === 'deleted'" nzColor="red">deleted</nz-tag>
+ <nz-tag *ngIf="p.type === 'compared' && !p.identical"
nzColor="orange">differences</nz-tag>
+ <nz-tag *ngIf="p.type === 'compared' && p.identical"
nzColor="default">identical</nz-tag>
+ <span class="paragraph-first-string">{{ p.firstString }}</span>
+ </div>
+ </div>
+ <div *ngIf="currentSecondRevisionLabel === 'Choose...'"
class="empty-paragraph-message">
+ Please select a revision
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div nz-col [nzSpan]="16" class="code-panel-col">
+ <span class="code-panel-title">
+ Revision:
+ <strong>{{ currentFirstRevisionLabel }} --> {{
currentSecondRevisionLabel }}</strong>
+ </span>
+ <pre *ngIf="currentParagraphDiffDisplay?.type === 'added'"
class="code-panel color-green-row">{{
+ currentParagraphDiffDisplay?.paragraph?.text
+ }}</pre>
+ <pre *ngIf="currentParagraphDiffDisplay?.type === 'deleted'"
class="code-panel color-red-row">{{
+ currentParagraphDiffDisplay?.paragraph?.text
+ }}</pre>
+ <pre *ngIf="currentParagraphDiffDisplay?.type === 'compared'"
class="code-panel"><span
+ *ngFor="let seg of currentParagraphDiffDisplay?.segments"
+ [class.color-green-row]="seg.type === 'insert'"
+ [class.color-red-row]="seg.type === 'delete'"
+ [class.color-black]="seg.type === 'equal'"
+ >{{ seg.text }}</span></pre>
+ <pre *ngIf="currentParagraphDiffDisplay === null" class="code-panel
empty-code-panel">
+ <div>Nothing to display</div>
+ </pre>
</div>
- <nz-divider></nz-divider>
- <div></div>
</div>
diff --git
a/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.less
b/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.less
index 019b5ca53b..8eae3f0cc4 100644
---
a/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.less
+++
b/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.less
@@ -9,4 +9,150 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+@import "theme-mixin";
+.themeMixin({
+ .revisions-comparator {
+ padding: 10px 15px 15px;
+ }
+
+ .commit-tree {
+ margin-bottom: 10px;
+
+ ::ng-deep .ant-table-body {
+ overflow-y: auto !important;
+ }
+ }
+
+ .cursor-hand {
+ cursor: pointer;
+ }
+
+ .selected-revision {
+ background-color: fade(@primary-6, 15%) !important;
+ }
+
+ .revisions-comparator-bar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding-bottom: 12px;
+ flex-wrap: wrap;
+
+ .revision-select {
+ flex: 1;
+ min-width: 100px;
+ display: block;
+ }
+
+ .compare-label {
+ white-space: nowrap;
+ }
+ }
+
+ .diff-panel {
+ border: 1px solid @border-color-base;
+ border-radius: 4px;
+ }
+
+ .paragraphs-div {
+ overflow: auto;
+ max-height: 35vh;
+ }
+
+ .paragraph-item {
+ transition: background-color 200ms ease-out;
+ border-bottom: 1px solid @border-color-split;
+ cursor: pointer;
+
+ &:hover {
+ background-color: fade(@primary-6, 8%);
+ }
+
+ &.paragraph-item-selected {
+ background-color: fade(@primary-6, 15%);
+ }
+ }
+
+ .paragraph-item-heading {
+ padding: 8px 12px;
+ }
+
+ .paragraph-id {
+ font-family: monospace;
+ font-size: 12px;
+ color: @text-color-secondary;
+ }
+
+ .paragraph-title {
+ padding: 0 5px;
+ }
+
+ .paragraph-first-string {
+ display: block;
+ height: 1.8em;
+ overflow: hidden;
+ padding-top: 4px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ font-size: 12px;
+ color: @text-color-secondary;
+ }
+
+ .empty-paragraph-message {
+ font-size: 1.5em;
+ color: @text-color-secondary;
+ text-align: center;
+ padding: 40px 0;
+ }
+
+ .code-panel-col {
+ display: flex;
+ flex-direction: column;
+ }
+
+ .code-panel-title {
+ font-size: 14px;
+ padding: 5px 0 8px;
+ }
+
+ .code-panel {
+ flex: 1;
+ width: 100%;
+ min-height: 50vh;
+ max-height: 70vh;
+ overflow-y: auto;
+ border: 1px solid @border-color-base;
+ border-radius: 4px;
+ padding: 8px;
+ margin: 0;
+ font-size: 13px;
+ }
+
+ .empty-code-panel {
+ text-align: center;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 24px;
+ color: @text-color-secondary;
+ }
+
+ ::ng-deep {
+ .color-green-row {
+ background-color: fade(@green-6, 15%);
+ display: block;
+ color: @green-6;
+ }
+
+ .color-red-row {
+ background-color: fade(@red-6, 15%);
+ display: block;
+ color: @red-6;
+ }
+
+ .color-black {
+ color: inherit;
+ }
+ }
+});
diff --git
a/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.ts
b/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.ts
index 1876b3cbbd..3b45e77d44 100644
---
a/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.ts
+++
b/zeppelin-web-angular/src/app/pages/workspace/notebook/revisions-comparator/revisions-comparator.component.ts
@@ -10,16 +10,204 @@
* limitations under the License.
*/
-import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { DatePipe } from '@angular/common';
+import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input,
OnDestroy, OnInit } from '@angular/core';
+import * as DiffMatchPatch from 'diff-match-patch';
+import { Subscription } from 'rxjs';
+
+import { NoteRevisionForCompareReceived, OP, ParagraphItem, RevisionListItem }
from '@zeppelin/sdk';
+import { MessageService } from '@zeppelin/services';
+
+interface DiffSegment {
+ type: 'insert' | 'delete' | 'equal';
+ text: string;
+}
+
+interface MergedParagraphDiff {
+ paragraph: ParagraphItem;
+ firstString: string;
+ type: 'added' | 'deleted' | 'compared';
+ segments?: DiffSegment[];
+ identical?: boolean;
+}
@Component({
selector: 'zeppelin-notebook-revisions-comparator',
templateUrl: './revisions-comparator.component.html',
styleUrls: ['./revisions-comparator.component.less'],
- changeDetection: ChangeDetectionStrategy.OnPush
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [DatePipe]
})
-export class NotebookRevisionsComparatorComponent implements OnInit {
- constructor() {}
+export class NotebookRevisionsComparatorComponent implements OnInit, OnDestroy
{
+ @Input() noteRevisions: RevisionListItem[] = [];
+ @Input() noteId!: string;
+
+ firstNoteRevisionForCompare: NoteRevisionForCompareReceived | null = null;
+ secondNoteRevisionForCompare: NoteRevisionForCompareReceived | null = null;
+ currentFirstRevisionLabel = 'Choose...';
+ currentSecondRevisionLabel = 'Choose...';
+ mergeNoteRevisionsDiff: MergedParagraphDiff[] = [];
+ currentParagraphDiffDisplay: MergedParagraphDiff | null = null;
+ selectedFirstRevisionId: string | null = null;
+ selectedSecondRevisionId: string | null = null;
+ private subscription: Subscription | null = null;
+ private dmp = new DiffMatchPatch();
+
+ get sortedRevisions(): RevisionListItem[] {
+ return [...this.noteRevisions].sort((a, b) => (b.time || 0) - (a.time ||
0));
+ }
+
+ constructor(
+ private messageService: MessageService,
+ private cdr: ChangeDetectorRef,
+ private datePipe: DatePipe
+ ) {}
+
+ ngOnInit(): void {
+ this.subscription = this.messageService
+ .receive(OP.NOTE_REVISION_FOR_COMPARE)
+ .subscribe((data: NoteRevisionForCompareReceived) => {
+ if (data.note && data.position) {
+ if (data.position === 'first') {
+ this.firstNoteRevisionForCompare = data;
+ } else {
+ this.secondNoteRevisionForCompare = data;
+ }
+
+ if (
+ this.firstNoteRevisionForCompare !== null &&
+ this.secondNoteRevisionForCompare !== null &&
+ this.firstNoteRevisionForCompare.revisionId !==
this.secondNoteRevisionForCompare.revisionId
+ ) {
+ this.compareRevisions();
+ }
+ this.cdr.markForCheck();
+ }
+ });
+ }
+
+ getNoteRevisionForReview(revision: RevisionListItem, position: 'first' |
'second'): void {
+ if (!revision) {
+ return;
+ }
+ if (position === 'first') {
+ this.currentFirstRevisionLabel = revision.message;
+ this.selectedFirstRevisionId = revision.id;
+ } else {
+ this.currentSecondRevisionLabel = revision.message;
+ this.selectedSecondRevisionId = revision.id;
+ }
+ this.messageService.noteRevisionForCompare(this.noteId, revision.id,
position);
+ }
+
+ onFirstRevisionSelect(revisionId: string): void {
+ const revision = this.noteRevisions.find(r => r.id === revisionId);
+ if (revision) {
+ this.getNoteRevisionForReview(revision, 'first');
+ }
+ }
+
+ onSecondRevisionSelect(revisionId: string): void {
+ const revision = this.noteRevisions.find(r => r.id === revisionId);
+ if (revision) {
+ this.getNoteRevisionForReview(revision, 'second');
+ }
+ }
+
+ onRevisionRowClick(index: number): void {
+ const sorted = this.sortedRevisions;
+ if (index < sorted.length - 1) {
+ this.getNoteRevisionForReview(sorted[index + 1], 'first');
+ this.getNoteRevisionForReview(sorted[index], 'second');
+ }
+ }
+
+ compareRevisions(): void {
+ if (!this.firstNoteRevisionForCompare ||
!this.secondNoteRevisionForCompare) {
+ return;
+ }
+ const baseParagraphs = this.secondNoteRevisionForCompare.note?.paragraphs
|| [];
+ const compareParagraphs =
this.firstNoteRevisionForCompare.note?.paragraphs || [];
+ const paragraphDiffs: MergedParagraphDiff[] = [];
+
+ for (const p1 of baseParagraphs) {
+ const p2 = compareParagraphs.find((p: ParagraphItem) => p.id === p1.id)
|| null;
+ if (p2 === null) {
+ paragraphDiffs.push({
+ paragraph: p1,
+ firstString: (p1.text || '').split('\n')[0],
+ type: 'added'
+ });
+ } else {
+ const text1 = p1.text || '';
+ const text2 = p2.text || '';
+ const diffResult = this.buildLineDiff(text1, text2);
+ paragraphDiffs.push({
+ paragraph: p1,
+ segments: diffResult.segments,
+ identical: diffResult.identical,
+ firstString: (p1.text || '').split('\n')[0],
+ type: 'compared'
+ });
+ }
+ }
+
+ for (const p2 of compareParagraphs) {
+ const p1 = baseParagraphs.find((p: ParagraphItem) => p.id === p2.id) ||
null;
+ if (p1 === null) {
+ paragraphDiffs.push({
+ paragraph: p2,
+ firstString: (p2.text || '').split('\n')[0],
+ type: 'deleted'
+ });
+ }
+ }
+
+ this.mergeNoteRevisionsDiff = paragraphDiffs;
+
+ if (this.currentParagraphDiffDisplay !== null) {
+
this.changeCurrentParagraphDiffDisplay(this.currentParagraphDiffDisplay.paragraph.id);
+ }
+ }
+
+ changeCurrentParagraphDiffDisplay(paragraphId: string): void {
+ const found = this.mergeNoteRevisionsDiff.find(p => p.paragraph.id ===
paragraphId);
+ this.currentParagraphDiffDisplay = found || null;
+ }
+
+ formatRevisionDate(time: number | undefined): string {
+ if (!time) {
+ return '';
+ }
+ return this.datePipe.transform(time * 1000, 'MMMM d yyyy, h:mm:ss a') ||
'';
+ }
+
+ private buildLineDiff(text1: string, text2: string): { segments:
DiffSegment[]; identical: boolean } {
+ const { chars1, chars2, lineArray } = this.dmp.diff_linesToChars_(text1,
text2);
+ const diffs = this.dmp.diff_main(chars1, chars2, false);
+ this.dmp.diff_charsToLines_(diffs, lineArray);
+
+ let identical = true;
+ const segments: DiffSegment[] = [];
+
+ for (const [op, text] of diffs) {
+ if (op === DiffMatchPatch.DIFF_INSERT) {
+ segments.push({ type: 'insert', text });
+ identical = false;
+ } else if (op === DiffMatchPatch.DIFF_DELETE) {
+ segments.push({ type: 'delete', text });
+ identical = false;
+ } else {
+ segments.push({ type: 'equal', text });
+ }
+ }
+
+ return { segments, identical };
+ }
- ngOnInit() {}
+ ngOnDestroy(): void {
+ if (this.subscription) {
+ this.subscription.unsubscribe();
+ }
+ }
}
diff --git a/zeppelin-web-angular/src/styles/theme/dark-theme-overrides.css
b/zeppelin-web-angular/src/styles/theme/dark-theme-overrides.css
index 6b762b2315..df1106ec29 100644
--- a/zeppelin-web-angular/src/styles/theme/dark-theme-overrides.css
+++ b/zeppelin-web-angular/src/styles/theme/dark-theme-overrides.css
@@ -166,6 +166,21 @@ html.dark .ant-menu-submenu-title:hover {
color: rgba(255, 255, 255, 0.95) !important;
}
+html.dark .ant-select-selector {
+ background-color: #262626 !important;
+ border-color: #434343 !important;
+ color: rgba(255, 255, 255, 0.85) !important;
+}
+
+html.dark .ant-select-selector:hover,
+html.dark .ant-select-selector:focus {
+ border-color: #177ddc !important;
+}
+
+html.dark .ant-select-item-option-active:not(html.dark
.ant-select-item-option-disabled) {
+ background-color: #262626 !important;
+}
+
html.dark .ant-dropdown-menu-item-selected,
html.dark .ant-dropdown-menu-submenu-title-selected,
html.dark .ant-dropdown-menu-item-selected > a,