Reimplement modal-wrapper as augmenting service

This allows us to use a single service implementation to augment
multiple modal-wrappers instead of bootstrapping the legacy frontend
there on every page load.
pull/6380/head
Oliver Günther 6 years ago
parent 0ebf2ef731
commit 946758bb35
No known key found for this signature in database
GPG Key ID: 88872239EB414F99
  1. 8
      app/views/account/_password_login_form.html.erb
  2. 6
      app/views/account/register.html.erb
  3. 9
      app/views/layouts/base.html.erb
  4. 7
      app/views/onboarding/_menu_item.html.erb
  5. 110
      frontend/legacy/app/components/modals/modal-wrapper/modal-wrapper.directive.ts
  6. 12
      frontend/src/app/angular4-modules.ts
  7. 104
      frontend/src/app/globals/augmenting/modal-wrapper.augment.service.ts

@ -67,14 +67,14 @@ See docs/COPYRIGHT.rdoc for more details.
<%= link_to t(:label_register),
'',
title: t(:label_register),
class: 'modal-wrapper--activation-link',
id: 'registration-modal--activation-link--login-form' %>
class: 'modal-wrapper--activation-link' %>
<% end %>
</div>
</div>
<% end %>
<modal-wrapper activation-link-id="'#registration-modal--activation-link--login-form'" modal-class-name="registration-modal modal-wrapper">
<section data-augmented-model-wrapper
data-modal-class-name="registration-modal modal-wrapper">
<% @user ||= User.new %>
<%= render partial: '/account/register' %>
</modal-wrapper>
</section>

@ -28,8 +28,10 @@ See docs/COPYRIGHT.rdoc for more details.
++#%>
<%= activate_angular_js do %>
<modal-wrapper initialize="true" modal-class-name="registration-modal modal-wrapper -highlight">
<section data-augmented-model-wrapper
data-modal-initialize-now="true"
data-modal-class-name="registration-modal modal-wrapper -highlight">
<% @user ||= User.new %>
<%= render partial: '/account/register' %>
</modal-wrapper>
</section>
<% end %>

@ -146,11 +146,12 @@ See docs/COPYRIGHT.rdoc for more details.
<%= render_flash_messages %>
<notifications-container></notifications-container>
<% if show_onboarding_modal? %>
<modal-wrapper initialize="true"
iframe-url="<%= OpenProject::Configuration.onboarding_video_url %>"
modal-class-name="onboarding-modal -highlight">
<section data-augmented-model-wrapper
data-modal-initialize-now="true"
data-modal-iframe-url="<%= OpenProject::Configuration.onboarding_video_url %>"
data-modal-class-name="onboarding-modal -highlight">
<%= render partial: '/onboarding/starting_video_modal' %>
</modal-wrapper>
</section>
<% end %>
<div id="content" class="<%= initial_classes %>">
<h1 class="hidden-for-sighted"><%= l(:label_content) %></h1>

@ -26,8 +26,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See docs/COPYRIGHT.rdoc for more details.
++#%>
<modal-wrapper iframe-url="<%= OpenProject::Configuration.onboarding_video_url %>"
modal-class-name="onboarding-modal -highlight">
<section data-augmented-model-wrapper
data-modal-iframe-url="<%= OpenProject::Configuration.onboarding_video_url %>"
data-modal-class-name="onboarding-modal -highlight">
<li>
<%= link_to l(:label_introduction_video),
'',
@ -35,4 +36,4 @@ See docs/COPYRIGHT.rdoc for more details.
class: 'modal-wrapper--activation-link' %>
</li>
<%= render partial: '/onboarding/starting_video_modal' %>
</modal-wrapper>
</section>

@ -1,110 +0,0 @@
// -- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {openprojectLegacyModule} from "../../../openproject-legacy-app";
export class ModalWrapperController {
public activationLinkId:string;
public iframeSelector = '.iframe-target-wrapper';
private modalBody:string;
public modalClassName = 'ngdialog-theme-openproject';
protected opModalService:any;
protected dynamicContentModal:any;
constructor(protected $element:ng.IAugmentedJQuery,
protected $attrs:ng.IAttributes) {
window.OpenProject.pluginContext.valuesPromise().then((context) => {
this.opModalService = context.services.opModalService;
this.dynamicContentModal = context.classes.modals.dynamicContent;
// Find activation link
var activationLink = $element.find('.modal-wrapper--activation-link');
if (this.activationLinkId) {
activationLink = jQuery(this.activationLinkId);
}
// Set modal class name
if ($attrs['modalClassName']) {
this.modalClassName = $attrs['modalClassName'];
}
// Set template from wrapped element
const wrappedElement = $element.find('.modal-wrapper--content');
this.modalBody = wrappedElement.html();
if ($attrs['iframeUrl']) {
this.appendIframe($attrs['iframeUrl']);
}
if (!!$attrs['initialize']) {
this.initialize();
}
else {
activationLink.click(() => this.initialize());
}
});
}
public initialize() {
let modal = this.opModalService.show(this.dynamicContentModal, { modalBody: this.modalBody, modalClassName: this.modalClassName });
modal.openingEvent.subscribe((modal:any) => {
//HACK: need to trigger an angular digest in order to have the
//modal template be evaluated. Without it, the onInit will not be run.
jQuery('.op-modal--modal-container').click();
});
}
private appendIframe(url:string) {
let subdom = angular.element(this.modalBody);
let iframe = angular.element('<iframe frameborder="0" height="400" allowfullscreen>></iframe>');
iframe.attr('src', url);
subdom.find(this.iframeSelector).append(iframe);
this.modalBody = subdom.html();
}
}
function modalWrapper():any {
return {
restrict: 'E',
scope: {
modalParams: '=',
activationLinkId: '=?'
},
controller: ModalWrapperController,
controllerAs: '$ctrl',
bindToController: true,
};
}
openprojectLegacyModule.directive('modalWrapper', modalWrapper);

@ -207,6 +207,7 @@ import {OpenProjectFileUploadService} from "core-components/api/op-file-upload/o
import {AttributeHelpTextModal} from "./modules/common/help-texts/attribute-help-text.modal";
import {CopyToClipboardDirective} from 'core-app/modules/common/copy-to-clipboard/copy-to-clipboard.directive';
import {WorkPackageEmbeddedTableEntryComponent} from "core-components/wp-table/embedded/wp-embedded-table-entry.component";
import {ModalWrapperAugmentService} from "core-app/globals/augmenting/modal-wrapper.augment.service";
@NgModule({
imports: [
@ -306,6 +307,10 @@ import {WorkPackageEmbeddedTableEntryComponent} from "core-components/wp-table/e
// External query configuration
ExternalQueryConfigurationService,
// Augmenting Rails
ModalWrapperAugmentService,
],
declarations: [
ConfirmFormSubmitController,
@ -566,8 +571,11 @@ export function bootstrapOptional(appRef:ApplicationRef, ...elements:{ tagName:s
export function initializeServices(injector:Injector) {
return () => {
const ExternalQueryConfiguration:ExternalQueryConfigurationService = injector.get(ExternalQueryConfigurationService);
const global = (window as any);
const ExternalQueryConfiguration = injector.get(ExternalQueryConfigurationService);
const ModalWrapper = injector.get(ModalWrapperAugmentService);
// Setup modal wrapping
ModalWrapper.setupListener();
// Setup query configuration listener
ExternalQueryConfiguration.setupListener();

@ -0,0 +1,104 @@
// -- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
// ++
import {Inject, Injectable} from "@angular/core";
import {DOCUMENT} from "@angular/common";
import {DynamicContentModal} from "core-components/modals/modal-wrapper/dynamic-content.modal";
import {OpModalService} from "core-components/op-modals/op-modal.service";
const iframeSelector = '.iframe-target-wrapper';
@Injectable()
export class ModalWrapperAugmentService {
constructor(@Inject(DOCUMENT) protected documentElement:Document,
protected opModalService:OpModalService) {
}
/**
* Create initial listeners for Rails-rendered modals
*/
public setupListener() {
const matches = this.documentElement.querySelectorAll('section[data-augmented-model-wrapper]');
for (let i = 0; i < matches.length; ++i) {
this.wrapElement(jQuery(matches[i]));
}
}
/**
* Wrap a section[data-augmented-modal-wrapper] element
*/
public wrapElement(element:JQuery) {
// Find activation link
const activationLink = element.find('.modal-wrapper--activation-link');
const initializeNow = element.data('modalInitializeNow');
if (initializeNow) {
this.show(element);
} else {
activationLink.click((evt:JQueryEventObject) => {
this.show(element);
evt.preventDefault();
});
}
}
private show(element:JQuery) {
// Set modal class name
const modalClassName = element.data('modalClassName');
// Append CSP-whitelisted IFrame for onboarding
const iframeUrl = element.data('modalIframeUrl');
// Set template from wrapped element
const wrappedElement = element.find('.modal-wrapper--content');
let modalBody = wrappedElement.html();
if (iframeUrl) {
modalBody = this.appendIframe(modalBody, iframeUrl);
}
this.opModalService.show(
DynamicContentModal,
{
modalBody: modalBody,
modalClassName: modalClassName
}
);
}
private appendIframe(body:string, url:string) {
let subdom = jQuery(body);
let iframe = jQuery('<iframe frameborder="0" height="400" allowfullscreen>></iframe>');
iframe.attr('src', url);
subdom.find(iframeSelector).append(iframe);
return subdom.html();
}
}
Loading…
Cancel
Save