mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Create rails based split create
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
<%= helpers.angular_component_tag "opce-wp-split-create",
|
||||
inputs: { projectIdentifier: @project_identifier } %>
|
||||
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- 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.
|
||||
#++
|
||||
|
||||
class WorkPackages::SplitCreateComponent < ApplicationComponent
|
||||
def initialize(project_identifier:)
|
||||
super
|
||||
|
||||
@project_identifier = project_identifier
|
||||
end
|
||||
end
|
||||
@@ -33,6 +33,14 @@ module WorkPackages::SplitViewHelper
|
||||
params[:work_package_split_view].present?
|
||||
end
|
||||
|
||||
def render_work_package_split_create?
|
||||
params[:work_package_split_create].present?
|
||||
end
|
||||
|
||||
def split_create_instance
|
||||
WorkPackages::SplitCreateComponent.new(project_identifier: params[:project_id])
|
||||
end
|
||||
|
||||
def split_view_instance
|
||||
WorkPackages::SplitViewComponent.new(id: params[:work_package_id],
|
||||
tab: params[:tab],
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<%= turbo_frame_tag "content-bodyRight" do %>
|
||||
<%= render(split_create_instance) %>
|
||||
<% end %>
|
||||
@@ -144,6 +144,9 @@ import {
|
||||
import {
|
||||
WorkPackageSplitViewEntryComponent,
|
||||
} from 'core-app/features/work-packages/routing/wp-split-view/wp-split-view-entry.component';
|
||||
import {
|
||||
WorkPackageSplitCreateEntryComponent,
|
||||
} from 'core-app/features/work-packages/routing/wp-split-create/wp-split-create-entry.component';
|
||||
import {
|
||||
BoardEntryComponent,
|
||||
} from 'core-app/features/boards/board/board-partitioned-page/board-entry.component';
|
||||
@@ -393,6 +396,7 @@ export class OpenProjectModule implements DoBootstrap {
|
||||
|
||||
registerCustomElement('opce-notification-center', InAppNotificationCenterComponent, { injector });
|
||||
registerCustomElement('opce-wp-split-view', WorkPackageSplitViewEntryComponent, { injector });
|
||||
registerCustomElement('opce-wp-split-create', WorkPackageSplitCreateEntryComponent, { injector });
|
||||
registerCustomElement('opce-board-view', BoardEntryComponent, { injector });
|
||||
registerCustomElement('opce-calendar-view', CalendarEntryComponent, { injector });
|
||||
registerCustomElement('opce-wp-full-view', WorkPackageFullViewEntryComponent, { injector });
|
||||
|
||||
@@ -289,6 +289,12 @@ export class OpWorkPackagesCalendarService extends UntilDestroyedMixin {
|
||||
Turbo.visit(link, { frame: 'content-bodyRight', action: 'advance' });
|
||||
}
|
||||
|
||||
public openSplitCreate():void {
|
||||
const basePath = window.location.pathname.replace(/\/details\/.*$/, '');
|
||||
const link = `${basePath}/details/new${window.location.search}`;
|
||||
Turbo.visit(link, { frame: 'content-bodyRight', action: 'advance' });
|
||||
}
|
||||
|
||||
public openFullView(id:string):void {
|
||||
this.wpTableSelection.setSelection(id, -1);
|
||||
Turbo.visit(this.pathHelper.workPackagePath(id));
|
||||
|
||||
+82
-1
@@ -27,7 +27,10 @@
|
||||
//++
|
||||
|
||||
import { WorkPackageCreateComponent } from 'core-app/features/work-packages/components/wp-new/wp-create.component';
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource';
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { WorkPackagesListService } from 'core-app/features/work-packages/components/wp-list/wp-list.service';
|
||||
|
||||
@Component({
|
||||
selector: 'wp-new-split-view',
|
||||
@@ -36,4 +39,82 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
standalone: false,
|
||||
})
|
||||
export class WorkPackageNewSplitViewComponent extends WorkPackageCreateComponent {
|
||||
private readonly wpListService = inject(WorkPackagesListService);
|
||||
|
||||
/**
|
||||
* Before creating the new WP form, load the current query (with its active filters)
|
||||
* into the isolated query space so that WorkPackageCreateService.defaultsFromFilters()
|
||||
* can pre-populate the form fields automatically — no manual filter mapping needed.
|
||||
*/
|
||||
protected override async createdWorkPackage() {
|
||||
if (!this.routedFromAngular) {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
// Load the active query into the isolated query space so that
|
||||
// WorkPackageCreateService.defaultsFromFilters() can pre-populate filter-based fields.
|
||||
const queryId = params.get('query_id');
|
||||
const queryProps = params.get('query_props');
|
||||
if (queryId || queryProps) {
|
||||
await firstValueFrom(
|
||||
this.wpListService.fromQueryParams(
|
||||
{ query_id: queryId ?? undefined, query_props: queryProps ?? undefined },
|
||||
this.currentProjectService.identifier ?? undefined,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Apply date defaults passed via URL params (e.g. when dragging to create on the calendar).
|
||||
const startDate = params.get('startDate');
|
||||
const dueDate = params.get('dueDate');
|
||||
const ignoreNonWorkingDays = params.get('ignoreNonWorkingDays');
|
||||
if (startDate || dueDate || ignoreNonWorkingDays) {
|
||||
this.stateParams = {
|
||||
...this.stateParams,
|
||||
defaults: {
|
||||
_links: {},
|
||||
...this.stateParams?.defaults,
|
||||
...(startDate ? { startDate } : {}),
|
||||
...(dueDate ? { dueDate } : {}),
|
||||
...(ignoreNonWorkingDays ? { ignoreNonWorkingDays: true } : {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return super.createdWorkPackage();
|
||||
}
|
||||
|
||||
public override cancelAndBack():void {
|
||||
if (this.routedFromAngular) {
|
||||
super.cancelAndBack();
|
||||
return;
|
||||
}
|
||||
|
||||
this.wpCreate.cancelCreation();
|
||||
|
||||
// Close the split panel by navigating to the base URL (strips /details/new),
|
||||
// replacing the history entry so back-navigation skips the create state.
|
||||
const basePath = window.location.pathname.replace(/\/details\/.*$/, '');
|
||||
Turbo.visit(basePath + window.location.search, { frame: 'content-bodyRight', action: 'replace' });
|
||||
}
|
||||
|
||||
public override onSaved(params:{ savedResource:WorkPackageResource, isInitial:boolean }):void {
|
||||
if (this.routedFromAngular) {
|
||||
super.onSaved(params);
|
||||
return;
|
||||
}
|
||||
|
||||
const { savedResource, isInitial } = params;
|
||||
this.editForm?.cancel(false);
|
||||
|
||||
this.notificationService.showSave(savedResource, isInitial);
|
||||
window.OpenProject.pageState = 'submitted';
|
||||
|
||||
// Open the newly created WP in the split panel.
|
||||
const basePath = window.location.pathname.replace(/\/details\/.*$/, '');
|
||||
Turbo.visit(`${basePath}/details/${savedResource.id}${window.location.search}`, {
|
||||
frame: 'content-bodyRight',
|
||||
action: 'advance',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,6 +406,9 @@ import {
|
||||
import { WorkPackageFullCopyEntryComponent } from 'core-app/features/work-packages/routing/wp-full-copy/wp-full-copy-entry.component';
|
||||
import { WorkPackageFullCreateEntryComponent } from 'core-app/features/work-packages/routing/wp-full-create/wp-full-create-entry.component';
|
||||
import { WorkPackageFullViewEntryComponent } from 'core-app/features/work-packages/routing/wp-full-view/wp-full-view-entry.component';
|
||||
import {
|
||||
WorkPackageSplitCreateEntryComponent,
|
||||
} from 'core-app/features/work-packages/routing/wp-split-create/wp-split-create-entry.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -590,6 +593,7 @@ import { WorkPackageFullViewEntryComponent } from 'core-app/features/work-packag
|
||||
WorkPackageDetailsViewButtonComponent,
|
||||
WorkPackageSplitViewComponent,
|
||||
WorkPackageSplitViewEntryComponent,
|
||||
WorkPackageSplitCreateEntryComponent,
|
||||
WorkPackageBreadcrumbComponent,
|
||||
WorkPackageSplitViewToolbarComponent,
|
||||
WorkPackageWatcherButtonComponent,
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
//-- 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.
|
||||
//++
|
||||
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
Input,
|
||||
OnDestroy,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
WorkPackageIsolatedQuerySpaceDirective,
|
||||
} from 'core-app/features/work-packages/directives/query-space/wp-isolated-query-space.directive';
|
||||
import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs';
|
||||
|
||||
/**
|
||||
* An entry component to be rendered by Rails which opens an isolated query space
|
||||
* for the work package split create (create form in the split panel).
|
||||
*/
|
||||
@Component({
|
||||
hostDirectives: [WorkPackageIsolatedQuerySpaceDirective],
|
||||
standalone: false,
|
||||
template: `
|
||||
<div class="op-work-package-split-view">
|
||||
<wp-new-split-view
|
||||
[stateParams]="{ projectPath: projectIdentifier, type: type }"
|
||||
[routedFromAngular]="false"
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class WorkPackageSplitCreateEntryComponent implements AfterViewInit, OnDestroy {
|
||||
@Input() projectIdentifier?:string;
|
||||
@Input() type?:string;
|
||||
|
||||
constructor(readonly elementRef:ElementRef) {
|
||||
populateInputsFromDataset(this);
|
||||
document.body.classList.add('\'router--work-packages-partitioned-split-view-new');
|
||||
}
|
||||
|
||||
ngAfterViewInit():void {
|
||||
// wp-new-split-view sets pageState = 'edited' unconditionally on mount,
|
||||
// which would block Turbo navigation in the split panel context.
|
||||
// Reset it here after all children have initialized.
|
||||
window.OpenProject.pageState = 'pristine';
|
||||
}
|
||||
|
||||
ngOnDestroy():void {
|
||||
document.body.classList.remove('\'router--work-packages-partitioned-split-view-new');
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ module ::Calendar
|
||||
before_action :authorize_global, only: %i[index create]
|
||||
before_action :authorize_new, only: %i[new]
|
||||
authorization_checked! :new
|
||||
authorize_with_permission :add_work_packages, only: %i[split_create]
|
||||
|
||||
before_action :find_calendar, only: %i[show split_view destroy]
|
||||
menu_item :calendar_view
|
||||
@@ -66,6 +67,18 @@ module ::Calendar
|
||||
end
|
||||
end
|
||||
|
||||
def split_create
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
if turbo_frame_request?
|
||||
render "work_packages/split_create", layout: false
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def new
|
||||
# In a project context, show the calendar view with an unsaved query.
|
||||
# In the global context (no project), show the form so the user can select a project.
|
||||
|
||||
@@ -36,4 +36,5 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
<% content_for :content_body_right do %>
|
||||
<%= turbo_stream.set_title(title: page_title(*html_title_parts)) if turbo_frame_request? %>
|
||||
<%= render(split_view_instance) if render_work_package_split_view? %>
|
||||
<%= render(split_create_instance) if render_work_package_split_create? %>
|
||||
<% end %>
|
||||
|
||||
@@ -9,6 +9,10 @@ Rails.application.routes.draw do
|
||||
end
|
||||
get "/ical" => "calendar/ical#show", on: :member, as: "ical"
|
||||
member do
|
||||
get "details/new",
|
||||
action: :split_create,
|
||||
as: :split_create,
|
||||
work_package_split_create: true
|
||||
get "details/:work_package_id(/:tab)",
|
||||
action: :split_view,
|
||||
defaults: { tab: :overview },
|
||||
|
||||
@@ -28,7 +28,7 @@ module OpenProject::Calendar
|
||||
settings: {} do
|
||||
project_module :calendar_view, dependencies: :work_package_tracking do
|
||||
permission :view_calendar,
|
||||
{ "calendar/calendars": %i[index show split_view new],
|
||||
{ "calendar/calendars": %i[index show split_view split_create new],
|
||||
"calendar/menus": %i[show] },
|
||||
permissible_on: :project,
|
||||
dependencies: %i[view_work_packages],
|
||||
|
||||
@@ -130,8 +130,9 @@ RSpec.describe "Team planner add existing work packages",
|
||||
# Select work package in add existing
|
||||
add_existing_pane.card(second_wp).click
|
||||
split_screen = Pages::SplitWorkPackage.new second_wp
|
||||
split_screen.expect_subject
|
||||
# Wait for navigation to complete before checking the split panel DOM
|
||||
expect(page).to have_current_path /\/details\/#{second_wp.id}/
|
||||
split_screen.expect_subject
|
||||
end
|
||||
|
||||
it "allows to add work packages via drag&drop from the left hand shortlist" do
|
||||
|
||||
Reference in New Issue
Block a user