Skip to content

Commit

Permalink
Allow users to drop .ics files on calendar views to import them
Browse files Browse the repository at this point in the history
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
  • Loading branch information
epriestley committed Oct 18, 2016
1 parent 67cb277 commit f9f25c1
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 31 deletions.
20 changes: 10 additions & 10 deletions resources/celerity/map.php
Expand Up @@ -10,7 +10,7 @@
'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',
Expand Down Expand Up @@ -555,7 +555,7 @@
'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',
Expand Down Expand Up @@ -701,7 +701,7 @@
'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',
Expand Down Expand Up @@ -1735,6 +1735,13 @@
'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',
Expand Down Expand Up @@ -1982,13 +1989,6 @@
'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',
Expand Down
2 changes: 2 additions & 0 deletions src/__phutil_library_map__.php
Expand Up @@ -2109,6 +2109,7 @@
'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',
Expand Down Expand Up @@ -6944,6 +6945,7 @@
'PhabricatorCalendarImportDeleteTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportDisableController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportDisableTransaction' => 'PhabricatorCalendarImportTransactionType',
'PhabricatorCalendarImportDropController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportDuplicateLogType' => 'PhabricatorCalendarImportLogType',
'PhabricatorCalendarImportEditController' => 'PhabricatorCalendarController',
'PhabricatorCalendarImportEditEngine' => 'PhabricatorEditEngine',
Expand Down
Expand Up @@ -85,6 +85,8 @@ public function getRoutes() {
=> 'PhabricatorCalendarImportDisableController',
'delete/(?P<id>[1-9]\d*)/'
=> 'PhabricatorCalendarImportDeleteController',
'drop/'
=> 'PhabricatorCalendarImportDropController',
'log/' => array(
$this->getQueryRoutePattern()
=> 'PhabricatorCalendarImportLogListController',
Expand Down
Expand Up @@ -7,6 +7,10 @@ public function shouldAllowPublic() {
return true;
}

public function isGlobalDragAndDropUploadEnabled() {
return true;
}

public function handleRequest(AphrontRequest $request) {
$year = $request->getURIData('year');
$month = $request->getURIData('month');
Expand Down
@@ -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);
}

}
Expand Up @@ -390,9 +390,9 @@ private function updateEventFromNode(
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);
}
}
Expand Down
Expand Up @@ -321,11 +321,9 @@ private function buildCalendarListView(
$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(
Expand Down Expand Up @@ -393,10 +391,9 @@ private function buildCalendarMonthView(
->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(
Expand Down Expand Up @@ -467,10 +464,9 @@ private function buildCalendarDayView(
->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(
Expand Down Expand Up @@ -596,4 +592,26 @@ public function newUseResultsActions(PhabricatorSavedQuery $saved) {
);
}


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);
}

}
Expand Up @@ -21,6 +21,7 @@ public static function initializeNewCalendarImport(
PhabricatorUser $actor,
PhabricatorCalendarImportEngine $engine) {
return id(new self())
->setName('')
->setAuthorPHID($actor->getPHID())
->setViewPolicy($actor->getPHID())
->setEditPolicy($actor->getPHID())
Expand Down
52 changes: 48 additions & 4 deletions src/applications/files/view/PhabricatorGlobalUploadTargetView.php
Expand Up @@ -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;
Expand All @@ -24,8 +27,37 @@ public function getShowIfSupportedID() {
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;
}
Expand All @@ -34,18 +66,30 @@ public function render() {

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(),
));
Expand All @@ -57,6 +101,6 @@ public function render() {
'class' => 'phabricator-global-upload-instructions',
'style' => 'display: none;',
),
"\xE2\x87\xAA ".pht('Drop Files to Upload'));
$hint_text);
}
}
Expand Up @@ -94,6 +94,4 @@ public function getHeader() {
return $this->header;
}



}
16 changes: 14 additions & 2 deletions webroot/rsrc/js/core/behavior-global-drag-and-drop.js
Expand Up @@ -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());
Expand All @@ -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();
}
}
});

Expand Down

0 comments on commit f9f25c1

Please sign in to comment.