mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Rename "wpPreviewModal" to a more generic "hoverCard" and use a turboFrame inside to be more flexible about the content
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# 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 COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
module WorkPackages
|
||||
class HoverCardController < ApplicationController
|
||||
before_action :load_and_authorize_in_optional_project
|
||||
|
||||
def show
|
||||
@id = params[:id]
|
||||
render layout: nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -173,7 +173,7 @@ module WorkPackagesHelper
|
||||
|
||||
# Returns a string of css classes that apply to the issue
|
||||
def work_package_css_classes(work_package)
|
||||
s = "work_package preview-trigger".html_safe
|
||||
s = "work_package op-hover-card--preview-trigger".html_safe
|
||||
s << " status-#{work_package.status.position}" if work_package.status
|
||||
s << " priority-#{work_package.priority.position}" if work_package.priority
|
||||
s << " closed" if work_package.closed?
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<turbo-frame id="op-hover-card-body">
|
||||
Hallo WELT
|
||||
</turbo-frame>
|
||||
@@ -217,7 +217,8 @@ Rails.application.reloader.to_prepare do
|
||||
work_packages: %i[show index],
|
||||
work_packages_api: [:get],
|
||||
"work_packages/reports": %i[report report_details],
|
||||
"work_packages/menus": %i[show]
|
||||
"work_packages/menus": %i[show],
|
||||
"work_packages/hover_card": %i[show]
|
||||
},
|
||||
permissible_on: %i[work_package project],
|
||||
contract_actions: { work_packages: %i[read] }
|
||||
|
||||
@@ -571,6 +571,8 @@ Rails.application.routes.draw do
|
||||
resources :work_packages, only: [:index] do
|
||||
concerns :shareable
|
||||
|
||||
get "hover_card" => "work_packages/hover_card#show", on: :member
|
||||
|
||||
# move bulk of wps
|
||||
get "move/new" => "work_packages/moves#new", on: :collection, as: "new_move"
|
||||
post "move" => "work_packages/moves#create", on: :collection, as: "move"
|
||||
|
||||
@@ -53,7 +53,7 @@ import { OpenprojectDashboardsModule } from 'core-app/features/dashboards/openpr
|
||||
import {
|
||||
OpenprojectWorkPackageGraphsModule,
|
||||
} from 'core-app/shared/components/work-package-graphs/openproject-work-package-graphs.module';
|
||||
import { PreviewTriggerService } from 'core-app/core/setup/globals/global-listeners/preview-trigger.service';
|
||||
import { HoverCardTriggerService } from 'core-app/core/setup/globals/global-listeners/hover-card-trigger.service';
|
||||
import { OpenprojectOverviewModule } from 'core-app/features/overview/openproject-overview.module';
|
||||
import { OpenprojectMyPageModule } from 'core-app/features/my-page/openproject-my-page.module';
|
||||
import { OpenprojectProjectsModule } from 'core-app/features/projects/openproject-projects.module';
|
||||
@@ -77,8 +77,8 @@ import {
|
||||
PasswordConfirmationModalComponent,
|
||||
} from 'core-app/shared/components/modals/request-for-confirmation/password-confirmation.modal';
|
||||
import {
|
||||
WpPreviewModalComponent,
|
||||
} from 'core-app/shared/components/modals/preview-modal/wp-preview-modal/wp-preview.modal';
|
||||
HoverCardComponent,
|
||||
} from 'core-app/shared/components/modals/preview-modal/hover-card-modal/hover-card.modal';
|
||||
import {
|
||||
OpHeaderProjectSelectComponent,
|
||||
} from 'core-app/shared/components/header-project-select/header-project-select.component';
|
||||
@@ -240,7 +240,7 @@ import { SpotSwitchComponent } from 'core-app/spot/components/switch/switch.comp
|
||||
|
||||
export function initializeServices(injector:Injector) {
|
||||
return () => {
|
||||
const PreviewTrigger = injector.get(PreviewTriggerService);
|
||||
const PreviewTrigger = injector.get(HoverCardTriggerService);
|
||||
const topMenuService = injector.get(TopMenuService);
|
||||
const keyboardShortcuts = injector.get(KeyboardShortcutService);
|
||||
// Conditionally add the Revit Add-In settings button
|
||||
@@ -370,7 +370,7 @@ export function initializeServices(injector:Injector) {
|
||||
ConfirmDialogModalComponent,
|
||||
DynamicContentModalComponent,
|
||||
PasswordConfirmationModalComponent,
|
||||
WpPreviewModalComponent,
|
||||
HoverCardComponent,
|
||||
|
||||
// Main menu
|
||||
MainMenuResizerComponent,
|
||||
|
||||
+8
-24
@@ -28,10 +28,10 @@
|
||||
|
||||
import { Injectable, Injector, NgZone } from '@angular/core';
|
||||
import { OpModalService } from 'core-app/shared/components/modal/modal.service';
|
||||
import { WpPreviewModalComponent } from 'core-app/shared/components/modals/preview-modal/wp-preview-modal/wp-preview.modal';
|
||||
import { HoverCardComponent } from 'core-app/shared/components/modals/preview-modal/hover-card-modal/hover-card.modal';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PreviewTriggerService {
|
||||
export class HoverCardTriggerService {
|
||||
private modalElement:HTMLElement;
|
||||
|
||||
private mouseInModal = false;
|
||||
@@ -44,7 +44,7 @@ export class PreviewTriggerService {
|
||||
}
|
||||
|
||||
setupListener() {
|
||||
jQuery(document.body).on('mouseover', '.preview-trigger', (e) => {
|
||||
jQuery(document.body).on('mouseover', '.op-hover-card--preview-trigger', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
@@ -58,8 +58,9 @@ export class PreviewTriggerService {
|
||||
}
|
||||
|
||||
this.opModalService.show(
|
||||
WpPreviewModalComponent,
|
||||
HoverCardComponent,
|
||||
this.injector,
|
||||
// TODO
|
||||
{ workPackageLink: href, event: e },
|
||||
true,
|
||||
).subscribe((previewModal) => {
|
||||
@@ -69,16 +70,16 @@ export class PreviewTriggerService {
|
||||
}
|
||||
});
|
||||
|
||||
jQuery(document.body).on('mouseleave', '.preview-trigger', () => {
|
||||
jQuery(document.body).on('mouseleave', '.op-hover-card--preview-trigger', () => {
|
||||
this.closeAfterTimeout();
|
||||
});
|
||||
|
||||
jQuery(document.body).on('mouseleave', '.op-wp-preview-modal', () => {
|
||||
jQuery(document.body).on('mouseleave', '.op-hover-card', () => {
|
||||
this.mouseInModal = false;
|
||||
this.closeAfterTimeout();
|
||||
});
|
||||
|
||||
jQuery(document.body).on('mouseenter', '.op-wp-preview-modal', () => {
|
||||
jQuery(document.body).on('mouseenter', '.op-hover-card', () => {
|
||||
this.mouseInModal = true;
|
||||
});
|
||||
}
|
||||
@@ -92,21 +93,4 @@ export class PreviewTriggerService {
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
private isMouseOverPreview(e:JQuery.MouseLeaveEvent) {
|
||||
if (!this.modalElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const previewElement = jQuery(this.modalElement.children[0]);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
if (previewElement && previewElement.offset()) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const horizontalHover = e.pageX >= Math.floor(previewElement.offset()!.left) && e.pageX < previewElement.offset()!.left + previewElement.width()!;
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const verticalHover = e.pageY >= Math.floor(previewElement.offset()!.top) && e.pageY < previewElement.offset()!.top + previewElement.height()!;
|
||||
return horizontalHover && verticalHover;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
[displayFieldOptions]="{ writable: false }"
|
||||
fieldName="type">
|
||||
</display-field>
|
||||
<a class="work-package--quickinfo preview-trigger"
|
||||
<a class="op-hover-card--preview-trigger"
|
||||
target="_top"
|
||||
[href]="workPackageLink"
|
||||
[attr.data-work-package-id]="workPackage.id">
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<div
|
||||
class="op-hover-card"
|
||||
*ngIf="turboFrameSrc"
|
||||
>
|
||||
<turbo-frame
|
||||
loading="lazy"
|
||||
id="op-hover-card-body"
|
||||
[src]="turboFrameSrc">
|
||||
<op-content-loader
|
||||
viewBox="0 0 180 80"
|
||||
>
|
||||
<svg:rect x="10" y="10" width="80%" height="16" rx="1" />
|
||||
<svg:rect x="10" y="30" width="80%" height="16" rx="1" />
|
||||
<svg:rect x="10" y="50" width="60%" height="16" rx="1" />
|
||||
</op-content-loader>
|
||||
</turbo-frame>
|
||||
</div>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
@import "helpers"
|
||||
|
||||
.op-hover-card
|
||||
position: absolute
|
||||
background-color: var(--body-background)
|
||||
z-index: 5000
|
||||
min-width: 350px
|
||||
box-shadow: var(--shadow-floating-large)
|
||||
pointer-events: all
|
||||
padding: 1rem
|
||||
+30
-40
@@ -32,17 +32,12 @@ import {
|
||||
Component,
|
||||
ElementRef,
|
||||
Inject,
|
||||
OnInit,
|
||||
Input,
|
||||
OnInit,
|
||||
} from '@angular/core';
|
||||
import { OpModalComponent } from 'core-app/shared/components/modal/modal.component';
|
||||
import { OpModalLocalsToken, OpModalService } from 'core-app/shared/components/modal/modal.service';
|
||||
import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service';
|
||||
import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types';
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
|
||||
import idFromLink from 'core-app/features/hal/helpers/id-from-link';
|
||||
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
|
||||
import { StateService } from '@uirouter/core';
|
||||
import {
|
||||
computePosition,
|
||||
flip,
|
||||
@@ -50,57 +45,57 @@ import {
|
||||
Placement,
|
||||
shift,
|
||||
} from '@floating-ui/dom';
|
||||
import { WorkPackageIsolatedQuerySpaceDirective } from 'core-app/features/work-packages/directives/query-space/wp-isolated-query-space.directive';
|
||||
import { fromEvent } from 'rxjs';
|
||||
import {
|
||||
WorkPackageIsolatedQuerySpaceDirective,
|
||||
} from 'core-app/features/work-packages/directives/query-space/wp-isolated-query-space.directive';
|
||||
filter,
|
||||
tap,
|
||||
throttleTime,
|
||||
} from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
templateUrl: './wp-preview.modal.html',
|
||||
styleUrls: ['./wp-preview.modal.sass'],
|
||||
templateUrl: './hover-card.modal.html',
|
||||
styleUrls: ['./hover-card.modal.sass'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
hostDirectives: [WorkPackageIsolatedQuerySpaceDirective],
|
||||
})
|
||||
export class WpPreviewModalComponent extends OpModalComponent implements OnInit {
|
||||
public workPackage:WorkPackageResource;
|
||||
|
||||
public text = {
|
||||
created_by: this.i18n.t('js.label_created_by'),
|
||||
};
|
||||
export class HoverCardComponent extends OpModalComponent implements OnInit {
|
||||
@Input() public turboFrameSrc:string = "/work_packages/50/hover_card";
|
||||
|
||||
@Input() public alignment?:Placement = 'bottom-end';
|
||||
|
||||
@Input() public allowRepositioning? = true;
|
||||
|
||||
public test:string;
|
||||
|
||||
constructor(
|
||||
readonly elementRef:ElementRef,
|
||||
@Inject(OpModalLocalsToken) readonly locals:OpModalLocalsMap,
|
||||
readonly cdRef:ChangeDetectorRef,
|
||||
readonly i18n:I18nService,
|
||||
readonly apiV3Service:ApiV3Service,
|
||||
readonly opModalService:OpModalService,
|
||||
readonly $state:StateService,
|
||||
) {
|
||||
super(locals, cdRef, elementRef);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
super.ngOnInit();
|
||||
const { workPackageLink } = this.locals;
|
||||
const workPackageId = idFromLink(workPackageLink as string|null);
|
||||
|
||||
this
|
||||
.apiV3Service
|
||||
.work_packages
|
||||
.id(workPackageId)
|
||||
.requireAndStream()
|
||||
.subscribe((workPackage:WorkPackageResource) => {
|
||||
this.workPackage = workPackage;
|
||||
this.cdRef.detectChanges();
|
||||
this.test = this.turboFrameSrc;
|
||||
|
||||
const modal = this.elementRef.nativeElement as HTMLElement;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-explicit-any
|
||||
void this.reposition(modal, this.locals.event.target as HTMLElement);
|
||||
});
|
||||
// TODO
|
||||
fromEvent(document, 'turbo:frame-load')
|
||||
.pipe(
|
||||
filter((event:CustomEvent) => {
|
||||
return (event.target as HTMLElement).id?.includes('op-hover-card-body');
|
||||
}),
|
||||
throttleTime(100),
|
||||
tap(() => {
|
||||
this.cdRef.detectChanges();
|
||||
|
||||
const modal = this.elementRef.nativeElement as HTMLElement;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-explicit-any
|
||||
void this.reposition(modal, this.locals.event.target as HTMLElement);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async reposition(element:HTMLElement, target:HTMLElement) {
|
||||
@@ -125,9 +120,4 @@ export class WpPreviewModalComponent extends OpModalComponent implements OnInit
|
||||
top: `${y}px`,
|
||||
});
|
||||
}
|
||||
|
||||
public openStateLink(event:{ workPackageId:string; requestedState:string }) {
|
||||
const params = { workPackageId: event.workPackageId };
|
||||
void this.$state.go(event.requestedState, params);
|
||||
}
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
<div
|
||||
class="op-wp-preview-modal"
|
||||
*ngIf="workPackage"
|
||||
>
|
||||
<wp-single-card
|
||||
[workPackage]="workPackage"
|
||||
orientation="horizontal"
|
||||
(stateLinkClicked)="openStateLink($event)"
|
||||
></wp-single-card>
|
||||
</div>
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
@import "helpers"
|
||||
|
||||
.op-wp-preview-modal
|
||||
position: absolute
|
||||
z-index: 5000
|
||||
min-width: 350px
|
||||
padding: 0px
|
||||
box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.25)
|
||||
pointer-events: all
|
||||
@@ -75,7 +75,7 @@ module OpenProject::TextFormatting
|
||||
def work_package_mention(work_package)
|
||||
link_to("##{work_package.id}",
|
||||
work_package_path_or_url(id: work_package.id, only_path: context[:only_path]),
|
||||
class: "issue work_package preview-trigger")
|
||||
class: "issue work_package op-hover-card--preview-trigger")
|
||||
end
|
||||
|
||||
def class_from_mention(mention)
|
||||
|
||||
@@ -66,7 +66,7 @@ module OpenProject::TextFormatting::Matchers
|
||||
def render_work_package_link(wp_id)
|
||||
link_to("##{wp_id}",
|
||||
work_package_path_or_url(id: wp_id, only_path: context[:only_path]),
|
||||
class: "issue work_package preview-trigger")
|
||||
class: "issue work_package op-hover-card--preview-trigger")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -247,7 +247,7 @@ RSpec.describe "activity comments", :js do
|
||||
|
||||
wp_page.expect_comment text: "Single ##{work_package2.id}"
|
||||
expect(page).to have_css(".user-comment opce-macro-wp-quickinfo", count: 2)
|
||||
expect(page).to have_css(".user-comment .work-package--quickinfo.preview-trigger", count: 2)
|
||||
expect(page).to have_css(".user-comment .op-hover-card--preview-trigger", count: 2)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ RSpec.describe "Wysiwyg work package quicklink macros", :js do
|
||||
# Expect output widget
|
||||
within("#content") do
|
||||
expect(page).to have_link("##{work_package.id}")
|
||||
expect(page).to have_no_css(".work-package--quickinfo.preview-trigger")
|
||||
expect(page).to have_no_css(".op-hover-card--preview-trigger")
|
||||
end
|
||||
|
||||
# Edit page again
|
||||
@@ -77,7 +77,7 @@ RSpec.describe "Wysiwyg work package quicklink macros", :js do
|
||||
expected_macro_text = "#{work_package.type.name.upcase} ##{work_package.id}: My subject"
|
||||
expect(page).to have_css("opce-macro-wp-quickinfo", text: expected_macro_text)
|
||||
expect(page).to have_css("span", text: work_package.type.name.upcase)
|
||||
expect(page).to have_css(".work-package--quickinfo.preview-trigger", text: "##{work_package.id}")
|
||||
expect(page).to have_css(".op-hover-card--preview-trigger", text: "##{work_package.id}")
|
||||
expect(page).to have_css("span", text: "My subject")
|
||||
end
|
||||
|
||||
@@ -102,7 +102,7 @@ RSpec.describe "Wysiwyg work package quicklink macros", :js do
|
||||
expect(page).to have_css("opce-macro-wp-quickinfo", text: expected_macro_text)
|
||||
expect(page).to have_css("span", text: work_package.status.name)
|
||||
expect(page).to have_css("span", text: work_package.type.name.upcase)
|
||||
expect(page).to have_css(".work-package--quickinfo.preview-trigger", text: "##{work_package.id}")
|
||||
expect(page).to have_css(".op-hover-card--preview-trigger", text: "##{work_package.id}")
|
||||
expect(page).to have_css("span", text: "My subject")
|
||||
# Dates are being rendered in two nested spans
|
||||
expect(page).to have_css("span", text: "01/01/2020", count: 2)
|
||||
|
||||
@@ -95,7 +95,7 @@ RSpec.describe API::V3::Repositories::RevisionRepresenter do
|
||||
id = work_package.id
|
||||
|
||||
str = "Totally references <a"
|
||||
str << " class=\"issue work_package preview-trigger\""
|
||||
str << " class=\"issue work_package op-hover-card--preview-trigger\""
|
||||
str << " href=\"/work_packages/#{id}\">"
|
||||
str << "##{id}</a>"
|
||||
end
|
||||
|
||||
@@ -267,7 +267,7 @@ RSpec.describe OpenProject::TextFormatting,
|
||||
let(:work_package_link) do
|
||||
link_to("##{work_package.id}",
|
||||
work_package_path(work_package),
|
||||
class: "issue work_package preview-trigger op-uc-link",
|
||||
class: "issue work_package op-hover-card--preview-trigger op-uc-link",
|
||||
target: "_top")
|
||||
end
|
||||
|
||||
@@ -337,7 +337,7 @@ RSpec.describe OpenProject::TextFormatting,
|
||||
let(:work_package_link) do
|
||||
link_to("##{work_package.id}",
|
||||
work_package_path(work_package),
|
||||
class: "issue work_package preview-trigger op-uc-link",
|
||||
class: "issue work_package op-hover-card--preview-trigger op-uc-link",
|
||||
target: "_top")
|
||||
end
|
||||
|
||||
@@ -656,7 +656,7 @@ RSpec.describe OpenProject::TextFormatting,
|
||||
let(:expected) do
|
||||
<<~EXPECTED
|
||||
<p class='op-uc-p'><a class="wiki-page op-uc-link" target="_top" href="/projects/#{project.identifier}/wiki/cookbook-documentation">CookBook documentation</a></p>
|
||||
<p class='op-uc-p'><a class="issue work_package preview-trigger op-uc-link" target="_top" href="/work_packages/#{work_package.id}">##{work_package.id}</a></p>
|
||||
<p class='op-uc-p'><a class="issue work_package op-hover-card--preview-trigger op-uc-link" target="_top" href="/work_packages/#{work_package.id}">##{work_package.id}</a></p>
|
||||
<pre class="op-uc-code-block">
|
||||
[[CookBook documentation]]
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ RSpec.describe "API v3 Render resource" do
|
||||
<<~HTML
|
||||
<p class="op-uc-p">
|
||||
Hello World! Have a look at
|
||||
<a class="issue work_package preview-trigger op-uc-link"
|
||||
<a class="issue work_package op-hover-card--preview-trigger op-uc-link"
|
||||
target="_top"
|
||||
href="#{href}">##{id}</a>
|
||||
</p>
|
||||
@@ -180,7 +180,7 @@ RSpec.describe "API v3 Render resource" do
|
||||
|
||||
it_behaves_like "valid response" do
|
||||
let(:text) do
|
||||
"<p>Hello *World*! Have a look at <a class=\"issue work_package preview-trigger\" href=\"/work_packages/1\">#1</a></p>\n\n<p>with two lines.</p>"
|
||||
"<p>Hello *World*! Have a look at <a class=\"issue work_package op-hover-card--preview-trigger\" href=\"/work_packages/1\">#1</a></p>\n\n<p>with two lines.</p>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user