diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-side.component.ts b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-side.component.ts index f3bf9f58..b031b7af 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-side.component.ts +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-side.component.ts @@ -221,6 +221,8 @@ export class LeftSideComponent implements OnInit, OnDestroy { >(MessageWriteDialogComponent, { width: '600px', height: '600px', + disableClose: true, + hasBackdrop: false, data: { loginRes: this.loginRes, environmentsInfo: this.environmentsInfo diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/message/message-write.dialog.component.html b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/message/message-write.dialog.component.html index 8a9c93cd..16eed9f2 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/message/message-write.dialog.component.html +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/message/message-write.dialog.component.html @@ -9,6 +9,7 @@ #messageWrite (send)="onSend($event)" (selectReceiver)="onSelectReceiver($event)" + (cancel)="onCancel()" > diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/message/message-write.dialog.component.ts b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/message/message-write.dialog.component.ts index d24ada79..24238923 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/message/message-write.dialog.component.ts +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/message/message-write.dialog.component.ts @@ -98,14 +98,34 @@ export class MessageWriteDialogComponent implements OnInit { .pipe(take(1)) .subscribe( res => { - this.logger.debug('onSend', res); + let msg = ''; + if (!!message.reservationTime) { + msg = `쪽지 전송을 예약 하였습니다.`; + } else { + msg = `쪽지를 전송하였습니다.`; + } + + this.snackBarService.open(msg, '', { + duration: 3000, + verticalPosition: 'bottom' + }); + + this.dialogRef.close({}); }, error => { - this.logger.debug('onSend', error); + this.snackBarService.open(`쪽지를 전송에 실패 하였습니다.`, '', { + duration: 3000, + verticalPosition: 'bottom' + }); + this.dialogRef.close({}); } ); } + onCancel() { + this.dialogRef.close({}); + } + getBtnValid() { return true; } diff --git a/projects/ucap-webmessenger-app/src/assets/fonts/materialdesignicons/scss/_extras.scss b/projects/ucap-webmessenger-app/src/assets/fonts/materialdesignicons/scss/_extras.scss index bebf66ff..2e178c90 100644 --- a/projects/ucap-webmessenger-app/src/assets/fonts/materialdesignicons/scss/_extras.scss +++ b/projects/ucap-webmessenger-app/src/assets/fonts/materialdesignicons/scss/_extras.scss @@ -1,4 +1,4 @@ -$mdi-sizes: 18 24 36 48; +$mdi-sizes: 18 20 24 36 48; @each $mdi-size in $mdi-sizes { .#{$mdi-css-prefix}-#{$mdi-size}px { &.#{$mdi-css-prefix}-set, diff --git a/projects/ucap-webmessenger-app/src/assets/scss/global/_default.scss b/projects/ucap-webmessenger-app/src/assets/scss/global/_default.scss index 0dc5dba1..46c33237 100644 --- a/projects/ucap-webmessenger-app/src/assets/scss/global/_default.scss +++ b/projects/ucap-webmessenger-app/src/assets/scss/global/_default.scss @@ -294,6 +294,12 @@ $daesang-grey: ( } } + .mat-button-toggle-appearance-standard .mat-button-toggle-label-content { + height: 34px; + line-height: 34px; + padding: 0 12px; + } + .app-dialog-full .mat-dialog-container { overflow: hidden; padding: 0px; diff --git a/projects/ucap-webmessenger-ui-message/src/lib/components/write.component.html b/projects/ucap-webmessenger-ui-message/src/lib/components/write.component.html index f17172ea..2199f21f 100644 --- a/projects/ucap-webmessenger-ui-message/src/lib/components/write.component.html +++ b/projects/ucap-webmessenger-ui-message/src/lib/components/write.component.html @@ -52,17 +52,6 @@
-
- - - - - -
-
- + + + 보내기 - 보내기 - +
diff --git a/projects/ucap-webmessenger-ui-message/src/lib/components/write.component.ts b/projects/ucap-webmessenger-ui-message/src/lib/components/write.component.ts index d4d8ddbb..05b17b8c 100644 --- a/projects/ucap-webmessenger-ui-message/src/lib/components/write.component.ts +++ b/projects/ucap-webmessenger-ui-message/src/lib/components/write.component.ts @@ -10,9 +10,12 @@ import { ElementRef } from '@angular/core'; -import { ucapAnimations } from '@ucap-webmessenger/ui'; +import { ucapAnimations, DialogService } from '@ucap-webmessenger/ui'; import { NGXLogger } from 'ngx-logger'; + +import moment from 'moment'; + import { FileUtil } from '@ucap-webmessenger/core'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { @@ -22,6 +25,11 @@ import { } from '@ucap-webmessenger/api-message'; import { FileUploadItem } from '@ucap-webmessenger/api'; import { UserInfo } from '@ucap-webmessenger/protocol-sync'; +import { + ScheduleSendDialogComponent, + ScheduleSendDialogData, + ScheduleSendDialogResult +} from '../dialogs/schedule-send.dialog.component'; const ATTR_FILE = 'UCAP_ATTR_FILE'; @@ -53,6 +61,9 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit { @Output() send = new EventEmitter(); + @Output() + cancel = new EventEmitter(); + @Output() selectReceiver = new EventEmitter(); @@ -69,6 +80,7 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit { constructor( private formBuilder: FormBuilder, + private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, private logger: NGXLogger ) {} @@ -147,6 +159,31 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit { } onClickSend() { + this.sendMessage(); + } + + onClickCancel() { + this.cancel.emit(); + } + + async onClickSendSchedule() { + const result = await this.dialogService.open< + ScheduleSendDialogComponent, + ScheduleSendDialogData, + ScheduleSendDialogResult + >(ScheduleSendDialogComponent, { + width: '600px', + height: '600px', + disableClose: true, + data: {} + }); + + if (!!result && !!result.scheduleSendDate) { + this.sendMessage(result.scheduleSendDate); + } + } + + private sendMessage(reservationDate?: moment.Moment) { const contentList: Content[] = this.generateContent(); if (!contentList || 0 === contentList.length) { return; @@ -184,18 +221,19 @@ export class WriteComponent implements OnInit, OnDestroy, AfterViewInit { this.send.emit({ category: CategoryType.General, - type: MessageType.Send, + type: !!reservationDate ? MessageType.Reservation : MessageType.Send, title, listOrder, textContent, recvUserList, + reservationTime: !!reservationDate + ? `${reservationDate.format('YYYY-MM-DD HH:mm')}:00` + : undefined, files, fileUploadItem: this.fileUploadItem }); } - onClickCancel() {} - private generateContent(): Content[] { const contentList: Content[] = []; diff --git a/projects/ucap-webmessenger-ui-message/src/lib/dialogs/schedule-send.dialog.component.html b/projects/ucap-webmessenger-ui-message/src/lib/dialogs/schedule-send.dialog.component.html new file mode 100644 index 00000000..29cafcd3 --- /dev/null +++ b/projects/ucap-webmessenger-ui-message/src/lib/dialogs/schedule-send.dialog.component.html @@ -0,0 +1,114 @@ + + + + 예약 발송 + + + +
+
+
+
+ +
+
+ + + + + + +
+
+
+
+
+
+ + {{ selectedDate.format('MMM DD YYYY, HH:mm') }} + +
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ +
+
+ + +
+
+
diff --git a/projects/ucap-webmessenger-ui-message/src/lib/dialogs/schedule-send.dialog.component.scss b/projects/ucap-webmessenger-ui-message/src/lib/dialogs/schedule-send.dialog.component.scss new file mode 100644 index 00000000..b5636aec --- /dev/null +++ b/projects/ucap-webmessenger-ui-message/src/lib/dialogs/schedule-send.dialog.component.scss @@ -0,0 +1,3 @@ +.preset-button { + width: 100%; +} diff --git a/projects/ucap-webmessenger-ui-message/src/lib/dialogs/schedule-send.dialog.component.spec.ts b/projects/ucap-webmessenger-ui-message/src/lib/dialogs/schedule-send.dialog.component.spec.ts new file mode 100644 index 00000000..d852b16b --- /dev/null +++ b/projects/ucap-webmessenger-ui-message/src/lib/dialogs/schedule-send.dialog.component.spec.ts @@ -0,0 +1,24 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ScheduleSendDialogComponent } from './schedule-send.dialog.component'; + +describe('message::ScheduleSendDialogComponent', () => { + let component: ScheduleSendDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ScheduleSendDialogComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ScheduleSendDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/ucap-webmessenger-ui-message/src/lib/dialogs/schedule-send.dialog.component.ts b/projects/ucap-webmessenger-ui-message/src/lib/dialogs/schedule-send.dialog.component.ts new file mode 100644 index 00000000..accde7e4 --- /dev/null +++ b/projects/ucap-webmessenger-ui-message/src/lib/dialogs/schedule-send.dialog.component.ts @@ -0,0 +1,99 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; + +import { NGXLogger } from 'ngx-logger'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import moment from 'moment'; + +// tslint:disable-next-line: no-empty-interface +export interface ScheduleSendDialogData {} + +export interface ScheduleSendDialogResult { + scheduleSendDate?: moment.Moment; +} + +@Component({ + selector: 'ucap-message-schedule-send', + templateUrl: './schedule-send.dialog.component.html', + styleUrls: ['./schedule-send.dialog.component.scss'] +}) +export class ScheduleSendDialogComponent implements OnInit { + scheduleSendForm: FormGroup; + + hourStep = 1; + minuteStep = 10; + + get selectedDate() { + return this._selectedDate; + } + set selectedDate(v: moment.Moment) { + v.hour(this.hourStep * Math.round(v.hour() / this.hourStep)).minute( + this.minuteStep * Math.round(v.minute() / this.minuteStep) + ); + this._selectedDate = v; + } + // tslint:disable-next-line: variable-name + private _selectedDate: moment.Moment; + + constructor( + public dialogRef: MatDialogRef< + ScheduleSendDialogData, + ScheduleSendDialogResult + >, + @Inject(MAT_DIALOG_DATA) public data: ScheduleSendDialogData, + private formBuilder: FormBuilder, + private logger: NGXLogger + ) { + this.selectedDate = moment().add(1, 'hours'); + } + + ngOnInit(): void { + this.scheduleSendForm = this.formBuilder.group({ + scheduleDate: ['', [Validators.required]], + scheduleTime: ['', [Validators.required]] + }); + } + + onSelectedDate(date: moment.Moment) { + this.selectedDate = date; + } + + onHourSelected(hour: number) { + this.selectedDate.hour(hour); + } + onMinuteSelected(minute: number) { + this.selectedDate.minute(minute); + } + + onClickPresetTommorowMorning() { + this.selectedDate = moment() + .add(1, 'days') + .hour(10) + .minute(0); + } + + onClickPresetHour(interval: number) { + this.selectedDate = moment().add(interval, 'hours'); + } + + onClickPresetDay(interval: number) { + this.selectedDate = moment().add(interval, 'days'); + } + + onClickPresetTommorowAfternoon() { + this.selectedDate = moment() + .add(1, 'days') + .hour(15) + .minute(0); + } + + onClickSend() { + this.dialogRef.close({ + scheduleSendDate: this.selectedDate + }); + } + + onClickCancel() { + this.dialogRef.close({}); + } +} diff --git a/projects/ucap-webmessenger-ui-message/src/lib/ucap-ui-message.module.ts b/projects/ucap-webmessenger-ui-message/src/lib/ucap-ui-message.module.ts index 98148b38..4aede558 100644 --- a/projects/ucap-webmessenger-ui-message/src/lib/ucap-ui-message.module.ts +++ b/projects/ucap-webmessenger-ui-message/src/lib/ucap-ui-message.module.ts @@ -18,6 +18,9 @@ import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatListModule } from '@angular/material/list'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; + import { MatMomentDateModule } from '@angular/material-moment-adapter'; import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar'; @@ -27,8 +30,10 @@ import { UCapUiModule } from '@ucap-webmessenger/ui'; import { ListItemComponent } from './components/list-item.component'; import { WriteComponent } from './components/write.component'; +import { ScheduleSendDialogComponent } from './dialogs/schedule-send.dialog.component'; + const COMPONENTS = [ListItemComponent, WriteComponent]; -const DIALOGS = []; +const DIALOGS = [ScheduleSendDialogComponent]; const DIRECTIVES = []; const SERVICES = []; @@ -53,6 +58,9 @@ const SERVICES = []; MatListModule, MatMomentDateModule, + MatMenuModule, + MatButtonToggleModule, + PerfectScrollbarModule, UCapUiModule diff --git a/projects/ucap-webmessenger-ui-message/src/public-api.ts b/projects/ucap-webmessenger-ui-message/src/public-api.ts index a458dd76..fbb8ef71 100644 --- a/projects/ucap-webmessenger-ui-message/src/public-api.ts +++ b/projects/ucap-webmessenger-ui-message/src/public-api.ts @@ -2,6 +2,8 @@ * Public API Surface of ucap-webmessenger-ui-message */ +export * from './lib/components/list-item.component'; export * from './lib/components/write.component'; +export * from './lib/dialogs/schedule-send.dialog.component'; export * from './lib/ucap-ui-message.module'; diff --git a/projects/ucap-webmessenger-ui/src/lib/components/message-editor.component.html b/projects/ucap-webmessenger-ui/src/lib/components/message-editor.component.html deleted file mode 100644 index 6c3813ff..00000000 --- a/projects/ucap-webmessenger-ui/src/lib/components/message-editor.component.html +++ /dev/null @@ -1,25 +0,0 @@ -
- - - - -
-
-
-
diff --git a/projects/ucap-webmessenger-ui/src/lib/components/message-editor.component.scss b/projects/ucap-webmessenger-ui/src/lib/components/message-editor.component.scss deleted file mode 100644 index cb4a5c84..00000000 --- a/projects/ucap-webmessenger-ui/src/lib/components/message-editor.component.scss +++ /dev/null @@ -1,9 +0,0 @@ -.container { - height: 500px; - overflow: auto; - - .editor { - height: 100%; - width: 100%; - } -} diff --git a/projects/ucap-webmessenger-ui/src/lib/components/message-editor.component.spec.ts b/projects/ucap-webmessenger-ui/src/lib/components/message-editor.component.spec.ts deleted file mode 100644 index cafed007..00000000 --- a/projects/ucap-webmessenger-ui/src/lib/components/message-editor.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { MessageEditorComponent } from './message-editor.component'; - -describe('MessageEditorComponent', () => { - let component: MessageEditorComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ MessageEditorComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(MessageEditorComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/projects/ucap-webmessenger-ui/src/lib/components/message-editor.component.ts b/projects/ucap-webmessenger-ui/src/lib/components/message-editor.component.ts deleted file mode 100644 index ec1baf54..00000000 --- a/projects/ucap-webmessenger-ui/src/lib/components/message-editor.component.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { - Component, - OnInit, - ElementRef, - ViewChild, - AfterViewInit, - HostListener -} from '@angular/core'; - -@Component({ - selector: 'ucap-message-editor', - templateUrl: './message-editor.component.html', - styleUrls: ['./message-editor.component.scss'] -}) -export class MessageEditorComponent implements OnInit, AfterViewInit { - @ViewChild('fileInput', { static: false }) - fileInput: ElementRef; - - @ViewChild('contentArea', { static: true }) contentArea: ElementRef; - - attachFileType: string; - - fileIndex = 0; - attachFiles: { file: File; idx: number }[] = []; - imageFiles: { file: File; idx: number }[] = []; - - constructor() {} - - ngOnInit() {} - - ngAfterViewInit(): void { - // setTimeout(() => { - // this.contentArea.nativeElement.focus(); - // document - // .querySelector('#contentArea') - // .addEventListener('paste', (event: ClipboardEvent) => { - // event.stopPropagation(); - // event.preventDefault(); - // const clipboardData = event.clipboardData; - // let pastedData = clipboardData.getData('Text'); - // pastedData = this.convertHtmltoEntity(pastedData); - // clipboardData.setData('Text', pastedData); - // const div = document.createElement('div'); - // div.innerHTML = pastedData; - // this.attachElementNextFocused(div); - // }); - // document - // .querySelector('#contentArea') - // .addEventListener('keyup', event => { - // if ( - // document.querySelector('#contentArea').innerHTML === '' || - // document.querySelector('#contentArea').innerHTML === '
' - // ) { - // const div = document.createElement('div'); - // div.innerHTML = ' 
'; - // document.querySelector('#contentArea').innerHTML = ''; - // this.attachElementNextFocused(div); - // } - // }); - // // init.. - // if (document.querySelector('#contentArea').innerHTML === '') { - // const div = document.createElement('div'); - // div.innerHTML = ' 
'; - // this.attachElementNextFocused(div); - // } - // }, 700); - } - - onClickFileInput(type: string) { - this.attachFileType = type; - if (type === 'attach') { - this.fileInput.nativeElement.setAttribute('multiple', 'true'); - this.fileInput.nativeElement.setAttribute('accept', '*.*'); - } else { - this.fileInput.nativeElement.removeAttribute('multiple'); - this.fileInput.nativeElement.setAttribute('accept', 'image/*'); - } - this.fileInput.nativeElement.click(); - } - - onChangeFileInput() { - const files: FileList = this.fileInput.nativeElement.files; - - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < files.length; i++) { - if (this.attachFileType === 'attach') { - this.attachFiles.push({ file: files[i], idx: this.fileIndex }); - } else { - this.imageFiles.push({ file: files[i], idx: this.fileIndex }); - // this.addImageInInputField(files[i], this.fileIndex); - } - this.fileIndex++; - } - this.fileInput.nativeElement.value = ''; - } - - getContents(): string { - return 'this is contents'; - } - - // addImageInInputField(image: File, index: number) { - // const reader = new FileReader(); - // reader.readAsDataURL(image); - // reader.onloadend = () => { - // const img = document.createElement('img'); - // img.setAttribute('src', reader.result.toString()); - // img.setAttribute('data-seq', index.toString()); - - // this.attachElementNextFocused(img); - // }; - // } - - // attachElementNextFocused(el: HTMLElement) { - // if (window.getSelection) { - // const sel = window.getSelection(); - // const selAnchorNode: Node = sel.anchorNode; - // const focusedEl = selAnchorNode.parentElement; - - // if (this.isParentIdCheck(focusedEl, 'contentArea')) { - // if (sel.rangeCount) { - // const range = sel.getRangeAt(0); - - // if (selAnchorNode.nodeType === Node.TEXT_NODE) { - // const content: string = focusedEl.textContent; - - // focusedEl.innerText = - // content.substr(0, range.startOffset) + - // el.textContent + - // content.substr(range.endOffset); - // } else if ( - // selAnchorNode.nodeType === Node.ELEMENT_NODE && - // focusedEl.id === 'contentArea' - // ) { - // const selEl = selAnchorNode as HTMLElement; - // const content: string = selEl.textContent; - - // selEl.innerText = - // content.substr(0, range.startOffset) + - // el.textContent + - // content.substr(range.endOffset); - // } else { - // focusedEl.append(el); - // } - // } else { - // focusedEl.append(el); - // } - // } else { - // this.contentArea.nativeElement.appendChild(el); - // } - // } else { - // this.contentArea.nativeElement.appendChild(el); - // } - // } - - // isParentIdCheck(el: HTMLElement, id: string): boolean { - // if (el.id === id) { - // return true; - // } else { - // if (el.tagName === 'BODY' || el.tagName === 'body') { - // return false; - // } else { - // return this.isParentIdCheck(el.parentElement, id); - // } - // } - // } - - test(): void { - if (!!this.contentArea) { - const els: HTMLCollection = this.contentArea.nativeElement.children; - - // tslint:disable-next-line: prefer-for-of - for (let i = 0; i < els.length; i++) { - console.log(this.convertEntitytoHtml(els[i].textContent)); - } - } - } - - convertHtmltoEntity(str: string): string { - return str - .replace(/ /g, '  ') - .replace(//g, '>') - .replace(/\n/g, '
'); - } - convertEntitytoHtml(str: string): string { - return str - .replace(/ /g, ' ') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/
/g, ' \n'); - } -} diff --git a/projects/ucap-webmessenger-ui/src/lib/components/pick-date.component.html b/projects/ucap-webmessenger-ui/src/lib/components/pick-date.component.html new file mode 100644 index 00000000..6deec282 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/pick-date.component.html @@ -0,0 +1,10 @@ +
+ + +
diff --git a/projects/ucap-webmessenger-ui/src/lib/components/pick-date.component.scss b/projects/ucap-webmessenger-ui/src/lib/components/pick-date.component.scss new file mode 100644 index 00000000..5143d639 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/pick-date.component.scss @@ -0,0 +1,4 @@ +.ucap-pick-date-container { + width: 100%; + height: 100%; +} diff --git a/projects/ucap-webmessenger-ui/src/lib/components/pick-date.component.spec.ts b/projects/ucap-webmessenger-ui/src/lib/components/pick-date.component.spec.ts new file mode 100644 index 00000000..47d0b420 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/pick-date.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { PickDateComponent } from './pick-date.component'; + +describe('PickDateComponent', () => { + let component: PickDateComponent; + let fixture: ComponentFixture>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [PickDateComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PickDateComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/ucap-webmessenger-ui/src/lib/components/pick-date.component.ts b/projects/ucap-webmessenger-ui/src/lib/components/pick-date.component.ts new file mode 100644 index 00000000..c936a745 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/pick-date.component.ts @@ -0,0 +1,59 @@ +import { + Component, + OnInit, + Input, + Output, + EventEmitter, + ViewChild, + Optional +} from '@angular/core'; +import { ucapAnimations } from '../animations'; +import { MatCalendar, DateAdapter } from '@angular/material'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'ucap-pick-date', + templateUrl: './pick-date.component.html', + styleUrls: ['./pick-date.component.scss'], + animations: ucapAnimations +}) +export class PickDateComponent implements OnInit { + @Input() + initSelected: D; + + @Output() + readonly selected: EventEmitter = new EventEmitter(); + + @Output() + readonly yearSelected: EventEmitter = new EventEmitter(); + + @Output() + readonly monthSelected: EventEmitter = new EventEmitter(); + + @ViewChild('calendar', { static: true }) + calendar: MatCalendar; + + // tslint:disable-next-line: variable-name + constructor(@Optional() private _dateAdapter: DateAdapter) {} + + ngOnInit() {} + + /** Selects the given date */ + select(date: D): void { + const oldValue = this.initSelected; + this.initSelected = date; + if (!this._dateAdapter.sameDate(oldValue, this.initSelected)) { + this.selected.emit(date); + } + } + + /** Emits the selected year in multiyear view */ + _selectYear(normalizedYear: D): void { + this.yearSelected.emit(normalizedYear); + } + + /** Emits selected month in year view */ + _selectMonth(normalizedMonth: D): void { + this.monthSelected.emit(normalizedMonth); + } +} diff --git a/projects/ucap-webmessenger-ui/src/lib/components/pick-time.component.html b/projects/ucap-webmessenger-ui/src/lib/components/pick-time.component.html new file mode 100644 index 00000000..92f24120 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/pick-time.component.html @@ -0,0 +1,25 @@ +
+ + + + : + + +
diff --git a/projects/ucap-webmessenger-ui/src/lib/components/pick-time.component.scss b/projects/ucap-webmessenger-ui/src/lib/components/pick-time.component.scss new file mode 100644 index 00000000..260c3809 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/pick-time.component.scss @@ -0,0 +1,15 @@ +.ucap-pick-time-container { + width: 100%; + height: 100%; + + display: flex; + align-items: center; + border-bottom: 1px solid rgba(0, 0, 0, 0.12); + + .hour-input { + width: 40px; + } + .minute-input { + width: 40px; + } +} diff --git a/projects/ucap-webmessenger-ui/src/lib/components/pick-time.component.spec.ts b/projects/ucap-webmessenger-ui/src/lib/components/pick-time.component.spec.ts new file mode 100644 index 00000000..9e3404a1 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/pick-time.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { PickTimeComponent } from './pick-time.component'; + +describe('PickTimeComponent', () => { + let component: PickTimeComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [PickTimeComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PickTimeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/ucap-webmessenger-ui/src/lib/components/pick-time.component.ts b/projects/ucap-webmessenger-ui/src/lib/components/pick-time.component.ts new file mode 100644 index 00000000..f6ac40c5 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/pick-time.component.ts @@ -0,0 +1,47 @@ +import { + Component, + OnInit, + Input, + Output, + EventEmitter, + ViewChild, + Optional +} from '@angular/core'; +import { ucapAnimations } from '../animations'; + +@Component({ + selector: 'ucap-pick-time', + templateUrl: './pick-time.component.html', + styleUrls: ['./pick-time.component.scss'], + animations: ucapAnimations +}) +export class PickTimeComponent implements OnInit { + @Input() + hour: number; + + @Input() + hourStep = 1; + + @Input() + minute: number; + + @Input() + minuteStep = 1; + + @Output() + readonly hourSelected: EventEmitter = new EventEmitter(); + + @Output() + readonly minuteSelected: EventEmitter = new EventEmitter(); + + constructor() {} + + ngOnInit() {} + + onChangedHour(hour: number) { + this.hourSelected.emit(hour); + } + onChangedMinute(minute: number) { + this.minuteSelected.emit(minute); + } +} diff --git a/projects/ucap-webmessenger-ui/src/lib/components/split-button.component.html b/projects/ucap-webmessenger-ui/src/lib/components/split-button.component.html new file mode 100644 index 00000000..3f57c581 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/split-button.component.html @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/projects/ucap-webmessenger-ui/src/lib/components/split-button.component.scss b/projects/ucap-webmessenger-ui/src/lib/components/split-button.component.scss new file mode 100644 index 00000000..a1624ec3 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/split-button.component.scss @@ -0,0 +1,5 @@ +.ucap-split-button-container { + .mat-button-toggle-label-content { + height: 36px !important; + } +} diff --git a/projects/ucap-webmessenger-ui/src/lib/components/split-button.component.spec.ts b/projects/ucap-webmessenger-ui/src/lib/components/split-button.component.spec.ts new file mode 100644 index 00000000..820d0ab1 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/split-button.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { SplitButtonComponent } from './split-button.component'; + +describe('SplitButtonComponent', () => { + let component: SplitButtonComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [SplitButtonComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SplitButtonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/ucap-webmessenger-ui/src/lib/components/split-button.component.ts b/projects/ucap-webmessenger-ui/src/lib/components/split-button.component.ts new file mode 100644 index 00000000..259498ad --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/split-button.component.ts @@ -0,0 +1,71 @@ +import { + Component, + OnInit, + Input, + Output, + EventEmitter, + ViewChild, + OnDestroy, + ElementRef, + Renderer2 +} from '@angular/core'; +import { MatMenuPanel, MatMenuTrigger, MatButton } from '@angular/material'; +import { Subscription } from 'rxjs'; +import { NGXLogger } from 'ngx-logger'; + +@Component({ + selector: 'ucap-split-button', + templateUrl: './split-button.component.html', + styleUrls: ['./split-button.component.scss'] +}) +export class SplitButtonComponent implements OnInit, OnDestroy { + @Input() + menu: MatMenuPanel; + + @Input() + disabled: boolean; + + @Output() + buttonClick = new EventEmitter(); + + @Output() + splitClick = new EventEmitter(); + + @ViewChild('matMenuTrigger', { static: true }) + private matMenuTrigger: MatMenuTrigger; + + // tslint:disable-next-line: variable-name + private _handleClick: (event: MouseEvent) => void; + private menuCloseSubscription: Subscription; + private splitOpened = false; + + constructor( + private elementRef: ElementRef, + private logger: NGXLogger + ) {} + + ngOnInit() { + this.menuCloseSubscription = this.menu.close.subscribe(() => { + this.matMenuTrigger._handleClick = e => {}; + this.splitOpened = false; + }); + this._handleClick = this.matMenuTrigger._handleClick; + this.matMenuTrigger._handleClick = e => {}; + } + + ngOnDestroy(): void { + if (!!this.menuCloseSubscription) { + this.menuCloseSubscription.unsubscribe(); + } + } + + onClick(event: MouseEvent) { + this.buttonClick.emit(event); + } + + onClickSplit(event: MouseEvent) { + this.matMenuTrigger._handleClick = this._handleClick; + this.splitOpened = true; + this.splitClick.emit(event); + } +} diff --git a/projects/ucap-webmessenger-ui/src/lib/components/step-input.component.html b/projects/ucap-webmessenger-ui/src/lib/components/step-input.component.html new file mode 100644 index 00000000..823f0ece --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/step-input.component.html @@ -0,0 +1,20 @@ +
+ +
+ + +
+
diff --git a/projects/ucap-webmessenger-ui/src/lib/components/step-input.component.scss b/projects/ucap-webmessenger-ui/src/lib/components/step-input.component.scss new file mode 100644 index 00000000..bac472c4 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/step-input.component.scss @@ -0,0 +1,56 @@ +.ucap-step-input-container { + position: relative; + display: flex; + width: 100%; + height: 30px; + padding: 0 5px; + box-sizing: border-box; + + &--active { + &:after { + content: ''; + position: absolute; + bottom: -2px; + left: 0; + width: 100%; + height: 1px; + background-color: deepskyblue; + } + } + + .stepper-input { + width: 100%; + height: 100%; + padding: 0 10px 0 0; + border: 0; + font-size: 1rem; + color: inherit; + outline: none; + text-align: end; + + &:disabled { + background-color: transparent; + } + } + + .steppers { + position: absolute; + right: 3px; + top: -2px; + display: flex; + flex-direction: column; + } + + .stepper { + height: 15px; + font-size: 11px; + cursor: pointer; + color: rgba(0, 0, 0, 0.4); + transition: color 0.2s; + user-select: none; + + &:hover { + color: rgba(0, 0, 0, 0.9); + } + } +} diff --git a/projects/ucap-webmessenger-ui/src/lib/components/step-input.component.spec.ts b/projects/ucap-webmessenger-ui/src/lib/components/step-input.component.spec.ts new file mode 100644 index 00000000..944ff807 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/step-input.component.spec.ts @@ -0,0 +1,27 @@ +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +import { StepInputComponent } from './step-input.component'; + +describe('StepInputComponent', () => { + let component: StepInputComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [StepInputComponent] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StepInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/ucap-webmessenger-ui/src/lib/components/step-input.component.ts b/projects/ucap-webmessenger-ui/src/lib/components/step-input.component.ts new file mode 100644 index 00000000..05ecaab6 --- /dev/null +++ b/projects/ucap-webmessenger-ui/src/lib/components/step-input.component.ts @@ -0,0 +1,189 @@ +import { + Component, + OnInit, + Input, + Output, + EventEmitter, + ViewChild, + ElementRef, + ChangeDetectorRef, + OnChanges, + SimpleChanges +} from '@angular/core'; +import { ucapAnimations } from '../animations'; +import { StringUtil } from '@ucap-webmessenger/core'; + +const ChangesVALUE = 'value'; + +@Component({ + selector: 'ucap-step-input', + templateUrl: './step-input.component.html', + styleUrls: ['./step-input.component.scss'], + animations: ucapAnimations +}) +export class StepInputComponent implements OnInit, OnChanges { + @Input() + value = 0; + + @Input() + readonly maxLength = 2; + + @Input() + readonly min: number; + + @Input() + readonly max: number; + + @Input() + placeholder = ''; + + @Input() + readonly step = 1; + + @Input() + disabled: boolean; + + @Input() + paddingZero: boolean; + + @Output() + changed = new EventEmitter(); + + @ViewChild('input', { static: true }) + input: ElementRef; + + isFocused: boolean; + + private get displayValue() { + return this._displayValue; + } + private set displayValue(v: string) { + this._displayValue = v; + this.input.nativeElement.value = v; + } + // tslint:disable-next-line: variable-name + private _displayValue: string; + + constructor(private changeDetectorRef: ChangeDetectorRef) {} + + ngOnInit() { + this.changeValue(this.value, true); + } + + ngOnChanges(changes: SimpleChanges): void { + const valueChanges = changes[ChangesVALUE]; + + if (!!valueChanges && !valueChanges.firstChange) { + this.changeValue(valueChanges.currentValue, true); + } + } + + increase() { + if (!this.disabled) { + this.changeValue(this.value + this.step); + } + } + + decrease() { + if (!this.disabled) { + this.changeValue(this.value - this.step); + } + } + + onKeydown(event: KeyboardEvent) { + if (!this.permittedKey(event)) { + event.preventDefault(); + return; + } + + if (/^[0-9]$/i.test(event.key)) { + this.changeValue(Number(`${String(this.displayValue)}${event.key}`)); + return; + } + + switch (event.key) { + case 'ArrowUp': + this.increase(); + return; + case 'ArrowDown': + this.decrease(); + return; + } + } + + onFocus() { + this.isFocused = true; + } + + onBlur() { + this.isFocused = false; + + const dv = Number(this.input.nativeElement.value); + + if (isNaN(dv)) { + this.changeValue(this.min); + } else { + this.changeValue(dv); + } + } + + private changeValue(v: number, force: boolean = false) { + if (!isNaN(v)) { + if (this.max < v) { + v = this.min; + } else if (this.min > v) { + v = this.max; + } + + v = this.step * Math.round(v / this.step); + + let dv: string; + if (this.paddingZero) { + dv = StringUtil.zeroFill(v, this.maxLength); + } else { + dv = String(v); + } + + if (force || v !== this.value) { + this.value = v; + this.changed.emit(v); + } + + if (this.input.nativeElement.value !== dv) { + this.displayValue = dv; + } + } + } + + private permittedKey(event: KeyboardEvent): boolean { + if ( + ['.', 'Backspace', 'Tab', 'Escape', 'Esc'].some(n => n === event.key) || + // Allow: Ctrl/cmd+A + ('A' === event.key && + (true === event.ctrlKey || true === event.metaKey)) || + // Allow: Ctrl/cmd+C + ('C' === event.key && + (true === event.ctrlKey || true === event.metaKey)) || + // Allow: Ctrl/cmd+X + ('X' === event.key && + (true === event.ctrlKey || true === event.metaKey)) || + // Allow: home, end, left, right, up, down + [ + 'Home', + 'End', + 'ArrowDown', + 'Down', + 'ArrowLeft', + 'Left', + 'ArrowRight', + 'Right', + 'ArrowUp', + 'Up' + ].some(n => n === event.key) + ) { + return true; + } + + return /^[0-9]$/i.test(event.key); + } +} diff --git a/projects/ucap-webmessenger-ui/src/lib/ucap-ui.module.ts b/projects/ucap-webmessenger-ui/src/lib/ucap-ui.module.ts index 8ebef4f7..f6855bce 100644 --- a/projects/ucap-webmessenger-ui/src/lib/ucap-ui.module.ts +++ b/projects/ucap-webmessenger-ui/src/lib/ucap-ui.module.ts @@ -16,6 +16,9 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatInputModule } from '@angular/material/input'; import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatButtonToggleModule } from '@angular/material/button-toggle'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatDatepickerModule } from '@angular/material/datepicker'; import { DragDropModule } from '@angular/cdk/drag-drop'; @@ -23,6 +26,11 @@ import { FileUploadQueueComponent } from './components/file-upload-queue.compone import { FloatActionButtonComponent } from './components/float-action-button.component'; import { FileViewerComponent } from './components/file-viewer.component'; import { ExpansionPanelComponent } from './components/expansion-panel.component'; +import { SplitButtonComponent } from './components/split-button.component'; +import { PickDateComponent } from './components/pick-date.component'; +import { PickTimeComponent } from './components/pick-time.component'; +import { StepInputComponent } from './components/step-input.component'; +import { StickerSelectorComponent } from './components/sticker-selector.component'; import { BinaryViewerComponent } from './components/file-viewer/binary-viewer.component'; import { DocumentViewerComponent } from './components/file-viewer/document-viewer.component'; @@ -53,8 +61,7 @@ import { } from './pipes/dates.pipe'; import { SecondsToMinutesPipe } from './pipes/seconds-to-minutes.pipe'; import { LinkyPipe } from './pipes/linky.pipe'; -import { StickerSelectorComponent } from './components/sticker-selector.component'; -import { MessageEditorComponent } from './components/message-editor.component'; + import { MatTabsModule } from '@angular/material'; const COMPONENTS = [ @@ -63,7 +70,10 @@ const COMPONENTS = [ FileViewerComponent, ExpansionPanelComponent, StickerSelectorComponent, - MessageEditorComponent, + SplitButtonComponent, + PickDateComponent, + PickTimeComponent, + StepInputComponent, BinaryViewerComponent, DocumentViewerComponent, @@ -113,6 +123,9 @@ const SERVICES = [ MatTooltipModule, MatTabsModule, MatFormFieldModule, + MatButtonToggleModule, + MatMenuModule, + MatDatepickerModule, DragDropModule ], exports: [...COMPONENTS, ...DIRECTIVES, ...PIPES], diff --git a/projects/ucap-webmessenger-ui/src/public-api.ts b/projects/ucap-webmessenger-ui/src/public-api.ts index 81892d49..db78e22a 100644 --- a/projects/ucap-webmessenger-ui/src/public-api.ts +++ b/projects/ucap-webmessenger-ui/src/public-api.ts @@ -9,9 +9,15 @@ export * from './lib/components/file-viewer/image-viewer.component'; export * from './lib/components/file-viewer/sound-viewer.component'; export * from './lib/components/file-viewer/video-viewer.component'; +export * from './lib/components/expansion-panel.component'; export * from './lib/components/file-upload-queue.component'; export * from './lib/components/file-viewer.component'; export * from './lib/components/float-action-button.component'; +export * from './lib/components/pick-date.component'; +export * from './lib/components/pick-time.component'; +export * from './lib/components/step-input.component'; +export * from './lib/components/split-button.component'; +export * from './lib/components/sticker-selector.component'; export * from './lib/data-source/virtual-scroll-tree-flat.data-source';