editCommercialProps, confirmationService, MessageService, Drag & Drop
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { ConfirmationService } from './confirmation.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-confirmation',
|
||||
standalone: true,
|
||||
imports: [AsyncPipe, NgIf],
|
||||
template: `
|
||||
<div *ngIf="confirmationService.modalVisible$ | async" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center">
|
||||
<div class="relative p-4 w-full max-w-md max-h-full">
|
||||
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||
<button
|
||||
(click)="confirmationService.reject()"
|
||||
type="button"
|
||||
class="absolute top-3 end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
|
||||
>
|
||||
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
||||
</svg>
|
||||
<span class="sr-only">Close modal</span>
|
||||
</button>
|
||||
<div class="p-4 md:p-5 text-center">
|
||||
<svg class="mx-auto mb-4 text-gray-400 w-12 h-12 dark:text-gray-200" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">{{ confirmationService.message$ | async }}</h3>
|
||||
<button
|
||||
(click)="confirmationService.accept()"
|
||||
type="button"
|
||||
class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center mr-2"
|
||||
>
|
||||
Yes, I'm sure
|
||||
</button>
|
||||
<button
|
||||
(click)="confirmationService.reject()"
|
||||
type="button"
|
||||
class="py-2.5 px-5 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
|
||||
>
|
||||
No, cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class ConfirmationComponent {
|
||||
constructor(public confirmationService: ConfirmationService) {}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ConfirmationService {
|
||||
private modalVisibleSubject = new BehaviorSubject<boolean>(false);
|
||||
private messageSubject = new BehaviorSubject<string>('');
|
||||
private resolvePromise!: (value: boolean) => void;
|
||||
|
||||
modalVisible$: Observable<boolean> = this.modalVisibleSubject.asObservable();
|
||||
message$: Observable<string> = this.messageSubject.asObservable();
|
||||
|
||||
showConfirmation(message: string): Promise<boolean> {
|
||||
this.messageSubject.next(message);
|
||||
this.modalVisibleSubject.next(true);
|
||||
return new Promise<boolean>(resolve => {
|
||||
this.resolvePromise = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
accept(): void {
|
||||
this.modalVisibleSubject.next(false);
|
||||
this.resolvePromise(true);
|
||||
}
|
||||
|
||||
reject(): void {
|
||||
this.modalVisibleSubject.next(false);
|
||||
this.resolvePromise(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<div #_container class="container">
|
||||
<!-- <div
|
||||
*ngFor="let item of items"
|
||||
cdkDrag
|
||||
(cdkDragEnded)="dragEnded($event)"
|
||||
(cdkDragStarted)="dragStarted()"
|
||||
(cdkDragMoved)="dragMoved($event)"
|
||||
class="item"
|
||||
[class.animation]="isAnimationActive"
|
||||
[class.large]="item === 3"
|
||||
>
|
||||
Drag Item {{ item }}
|
||||
</div> -->
|
||||
<div *ngFor="let item of items" cdkDrag (cdkDragEnded)="dragEnded($event)" (cdkDragStarted)="dragStarted()" (cdkDragMoved)="dragMoved($event)" [class.animation]="isAnimationActive" class="grid-item item">
|
||||
<div class="image-box hover:cursor-pointer">
|
||||
<img [src]="getImageUrl(item)" class="w-full h-full object-cover rounded-lg shadow-md" />
|
||||
<div class="absolute top-2 right-2 bg-white rounded-full p-1 shadow-md" (click)="imageToDelete.emit(item)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="w-4 h-4 text-gray-600">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,41 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
// max-width: 200px;
|
||||
max-height: 150px;
|
||||
// background-color: blanchedalmond;
|
||||
}
|
||||
|
||||
.animation.cdk-drag:not(.cdk-drag-dragging) {
|
||||
transition: transform 200ms cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.large {
|
||||
width: 150px;
|
||||
}
|
||||
// --------------------
|
||||
.image-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.grid-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
aspect-ratio: 16 / 9;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 8px;
|
||||
transition: box-shadow 200ms cubic-bezier(0, 0, 0.2, 1);
|
||||
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.grid-item:active {
|
||||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
@@ -0,0 +1,476 @@
|
||||
import { CdkDrag, CdkDragEnd, CdkDragMove, DragDropModule, DragRef, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
import { _getShadowRoot } from '@angular/cdk/platform';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, ElementRef, input, output, QueryList, ViewChild, ViewChildren } from '@angular/core';
|
||||
import { CommercialPropertyListing } from '../../../../../bizmatch-server/src/models/db.model';
|
||||
import { environment } from '../../../environments/environment';
|
||||
@Component({
|
||||
selector: 'app-drag-drop-mixed',
|
||||
standalone: true,
|
||||
imports: [CommonModule, DragDropModule],
|
||||
templateUrl: './drag-drop-mixed.component.html',
|
||||
styleUrl: './drag-drop-mixed.component.scss',
|
||||
})
|
||||
export class DragDropMixedComponent {
|
||||
@ViewChild('_container') _container!: ElementRef<HTMLDivElement>;
|
||||
@ViewChildren(CdkDrag) _drags!: QueryList<CdkDrag>;
|
||||
|
||||
listing = input<CommercialPropertyListing>();
|
||||
imageOrderChanged = output<string[]>();
|
||||
imageToDelete = output<string>();
|
||||
env = environment;
|
||||
ts = new Date().getTime();
|
||||
items: string[] = []; //[1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
private _cachedItems: string[] = []; //[1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
|
||||
private _itemPositions: CachedItemPosition<DragRef>[] = [];
|
||||
private _rootNode: DocumentOrShadowRoot | undefined;
|
||||
private _activeItems: DragRef[] = [];
|
||||
private _previousSwap = {
|
||||
drag: null as DragRef | null,
|
||||
deltaX: 0,
|
||||
deltaY: 0,
|
||||
overlaps: false,
|
||||
};
|
||||
private _containerStyle: CSSStyleDeclaration | null = null;
|
||||
public isAnimationActive = false;
|
||||
|
||||
ngOnChanges() {
|
||||
this.items = this.listing()?.imageOrder;
|
||||
this._cachedItems = this.items.slice();
|
||||
}
|
||||
|
||||
getImageUrl(image: string): string {
|
||||
return `${this.env.imageBaseUrl}/pictures/property/${this.listing().imagePath}/${this.listing().serialId}/${image}?_ts=${this.ts}`;
|
||||
}
|
||||
|
||||
dragStarted() {
|
||||
this.start();
|
||||
}
|
||||
|
||||
dragMoved(event: CdkDragMove) {
|
||||
const item = event.source._dragRef;
|
||||
this.sort(item, event.pointerPosition.x, event.pointerPosition.y, event.delta);
|
||||
}
|
||||
|
||||
dragEnded(event: CdkDragEnd) {
|
||||
this.imageOrderChanged.emit(this._cachedItems);
|
||||
this.reset();
|
||||
}
|
||||
|
||||
start() {
|
||||
const dragRefs: DragRef[] = [];
|
||||
|
||||
this._drags.forEach(drag => {
|
||||
dragRefs.push(drag._dragRef);
|
||||
});
|
||||
|
||||
this._activeItems = dragRefs;
|
||||
this._cacheItemPosition();
|
||||
this.isAnimationActive = true;
|
||||
}
|
||||
|
||||
sort(item: DragRef, pointerX: number, pointerY: number, pointerDelta: { x: number; y: number }) {
|
||||
const siblings = this._itemPositions.slice();
|
||||
const newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY);
|
||||
|
||||
const previousSwap = this._previousSwap;
|
||||
|
||||
if (newIndex === -1 || this._activeItems[newIndex] === item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toSwapWith = this._activeItems[newIndex];
|
||||
|
||||
if (previousSwap.drag === toSwapWith && previousSwap.overlaps && previousSwap.deltaX === pointerDelta.x && previousSwap.deltaY === pointerDelta.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousIndex = this.getItemIndex(item);
|
||||
const siblingAtNewPosition = siblings[newIndex];
|
||||
const previousPosition = siblings[previousIndex].clientRect;
|
||||
const newPosition = siblingAtNewPosition.clientRect;
|
||||
|
||||
const delta = this.getDelta(newPosition.top, previousPosition.top, pointerDelta);
|
||||
|
||||
if (delta === 0) return;
|
||||
if (delta === 1 && previousIndex > newIndex) return;
|
||||
if (delta === -1 && previousIndex < newIndex) return;
|
||||
|
||||
const startIndex = Math.min(previousIndex, newIndex);
|
||||
const endIndex = Math.max(previousIndex, newIndex);
|
||||
|
||||
let itemPositions = this._itemPositions.slice();
|
||||
|
||||
if (delta === 1) {
|
||||
for (let i = startIndex; i < endIndex; i++) {
|
||||
itemPositions = this._updateItemPosition(i, itemPositions, delta);
|
||||
|
||||
const newIndex = i + 1;
|
||||
moveItemInArray(itemPositions, i, newIndex);
|
||||
}
|
||||
} else if (delta === -1) {
|
||||
for (let i = endIndex; i > startIndex; i--) {
|
||||
itemPositions = this._updateItemPosition(i, itemPositions, delta);
|
||||
|
||||
const newIndex = i - 1;
|
||||
moveItemInArray(itemPositions, i, newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
const threshold = getMutableClientRect(this._container.nativeElement).right;
|
||||
|
||||
let currentTop = itemPositions[0].clientRect.top;
|
||||
|
||||
for (let i = 0; i < itemPositions.length; i++) {
|
||||
const itemPosition = itemPositions[i];
|
||||
if (Math.round(itemPosition.clientRect.right) > Math.round(threshold)) {
|
||||
const nextPosition = itemPositions[i + 1];
|
||||
if (nextPosition) {
|
||||
currentTop = nextPosition.clientRect.top;
|
||||
}
|
||||
itemPositions = this._updateItemPositionToDown(itemPositions, i);
|
||||
} else if (itemPosition.clientRect.top !== currentTop) {
|
||||
currentTop = itemPosition.clientRect.top;
|
||||
itemPositions = this._updateItemPositionToUp(itemPositions, i);
|
||||
}
|
||||
}
|
||||
|
||||
const oldOrder = this._itemPositions.slice();
|
||||
this._itemPositions = itemPositions;
|
||||
moveItemInArray(this._activeItems, previousIndex, newIndex);
|
||||
moveItemInArray(this._cachedItems, previousIndex, newIndex);
|
||||
|
||||
itemPositions.forEach((sibling, index) => {
|
||||
if (oldOrder[index] === sibling) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isDraggedItem = sibling.drag === item;
|
||||
if (isDraggedItem) return;
|
||||
const elementToOffset = sibling.drag.getRootElement();
|
||||
|
||||
elementToOffset.style.transform = `translate3d(${Math.round(sibling.transform.x)}px, ${Math.round(sibling.transform.y)}px, 0)`;
|
||||
});
|
||||
|
||||
previousSwap.deltaX = pointerDelta.x;
|
||||
previousSwap.deltaY = pointerDelta.y;
|
||||
previousSwap.drag = toSwapWith;
|
||||
previousSwap.overlaps = isInsideClientRect(newPosition, pointerX, pointerY);
|
||||
}
|
||||
|
||||
reset() {
|
||||
// ignore animation
|
||||
this.isAnimationActive = false;
|
||||
const previousSwap = this._previousSwap;
|
||||
this.items = this._cachedItems.slice();
|
||||
this._activeItems.forEach(item => {
|
||||
item.reset();
|
||||
});
|
||||
this._itemPositions = [];
|
||||
this._activeItems = [];
|
||||
previousSwap.drag = null;
|
||||
previousSwap.deltaX = previousSwap.deltaY = 0;
|
||||
previousSwap.overlaps = false;
|
||||
}
|
||||
|
||||
getItemIndex(item: DragRef): number {
|
||||
return this._activeItems.indexOf(item);
|
||||
}
|
||||
|
||||
getDelta(newTop: number, previousTop: number, pointerDelta: { x: number; y: number }) {
|
||||
if (newTop === previousTop) {
|
||||
return pointerDelta.x;
|
||||
}
|
||||
|
||||
return newTop > previousTop ? 1 : -1;
|
||||
}
|
||||
|
||||
private _getRootNode(): DocumentOrShadowRoot {
|
||||
if (!this._rootNode) {
|
||||
this._rootNode = _getShadowRoot(this._container.nativeElement) || document;
|
||||
}
|
||||
return this._rootNode;
|
||||
}
|
||||
|
||||
private _cacheItemPosition() {
|
||||
this._itemPositions = this._activeItems.map(drag => {
|
||||
const elementToMeasure = drag.getRootElement();
|
||||
return {
|
||||
drag,
|
||||
clientRect: getMutableClientRect(elementToMeasure),
|
||||
transform: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
this._containerStyle = getComputedStyle(this._container.nativeElement);
|
||||
}
|
||||
|
||||
private _updateItemPosition(currentIndex: number, siblings: CachedItemPosition<DragRef>[], delta: number) {
|
||||
let siblingsUpdated = siblings.slice();
|
||||
const offsetVertical = this._getOffset(currentIndex, siblingsUpdated, delta, false);
|
||||
const offsetHorizontal = this._getOffset(currentIndex, siblingsUpdated, delta, true);
|
||||
|
||||
const immediateIndex = currentIndex + delta * 1;
|
||||
const currentItem = siblingsUpdated[currentIndex];
|
||||
const immediateSibling = siblingsUpdated[immediateIndex];
|
||||
|
||||
const currentItemUpdated: CachedItemPosition<DragRef> = {
|
||||
...currentItem,
|
||||
clientRect: {
|
||||
...currentItem.clientRect,
|
||||
x: currentItem.clientRect.x + offsetHorizontal.itemOffset,
|
||||
left: currentItem.clientRect.left + offsetHorizontal.itemOffset,
|
||||
right: currentItem.clientRect.right + offsetHorizontal.itemOffset,
|
||||
y: currentItem.clientRect.y + offsetVertical.itemOffset,
|
||||
top: currentItem.clientRect.top + offsetVertical.itemOffset,
|
||||
bottom: currentItem.clientRect.bottom + offsetVertical.itemOffset,
|
||||
},
|
||||
transform: {
|
||||
x: currentItem.transform.x + offsetHorizontal.itemOffset,
|
||||
y: currentItem.transform.y + offsetVertical.itemOffset,
|
||||
},
|
||||
};
|
||||
|
||||
const immediateSiblingUpdated: CachedItemPosition<DragRef> = {
|
||||
...immediateSibling,
|
||||
clientRect: {
|
||||
...immediateSibling.clientRect,
|
||||
x: immediateSibling.clientRect.x + offsetHorizontal.siblingOffset,
|
||||
left: immediateSibling.clientRect.left + offsetHorizontal.siblingOffset,
|
||||
right: immediateSibling.clientRect.right + offsetHorizontal.siblingOffset,
|
||||
y: immediateSibling.clientRect.y + offsetVertical.siblingOffset,
|
||||
top: immediateSibling.clientRect.top + offsetVertical.siblingOffset,
|
||||
bottom: immediateSibling.clientRect.bottom + offsetVertical.siblingOffset,
|
||||
},
|
||||
transform: {
|
||||
x: immediateSibling.transform.x + offsetHorizontal.siblingOffset,
|
||||
y: immediateSibling.transform.y + offsetVertical.siblingOffset,
|
||||
},
|
||||
};
|
||||
|
||||
if (offsetVertical.itemOffset !== offsetVertical.siblingOffset) {
|
||||
const offset = (currentItemUpdated.clientRect.right - immediateSibling.clientRect.right) * delta;
|
||||
const top = delta === 1 ? immediateSibling.clientRect.top : currentItem.clientRect.top;
|
||||
|
||||
const ignoreItem = delta === 1 ? immediateSibling.drag : currentItem.drag;
|
||||
|
||||
siblingsUpdated = this._updateItemPositionHorizontalOnRow(siblingsUpdated, top, offset, ignoreItem);
|
||||
}
|
||||
siblingsUpdated[currentIndex] = currentItemUpdated;
|
||||
siblingsUpdated[immediateIndex] = immediateSiblingUpdated;
|
||||
|
||||
return siblingsUpdated;
|
||||
}
|
||||
|
||||
private _updateItemPositionToUp(siblings: CachedItemPosition<DragRef>[], currentIndex: number) {
|
||||
let siblingsUpdated = siblings.slice();
|
||||
const immediateSibling = siblingsUpdated[currentIndex - 1];
|
||||
const currentItem = siblingsUpdated[currentIndex];
|
||||
|
||||
const nextEmptySlotLeft = immediateSibling.clientRect.right + this.getContainerGapPixel();
|
||||
|
||||
const threshold = getMutableClientRect(this._container.nativeElement).right;
|
||||
if (nextEmptySlotLeft + currentItem.clientRect.right - currentItem.clientRect.left <= threshold) {
|
||||
const offsetLeft = nextEmptySlotLeft - currentItem.clientRect.left;
|
||||
const offsetTop = immediateSibling.clientRect.top - currentItem.clientRect.top;
|
||||
|
||||
const nextSibling = siblingsUpdated[currentIndex + 1];
|
||||
if (nextSibling) {
|
||||
const offset = currentItem.clientRect.left - nextSibling.clientRect.left;
|
||||
siblingsUpdated = this._updateItemPositionHorizontalOnRow(siblingsUpdated, currentItem.clientRect.top, offset, currentItem.drag);
|
||||
}
|
||||
|
||||
siblingsUpdated[currentIndex] = {
|
||||
...currentItem,
|
||||
clientRect: {
|
||||
...currentItem.clientRect,
|
||||
x: nextEmptySlotLeft,
|
||||
left: nextEmptySlotLeft,
|
||||
right: currentItem.clientRect.right - currentItem.clientRect.left + nextEmptySlotLeft,
|
||||
y: immediateSibling.clientRect.y,
|
||||
top: immediateSibling.clientRect.top,
|
||||
bottom: currentItem.clientRect.bottom - currentItem.clientRect.top + immediateSibling.clientRect.top,
|
||||
},
|
||||
transform: {
|
||||
x: currentItem.transform.x + offsetLeft,
|
||||
y: currentItem.transform.y + offsetTop,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return siblingsUpdated;
|
||||
}
|
||||
|
||||
private _updateItemPositionToDown(siblings: CachedItemPosition<DragRef>[], currentIndex: number) {
|
||||
let siblingsUpdated = siblings.slice();
|
||||
const currentItem = siblingsUpdated[currentIndex];
|
||||
const immediateSibling = siblingsUpdated[currentIndex + 1];
|
||||
let offsetLeft = 0;
|
||||
let offsetTop = 0;
|
||||
|
||||
if (immediateSibling) {
|
||||
offsetLeft = immediateSibling.clientRect.left - currentItem.clientRect.left;
|
||||
offsetTop = immediateSibling.clientRect.top - currentItem.clientRect.top;
|
||||
} else {
|
||||
const firstSibling = siblings.find(item => item.clientRect.top === currentItem.clientRect.top);
|
||||
|
||||
if (firstSibling) {
|
||||
offsetLeft = firstSibling.clientRect.left - currentItem.clientRect.left;
|
||||
}
|
||||
|
||||
offsetTop = currentItem.clientRect.bottom - currentItem.clientRect.top + this.getContainerGapPixel();
|
||||
}
|
||||
|
||||
const currentItemUpdated: CachedItemPosition<DragRef> = {
|
||||
...currentItem,
|
||||
clientRect: {
|
||||
...currentItem.clientRect,
|
||||
x: currentItem.clientRect.x + offsetLeft,
|
||||
left: currentItem.clientRect.left + offsetLeft,
|
||||
right: currentItem.clientRect.right + offsetLeft,
|
||||
y: currentItem.clientRect.y + offsetTop,
|
||||
top: currentItem.clientRect.top + offsetTop,
|
||||
bottom: currentItem.clientRect.bottom + offsetTop,
|
||||
},
|
||||
transform: {
|
||||
x: currentItem.transform.x + offsetLeft,
|
||||
y: currentItem.transform.y + offsetTop,
|
||||
},
|
||||
};
|
||||
|
||||
if (immediateSibling) {
|
||||
const offset = currentItemUpdated.clientRect.right - immediateSibling.clientRect.left + this.getContainerGapPixel();
|
||||
|
||||
siblingsUpdated = this._updateItemPositionHorizontalOnRow(siblingsUpdated, immediateSibling.clientRect.top, offset);
|
||||
}
|
||||
|
||||
siblingsUpdated[currentIndex] = currentItemUpdated;
|
||||
return siblingsUpdated;
|
||||
}
|
||||
|
||||
private _updateItemPositionHorizontalOnRow(siblings: CachedItemPosition<DragRef>[], top: number, offset: number, ignoreItem?: DragRef) {
|
||||
const siblingsUpdated = siblings.slice();
|
||||
|
||||
siblingsUpdated
|
||||
.filter(item => (!ignoreItem || item.drag !== ignoreItem) && item.clientRect.top === top)
|
||||
.forEach(currentItem => {
|
||||
const index = siblingsUpdated.findIndex(item => item.drag === currentItem.drag);
|
||||
|
||||
siblingsUpdated[index] = {
|
||||
...siblingsUpdated[index],
|
||||
clientRect: {
|
||||
...siblingsUpdated[index].clientRect,
|
||||
x: siblingsUpdated[index].clientRect.x + offset,
|
||||
left: siblingsUpdated[index].clientRect.left + offset,
|
||||
right: siblingsUpdated[index].clientRect.right + offset,
|
||||
},
|
||||
transform: {
|
||||
...siblingsUpdated[index].transform,
|
||||
x: siblingsUpdated[index].transform.x + offset,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return siblingsUpdated;
|
||||
}
|
||||
|
||||
private _getItemIndexFromPointerPosition(item: DragRef, pointerX: number, pointerY: number) {
|
||||
const elementAtPoints = this._getRootNode().elementsFromPoint(Math.floor(pointerX), Math.floor(pointerY));
|
||||
|
||||
const elementAtPoint = elementAtPoints.find(element => {
|
||||
// ignore element is transiting
|
||||
const animations = element.getAnimations();
|
||||
const isTransitionRunning = animations.length > 0;
|
||||
|
||||
return !isTransitionRunning && this._itemPositions.some(item => item.drag.getRootElement() === element) && element !== item.getRootElement();
|
||||
});
|
||||
|
||||
const index = elementAtPoint
|
||||
? this._itemPositions.findIndex(({ drag }) => {
|
||||
// Skip the item itself.
|
||||
if (drag === item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const root = drag.getRootElement();
|
||||
return elementAtPoint === root || root.contains(elementAtPoint);
|
||||
})
|
||||
: -1;
|
||||
return index;
|
||||
}
|
||||
|
||||
private _getOffset(currentIndex: number, siblings: CachedItemPosition<DragRef>[], delta: number, isHorizontal: boolean) {
|
||||
const currentPosition = siblings[currentIndex].clientRect;
|
||||
const immediateSibling = siblings[currentIndex + delta].clientRect;
|
||||
|
||||
let itemOffset = 0;
|
||||
let siblingOffset = 0;
|
||||
|
||||
if (immediateSibling) {
|
||||
const start = isHorizontal ? 'left' : 'top';
|
||||
const end = isHorizontal ? 'right' : 'bottom';
|
||||
|
||||
if (delta === 1) {
|
||||
itemOffset = immediateSibling[end] - currentPosition[end];
|
||||
siblingOffset = currentPosition[start] - immediateSibling[start];
|
||||
|
||||
if (isHorizontal && immediateSibling[end] < currentPosition[end]) {
|
||||
itemOffset = immediateSibling[start] - currentPosition[start];
|
||||
}
|
||||
} else {
|
||||
itemOffset = immediateSibling[start] - currentPosition[start];
|
||||
siblingOffset = currentPosition[end] - immediateSibling[end];
|
||||
|
||||
if (isHorizontal && immediateSibling[end] > currentPosition[end]) {
|
||||
siblingOffset = currentPosition[start] - immediateSibling[start];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
itemOffset,
|
||||
siblingOffset,
|
||||
};
|
||||
}
|
||||
|
||||
private getContainerGapPixel() {
|
||||
if (this._containerStyle && (this._containerStyle.display === 'flex' || this._containerStyle.display === 'grid')) {
|
||||
return this._containerStyle.gap ? +this._containerStyle.gap.split('px')[0] : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const getMutableClientRect = (element: Element): DOMRect => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
} as DOMRect;
|
||||
};
|
||||
|
||||
const isInsideClientRect = (clientRect: DOMRect, x: number, y: number) => {
|
||||
const { top, bottom, left, right } = clientRect;
|
||||
return y >= top && y <= bottom && x >= left && x <= right;
|
||||
};
|
||||
|
||||
interface CachedItemPosition<T> {
|
||||
drag: T;
|
||||
clientRect: DOMRect;
|
||||
transform: {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
}
|
||||
35
bizmatch/src/app/components/message/message.component.ts
Normal file
35
bizmatch/src/app/components/message/message.component.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { AsyncPipe, NgIf } from '@angular/common';
|
||||
import { MessageService } from './message.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-message',
|
||||
standalone: true,
|
||||
imports: [AsyncPipe, NgIf],
|
||||
template: `
|
||||
<div id="toast-success" class="flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800" role="alert">
|
||||
<div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-green-500 bg-green-100 rounded-lg dark:bg-green-800 dark:text-green-200">
|
||||
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z" />
|
||||
</svg>
|
||||
<span class="sr-only">Check icon</span>
|
||||
</div>
|
||||
<div class="ms-3 text-sm font-normal">Item moved successfully.</div>
|
||||
<button
|
||||
type="button"
|
||||
class="ms-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700"
|
||||
data-dismiss-target="#toast-success"
|
||||
aria-label="Close"
|
||||
>
|
||||
<span class="sr-only">Close</span>
|
||||
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class MessageComponent {
|
||||
constructor(public messageService: MessageService) {}
|
||||
}
|
||||
32
bizmatch/src/app/components/message/message.service.ts
Normal file
32
bizmatch/src/app/components/message/message.service.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class MessageService {
|
||||
private modalVisibleSubject = new BehaviorSubject<boolean>(false);
|
||||
private messageSubject = new BehaviorSubject<string>('');
|
||||
private resolvePromise!: (value: boolean) => void;
|
||||
|
||||
modalVisible$: Observable<boolean> = this.modalVisibleSubject.asObservable();
|
||||
message$: Observable<string> = this.messageSubject.asObservable();
|
||||
|
||||
showMessage(message: string): Promise<boolean> {
|
||||
this.messageSubject.next(message);
|
||||
this.modalVisibleSubject.next(true);
|
||||
return new Promise<boolean>(resolve => {
|
||||
this.resolvePromise = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
accept(): void {
|
||||
this.modalVisibleSubject.next(false);
|
||||
this.resolvePromise(true);
|
||||
}
|
||||
|
||||
reject(): void {
|
||||
this.modalVisibleSubject.next(false);
|
||||
this.resolvePromise(false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user