mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
moved ical sharing UI to calendar detail page (angular scope) as requested in specification
This commit is contained in:
@@ -41,7 +41,8 @@ class QueryPolicy < BasePolicy
|
||||
depublicize: depublicize_allowed?(cached_query),
|
||||
star: persisted_and_own_or_public?(cached_query),
|
||||
unstar: persisted_and_own_or_public?(cached_query),
|
||||
reorder_work_packages: reorder_work_packages?(cached_query)
|
||||
reorder_work_packages: reorder_work_packages?(cached_query),
|
||||
create_ical_url: create_ical_url_allowed?(cached_query)
|
||||
}
|
||||
end
|
||||
|
||||
@@ -119,4 +120,12 @@ class QueryPolicy < BasePolicy
|
||||
|
||||
@manage_public_queries_cache[query.project]
|
||||
end
|
||||
|
||||
def create_ical_url_allowed?(query)
|
||||
@create_ical_url_cache ||= Hash.new do |hash, project|
|
||||
hash[project] = user.allowed_to?(:share_calendars, project, global: project.nil?)
|
||||
end
|
||||
|
||||
@create_ical_url_cache[query.project]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -112,6 +112,7 @@ export class WorkPackagesCalendarPageComponent extends PartitionedQuerySpacePage
|
||||
show: ():boolean => this.authorisationService.can('query', 'updateImmediately'),
|
||||
inputs: {
|
||||
hideTableOptions: true,
|
||||
showCalendarSharingOption: true
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@
|
||||
class="button last work-packages-settings-button toolbar-icon"
|
||||
opSettingsContextMenu
|
||||
opSettingsContextMenu-query="query"
|
||||
[hideTableOptions]="hideTableOptions">
|
||||
[hideTableOptions]="hideTableOptions"
|
||||
[showCalendarSharingOption]="showCalendarSharingOption">
|
||||
<op-icon icon-classes="button--icon icon-show-more"></op-icon>
|
||||
</button>
|
||||
|
||||
+1
@@ -39,6 +39,7 @@ import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
})
|
||||
export class WorkPackageSettingsButtonComponent {
|
||||
@Input() hideTableOptions = false;
|
||||
@Input() showCalendarSharingOption = false;
|
||||
|
||||
public text = {
|
||||
more_actions: this.I18n.t('js.button_more_actions'),
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<div
|
||||
class="spot-modal loading-indicator--location op-query-get-ical-url"
|
||||
data-indicator-name="modal"
|
||||
>
|
||||
<div class="spot-modal--header">{{text.label_ical_sharing}}</div>
|
||||
|
||||
<div class="spot-divider"></div>
|
||||
|
||||
<div class="spot-modal--body spot-container">
|
||||
<p>
|
||||
{{text.description_ical_sharing}}
|
||||
</p>
|
||||
<p class="ical-url">
|
||||
<a href="{{icalUrl}}" target="_blank">{{icalUrl}}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="spot-action-bar">
|
||||
<div class="spot-action-bar--right">
|
||||
<button class="button button_no-margin spot-modal--cancel-button spot-action-bar--action"
|
||||
[textContent]="text.button_cancel"
|
||||
(click)="closeMe($event)">
|
||||
</button>
|
||||
<button class="button button_no-margin -highlight -with-icon icon-copy spot-action-bar--action"
|
||||
(click)="copyUrl($event)"
|
||||
[textContent]="text.button_copy">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
.op-query-get-ical-url
|
||||
.ical-url
|
||||
word-break: break-word
|
||||
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
// -- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) 2012-2023 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 { WorkPackagesListService } from 'core-app/features/work-packages/components/wp-list/wp-list.service';
|
||||
import { States } from 'core-app/core/states/states.service';
|
||||
import { HalResourceNotificationService } from 'core-app/features/hal/services/hal-resource-notification.service';
|
||||
import { QueryResource } from 'core-app/features/hal/resources/query-resource';
|
||||
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
|
||||
import { OpModalComponent } from 'core-app/shared/components/modal/modal.component';
|
||||
import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service';
|
||||
import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types';
|
||||
import {
|
||||
ChangeDetectorRef, Component, ElementRef, Inject, OnInit, resolveForwardRef,
|
||||
} from '@angular/core';
|
||||
import { I18nService } from 'core-app/core/i18n/i18n.service';
|
||||
import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space';
|
||||
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
|
||||
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
|
||||
|
||||
@Component({
|
||||
templateUrl: './query-get-ical-url.modal.html',
|
||||
styleUrls: ['./query-get-ical-url.modal.sass']
|
||||
})
|
||||
export class QueryGetIcalUrlModalComponent extends OpModalComponent implements OnInit {
|
||||
public query:QueryResource;
|
||||
|
||||
public isBusy = false;
|
||||
|
||||
public icalUrl: string;
|
||||
|
||||
public text = {
|
||||
label_ical_sharing: 'Share calendar', // TODO: translate
|
||||
description_ical_sharing: 'You can share and import this calendar by using the following iCalendar URL:', // TODO: translate
|
||||
button_copy: 'Copy URL', // TODO: translate
|
||||
copy_success_text: 'URL copied to clipboard', // TODO: translate
|
||||
button_cancel: this.I18n.t('js.button_cancel'),
|
||||
close_popup: this.I18n.t('js.close_popup_title')
|
||||
};
|
||||
|
||||
constructor(
|
||||
readonly elementRef:ElementRef,
|
||||
@Inject(OpModalLocalsToken) public locals:OpModalLocalsMap,
|
||||
readonly I18n:I18nService,
|
||||
readonly states:States,
|
||||
readonly querySpace:IsolatedQuerySpace,
|
||||
readonly cdRef:ChangeDetectorRef,
|
||||
readonly wpListService:WorkPackagesListService,
|
||||
readonly halNotification:HalResourceNotificationService,
|
||||
readonly toastService:ToastService,
|
||||
protected apiV3Service:ApiV3Service
|
||||
) {
|
||||
super(locals, cdRef, elementRef);
|
||||
}
|
||||
|
||||
ngOnInit():void {
|
||||
super.ngOnInit();
|
||||
|
||||
this.query = this.querySpace.query.value!;
|
||||
|
||||
this.isBusy = true;
|
||||
|
||||
this
|
||||
.query
|
||||
.createIcalUrl()
|
||||
.then((response:HalResource) => {
|
||||
this.icalUrl = response.icalUrl;
|
||||
this.isBusy = false;
|
||||
this.cdRef.detectChanges();
|
||||
// or would that be better?
|
||||
// this.ngZone.run(() => {
|
||||
// this.icalUrl = response.icalUrl;
|
||||
// this.isBusy = false;
|
||||
// });
|
||||
})
|
||||
}
|
||||
|
||||
public copyUrl($event:Event):void {
|
||||
if (this.isBusy) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(this.icalUrl)
|
||||
.then(() => {
|
||||
this.toastService.addSuccess(this.text.copy_success_text);
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
+16
@@ -44,6 +44,7 @@ import {
|
||||
triggerEditingEvent,
|
||||
} from 'core-app/shared/components/editable-toolbar-title/editable-toolbar-title.component';
|
||||
import { QuerySharingModalComponent } from 'core-app/shared/components/modals/share-modal/query-sharing.modal';
|
||||
import { QueryGetIcalUrlModalComponent } from 'core-app/shared/components/modals/get-ical-url-modal/query-get-ical-url.modal';
|
||||
import { WpTableExportModalComponent } from 'core-app/shared/components/modals/export-modal/wp-table-export.modal';
|
||||
import { SaveQueryModalComponent } from 'core-app/shared/components/modals/save-modal/save-query.modal';
|
||||
import { QueryFormResource } from 'core-app/features/hal/resources/query-form-resource';
|
||||
@@ -56,6 +57,7 @@ export class OpSettingsMenuDirective extends OpContextMenuTrigger {
|
||||
@Input('opSettingsContextMenu-query') public query:QueryResource;
|
||||
|
||||
@Input() public hideTableOptions:boolean;
|
||||
@Input() public showCalendarSharingOption:boolean;
|
||||
|
||||
private form:QueryFormResource;
|
||||
|
||||
@@ -313,6 +315,20 @@ export class OpSettingsMenuDirective extends OpContextMenuTrigger {
|
||||
icon: 'icon-custom-fields',
|
||||
onClick: () => false,
|
||||
},
|
||||
{
|
||||
// Calendar sharing modal
|
||||
hidden: !this.showCalendarSharingOption,
|
||||
disabled: this.authorisationService.cannot('query', 'createIcalUrl'),
|
||||
linkText: "Share Calendar", // TODO: translate
|
||||
icon: 'icon-link', // TODO: find sharing icons
|
||||
onClick: ($event:JQuery.TriggeredEvent) => {
|
||||
if (this.authorisationService.can('query', 'createIcalUrl')) {
|
||||
this.opModalService.show(QueryGetIcalUrlModalComponent, this.injector);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,6 +159,24 @@ module API
|
||||
.mount
|
||||
end
|
||||
|
||||
namespace :create_ical_url do
|
||||
post do
|
||||
authorize_by_policy(:create_ical_url)
|
||||
|
||||
# currently the generated URL points to controller action in calendar module
|
||||
# correct approach? or should it be implemented as a API here?
|
||||
call = ::Calendar::GenerateIcalUrl.new().call(
|
||||
user: current_user,
|
||||
query_id: @query.id,
|
||||
project_id: @query.project_id
|
||||
)
|
||||
|
||||
{
|
||||
icalUrl: call.result
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
mount API::V3::Queries::Order::QueryOrderAPI
|
||||
end
|
||||
end
|
||||
|
||||
@@ -146,6 +146,16 @@ module API
|
||||
method: :delete
|
||||
}
|
||||
end
|
||||
|
||||
link :createIcalUrl do
|
||||
next if represented.new_record? ||
|
||||
!allowed_to?(:create_ical_url)
|
||||
|
||||
{
|
||||
href: api_v3_paths.query_create_ical_url(represented.id),
|
||||
method: :post
|
||||
}
|
||||
end
|
||||
|
||||
associated_resource :user
|
||||
|
||||
|
||||
@@ -329,6 +329,10 @@ module API
|
||||
"#{query(id)}/order"
|
||||
end
|
||||
|
||||
def self.query_create_ical_url(id)
|
||||
"#{query(id)}/create_ical_url"
|
||||
end
|
||||
|
||||
def self.query_column(name)
|
||||
"#{queries}/columns/#{name}"
|
||||
end
|
||||
|
||||
@@ -46,19 +46,7 @@ module Calendar
|
||||
end
|
||||
|
||||
def button_links
|
||||
[share_link, delete_link].compact
|
||||
end
|
||||
|
||||
def share_link
|
||||
if table.current_user.allowed_to?(:share_calendars, project)
|
||||
link_to(
|
||||
'',
|
||||
generate_ical_url_project_calendar_path(project, query.id),
|
||||
method: :post,
|
||||
class: 'icon icon-link', # TODO: use proper share icon
|
||||
title: "Share via iCal" # TODO: use translations
|
||||
)
|
||||
end
|
||||
[delete_link].compact
|
||||
end
|
||||
|
||||
def delete_link
|
||||
|
||||
@@ -42,30 +42,6 @@ module ::Calendar
|
||||
render layout: 'angular/angular'
|
||||
end
|
||||
|
||||
def generate_ical_url
|
||||
begin
|
||||
call = ::Calendar::GenerateIcalUrl.new().call(
|
||||
user: current_user,
|
||||
query_id: params[:id],
|
||||
project_id: @project.id
|
||||
)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_404
|
||||
return
|
||||
end
|
||||
|
||||
if call.present? && call.success?
|
||||
# TODO: Use translations
|
||||
flash[:info] = "
|
||||
You can share and import this calendar by using the following iCalendar URL:
|
||||
#{call.result}
|
||||
"
|
||||
redirect_to action: :index
|
||||
else
|
||||
render_404
|
||||
end
|
||||
end
|
||||
|
||||
def ical
|
||||
begin
|
||||
call = ::Calendar::IcalResponseService.new().call(
|
||||
|
||||
@@ -6,8 +6,6 @@ OpenProject::Application.routes.draw do
|
||||
as: :calendars do
|
||||
get '/new' => 'calendar/calendars#show', on: :collection, as: 'new'
|
||||
# TODO: discuss if other controller should be used
|
||||
post '/generate_ical_url' => 'calendar/calendars#generate_ical_url', on: :member, as: 'generate_ical_url'
|
||||
# TODO: discuss if other controller should be used
|
||||
get '/ical' => 'calendar/calendars#ical', on: :member, as: 'ical'
|
||||
get '(/*state)' => 'calendar/calendars#show', on: :member, as: ''
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user