Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
[animated]="true" [value]="100" color="primary"></c-progress-bar>
</c-progress>

<button cButton color="light" size="sm" *ngIf="uploadProgress === -1" (click)="editing = -1">
<button cButton color="light" size="sm" *ngIf="uploadProgress === -1" (click)="stopEditing()">
cancel
</button>
<button cButton color="primary" size="sm" *ngIf="uploadProgress === -1" (click)="updateTuple()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,15 @@
[attr.data-col]="$result().header[j].name"></app-input>
</td>
<td *ngIf="entityConfig().update || entityConfig().delete">
<button cButton color="primary" *ngIf="uploadProgress === -1" id="editColSave"
(click)="updateTuple()"><i class="fa fa-save"></i></button>
@if (uploadProgress === -1) {
<div class="flex">
<button cButton color="secondary" variant="outline" id="editColCancel" class="me-1"
(click)="stopEditing()"><i class="fa fa-undo" aria-hidden="true"></i></button>
<button cButton color="primary" id="editColSave"
(click)="updateTuple()"><i class="fa fa-save"></i></button>
</div>

}
</td>
</ng-container>
<!-- table which shows results -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export abstract class DataTemplateComponent implements OnInit, OnDestroy {
this.webSocket = new WebSocket();
this._route.params.subscribe(route => {
this.currentRoute.set(route['id']);
this.stopEditing();
});

this.entity = computed(() => {
Expand Down Expand Up @@ -382,6 +383,10 @@ export abstract class DataTemplateComponent implements OnInit, OnDestroy {
}
}

stopEditing() {
this.editing = -1;
}

getBoolean(value: any): Boolean {
switch (value) {
case true:
Expand Down
28 changes: 15 additions & 13 deletions src/app/components/data-view/input/input.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,35 @@
<!-- multiple cases: from: https://stackoverflow.com/questions/40176061/two-switch-case-values-in-angular2/40177408#40177408 -->
<ng-container *ngSwitchCase="_types.numericTypes().includes(header.dataType.toLowerCase()) ? header.dataType : ''">
<!-- use input type text to prevent problems when having current values such as "12." (dot at the end) -->
<span cInputGroupText *ngIf="showLabel">{{header.name}}</span>
<span cInputGroupText *ngIf="showLabel">{{ header.name }}</span>

<input cFormControl sizing="sm" type="text" inputmode="decimal" [value]="value"
[disabled]="value === null && header.nullable === true" #inputElement
[ngClass]="validate(inputElement)?.cssClass" (keyup)="onValueChange(inputElement.value, $event)">
[ngClass]="validate(inputElement)?.cssClass" (keyup)="onValueChange(inputElement.value, $event)"
(keydown)="restrictNumericInput($event)"
(paste)="sanitizePastedInput($event)">

<button cButton size="sm" *ngIf="header.nullable"
[ngClass]="{'btn-primary': value === null || value == undefined, 'btn-light': value !== null}"
(click)="onValueChange( triggerNull(value), inputElement )">null
</button>

<c-form-feedback *ngIf="validate(inputElement)?.message">{{validate( inputElement )?.message}}</c-form-feedback>
<c-form-feedback *ngIf="validate(inputElement)?.message">{{ validate( inputElement )?.message }}</c-form-feedback>
</ng-container>

<ng-container *ngSwitchCase="_types.booleanTypes().includes(header.dataType.toLowerCase()) ? header.dataType : ''">
<span cInputGroupText *ngIf="showLabel">{{header.name}}</span>
<span cInputGroupText *ngIf="showLabel">{{ header.name }}</span>
<c-button-group aria-label="boolean entry" class="mx-auto" role="group" size="sm">
<button cButton color="primary" style="border-top-left-radius: 4px !important; border-bottom-left-radius: 4px !important;" (click)="onValueChange(true)" [variant]="value === true ? undefined:'outline'">true</button>
<button cButton color="primary" (click)="onValueChange(false)" [variant]="value === false ? undefined:'outline'">false</button>
@if (header.nullable){
<button cButton color="primary" (click)="onValueChange(null)" [variant]="value === null ? undefined:'outline'" >null</button>
<button cButton color="primary" style="border-top-left-radius: 4px !important; border-bottom-left-radius: 4px !important;" (click)="onValueChange(true)" [variant]="value === true || value === 'true' ? undefined:'outline'">true</button>
<button cButton color="primary" (click)="onValueChange(false)" [variant]="value === false || value === 'false' ? undefined:'outline'">false</button>
@if (header.nullable) {
<button cButton color="primary" (click)="onValueChange(null)" [variant]="value === null ? undefined:'outline'">null</button>
}
</c-button-group>
</ng-container>

<ng-container *ngSwitchCase="_types.dateTimeTypes().includes(header.dataType.toLowerCase()) ? header.dataType : ''">
<span *ngIf="showLabel" cInputGroupText>{{header.name}}</span>
<span *ngIf="showLabel" cInputGroupText>{{ header.name }}</span>

<input type="text" cFormControl sizing="sm" class="fPickerInput" [value]="value"
[placeholder]="header.dataType.toLowerCase() === 'time' ? 'Select time..': 'Select date..'"
Expand All @@ -42,12 +44,12 @@

<ng-container *ngSwitchCase="_types.multimediaTypes().includes(header.dataType.toLowerCase()) ? header.dataType : ''">

<span *ngIf="showLabel" cInputGroupText>{{header.name}}</span>
<span *ngIf="showLabel" cInputGroupText>{{ header.name }}</span>

<label [for]="'customFile'+randomId" class="form-control form-control-sm file-label">
<!-- see https://stackoverflow.com/questions/49976714/how-to-upload-the-same-file-in-angular4 -->
<input type="file" style="display: none;" [id]="'customFile'+randomId" (change)="onFileChange($event.target.files)" #fileInput>
{{inputFileName}}
{{ inputFileName }}
</label>

<button cButton size="sm" color="primary" class="mb-0" *ngIf="value !== null && header.nullable"
Expand All @@ -60,7 +62,7 @@
</ng-container>

<ng-container *ngSwitchDefault>
<span cInputGroupText *ngIf="showLabel">{{header.name}}</span>
<span cInputGroupText *ngIf="showLabel">{{ header.name }}</span>
<input cFormControl sizing="sm" type="text" [value]="value"
[maxLength]="header.precision || 524288"
[disabled]="value === null && header.nullable === true"
Expand All @@ -74,7 +76,7 @@
(click)="onValueChange( triggerNull(value) )">null
</button>

<c-form-feedback *ngIf="validate(defaultInput)?.message">{{validate( defaultInput )?.message}}</c-form-feedback>
<c-form-feedback *ngIf="validate(defaultInput)?.message">{{ validate( defaultInput )?.message }}</c-form-feedback>
</ng-container>

</ng-container>
Expand Down
67 changes: 54 additions & 13 deletions src/app/components/data-view/input/input.component.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,4 @@
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
inject,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
ViewChild
} from '@angular/core';
import {AfterViewInit, Component, ElementRef, EventEmitter, inject, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild} from '@angular/core';
import {FieldDefinition, UiColumnDefinition} from '../models/result-set.model';
import {DbmsTypesService} from '../../../services/dbms-types.service';
import * as $ from 'jquery';
Expand Down Expand Up @@ -244,4 +232,57 @@ export class InputComponent implements OnInit, OnChanges, AfterViewInit {
this.valueChange.emit(file);
}

// Somewhat hacky fix to restrict input to numbers without relying on type="number".
restrictNumericInput(event: KeyboardEvent) {
const allowedKeys = [
'Backspace', 'Tab', 'ArrowLeft', 'ArrowRight', 'Delete', 'Home', 'End'
];

// Allow navigation and control keys
if (allowedKeys.includes(event.key)) {
return;
}

// Allow digits, one dot, and one minus at the start
const current = (event.target as HTMLInputElement).value;
const key = event.key;

// Prevent multiple dots
if (key === '.' && current.includes('.')) {
event.preventDefault();
return;
}

// Prevent minus except at the beginning
if (key === '-' && current.length > 0) {
event.preventDefault();
return;
}

// Prevent non-numeric characters
if (!/[0-9.-]/.test(key)) {
event.preventDefault();
}
}

// Enables pasting via context menu
sanitizePastedInput(event: ClipboardEvent) {
const pasted = event.clipboardData?.getData('text') ?? '';
const sanitized = pasted.replace(/[^0-9.-]/g, '');

event.preventDefault();

const input = event.target as HTMLInputElement;
const {selectionStart, selectionEnd, value} = input;

// Replace selected text with sanitized paste
input.value =
value.slice(0, selectionStart ?? 0) +
sanitized +
value.slice(selectionEnd ?? 0);

// Trigger manual input event
const nativeInputEvent = new Event('input', {bubbles: true});
input.dispatchEvent(nativeInputEvent);
}
}
6 changes: 4 additions & 2 deletions src/app/components/toast-exposer/toast-exposer.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ export class ToastExposerComponent implements OnInit {
color: toast.type,
autohide: true
};
const componentRef: ComponentRef<ToastComponent> = this.toaster.addToast(ToastComponent, {...options});
componentRef.instance.toast.next(toast);
if (this.toaster) {
const componentRef: ComponentRef<ToastComponent> = this.toaster.addToast(ToastComponent, {...options});
componentRef.instance.toast.next(toast);
}
}
});
}
Expand Down
22 changes: 15 additions & 7 deletions src/app/components/toast-exposer/toast/toast.component.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
<ng-container>
<div class="p-3">
<c-toast-header [closeButton]="true" [class]="(toast|async)?.type">
<strong class="me-auto" [ngClass]="{'hasGeneratedQuery': (toast | async)?.generatedQuery}"
(click)="copyGeneratedQuery(toast.value)">
{{(toast | async)?.title}}
<i class="fa fa-code" *ngIf="(toast | async)?.generatedQuery"></i>

<strong class="me-2">
{{ (toast | async)?.title }}
</strong>
@if ((toast | async)?.generatedQuery) {
<span class="me-3 copy-query-btn" title="Copy query" (click)="copyGeneratedQuery(toast.value)">
@if (showCopied) {
<span>Copied!</span>
} @else {
<i class="fa fa-code" *ngIf="(toast | async)?.generatedQuery"></i>
}
</span>
}
</c-toast-header>
<c-toast-body #t [cToastClose]="t.toast" class="text-bg-light">

<div (click)="showStackTraceModal(toast.value)">
{{(toast | async)?.message}}
{{ (toast | async)?.message }}
</div>
<ng-content></ng-content>
<c-progress thin="true">
Expand Down Expand Up @@ -42,10 +50,10 @@ <h5>Exception</h5>
<ng-container>
<c-toast-header [closeButton]="true">
<toast-sample-icon></toast-sample-icon>
<strong>{{e.message}}</strong>
<strong>{{ e.message }}</strong>
</c-toast-header>
<c-toast-body #toast [cToastClose]="toast.toast">
<p class="mb-1">This is a dynamic toast no {{toast.toast?.index}} {{toast.toast?.clock}}</p>
<p class="mb-1">This is a dynamic toast no {{ toast.toast?.index }} {{ toast.toast?.clock }}</p>
<ng-content></ng-content>
<c-progress thin>
<c-progress-bar [value]="25*(toast.toast?.clock ?? 1)"></c-progress-bar>
Expand Down
12 changes: 12 additions & 0 deletions src/app/components/toast-exposer/toast/toast.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.copy-query-btn {
cursor: pointer;
}

.copy-query-btn .fa-code {
transition: transform 0.1s ease, color 0.1s ease;
}

.copy-query-btn:hover .fa-code {
transform: scale(1.1);
color: black;
}
10 changes: 8 additions & 2 deletions src/app/components/toast-exposer/toast/toast.component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {ChangeDetectorRef, Component, ElementRef, forwardRef, Input, Renderer2, ViewChild} from '@angular/core';
import {ChangeDetectorRef, Component, ElementRef, forwardRef, inject, Input, Renderer2, ViewChild} from '@angular/core';
import {Toast} from '../toaster.model';
import {KeyValue} from '@angular/common';
import {ModalDirective} from 'ngx-bootstrap/modal';
import {ResultException} from '../../data-view/models/result-set.model';

import {ToastComponent as ToastParent, ToasterService} from '@coreui/angular';
import {BehaviorSubject} from 'rxjs';
import {UtilService} from '../../../services/util.service';

@Component({
selector: 'app-toast',
Expand All @@ -23,6 +24,9 @@ export class ToastComponent extends ToastParent {

public toast: BehaviorSubject<Toast> = new BehaviorSubject<Toast>(null);

public readonly _util = inject(UtilService);
showCopied = false;

constructor(
public override hostElement: ElementRef,
public override renderer: Renderer2,
Expand Down Expand Up @@ -58,7 +62,9 @@ export class ToastComponent extends ToastParent {
}

copyGeneratedQuery(toast: Toast) {
//this._util.clipboard(toast.generatedQuery);
this._util.clipboard(toast.generatedQuery);
this.showCopied = true;
setTimeout(() => this.showCopied = false, 3500);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -456,13 +456,15 @@ export class EditNotebookComponent implements OnInit, OnChanges, OnDestroy {
this._notebooks.restartKernel(this.session.kernel.id).pipe(
tap(() => this.nb.setKernelStatusBusy()),
delay(2500) // time for the kernel to restart
).subscribe(() => {
this.nb.requestExecutionState();
if (this.executeAllAfterRestart) {
this.nb.executeAll();
}
}, () => {
this._toast.error('Unable to restart the kernel.');
).subscribe({
next: () => {
this.nb.requestExecutionState();
this._toast.success('Kernel has been restarted.');
if (this.executeAllAfterRestart) {
this.nb.executeAll();
}
},
error: () => this._toast.error('Unable to restart the kernel.')
});
this.restartKernelModal.hide();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
(mouseleave)="resetDeleteConfirm()"
tooltip="Delete cell"
container="body">
<i class="no-select-cell"
<i class="no-select-cell text-danger"
[ngClass]="{'cil-trash': !confirmingDeletion, 'fa fa-warning': confirmingDeletion}"></i>
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,8 @@
import {KernelSpec, NotebookContent} from '../../models/notebooks-response.model';
import {
CellDisplayDataOutput,
CellErrorOutput,
CellExecuteResultOutput,
CellOutputType,
CellStreamOutput,
Notebook,
NotebookCell
} from '../../models/notebook.model';
import {CellDisplayDataOutput, CellErrorOutput, CellExecuteResultOutput, CellOutputType, CellStreamOutput, Notebook, NotebookCell} from '../../models/notebook.model';
import * as uuid from 'uuid';
import {NotebooksWebSocket} from '../../services/notebooks-webSocket';
import {
KernelDisplayData,
KernelErrorMsg,
KernelExecuteInput,
KernelExecuteReply,
KernelExecuteResult,
KernelInterruptReply,
KernelMsg,
KernelShutdownReply,
KernelStatus,
KernelStream,
KernelUpdateDisplayData
} from '../../models/kernel-response.model';
import {KernelDisplayData, KernelErrorMsg, KernelExecuteInput, KernelExecuteReply, KernelExecuteResult, KernelInterruptReply, KernelMsg, KernelShutdownReply, KernelStatus, KernelStream, KernelUpdateDisplayData} from '../../models/kernel-response.model';
import {interval, Subscription} from 'rxjs';

export class NotebookWrapper {
Expand Down Expand Up @@ -58,7 +38,7 @@ export class NotebookWrapper {
this.kernelStatus = 'unknown';
});
this.socket.requestExecutionState();
this.keepAlive = interval(60000).subscribe(() => this.socket?.requestExecutionState()); // prevent 300s timeout
this.keepAlive = interval(10000).subscribe(() => this.socket?.requestExecutionState()); // prevent 300s timeout
}

closeSocket() {
Expand Down
Loading