diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index 8e1c4fb6f5ce..74d5ea509940 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -115,6 +115,9 @@ module.exports = { "indent": "off", "@typescript-eslint/indent": "off", + // Allow namespaces, they are generated into flat functions and we don't care about modules for helpers + "@typescript-eslint/no-namespace": "off", + /* // Disable use before define, as irrelevant for TS interfaces "no-use-before-define": "off", diff --git a/frontend/src/app/shared/components/datepicker/modal-single-date-picker/modal-single-date-picker.component.ts b/frontend/src/app/shared/components/datepicker/modal-single-date-picker/modal-single-date-picker.component.ts index 06a412e4b573..3f92c37bb712 100644 --- a/frontend/src/app/shared/components/datepicker/modal-single-date-picker/modal-single-date-picker.component.ts +++ b/frontend/src/app/shared/components/datepicker/modal-single-date-picker/modal-single-date-picker.component.ts @@ -241,6 +241,7 @@ export class OpModalSingleDatePickerComponent implements ControlValueAccessor, O inline: true, onReady: (_date:Date[], _datestr:string, instance:flatpickr.Instance) => { instance.calendarContainer.classList.add('op-datepicker-modal--flatpickr-instance'); + this.cdRef.detectChanges(); }, onChange: (dates:Date[]) => { if (dates.length > 0) { @@ -263,7 +264,6 @@ export class OpModalSingleDatePickerComponent implements ControlValueAccessor, O }, this.flatpickrTarget.nativeElement as HTMLElement, ); - this.cdRef.detectChanges(); } writeWorkingValue(value:string):void { diff --git a/frontend/src/stimulus/controllers/async-dialog.controller.ts b/frontend/src/stimulus/controllers/async-dialog.controller.ts index 3f65e3b6e655..2d0892701fe3 100644 --- a/frontend/src/stimulus/controllers/async-dialog.controller.ts +++ b/frontend/src/stimulus/controllers/async-dialog.controller.ts @@ -30,10 +30,9 @@ import { ApplicationController } from 'stimulus-use'; import { renderStreamMessage } from '@hotwired/turbo'; +import { TurboHelpers } from '../../turbo/helpers'; export default class AsyncDialogController extends ApplicationController { - private loadingDialog:HTMLDialogElement|null; - connect() { this.element.addEventListener('click', (e) => { e.preventDefault(); @@ -42,48 +41,20 @@ export default class AsyncDialogController extends ApplicationController { } triggerTurboStream():void { - let loaded = false; - - setTimeout(() => { - if (!loaded) { - this.addLoading(); - } - }, 100); + TurboHelpers.showProgressBar(); - fetch(this.href, { + void fetch(this.href, { method: this.method, headers: { Accept: 'text/vnd.turbo-stream.html', }, }).then((r) => r.text()) .then((html) => { - loaded = true; renderStreamMessage(html); }) - .finally(() => this.removeLoading()); - } - - removeLoading() { - this.loadingDialog?.remove(); - } - - addLoading() { - this.removeLoading(); - const dialog = document.createElement('dialog'); - dialog.classList.add('Overlay', 'Overlay--size-medium', 'Overlay--motion-scaleFade'); - dialog.style.height = '150px'; - dialog.style.display = 'grid'; - dialog.style.placeContent = 'center'; - dialog.id = 'loading'; - dialog.innerHTML = ` - - - - - `; - document.body.appendChild(dialog); - dialog.showModal(); - this.loadingDialog = dialog; + .finally(() => { + TurboHelpers.hideProgressBar(); + }); } get href() { diff --git a/frontend/src/turbo/helpers.ts b/frontend/src/turbo/helpers.ts new file mode 100644 index 000000000000..5922eb37f443 --- /dev/null +++ b/frontend/src/turbo/helpers.ts @@ -0,0 +1,11 @@ +import * as Turbo from '@hotwired/turbo'; + +export namespace TurboHelpers { + export function showProgressBar() { + Turbo.session.adapter.formSubmissionStarted(); + } + + export function hideProgressBar() { + Turbo.session.adapter.formSubmissionFinished(); + } +} diff --git a/frontend/src/typings/shims.d.ts b/frontend/src/typings/shims.d.ts index c3552ccee51c..d00919774d9e 100644 --- a/frontend/src/typings/shims.d.ts +++ b/frontend/src/typings/shims.d.ts @@ -29,8 +29,14 @@ declare module 'dom-autoscroller'; declare module 'core-vendor/enjoyhint'; declare module '@hotwired/turbo' { + interface BrowserAdapter { + formSubmissionStarted:() => void; + formSubmissionFinished:() => void; + } + export const session:{ drive:boolean; + adapter:BrowserAdapter; }; export const navigator:{ diff --git a/lookbook/docs/patterns/05-dialogs.md.erb b/lookbook/docs/patterns/05-dialogs.md.erb index 12e969038681..7890b6183e24 100644 --- a/lookbook/docs/patterns/05-dialogs.md.erb +++ b/lookbook/docs/patterns/05-dialogs.md.erb @@ -24,6 +24,7 @@ end ``` On the Rails controller you wish to render the dialog, you need to respond to the request with the dialog content. +The async-controller stimulus controller will ensure that a loading progress bar will be shown on the top of the page. ```ruby class TestController < ApplicationControler diff --git a/spec/support/components/datepicker/datepicker_modal.rb b/spec/support/components/datepicker/datepicker_modal.rb index 002f86a14bb9..2af089c86497 100644 --- a/spec/support/components/datepicker/datepicker_modal.rb +++ b/spec/support/components/datepicker/datepicker_modal.rb @@ -3,7 +3,7 @@ class DatepickerModal < Datepicker def open_modal! retry_block do click_on "Non-working day", wait: 10 - unless page.has_css?(".flatpickr-calendar") + unless page.has_css?(".flatpickr-calendar", wait: 10) click_on "Cancel" raise "Flatpickr should render a calendar" end