[TASK] Migrate TYPO3/CMS/Viewpage/Main to TypeScript
[Packages/TYPO3.CMS.git] / typo3 / sysext / viewpage / Resources / Private / TypeScript / Main.ts
1 /*
2  * This file is part of the TYPO3 CMS project.
3  *
4  * It is free software; you can redistribute it and/or modify it under
5  * the terms of the GNU General Public License, either version 2
6  * of the License, or any later version.
7  *
8  * For the full copyright and license information, please read the
9  * LICENSE.txt file that was distributed with this source code.
10  *
11  * The TYPO3 project - inspiring people to share!
12  */
13
14 import * as $ from 'jquery';
15 import 'jquery-ui/resizable';
16 import PersistentStorage = require('TYPO3/CMS/Backend/Storage/Persistent');
17
18 enum Selectors {
19   resizableContainerIdentifier = '.t3js-viewpage-resizeable',
20   sizeIdentifier = '.t3js-viewpage-size',
21   moduleBodySelector = '.t3js-module-body',
22   customSelector = '.t3js-preset-custom',
23   customWidthSelector = '.t3js-preset-custom',
24   customHeightSelector = '.t3js-preset-custom-height',
25   changeOrientationSelector = '.t3js-change-orientation',
26   changePresetSelector = '.t3js-change-preset',
27   inputWidthSelector = '.t3js-viewpage-input-width',
28   inputHeightSelector = '.t3js-viewpage-input-height',
29   currentLabelSelector = '.t3js-viewpage-current-label',
30   topbarContainerSelector = '.t3js-viewpage-topbar',
31 }
32
33 /**
34  * Module: TYPO3/CMS/Viewpage/Main
35  * Main logic for resizing the view of the frame
36  */
37 class ViewPage {
38   private defaultLabel: string = '';
39   private readonly minimalHeight: number = 300;
40   private readonly minimalWidth: number = 300;
41
42   private readonly storagePrefix: string = 'moduleData.web_view.States.';
43   private $iframe: JQuery;
44   private $resizableContainer: JQuery;
45   private $sizeSelector: JQuery;
46
47
48   private readonly queue: Array<any> = [];
49   private queueIsRunning: boolean = false;
50   private queueDelayTimer: any;
51
52   private static getCurrentWidth(): string {
53     return $(Selectors.inputWidthSelector).val();
54   }
55
56   private static getCurrentHeight(): string {
57     return $(Selectors.inputHeightSelector).val();
58   }
59
60   private static setLabel(label: string): void {
61     $(Selectors.currentLabelSelector).html(label);
62   }
63
64   private static getCurrentLabel(): string {
65     return $(Selectors.currentLabelSelector).html().trim();
66   }
67
68   constructor() {
69     $((): void => {
70       const $presetCustomLabel = $('.t3js-preset-custom-label');
71
72       this.defaultLabel = $presetCustomLabel.length > 0 ? $presetCustomLabel.html().trim() : '';
73       this.$iframe = $('#tx_this_iframe');
74       this.$resizableContainer = $(Selectors.resizableContainerIdentifier);
75       this.$sizeSelector = $(Selectors.sizeIdentifier);
76
77       this.initialize();
78     });
79   }
80
81   private persistQueue(): void {
82     if (this.queueIsRunning === false && this.queue.length >= 1) {
83       this.queueIsRunning = true;
84       let item = this.queue.shift();
85       PersistentStorage.set(item.storageIdentifier, item.data).done((): void => {
86         this.queueIsRunning = false;
87         this.persistQueue();
88       });
89     }
90   }
91
92   private addToQueue(storageIdentifier: string, data: any): void {
93     const item = {
94       storageIdentifier: storageIdentifier,
95       data: data
96     };
97     this.queue.push(item);
98     if (this.queue.length >= 1) {
99       this.persistQueue();
100     }
101   }
102
103   private setSize(width: number, height: number): void {
104     if (isNaN(height)) {
105       height = this.calculateContainerMaxHeight();
106     }
107     if (height < this.minimalHeight) {
108       height = this.minimalHeight;
109     }
110     if (isNaN(width)) {
111       width = this.calculateContainerMaxWidth();
112     }
113     if (width < this.minimalWidth) {
114       width = this.minimalWidth;
115     }
116
117     $(Selectors.inputWidthSelector).val(width);
118     $(Selectors.inputHeightSelector).val(height);
119
120     this.$resizableContainer.css({
121       width: width,
122       height: height,
123       left: 0
124     });
125   }
126
127   private persistCurrentPreset(): void {
128     let data = {
129       width: ViewPage.getCurrentWidth(),
130       height: ViewPage.getCurrentHeight(),
131       label: ViewPage.getCurrentLabel()
132     };
133     this.addToQueue(this.storagePrefix + 'current', data);
134   }
135
136   private persistCustomPreset(): void {
137     let data = {
138       width: ViewPage.getCurrentWidth(),
139       height: ViewPage.getCurrentHeight()
140     };
141     $(Selectors.customSelector).data('width', data.width);
142     $(Selectors.customSelector).data('height', data.height);
143     $(Selectors.customWidthSelector).html(data.width);
144     $(Selectors.customHeightSelector).html(data.height);
145     this.addToQueue(this.storagePrefix + 'custom', data);
146   }
147
148   private persistCustomPresetAfterChange(): void {
149     clearTimeout(this.queueDelayTimer);
150     this.queueDelayTimer = window.setTimeout(() => { this.persistCustomPreset(); }, 1000);
151   }
152
153   /**
154    * Initialize
155    */
156   private initialize (): void {
157     // Change orientation
158     $(document).on('click', Selectors.changeOrientationSelector, (): void => {
159       const width = $(Selectors.inputHeightSelector).val();
160       const height = $(Selectors.inputWidthSelector).val();
161       this.setSize(width, height);
162       this.persistCurrentPreset();
163     });
164
165     // On change
166     $(document).on('change', Selectors.inputWidthSelector, (): void => {
167       const width = $(Selectors.inputWidthSelector).val();
168       const height = $(Selectors.inputHeightSelector).val();
169       this.setSize(width, height);
170       ViewPage.setLabel(this.defaultLabel);
171       this.persistCustomPresetAfterChange();
172     });
173     $(document).on('change', Selectors.inputHeightSelector, (): void => {
174       const width = $(Selectors.inputWidthSelector).val();
175       const height = $(Selectors.inputHeightSelector).val();
176       this.setSize(width, height);
177       ViewPage.setLabel(this.defaultLabel);
178       this.persistCustomPresetAfterChange();
179     });
180
181     // Add event to width selector so the container is resized
182     $(document).on('click', Selectors.changePresetSelector, (evt: JQueryEventObject): void => {
183       const data = $(evt.currentTarget).data();
184       this.setSize(parseInt(data.width, 10), parseInt(data.height, 10));
185       ViewPage.setLabel(data.label);
186       this.persistCurrentPreset();
187     });
188
189     // Initialize the jQuery UI Resizable plugin
190     this.$resizableContainer.resizable({
191       handles: 'w, sw, s, se, e'
192     });
193
194     this.$resizableContainer.on('resizestart', (evt: JQueryEventObject): void => {
195       // Add iframe overlay to prevent losing the mouse focus to the iframe while resizing fast
196       $(evt.currentTarget)
197         .append('<div id="this-iframe-cover" style="z-index:99;position:absolute;width:100%;top:0;left:0;height:100%;"></div>');
198     });
199
200     this.$resizableContainer.on('resize', (evt: JQueryEventObject, ui: JQueryUI.ResizableUIParams): void => {
201       ui.size.width = ui.originalSize.width + ((ui.size.width - ui.originalSize.width) * 2);
202       if (ui.size.height < this.minimalHeight) {
203         ui.size.height = this.minimalHeight;
204       }
205       if (ui.size.width < this.minimalWidth) {
206         ui.size.width = this.minimalWidth;
207       }
208       $(Selectors.inputWidthSelector).val(ui.size.width);
209       $(Selectors.inputHeightSelector).val(ui.size.height);
210       this.$resizableContainer.css({
211         left: 0
212       });
213       ViewPage.setLabel(this.defaultLabel);
214     });
215
216     this.$resizableContainer.on('resizestop', (): void => {
217       $('#viewpage-iframe-cover').remove();
218       this.persistCurrentPreset();
219       this.persistCustomPreset();
220     });
221   }
222
223   private calculateContainerMaxHeight(): number {
224     this.$resizableContainer.hide();
225     let $moduleBody = $(Selectors.moduleBodySelector);
226     let padding = $moduleBody.outerHeight() - $moduleBody.height(),
227       documentHeight = $(document).height(),
228       topbarHeight = $(Selectors.topbarContainerSelector).outerHeight();
229     this.$resizableContainer.show();
230     return documentHeight - padding - topbarHeight - 8;
231   }
232
233   private calculateContainerMaxWidth(): number {
234     this.$resizableContainer.hide();
235     let $moduleBody: JQuery = $(Selectors.moduleBodySelector);
236     let padding: number = $moduleBody.outerWidth() - $moduleBody.width();
237     let documentWidth: number = $(document).width();
238     this.$resizableContainer.show();
239     return parseInt((documentWidth - padding) + '', 10);
240   }
241 }
242
243 export = new ViewPage();