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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 95 additions & 13 deletions src/components/dialogs/DialogNewProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,55 @@ import type { ISliderControl } from "../helpers/createSliderControl";
import createSliderControl from "../helpers/createSliderControl";
import type { ITextInput } from "../helpers/createTextInput";
import createTextInput from "../helpers/createTextInput";
import type { ISelectInput, ISelectOption } from "../helpers/createSelectInput";
import createSelectInput from "../helpers/createSelectInput";
import { OPTION_SEPARATOR_VALUE } from "src/constants";

const WORK_AREA_WIDTH = 1920;
const WORK_AREA_HEIGHT = 1080;

// Keys for the template data
const T_480P_WIDE = "480p (SD wide)";
const T_720P_HD = "720p (HD)";
const T_900P_HD_PLUS = "900p (HD+)";
const T_1080P_FULL_HD = "Youtube Miniatura (Full HD)";
const T_INSTA_SQUARE = "Instagram Post (Quadrado)";
const T_INSTA_PORTRAIT = "Instagram Post (Retrato)";
const T_INSTA_LANDSCAPE = "Instagram Post (Paisagem)";
const T_FACEBOOK_POST = "Facebook Post";

const TEMPLATE_DATA: Record<string, { height: number; width: number }> = {
[T_480P_WIDE]: { width: 854, height: 480 },
[T_720P_HD]: { width: 1280, height: 720 },
[T_900P_HD_PLUS]: { width: 1600, height: 900 },
[T_1080P_FULL_HD]: { width: 1920, height: 1080 },
[T_INSTA_SQUARE]: { width: 1080, height: 1080 },
[T_INSTA_PORTRAIT]: { width: 1080, height: 1350 },
[T_INSTA_LANDSCAPE]: { width: 1080, height: 566 },
[T_FACEBOOK_POST]: { width: 1200, height: 630 },
};

const TEMPLATE_OPTIONS: Array<ISelectOption> = [
{ label: "Personalizado", value: "CUSTOM" },
{ label: "--- Padrões de Vídeo ---", value: OPTION_SEPARATOR_VALUE },
{ label: T_480P_WIDE, value: T_480P_WIDE },
{ label: T_720P_HD, value: T_720P_HD },
{ label: T_900P_HD_PLUS, value: T_900P_HD_PLUS },
{ label: T_1080P_FULL_HD, value: T_1080P_FULL_HD },
{ label: "--- Redes Sociais ---", value: OPTION_SEPARATOR_VALUE },
{ label: T_INSTA_SQUARE, value: T_INSTA_SQUARE },
{ label: T_INSTA_PORTRAIT, value: T_INSTA_PORTRAIT },
{ label: T_INSTA_LANDSCAPE, value: T_INSTA_LANDSCAPE },
{ label: T_FACEBOOK_POST, value: T_FACEBOOK_POST },
];

export class DialogNewProject extends Dialog {
private eventBus: EventBus;
private projectData: IProjectData | null = null;
private workAreaWidthInput: ISliderControl | null = null;
private workAreaHeightInput: ISliderControl | null = null;
private projectNameInput: ITextInput | null = null;
private templatesInput: ISelectInput | null = null;

constructor(eventBus: EventBus) {
super({
Expand All @@ -41,55 +80,98 @@ export class DialogNewProject extends Dialog {
}

private handleWidthInput(newValue: number): void {
if (this.projectData) {
if (this.projectData && this.templatesInput) {
this.projectData.workAreaSize.width = newValue;
this.templatesInput.setValue("CUSTOM");
}
}

private handleHeightInput(newValue: number): void {
if (this.projectData) {
if (this.projectData && this.templatesInput) {
this.projectData.workAreaSize.height = newValue;
this.templatesInput.setValue("CUSTOM");
}
}

private handleTemplateChange(newValue: string): void {
const tpl = TEMPLATE_DATA[newValue];
if (!tpl || !this.projectData) return;
const width = tpl.width;
const height = tpl.height;
this.workAreaWidthInput?.setValue(width);
this.workAreaHeightInput?.setValue(height);
this.projectData.workAreaSize.width = width;
this.projectData.workAreaSize.height = height;
}

protected appendDialogContent(container: HTMLDivElement): void {
container.className = "container column jc-c g-05";

this.projectNameInput = createTextInput(
"project-name-input",
"Nome do Projeto",
{ min: 0, max: 75, style: { width: 'auto' }, value: "Sem título"},
this.handleNameInput.bind(this),
{ min: 0, max: 75, style: { width: "auto" }, value: "Sem título" },
(v) => this.handleNameInput(v),
);

this.workAreaWidthInput = createSliderControl(
"inp_workarea-width",
"Largura",
{ min: 16, max: 4096, step: 1, value: WORK_AREA_WIDTH },
this.handleWidthInput.bind(this),
(v) => this.handleWidthInput(v),
false,
);

this.workAreaHeightInput = createSliderControl(
"inp_workarea-width",
"inp_workarea-height",
"Altura",
{ min: 16, max: 4096, step: 1, value: WORK_AREA_HEIGHT },
this.handleHeightInput.bind(this),
(v) => this.handleHeightInput(v),
false,
);

this.projectNameInput.linkEvents();
this.workAreaWidthInput.linkEvents();
this.workAreaHeightInput.linkEvents();
this.templatesInput = createSelectInput(
"templates-input",
"Templates",
{
optionValues: TEMPLATE_OPTIONS,
style: {
width: 'auto',
},
value: TEMPLATE_OPTIONS[0].value,
},
(v) => this.handleTemplateChange(v),
);

container.appendChild(this.projectNameInput.element);
container.appendChild(this.templatesInput.element);
container.appendChild(this.workAreaWidthInput.element);
container.appendChild(this.workAreaHeightInput.element);
}

protected onOpen(): void {
this.projectNameInput?.enable();
this.workAreaWidthInput?.enable();
this.workAreaHeightInput?.enable();
this.templatesInput?.enable();

// Apply initial template after controls are present + enabled.
const initialTemplate = TEMPLATE_OPTIONS.find(
(opt) => opt.value !== OPTION_SEPARATOR_VALUE,
);
if (initialTemplate) {
this.templatesInput?.setValue(initialTemplate.value);
this.handleTemplateChange(initialTemplate.value);
}
}

protected onClose(): void {
this.projectNameInput?.unlinkEvents();
this.workAreaWidthInput?.unlinkEvents();
this.workAreaHeightInput?.unlinkEvents();
this.projectNameInput?.disable();
this.workAreaWidthInput?.disable();
this.workAreaHeightInput?.disable();
this.templatesInput?.disable();
}

protected appendDialogActions(menu: HTMLMenuElement): void {
const btnAccept = document.createElement("button");
btnAccept.id = "btn_create-project";
Expand Down
117 changes: 57 additions & 60 deletions src/components/dialogs/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,71 @@ import type { Position } from "../types";

interface IDialogOptions {
id: string;
isDraggable?: boolean;
style?: {
minWidth?: string;
};
title: string;
isDraggable?: boolean;
style?: { minWidth?: string };
}

export abstract class Dialog {
private dialogEl: HTMLDialogElement | null = null;
private headerEl: HTMLHeadingElement | null = null;
protected dialogContent: HTMLDivElement | null = null;
protected dialogActions: HTMLMenuElement | null = null;
private dialogEl: HTMLDialogElement;
private headerEl: HTMLHeadingElement | null;
protected dialogContent: HTMLDivElement | null;
protected dialogActions: HTMLMenuElement | null;
private isDragging = false;
private dragOffset: Position = { x: 0, y: 0 };

constructor(options: IDialogOptions) {
this.createDOMElements(options);
if (options.isDraggable) this.enableDrag();
}

private createDOMElements(options: IDialogOptions): void {
this.dialogEl = document.createElement("dialog");
this.dialogEl.id = `dialog-${options.id}`;
this.dialogEl.className = "dialog-common";
this.dialogEl.style.position = "fixed";
this.dialogEl.style.minWidth = options?.style?.minWidth || "fit-content";
if (!options.isDraggable) this.dialogEl.classList.add("fixed-dialog");
this.resetPosition();

this.dialogEl.innerHTML = `
<form method="dialog">
<h3 id="dialog-${options.id}-header" style="cursor: ${options.isDraggable ? "move" : "default"};">${options.title}</h3>
<div id="dialog-${options.id}-content" class="container g-05 ai-fs"></div>
<menu id="dialog-${options.id}-actions" class="container g-05 ai-fs"></menu>
</form>
`;
this.dialogEl = this.createDialogElement(options);
document.body.appendChild(this.dialogEl);

this.headerEl = this.dialogEl?.querySelector<HTMLHeadingElement>(
`#dialog-${options.id}-header`,
);
this.dialogContent = this.dialogEl?.querySelector<HTMLDivElement>(
this.headerEl = this.dialogEl.querySelector(`#dialog-${options.id}-header`);
this.dialogContent = this.dialogEl.querySelector(
`#dialog-${options.id}-content`,
);
this.dialogActions = this.dialogEl?.querySelector<HTMLMenuElement>(
this.dialogActions = this.dialogEl.querySelector(
`#dialog-${options.id}-actions`,
);
if (this.dialogContent && this.dialogActions) {
this.appendDialogContent(this.dialogContent);
this.appendDialogActions(this.dialogActions);
}

this.dialogEl.addEventListener("close", () => {
this.onClose();
});
if (options.isDraggable) this.enableDrag();
this.dialogEl.addEventListener("close", () => this.onClose());
}

private createDialogElement(options: IDialogOptions): HTMLDialogElement {
const dialog = document.createElement("dialog");
dialog.id = `dialog-${options.id}`;
dialog.className = "dialog-common";
dialog.style.position = "fixed";
dialog.style.minWidth = options.style?.minWidth ?? "fit-content";
if (!options.isDraggable) dialog.classList.add("fixed-dialog");
this.setCenteredPosition(dialog);

dialog.innerHTML = `
<form method="dialog">
<h3 id="dialog-${options.id}-header" style="cursor:${options.isDraggable ? "move" : "default"};">
${options.title}
</h3>
<div id="dialog-${options.id}-content" class="container g-05 ai-fs"></div>
<menu id="dialog-${options.id}-actions" class="container g-05 ai-fs"></menu>
</form>
`;
return dialog;
}

private enableDrag(): void {
if (!this.headerEl) return;
this.headerEl.addEventListener("mousedown", (event: MouseEvent) => {
event.preventDefault();
if (!this.dialogEl) return;

this.headerEl.addEventListener("mousedown", (event) => {
event.preventDefault();
const rect = this.dialogEl.getBoundingClientRect();

this.dialogEl.style.transform = "";
this.dialogEl.style.left = `${rect.left}px`;
this.dialogEl.style.top = `${rect.top}px`;

this.dragOffset.x = event.clientX - rect.left;
this.dragOffset.y = event.clientY - rect.top;
this.dragOffset = {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
};
this.isDragging = true;

window.addEventListener("mousemove", this.onMouseMove);
Expand All @@ -80,36 +75,38 @@ export abstract class Dialog {
}

private onMouseMove = (event: MouseEvent): void => {
if (!this.isDragging || !this.dialogEl) return;
if (!this.isDragging) return;
const x = event.clientX - this.dragOffset.x;
const y = event.clientY - this.dragOffset.y;
this.dialogEl.style.left = `${x}px`;
this.dialogEl.style.top = `${y}px`;
Object.assign(this.dialogEl.style, { left: `${x}px`, top: `${y}px` });
};

private onMouseUp = (): void => {
if (!this.isDragging) return;
this.isDragging = false;
window.removeEventListener("mousemove", this.onMouseMove.bind(this));
window.removeEventListener("mouseup", this.onMouseUp.bind(this));
window.removeEventListener("mousemove", this.onMouseMove);
window.removeEventListener("mouseup", this.onMouseUp);
};

private resetPosition(): void {
if (this.dialogEl) {
this.dialogEl.style.top = "50%";
this.dialogEl.style.left = "50%";
this.dialogEl.style.transform = "translate(-50%, -50%)";
}
private setCenteredPosition(dialog: HTMLDialogElement): void {
Object.assign(dialog.style, {
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
});
}

public open(): void {
if (this.dialogContent && this.dialogActions && !this.dialogContent.hasChildNodes()) {
this.appendDialogContent(this.dialogContent);
this.appendDialogActions(this.dialogActions);
}
this.onOpen();
this.resetPosition();
this.dialogEl?.showModal();
this.setCenteredPosition(this.dialogEl);
this.dialogEl.showModal();
}

public close(): void {
this.dialogEl?.close();
this.dialogEl.close();
}

protected abstract appendDialogContent(container: HTMLDivElement): void;
Expand Down
Loading