diff --git a/app/src/plugins/app/_theme/_components/modals.sass b/app/src/plugins/app/_theme/_components/modals.sass index a31a814..d1b0f25 100644 --- a/app/src/plugins/app/_theme/_components/modals.sass +++ b/app/src/plugins/app/_theme/_components/modals.sass @@ -37,7 +37,7 @@ justify-content: space-between .left - width: 70% !important + width: calc(75% - 15px) !important .right - width: 25% !important \ No newline at end of file + width: calc(25% - 15px) !important \ No newline at end of file diff --git a/app/src/plugins/tm@modal/_components/a-close-button.vue b/app/src/plugins/tm@modal/_components/a-close-button.vue new file mode 100644 index 0000000..3134360 --- /dev/null +++ b/app/src/plugins/tm@modal/_components/a-close-button.vue @@ -0,0 +1,37 @@ + + + + + + diff --git a/app/src/plugins/tm@modal/_components/a-reset-size-button.vue b/app/src/plugins/tm@modal/_components/a-reset-size-button.vue new file mode 100644 index 0000000..05ca165 --- /dev/null +++ b/app/src/plugins/tm@modal/_components/a-reset-size-button.vue @@ -0,0 +1,41 @@ + + + + + + diff --git a/app/src/plugins/tm@modal/_components/a-resize-handle.vue b/app/src/plugins/tm@modal/_components/a-resize-handle.vue new file mode 100644 index 0000000..78efeb6 --- /dev/null +++ b/app/src/plugins/tm@modal/_components/a-resize-handle.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/app/src/plugins/tm@modal/_mixins/modal.mixin.ts b/app/src/plugins/tm@modal/_mixins/modal.mixin.ts index 4850377..19a78d0 100644 --- a/app/src/plugins/tm@modal/_mixins/modal.mixin.ts +++ b/app/src/plugins/tm@modal/_mixins/modal.mixin.ts @@ -1,12 +1,107 @@ +import modalResizeUtils from '@/plugins/tm@modal/_utils/modal-resize.utils' + export default { + data() { + return { + modalWidth: '100%', + modalHeight: '100%', + modalName: '' // Should be set by component + } + }, + + computed: { + modalWidthPx() { + return this.modalWidth + }, + modalHeightPx() { + return this.modalHeight + } + }, + mounted() { window.addEventListener('keydown', this.handleKeyDown) + window.addEventListener('resize', this.handleWindowResize) + this.initModalSize() + }, + + beforeUnmount() { + window.removeEventListener('keydown', this.handleKeyDown) + window.removeEventListener('resize', this.handleWindowResize) }, methods: { handleKeyDown(event) { // if (event.keyCode === 27) this.closeModal() if (event.keyCode === 27) this.visible = false + }, + + /** + * Initialize modal size from localStorage or defaults + */ + initModalSize() { + if (!this.modalName) { + console.warn('Modal name not set for resize functionality') + return + } + + const size = modalResizeUtils.getModalSize(this.modalName) + this.modalWidth = typeof size.width === 'number' ? `${size.width}px` : size.width + this.modalHeight = typeof size.height === 'number' ? `${size.height}px` : size.height + }, + + /** + * Handle window resize - adjust modal if on mobile + */ + handleWindowResize() { + if (modalResizeUtils.isMobile()) { + this.modalWidth = '100%' + this.modalHeight = '100%' + } else { + // Reload stored or default size on desktop + this.initModalSize() + } + }, + + /** + * Handle modal resize from resize handle + */ + handleModalResize({ width, height }) { + if (modalResizeUtils.isMobile()) { + return // Don't resize on mobile + } + + const constrained = modalResizeUtils.constrainSize(width, height) + this.modalWidth = `${constrained.width}px` + this.modalHeight = `${constrained.height}px` + }, + + /** + * Handle resize end - save to localStorage + */ + handleResizeEnd() { + if (!this.modalName || modalResizeUtils.isMobile()) { + return + } + + // Parse current dimensions + const width = parseFloat(this.modalWidth) + const height = parseFloat(this.modalHeight) + + if (!isNaN(width) && !isNaN(height)) { + modalResizeUtils.saveSize(this.modalName, width, height) + } + }, + + /** + * Reset modal size to defaults + */ + resetModalSize() { + if (!this.modalName) { + return + } + + modalResizeUtils.resetSize(this.modalName) + this.initModalSize() } - }, + } } \ No newline at end of file diff --git a/app/src/plugins/tm@modal/_utils/modal-resize.utils.ts b/app/src/plugins/tm@modal/_utils/modal-resize.utils.ts new file mode 100644 index 0000000..a5335ac --- /dev/null +++ b/app/src/plugins/tm@modal/_utils/modal-resize.utils.ts @@ -0,0 +1,162 @@ +interface ModalSize { + width: string | number + height: string | number +} + +interface StoredModalSize { + width: number + height: number + timestamp: number +} + +const MOBILE_BREAKPOINT = 768 +const DEFAULT_DESKTOP_WIDTH_PERCENT = 80 +const DEFAULT_DESKTOP_MAX_WIDTH = 1440 +const DEFAULT_DESKTOP_HEIGHT_PERCENT = 90 + +export default { + /** + * Check if the current viewport is mobile + */ + isMobile(): boolean { + return window.innerWidth <= MOBILE_BREAKPOINT + }, + + /** + * Get default modal size based on viewport + */ + getDefaultSize(): ModalSize { + if (this.isMobile()) { + return { + width: '100%', + height: '100%' + } + } + + const viewportWidth = window.innerWidth + const calculatedWidth = Math.min( + (viewportWidth * DEFAULT_DESKTOP_WIDTH_PERCENT) / 100, + DEFAULT_DESKTOP_MAX_WIDTH + ) + const calculatedHeight = (window.innerHeight * DEFAULT_DESKTOP_HEIGHT_PERCENT) / 100 + + return { + width: calculatedWidth, + height: calculatedHeight + } + }, + + /** + * Get stored modal size from localStorage + */ + getStoredSize(modalName: string): ModalSize | null { + try { + const key = `modal-size-${modalName}` + const stored = localStorage.getItem(key) + + if (!stored) { + return null + } + + const parsed: StoredModalSize = JSON.parse(stored) + + // Return stored size if it exists and is valid + if (parsed && typeof parsed.width === 'number' && typeof parsed.height === 'number') { + return { + width: parsed.width, + height: parsed.height + } + } + + return null + } catch (error) { + console.error('Error reading stored modal size:', error) + return null + } + }, + + /** + * Save modal size to localStorage + */ + saveSize(modalName: string, width: number, height: number): void { + try { + const key = `modal-size-${modalName}` + const data: StoredModalSize = { + width, + height, + timestamp: Date.now() + } + localStorage.setItem(key, JSON.stringify(data)) + } catch (error) { + console.error('Error saving modal size:', error) + } + }, + + /** + * Reset modal size (clear from localStorage) + */ + resetSize(modalName: string): void { + try { + const key = `modal-size-${modalName}` + localStorage.removeItem(key) + } catch (error) { + console.error('Error resetting modal size:', error) + } + }, + + /** + * Get modal size (stored or default) + */ + getModalSize(modalName: string): ModalSize { + // On mobile, always return 100% + if (this.isMobile()) { + return { + width: '100%', + height: '100%' + } + } + + // Check for stored size + const storedSize = this.getStoredSize(modalName) + if (storedSize) { + return storedSize + } + + // Return default size + return this.getDefaultSize() + }, + + /** + * Convert size to pixels if percentage, otherwise return as is + */ + normalizeSize(size: string | number): number { + if (typeof size === 'number') { + return size + } + + // If it's a percentage, calculate pixel value + if (size.includes('%')) { + const percent = parseFloat(size) + return (window.innerWidth * percent) / 100 + } + + // If it's already pixels, parse it + return parseFloat(size) + }, + + /** + * Constrain size within reasonable bounds + */ + constrainSize(width: number, height: number): { width: number; height: number } { + const minWidth = 400 + const minHeight = 300 + const maxWidth = window.innerWidth + const maxHeight = window.innerHeight + + return { + width: Math.max(minWidth, Math.min(width, maxWidth)), + height: Math.max(minHeight, Math.min(height, maxHeight)) + } + } +} + diff --git a/app/src/plugins/tm@modal/blocker/z-blocker-modal.vue b/app/src/plugins/tm@modal/blocker/z-blocker-modal.vue index 9b04962..cd0b535 100644 --- a/app/src/plugins/tm@modal/blocker/z-blocker-modal.vue +++ b/app/src/plugins/tm@modal/blocker/z-blocker-modal.vue @@ -1,10 +1,13 @@