You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

261 lines
7.2 KiB

11 months ago
  1. import {Component, ElementRef, HostListener, Input, OnInit, ViewChildren} from '@angular/core';
  2. import HostRecord from '../../structures/HostRecord';
  3. import {ActivatedRoute, Router} from '@angular/router';
  4. import {AlertController, LoadingController, ModalController, PopoverController} from '@ionic/angular';
  5. import {NodePickerComponent} from '../../components/editor/node-picker/node-picker.component';
  6. import {HostOptionsComponent} from '../../components/editor/host-options/host-options.component';
  7. import {EditorService} from '../../service/editor.service';
  8. import {NodeTypeIcons} from '../../structures/node-types';
  9. import {OptionMenuComponent} from '../../components/option-menu/option-menu.component';
  10. import {VersionModalComponent} from '../../components/version-modal/version-modal.component';
  11. import {debug} from '../../utility';
  12. import {DatabaseLinkComponent} from '../../components/editor/forms/database-link.component';
  13. @Component({
  14. selector: 'app-editor',
  15. templateUrl: './editor.page.html',
  16. styleUrls: ['./editor.page.scss'],
  17. })
  18. export class EditorPage implements OnInit {
  19. public typeIcons = NodeTypeIcons;
  20. @Input() pageId: string;
  21. @Input() isFilloutMode = false;
  22. @Input() hosted = false;
  23. @Input() version?: number;
  24. @ViewChildren('nodeContainer') nodeContainers;
  25. @ViewChildren('nodeContainer', { read: ElementRef }) nodeElements;
  26. public pageType?: string;
  27. protected scrollToNodeId?: string;
  28. @Input()
  29. set readonly(val: boolean) {
  30. this.editorService.forceReadonly = val;
  31. }
  32. get readonly() {
  33. return this.editorService.forceReadonly;
  34. }
  35. constructor(
  36. protected route: ActivatedRoute,
  37. protected router: Router,
  38. protected loader: LoadingController,
  39. protected popover: PopoverController,
  40. protected alerts: AlertController,
  41. protected modals: ModalController,
  42. public readonly editorService: EditorService,
  43. ) {
  44. debug('Constructed editor page.');
  45. this.route.params.subscribe(params => {
  46. this.pageId = params.id;
  47. debug('Got page ID from route:', this.pageId, params);
  48. if ( params.node_id ) {
  49. debug('Scroll to node ID:', params.node_id);
  50. this.scrollToNodeId = params.node_id;
  51. }
  52. });
  53. this.editorService = editorService.getEditor();
  54. }
  55. ngOnInit() {}
  56. async ionViewDidEnter() {
  57. debug('Ion view did enter editor page.');
  58. if ( this.pageId ) {
  59. await this.editorService.startEditing(this.pageId, this.version);
  60. } else if ( !this.hosted ) {
  61. await this.router.navigate(['/home']);
  62. }
  63. setTimeout(() => {
  64. if ( this.scrollToNodeId ) {
  65. debug('Scrolling to node.');
  66. const nodes = this.nodeContainers.toArray();
  67. const elements = this.nodeElements.toArray();
  68. debug('Nodes', {nodes, elements});
  69. nodes.forEach((node, i) => {
  70. if ( node.nodeId === this.scrollToNodeId ) {
  71. elements[i].nativeElement.scrollIntoView();
  72. node.performUIActivation();
  73. }
  74. });
  75. }
  76. }, 150);
  77. }
  78. @HostListener('document:keydown.control.s', ['$event'])
  79. onManualSave(event) {
  80. event.preventDefault();
  81. this.editorService.save();
  82. }
  83. async onOptionsClick(event: MouseEvent, node: HostRecord) {
  84. if ( !this.editorService.canEdit() ) {
  85. return;
  86. }
  87. const popover = await this.popover.create({
  88. component: HostOptionsComponent,
  89. event,
  90. componentProps: {
  91. editor: this,
  92. index: this.editorService.immutableNodes.indexOf(node),
  93. event,
  94. hostRecord: node,
  95. }
  96. });
  97. popover.onDidDismiss().then(result => {
  98. const { event: dismissEvent , value } = result.data;
  99. if ( value === 'delete_node' ) {
  100. this.alerts.create({
  101. header: 'Delete node?',
  102. message: 'Are you sure you want to delete this node? Its contents will be unrecoverable.',
  103. buttons: [
  104. {
  105. text: 'Keep It',
  106. role: 'cancel',
  107. },
  108. {
  109. text: 'Delete It',
  110. role: 'ok',
  111. handler: () => {
  112. this.editorService.deleteNode(node.UUID);
  113. }
  114. }
  115. ],
  116. }).then(alert => alert.present());
  117. } else if ( value === 'move_up' ) {
  118. this.editorService.moveNode(node, 'up');
  119. } else if ( value === 'move_down' ) {
  120. this.editorService.moveNode(node, 'down');
  121. } else if ( value === 'add_before' ) {
  122. this.onAddClick(event, 'before', node.UUID);
  123. } else if ( value === 'add_after' ) {
  124. this.onAddClick(event, 'after', node.UUID);
  125. }
  126. });
  127. await popover.present();
  128. }
  129. async onAddClick(event: MouseEvent, position?: 'before' | 'after', positionNodeId?: string) {
  130. if ( !this.editorService.canEdit() ) {
  131. return;
  132. }
  133. const popover = await this.popover.create({
  134. component: NodePickerComponent,
  135. event,
  136. componentProps: {
  137. formMode: this.editorService.currentPageType === 'form',
  138. },
  139. });
  140. popover.onDidDismiss().then(result => {
  141. if ( !result.data ) {
  142. return;
  143. }
  144. this.editorService.addNode(result.data, position, positionNodeId);
  145. });
  146. await popover.present();
  147. }
  148. async onPageMenuClick(event: MouseEvent) {
  149. debug('Page type: ', this.pageType)
  150. const popover = await this.popover.create({
  151. event,
  152. component: OptionMenuComponent,
  153. componentProps: {
  154. menuItems: [
  155. {
  156. name: 'Versions',
  157. icon: 'fa fa-history',
  158. value: 'view-versions',
  159. title: 'View other versions of this page',
  160. },
  161. ...(this.editorService.currentPageType === 'form' ? [
  162. {
  163. name: 'Link to Database',
  164. icon: NodeTypeIcons.db,
  165. value: 'link-to-db',
  166. title: 'Configure the database where submissions of this form will be stored',
  167. },
  168. ] : []),
  169. ],
  170. },
  171. });
  172. popover.onDidDismiss().then(async value => {
  173. const { data } = value;
  174. if ( data === 'view-versions' ) {
  175. await this.showVersionModal();
  176. } else if ( data === 'link-to-db' ) {
  177. await this.showFormDatabaseLinkModal();
  178. }
  179. });
  180. await popover.present();
  181. }
  182. async showFormDatabaseLinkModal() {
  183. const modal = await this.modals.create({
  184. component: DatabaseLinkComponent,
  185. componentProps: {
  186. pageId: this.pageId,
  187. editorUUID: this.editorService.instanceUUID,
  188. },
  189. cssClass: 'modal-med',
  190. });
  191. modal.onDidDismiss().then(data => {
  192. debug('Database link closed', data);
  193. });
  194. const modalState = {
  195. modal : true,
  196. desc : 'Link form to database'
  197. };
  198. history.pushState(modalState, null);
  199. await modal.present();
  200. }
  201. async showVersionModal() {
  202. const modal = await this.modals.create({
  203. component: VersionModalComponent,
  204. componentProps: {
  205. pageId: this.pageId,
  206. },
  207. cssClass: 'modal-big',
  208. });
  209. modal.onDidDismiss().then(data => {
  210. if ( data.data ) {
  211. this.editorService.startEditing(this.pageId);
  212. }
  213. });
  214. const modalState = {
  215. modal : true,
  216. desc : 'Page versions'
  217. };
  218. history.pushState(modalState, null);
  219. await modal.present();
  220. }
  221. }