1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-18 11:30:55 +01:00

Allow users to drop .ics files on calendar views to import them

Summary:
Ref T10747. When a user drops a ".ics" file or a bunch of ".ics" files into a calendar view, import the events.

(Possibly we should just do this if you drop ".ics" files into any application, but we can look at that later.)

Test Plan: Dropped some .ics files into calendar views, got imports.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10747

Differential Revision: https://secure.phabricator.com/D16722
This commit is contained in:
epriestley 2016-10-18 14:24:17 -07:00
parent 67cb277bed
commit f9f25c1e4d
11 changed files with 198 additions and 31 deletions

View file

@ -10,7 +10,7 @@ return array(
'conpherence.pkg.css' => '6412a825',
'conpherence.pkg.js' => 'cbe4d9be',
'core.pkg.css' => 'b99bbf5e',
'core.pkg.js' => '3eb7abf7',
'core.pkg.js' => '2d9fc958',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => 'e1d704ce',
'differential.pkg.js' => '634399e9',
@ -555,7 +555,7 @@ return array(
'rsrc/js/core/behavior-file-tree.js' => '88236f00',
'rsrc/js/core/behavior-form.js' => '5c54cbf3',
'rsrc/js/core/behavior-gesture.js' => '3ab51e2c',
'rsrc/js/core/behavior-global-drag-and-drop.js' => 'c8e57404',
'rsrc/js/core/behavior-global-drag-and-drop.js' => '960f6a39',
'rsrc/js/core/behavior-high-security-warning.js' => 'a464fe03',
'rsrc/js/core/behavior-history-install.js' => '7ee2b591',
'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64',
@ -701,7 +701,7 @@ return array(
'javelin-behavior-error-log' => '6882e80a',
'javelin-behavior-event-all-day' => '937bb700',
'javelin-behavior-fancy-datepicker' => '568931f3',
'javelin-behavior-global-drag-and-drop' => 'c8e57404',
'javelin-behavior-global-drag-and-drop' => '960f6a39',
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
'javelin-behavior-high-security-warning' => 'a464fe03',
'javelin-behavior-history-install' => '7ee2b591',
@ -1735,6 +1735,13 @@ return array(
'javelin-dom',
'phabricator-busy',
),
'960f6a39' => array(
'javelin-behavior',
'javelin-dom',
'javelin-uri',
'javelin-mask',
'phabricator-drag-and-drop-file-upload',
),
'988040b4' => array(
'javelin-install',
'javelin-dom',
@ -1982,13 +1989,6 @@ return array(
'c7ccd872' => array(
'phui-fontkit-css',
),
'c8e57404' => array(
'javelin-behavior',
'javelin-dom',
'javelin-uri',
'javelin-mask',
'phabricator-drag-and-drop-file-upload',
),
'c90a04fc' => array(
'javelin-dom',
'javelin-dynval',

View file

@ -2109,6 +2109,7 @@ phutil_register_library_map(array(
'PhabricatorCalendarImportDeleteTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportDeleteTransaction.php',
'PhabricatorCalendarImportDisableController' => 'applications/calendar/controller/PhabricatorCalendarImportDisableController.php',
'PhabricatorCalendarImportDisableTransaction' => 'applications/calendar/xaction/PhabricatorCalendarImportDisableTransaction.php',
'PhabricatorCalendarImportDropController' => 'applications/calendar/controller/PhabricatorCalendarImportDropController.php',
'PhabricatorCalendarImportDuplicateLogType' => 'applications/calendar/importlog/PhabricatorCalendarImportDuplicateLogType.php',
'PhabricatorCalendarImportEditController' => 'applications/calendar/controller/PhabricatorCalendarImportEditController.php',
'PhabricatorCalendarImportEditEngine' => 'applications/calendar/editor/PhabricatorCalendarImportEditEngine.php',
@ -6944,6 +6945,7 @@ phutil_register_library_map(array(
'PhabricatorCalendarImportDeleteTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportDisableController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportDisableTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportDropController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportDuplicateLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportEditController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportEditEngine' => 'PhabricatorEditEngine',

View file

@ -85,6 +85,8 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication {
=> 'PhabricatorCalendarImportDisableController',
'delete/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarImportDeleteController',
'drop/'
=> 'PhabricatorCalendarImportDropController',
'log/' => array(
$this->getQueryRoutePattern()
=> 'PhabricatorCalendarImportLogListController',

View file

@ -7,6 +7,10 @@ final class PhabricatorCalendarEventListController
return true;
}
public function isGlobalDragAndDropUploadEnabled() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$year = $request->getURIData('year');
$month = $request->getURIData('month');

View file

@ -0,0 +1,86 @@
<?php
final class PhabricatorCalendarImportDropController
extends PhabricatorCalendarController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
if (!$request->validateCSRF()) {
return new Aphront400Response();
}
$cancel_uri = $this->getApplicationURI();
$ids = $request->getStrList('h');
if ($ids) {
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withIDs($ids)
->setRaisePolicyExceptions(true)
->execute();
} else {
$files = array();
}
if (!$files) {
return $this->newDialog()
->setTitle(pht('Nothing Uploaded'))
->appendParagraph(
pht(
'Drag and drop .ics files to upload them and import them into '.
'Calendar.'))
->addCancelButton($cancel_uri, pht('Done'));
}
$engine = new PhabricatorCalendarICSImportEngine();
$imports = array();
foreach ($files as $file) {
$import = PhabricatorCalendarImport::initializeNewCalendarImport(
$viewer,
clone $engine);
$xactions = array();
$xactions[] = id(new PhabricatorCalendarImportTransaction())
->setTransactionType(
PhabricatorCalendarImportICSFileTransaction::TRANSACTIONTYPE)
->setNewValue($file->getPHID());
$editor = id(new PhabricatorCalendarImportEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->setContentSourceFromRequest($request);
$editor->applyTransactions($import, $xactions);
$imports[] = $import;
}
$import_phids = mpull($imports, 'getPHID');
$events = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withImportSourcePHIDs($import_phids)
->execute();
if (count($events) == 1) {
// The user imported exactly one event. This is consistent with dropping
// a .ics file from an email; just take them to the event.
$event = head($events);
$next_uri = $event->getURI();
} else if (count($imports) > 1) {
// The user imported multiple different files. Take them to a summary
// list of generated import activity.
$source_phids = implode(',', $import_phids);
$next_uri = '/calendar/import/log/?importSourcePHIDs='.$source_phids;
} else {
// The user imported one file, which had zero or more than one event.
// Take them to the import detail page.
$import = head($imports);
$next_uri = $import->getURI();
}
return id(new AphrontRedirectResponse())->setURI($next_uri);
}
}

View file

@ -390,9 +390,9 @@ abstract class PhabricatorCalendarImportEngine
if ($rrule) {
$event->setRecurrenceRule($rrule);
$until_datetime = $rrule->getUntil()
->setViewerTimezone($timezone);
$until_datetime = $rrule->getUntil();
if ($until_datetime) {
$until_datetime->setViewerTimezone($timezone);
$event->setUntilDateTime($until_datetime);
}
}

View file

@ -321,11 +321,9 @@ final class PhabricatorCalendarEventSearchEngine
$list->addItem($item);
}
$result = new PhabricatorApplicationSearchResultView();
$result->setObjectList($list);
$result->setNoDataString(pht('No events found.'));
return $result;
return $this->newResultView()
->setObjectList($list)
->setNoDataString(pht('No events found.'));
}
private function buildCalendarMonthView(
@ -393,10 +391,9 @@ final class PhabricatorCalendarEventSearchEngine
->setProfileHeader(true)
->setHeader($from->format('F Y'));
return id(new PhabricatorApplicationSearchResultView())
return $this->newResultView($month_view)
->setCrumbs($crumbs)
->setHeader($header)
->setContent($month_view);
->setHeader($header);
}
private function buildCalendarDayView(
@ -467,10 +464,9 @@ final class PhabricatorCalendarEventSearchEngine
->setProfileHeader(true)
->setHeader($from->format('D, F jS'));
return id(new PhabricatorApplicationSearchResultView())
return $this->newResultView($day_view)
->setCrumbs($crumbs)
->setHeader($header)
->setContent($day_view);
->setHeader($header);
}
private function getDisplayYearAndMonthAndDay(
@ -596,4 +592,26 @@ final class PhabricatorCalendarEventSearchEngine
);
}
private function newResultView($content = null) {
// If we aren't rendering a dashboard panel, activate global drag-and-drop
// so you can import ".ics" files by dropping them directly onto the
// calendar.
if (!$this->isPanelContext()) {
$drop_upload = id(new PhabricatorGlobalUploadTargetView())
->setViewer($this->requireViewer())
->setHintText("\xE2\x87\xAA ".pht('Drop .ics Files to Import'))
->setSubmitURI('/calendar/import/drop/')
->setViewPolicy(PhabricatorPolicies::POLICY_NOONE);
$content = array(
$drop_upload,
$content,
);
}
return id(new PhabricatorApplicationSearchResultView())
->setContent($content);
}
}

View file

@ -21,6 +21,7 @@ final class PhabricatorCalendarImport
PhabricatorUser $actor,
PhabricatorCalendarImportEngine $engine) {
return id(new self())
->setName('')
->setAuthorPHID($actor->getPHID())
->setViewPolicy($actor->getPHID())
->setEditPolicy($actor->getPHID())

View file

@ -14,6 +14,9 @@
final class PhabricatorGlobalUploadTargetView extends AphrontView {
private $showIfSupportedID;
private $hintText;
private $viewPolicy;
private $submitURI;
public function setShowIfSupportedID($show_if_supported_id) {
$this->showIfSupportedID = $show_if_supported_id;
@ -24,8 +27,37 @@ final class PhabricatorGlobalUploadTargetView extends AphrontView {
return $this->showIfSupportedID;
}
public function setHintText($hint_text) {
$this->hintText = $hint_text;
return $this;
}
public function getHintText() {
return $this->hintText;
}
public function setViewPolicy($view_policy) {
$this->viewPolicy = $view_policy;
return $this;
}
public function getViewPolicy() {
return $this->viewPolicy;
}
public function setSubmitURI($submit_uri) {
$this->submitURI = $submit_uri;
return $this;
}
public function getSubmitURI() {
return $this->submitURI;
}
public function render() {
$viewer = $this->getUser();
$viewer = $this->getViewer();
if (!$viewer->isLoggedIn()) {
return null;
}
@ -34,18 +66,30 @@ final class PhabricatorGlobalUploadTargetView extends AphrontView {
require_celerity_resource('global-drag-and-drop-css');
$hint_text = $this->getHintText();
if (!strlen($hint_text)) {
$hint_text = "\xE2\x87\xAA ".pht('Drop Files to Upload');
}
// Use the configured default view policy. Drag and drop uploads use
// a more restrictive view policy if we don't specify a policy explicitly,
// as the more restrictive policy is correct for most drop targets (like
// Pholio uploads and Remarkup text areas).
$view_policy = PhabricatorFile::initializeNewFile()->getViewPolicy();
$view_policy = $this->getViewPolicy();
if ($view_policy === null) {
$view_policy = PhabricatorFile::initializeNewFile()->getViewPolicy();
}
$submit_uri = $this->getSubmitURI();
$done_uri = '/file/query/authored/';
Javelin::initBehavior('global-drag-and-drop', array(
'ifSupported' => $this->showIfSupportedID,
'instructions' => $instructions_id,
'uploadURI' => '/file/dropupload/',
'browseURI' => '/file/query/authored/',
'submitURI' => $submit_uri,
'browseURI' => $done_uri,
'viewPolicy' => $view_policy,
'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(),
));
@ -57,6 +101,6 @@ final class PhabricatorGlobalUploadTargetView extends AphrontView {
'class' => 'phabricator-global-upload-instructions',
'style' => 'display: none;',
),
"\xE2\x87\xAA ".pht('Drop Files to Upload'));
$hint_text);
}
}

View file

@ -94,6 +94,4 @@ final class PhabricatorApplicationSearchResultView extends Phobject {
return $this->header;
}
}

View file

@ -70,7 +70,14 @@ JX.behavior('global-drag-and-drop', function(config, statics) {
// If whatever the user dropped in has finished uploading, send them to
// their uploads.
var uri;
uri = JX.$U(config.browseURI);
var is_submit = !!config.submitURI;
if (is_submit) {
uri = JX.$U(config.submitURI);
} else {
uri = JX.$U(config.browseURI);
}
var ids = [];
for (var ii = 0; ii < statics.files.length; ii++) {
ids.push(statics.files[ii].getID());
@ -79,7 +86,12 @@ JX.behavior('global-drag-and-drop', function(config, statics) {
statics.files = [];
uri.go();
if (is_submit) {
new JX.Workflow(uri)
.start();
} else {
uri.go();
}
}
});