Files
marklieberman-ComfyUI-Liebs…/js/modal/ZoomModal.js
T

440 lines
15 KiB
JavaScript

import { BaseModal } from "./BaseModal.js";
export const MODAL_HTML = `
<header>
<h1>Image Picker</h1>
<span>? of ?</span>
<div class="spacer"></div>
<div class="p-component toggle-switch segs-control segs-switch">
SEGS
<label class="switch">
<input type="checkbox">
<span class="slider"></span>
</label>
</div>
<div class="p-component">
<button class="p-button p-button-secondary cancel-button">
<span class="p-button-label">Cancel</span>
</button>
<button class="p-button p-button-secondary back-button">
<span class="p-button-label">Back</span>
</button>
<button class="p-button p-button-primary send-button">
<span class="p-button-label">Send</span>
</button>
</div>
<div class="p-component">
<button class="p-button p-button-secondary first-button">
<span class="p-button-icon pi pi-step-backward"></span>
</button>
<button class="p-button p-button-secondary prev-button">
<span class="p-button-icon pi pi-play"></span>
</button>
<button class="p-button p-button-secondary next-button">
<span class="p-button-icon pi pi-play"></span>
</button>
<button class="p-button p-button-secondary last-button">
<span class="p-button-icon pi pi-step-forward"></span>
</button>
</div>
</header>
<content class="hide-segments">
<liebs-picker-image></liebs-picker-image>
</content>
`;
/**
* Displays one image at a time using all available space.
*/
export class ZoomModal extends BaseModal {
constructor(options) {
super();
this.attached = false;
// Construct the modal from HTML.
this.innerHTML = MODAL_HTML;
this.el = {
title: this.querySelector('header h1'),
subtitle: this.querySelector('header span'),
segsCheckbox: this.querySelector('.segs-switch input'),
cancelButton: this.querySelector('.cancel-button'),
sendButton: this.querySelector('.send-button'),
backButton: this.querySelector('.back-button'),
firstButton: this.querySelector('.first-button'),
prevButton: this.querySelector('.prev-button'),
nextButton: this.querySelector('.next-button'),
lastButton: this.querySelector('.last-button'),
content: this.querySelector('content'),
image: this.querySelector('liebs-picker-image')
};
// Create event handlers for this modal.
this.handlerMouseWheel = this.onMouseWheel.bind(this);
this.addEventListener('wheel', this.handlerMouseWheel);
this.handlerCancel = this.onCancel.bind(this);
this.el.cancelButton.addEventListener('click', this.handlerCancel);
this.handlerSend = this.onSend.bind(this);
this.el.sendButton.addEventListener('click', this.handlerSend);
this.handlerBack = this.onBack.bind(this);
this.el.backButton.addEventListener('click', this.handlerBack);
this.handlerFirst = this.onFirst.bind(this);
this.el.firstButton.addEventListener('click', this.handlerFirst);
this.handlerPrev = this.onPrevious.bind(this);
this.el.prevButton.addEventListener('click', this.handlerPrev);
this.handlerNext = this.onNext.bind(this);
this.el.nextButton.addEventListener('click', this.handlerNext);
this.handlerLast = this.onLast.bind(this);
this.el.lastButton.addEventListener('click', this.handlerLast);
this.handlerImageListSelect = this.onImageListSelectOrUnwanted.bind(this);
this.handlerImageListUnwanted = this.onImageListSelectOrUnwanted.bind(this);
this.handlerImageIndex = this.onImageIndex.bind(this);
this.el.image.addEventListener('image-index', this.handlerImageIndex);
this.handlerImageClick = this.onImageClick.bind(this);
this.el.image.addEventListener('image-click', this.handlerImageClick);
this.handlerImageAction = this.onImageAction.bind(this);
this.el.image.addEventListener('image-action', this.handlerImageAction);
this.handlerImageMousedown = this.onImageMousedown.bind(this);
this.el.image.addEventListener('image-mousedown', this.handlerImageMousedown);
this.el.segsCheckbox.addEventListener('click', () => {
this.displaySegments(this.el.segsCheckbox.checked);
});
// Initialize the modal controls.
this.pickerMode = options.pickerMode ?? 'picker';
this.pickerModeMustPick = options.pickerModeMustPick ?? false;
this.pickerLocked = options.pickerLocked ?? false;
this.setImageList(options.imageList, options.index);
this.setTitle(options.title);
this.setSubtitle();
if (this.pickerLocked) {
this.classList.add("picker-locked");
this.pickerModeMustPick = false;
}
this.updateSendButton();
// Display SEGS controls when features are enabled.
this.segsControls = options.segsControls ?? false;
if (this.segsControls) {
this.classList.add("segs-controls");
this.displaySegments(options.showSegments ?? true);
} else {
this.displaySegments(false);
}
}
// Prepare a grid style layout.
async layout() {
if (!this.attached) {
// Modal is not attached to the DOM.
return;
}
const imageList = this.el.image.imageList;
if (!imageList && !imageList.length) {
// No image to display.
return;
}
const [imageWidth, imageHeight] = await this.el.image.getImageSize(),
box = this.el.content.getBoundingClientRect(),
widthRatio = box.width / imageWidth,
heightRatio = box.height / imageHeight,
scale = widthRatio < heightRatio ? widthRatio : heightRatio,
scaledWidth = imageWidth * scale,
scaledHeight = imageHeight * scale;
this.el.image.style.maxHeight = `${scaledHeight}px`;
this.el.image.style.maxWidth = `${scaledWidth}px`;
// Can show the modal content now.
this.classList.remove('first-layout');
}
// Handle keydown in the modal.
onKeyDown(event) {
let handled = false;
const letterKey = event.key.toUpperCase();
let n = parseInt(event.key);
if (Number.isInteger(n)) {
n = (n === 0) ? 9 : n - 1;
if (this.segsControls && event.altKey && this.isSegmentsVisible()) {
// Cycle segment labels of the Nth segment of the focused image when alt-key is pressed.
const index = this.el.image.index;
this.el.image.imageList.getImageSegments(index)?.nextLabel(n);
}
} else
// Back to grid view with spacebar.
if (event.key === ' ') {
handled = true;
this.onBack();
} else
// Cancel the modal with Escape.
if (event.key === 'Escape') {
handled = true;
this.onCancel();
} else
// Submit the modal with Enter.
if ((event.key === 'Enter') && !this.el.sendButton.disabled) {
handled = true;
this.onSend();
} else
// Move back and forward with arrow keys or A and D.
if (event.key === 'ArrowLeft' || letterKey === 'A') {
handled = true;
this.onPrevious();
} else
if (event.key === 'ArrowRight' || letterKey === 'D') {
handled = true;
this.onNext();
} else
if (event.key === 'Home') {
handled = true;
this.onFirst();
} else
if (event.key === 'End') {
handled = true;
this.onLast();
} else
// Select an image with E or /.
if (!this.pickerLocked && (letterKey === 'E' || letterKey === '/')) {
handled = true;
const imageList = this.el.image.imageList,
index = this.el.image.index;
switch (this.pickerMode) {
case 'picker':
imageList.unwanted(index, false);
imageList.toggleSelect(index);
break;
case 'filter':
imageList.select(index, false);
imageList.toggleUnwanted(index);
break;
}
} else
// Unwant an image with X.
if (!this.pickerLocked && (letterKey === 'X' || letterKey === '\'')) {
handled = true;
const imageList = this.el.image.imageList,
index = this.el.image.index;
switch (this.pickerMode) {
case 'picker':
imageList.select(index, false);
imageList.toggleUnwanted(index);
break;
case 'filter':
imageList.unwanted(index, false);
imageList.toggleSelect(index);
break;
}
} else
// Toggle visibility of the segments overlay.
if (this.segsControls && (event.key === '`') && event.altKey) {
this.displaySegments(!this.isSegmentsVisible());
}
if (handled) {
event.preventDefault();
event.stopPropagation();
}
}
// Handle wheel events in the model.
onMouseWheel(event) {
if (event.deltaY < 0) {
this.el.nextButton.click();
} else
if (event.deltaY > 0) {
this.el.prevButton.click();
}
}
// Set the modal title.
setTitle(value) {
this.el.title.innerText = value ?? 'Image Picker';
}
// Set the modal subtitle.
setSubtitle(value) {
this.el.subtitle.innerText = value ??
`${this.el.image.index + 1} of ${this.el.image.imageList.length}`;
}
// Update the label on the Send button.
updateSendButton() {
const selectedCount = this.el.image.imageList.selectedCount,
wantedCount = this.el.image.imageList.wantedCount,
mustPick = (this.pickerMode === 'picker') && this.pickerModeMustPick;
if ((selectedCount > 0) || mustPick) {
this.el.sendButton.disabled = selectedCount < 1;
this.el.sendButton.innerText = `Send (${selectedCount} selected)`;
} else {
this.el.sendButton.disabled = wantedCount < 1;
if (this.pickerLocked) {
this.el.sendButton.innerText = `Send (${wantedCount} selected)`;
} else {
this.el.sendButton.innerText = `Send (${wantedCount} remaining)`;
}
}
}
// Set the list of images to display.
setImageList(value, index = 0) {
this.el.image.setImageList(value, index);
value.addEventListener('image-select', this.handlerImageListSelect);
value.addEventListener('image-unwanted', this.handlerImageListUnwanted);
}
// Set the index of the image to display from the image list.
setIndex(value) {
this.el.image.setIndex(value);
}
// True if segments overlay is visible, otherwise false.
isSegmentsVisible() {
return !this.el.content.classList.contains('hide-segments');
}
// Set visibility of the segments overlay.
displaySegments(value) {
this.showSegments = value;
this.el.segsCheckbox.checked = value;
if (value) {
this.el.content.classList.remove('hide-segments');
} else {
this.el.content.classList.add('hide-segments');
}
}
// Invoked when the image index in the image list is changed.
onImageIndex(event) {
const detail = event.detail,
lastIndex = detail.imageList.length - 1;
// Disable next and previous button when at the ends of the list.
this.el.nextButton.disabled = detail.index >= lastIndex;
this.el.prevButton.disabled = detail.index <= 0;
}
// Invoked when the selection or unwanted flag in the image list changes.
onImageListSelectOrUnwanted() {
this.updateSendButton();
}
// Invoked when an image is clicked.
onImageClick(event) {
const detail = event.detail,
imageList = this.el.image.imageList;
if (!this.pickerLocked) {
switch (this.pickerMode) {
case 'picker':
imageList.unwanted(detail.index, false);
imageList.toggleSelect(detail.index);
break;
case 'filter':
imageList.select(detail.index, false);
imageList.toggleUnwanted(detail.index);
break;
}
}
}
// Invoked when an action button is clicked on an image.
onImageAction(event) {
const detail = event.detail,
imageList = this.el.image.imageList;
if (!this.pickerLocked) {
switch (detail.action) {
case 'select':
imageList.toggleSelect(detail.index);
imageList.unwanted(detail.index, false);
break;
case 'unwanted':
imageList.select(detail.index, false);
imageList.toggleUnwanted(detail.index);
break;
}
}
}
// Invoked when mousedown on an image.
onImageMousedown(event) {
const detail = event.detail;
if (detail.button === 1) {
this.onBack();
}
}
// Invoked when the First button is clicked.
onFirst() {
this.el.image.setIndex(0);
this.setSubtitle();
}
// Invoked when the Previous button is clicked.
onPrevious() {
const index = this.el.image.index;
if (index > 0) {
this.el.image.setIndex(index - 1);
this.setSubtitle();
}
}
// Invoked when the Next button is clicked.
onNext() {
const index = this.el.image.index,
imageList = this.el.image.imageList,
lastIndex = imageList.length - 1;
if (index < lastIndex) {
this.el.image.setIndex(index + 1);
this.setSubtitle();
}
}
// Invoked when the Last button is clicked.
onLast() {
const imageList = this.el.image.imageList,
lastIndex = imageList.length - 1;
this.el.image.setIndex(lastIndex);
this.setSubtitle();
}
// Invoked when the Send button is clicked.
onSend() {
this.detach();
this.resolve('send');
}
// Invoked when the Back button is clicked.
onBack() {
this.detach();
this.resolve('back');
}
// Invoked when the Cancel button is clicked.
onCancel() {
this.detach();
this.resolve('cancel');
}
}
customElements.define('liebs-picker-zoom-modal', ZoomModal);