import {
AfterViewInit,
ApplicationRef,
ChangeDetectorRef,
Component,
Directive,
ElementRef,
EventEmitter,
HostBinding,
HostListener,
Injector,
Input,
OnChanges,
OnDestroy,
OnInit,
Optional,
Output,
SkipSelf
} from "@angular/core";
import {WindowComponent} from "../window/window.component";
import {WindowState} from "../window/window-state";
import {FormComponent} from "../form/form.component";
import {ngValueAccessor, ValueAccessorBase} from "../../core/form";
@Component({
selector: 'dui-button',
template: `
`,
host: {
'[attr.tabindex]': '1',
'[class.icon]': '!!icon',
'[class.small]': 'small !== false',
'[class.tight]': 'tight !== false',
'[class.active]': 'active !== false',
'[class.highlighted]': 'highlighted !== false',
'[class.primary]': 'primary !== false',
'[class.icon-left]': 'iconRight === false',
'[class.icon-right]': 'iconRight !== false',
},
styleUrls: ['./button.component.scss'],
})
export class ButtonComponent implements OnInit {
/**
* The icon for this button. Either a icon name same as for dui-icon, or an image path.
*/
@Input() icon?: string;
/**
* Change in the icon size. Should not be necessary usually.
*/
@Input() iconSize?: number;
@Input() iconRight?: boolean | '' = false;
@Input() iconColor?: string;
/**
* Whether the button is active (pressed)
*/
@Input() active: boolean | '' = false;
/**
* Whether the button has no padding and smaller font size
*/
@Input() small: boolean | '' = false;
/**
* Whether the button has smaller padding. Better for button with icons.
*/
@Input() tight: boolean | '' = false;
/**
* Whether the button is highlighted.
*/
@Input() highlighted: boolean | '' = false;
/**
* Whether the button is primary.
*/
@Input() primary: boolean | '' = false;
/**
* Whether the button is focused on initial loading.
*/
@Input() focused: boolean | '' = false;
/**
* Whether the button is focused on initial loading.
*/
@Input() submitForm?: FormComponent;
constructor(
public element: ElementRef,
@SkipSelf() public cdParent: ChangeDetectorRef,
@Optional() public formComponent: FormComponent,
) {
this.element.nativeElement.removeAttribute('tabindex');
}
@Input() disabled: boolean = false;
@HostBinding('class.disabled')
get isDisabled() {
if (this.formComponent && this.formComponent.disabled) return true;
if (this.submitForm && (this.submitForm.invalid || this.submitForm.disabled || this.submitForm.submitting)) {
return true;
}
return false !== this.disabled;
}
@Input() square: boolean | '' = false;
@HostBinding('class.square')
get isRound() {
return false !== this.square;
}
@Input() textured: boolean | '' = false;
@HostBinding('class.textured')
get isTextured() {
return false !== this.textured;
}
ngOnInit() {
if (this.focused !== false) {
setTimeout(() => {
this.element.nativeElement.focus();
}, 10);
}
}
@HostListener('click')
async onClick() {
if (this.isDisabled) return;
if (this.submitForm) {
this.submitForm.submitForm();
}
}
}
/**
* Used to group buttons together.
*/
@Component({
selector: 'dui-button-group',
template: '',
host: {
'[class.float-right]': "float==='right'",
'(transitionend)': 'transitionEnded()'
},
styleUrls: ['./button-group.component.scss']
})
export class ButtonGroupComponent implements AfterViewInit, OnDestroy {
/**
* How the button should behave.
* `sidebar` means it aligns with the sidebar. Is the sidebar open, this button-group has a left margin.
* Is it closed, the margin is gone.
*/
@Input() float: 'static' | 'sidebar' | 'float' | 'right' = 'static';
@Input() padding: 'normal' | 'none' = 'normal';
@HostBinding('class.padding-none')
get isPaddingNone() {
return this.padding === 'none';
}
// @HostBinding('class.ready')
// protected init = false;
constructor(
private windowState: WindowState,
private windowComponent: WindowComponent,
private element: ElementRef,
@SkipSelf() protected cd: ChangeDetectorRef,
) {
}
public activateOneTimeAnimation() {
(this.element.nativeElement as HTMLElement).classList.add('with-animation');
}
public sidebarMoved() {
this.updatePaddingLeft();
}
ngOnDestroy(): void {
}
transitionEnded() {
(this.element.nativeElement as HTMLElement).classList.remove('with-animation');
}
ngAfterViewInit(): void {
if (this.float === 'sidebar') {
this.windowState.buttonGroupAlignedToSidebar = this;
}
this.updatePaddingLeft();
}
updatePaddingLeft() {
if (this.float === 'sidebar') {
if (this.windowComponent.content) {
if (this.windowComponent.content!.isSidebarVisible()) {
const newLeft = Math.max(0, this.windowComponent.content!.getSidebarWidth() - this.element.nativeElement.offsetLeft) + 'px';
if (this.element.nativeElement.style.paddingLeft == newLeft) {
//no transition change, doesn't trigger transitionEnd
(this.element.nativeElement as HTMLElement).classList.remove('with-animation');
return;
}
this.element.nativeElement.style.paddingLeft = newLeft;
return;
}
}
}
this.element.nativeElement.style.paddingLeft = '0px';
}
}
@Component({
selector: 'dui-button-groups',
template: `
`,
host: {
'[class.align-left]': `align === 'left'`,
'[class.align-center]': `align === 'center'`,
'[class.align-right]': `align === 'right'`,
},
styleUrls: ['./button-groups.component.scss'],
})
export class ButtonGroupsComponent {
@Input() align: 'left' | 'center' | 'right' = 'left';
}
@Directive({
selector: '[duiFileChooser]',
providers: [ngValueAccessor(FileChooserDirective)]
})
export class FileChooserDirective extends ValueAccessorBase implements OnDestroy, OnChanges {
@Input() duiFileMultiple?: boolean | '' = false;
@Input() duiFileDirectory?: boolean | '' = false;
// @Input() duiFileChooser?: string | string[];
@Output() duiFileChooserChange = new EventEmitter();
protected input: HTMLInputElement;
constructor(
protected injector: Injector,
public readonly cd: ChangeDetectorRef,
@SkipSelf() public readonly cdParent: ChangeDetectorRef,
private app: ApplicationRef,
) {
super(injector, cd, cdParent);
const input = document.createElement('input');
input.setAttribute('type', 'file');
this.input = input;
this.input.addEventListener('change', (event: any) => {
const files = event.target.files as FileList;
if (files.length) {
if (this.duiFileMultiple !== false) {
const paths: string[] = [];
for (let i = 0; i < files.length; i++) {
const file = files.item(i) as any as { path: string, name: string };
paths.push(file.path);
}
this.innerValue = paths;
} else {
const file = files.item(0) as any as { path: string, name: string };
this.innerValue = file.path;
}
this.duiFileChooserChange.emit(this.innerValue);
this.app.tick();
}
})
}
ngOnDestroy() {
}
ngOnChanges(): void {
(this.input as any).webkitdirectory = this.duiFileDirectory !== false;
this.input.multiple = this.duiFileMultiple !== false;
}
@HostListener('click')
onClick() {
this.input.click();
}
}
export interface FilePickerItem {
data: Uint8Array;
name: string;
}
function readFile(file: File): Promise {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
if (reader.result) {
if (reader.result instanceof ArrayBuffer) {
resolve(new Uint8Array(reader.result));
} else {
resolve();
}
}
};
reader.onerror = (error) => {
console.log('Error: ', error);
reject();
};
reader.readAsArrayBuffer(file);
});
}
@Directive({
selector: '[duiFilePicker]',
providers: [ngValueAccessor(FileChooserDirective)]
})
export class FilePickerDirective extends ValueAccessorBase implements OnDestroy {
@Input() duiFileMultiple?: boolean | '' = false;
@Output() duiFilePickerChange = new EventEmitter();
protected input: HTMLInputElement;
constructor(
protected injector: Injector,
public readonly cd: ChangeDetectorRef,
@SkipSelf() public readonly cdParent: ChangeDetectorRef,
private app: ApplicationRef,
) {
super(injector, cd, cdParent);
const input = document.createElement('input');
input.setAttribute('type', 'file');
this.input = input;
this.input.addEventListener('change', async (event: any) => {
const files = event.target.files as FileList;
if (files.length) {
if (this.duiFileMultiple !== false) {
const res: FilePickerItem[] = [];
for (let i = 0; i < files.length; i++) {
const file = files.item(i);
if (file) {
const uint8Array = await readFile(file);
if (uint8Array) {
res.push({data: uint8Array, name: file.name});
}
}
}
this.innerValue = res;
} else {
const file = files.item(0);
if (file) {
this.innerValue = {data: await readFile(file), name: file.name};
}
}
this.duiFilePickerChange.emit(this.innerValue);
this.app.tick();
}
})
}
ngOnDestroy() {
}
@HostListener('click')
onClick() {
this.input.multiple = this.duiFileMultiple !== false;
this.input.click();
}
}
@Directive({
selector: '[duiFileDrop]',
host: {
'[class.hover]': 'hover',
},
providers: [ngValueAccessor(FileChooserDirective)]
})
export class FileDropDirective extends ValueAccessorBase implements OnDestroy {
@Input() duiFileDropMultiple?: boolean | '' = false;
@Output() duiFileDropChange = new EventEmitter();
hover = false;
constructor(
protected injector: Injector,
public readonly cd: ChangeDetectorRef,
@SkipSelf() public readonly cdParent: ChangeDetectorRef,
private app: ApplicationRef,
) {
super(injector, cd, cdParent);
}
@HostListener('dragenter', ['$event'])
onDragEnter(ev: any) {
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
this.hover = true;
this.cdParent.detectChanges();
}
@HostListener('dragover', ['$event'])
onDragOver(ev: any) {
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
}
@HostListener('dragleave', ['$event'])
onDragLeave(ev: any) {
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
this.hover = false;
this.cdParent.detectChanges();
}
@HostListener('drop', ['$event'])
async onDrop(ev: any) {
// Prevent default behavior (Prevent file from being opened)
ev.preventDefault();
const res: FilePickerItem[] = [];
if (ev.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
for (let i = 0; i < ev.dataTransfer.items.length; i++) {
// If dropped items aren't files, reject them
if (ev.dataTransfer.items[i].kind === 'file') {
const file = ev.dataTransfer.items[i].getAsFile();
if (file) {
const uint8Array = await readFile(file);
if (uint8Array) {
res.push({data: uint8Array, name: file.name});
}
}
}
}
} else {
// Use DataTransfer interface to access the file(s)
for (let i = 0; i < ev.dataTransfer.files.length; i++) {
const file = ev.dataTransfer.files.item(i);
if (file) {
const uint8Array = await readFile(file);
if (uint8Array) {
res.push({data: uint8Array, name: file.name});
}
}
}
}
if (this.duiFileDropMultiple !== false) {
this.innerValue = res;
} else {
if (res.length) {
this.innerValue = res[0];
} else {
this.innerValue = undefined;
}
}
this.duiFileDropChange.emit(this.innerValue);
this.hover = false;
this.cdParent.detectChanges();
}
ngOnDestroy() {
}
}