Page MenuHomePhabricator

D19238.diff
No OneTemporary

D19238.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -9,7 +9,7 @@
'names' => array(
'conpherence.pkg.css' => 'e68cf1fa',
'conpherence.pkg.js' => '15191c65',
- 'core.pkg.css' => '6a8ba174',
+ 'core.pkg.css' => '97dc0e74',
'core.pkg.js' => '8581cd02',
'differential.pkg.css' => '113e692c',
'differential.pkg.js' => 'f6d809c0',
@@ -168,7 +168,7 @@
'rsrc/css/phui/phui-object-box.css' => '9cff003c',
'rsrc/css/phui/phui-pager.css' => 'edcbc226',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
- 'rsrc/css/phui/phui-property-list-view.css' => '79fc3a02',
+ 'rsrc/css/phui/phui-property-list-view.css' => 'ef864066',
'rsrc/css/phui/phui-remarkup-preview.css' => '54a34863',
'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892',
'rsrc/css/phui/phui-spacing.css' => '042804d6',
@@ -392,6 +392,7 @@
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc',
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70',
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
+ 'rsrc/js/application/files/behavior-document-engine.js' => 'f6d6f389',
'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909',
@@ -606,6 +607,7 @@
'javelin-behavior-diffusion-jump-to' => '73d09eef',
'javelin-behavior-diffusion-locate-file' => '6d3e1947',
'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc',
+ 'javelin-behavior-document-engine' => 'f6d6f389',
'javelin-behavior-doorkeeper-tag' => '1db13e70',
'javelin-behavior-drydock-live-operation-status' => '901935ef',
'javelin-behavior-durable-column' => '2ae077e1',
@@ -848,7 +850,7 @@
'phui-oi-simple-ui-css' => 'a8beebea',
'phui-pager-css' => 'edcbc226',
'phui-pinboard-view-css' => '2495140e',
- 'phui-property-list-view-css' => '79fc3a02',
+ 'phui-property-list-view-css' => 'ef864066',
'phui-remarkup-preview-css' => '54a34863',
'phui-segment-bar-view-css' => 'b1d1b892',
'phui-spacing-css' => '042804d6',
@@ -2151,6 +2153,11 @@
'javelin-util',
'javelin-reactor',
),
+ 'f6d6f389' => array(
+ 'javelin-behavior',
+ 'javelin-dom',
+ 'javelin-stratcom',
+ ),
'f829edb3' => array(
'javelin-view',
'javelin-install',
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -3001,6 +3001,7 @@
'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php',
'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php',
'PhabricatorFileDeleteTransaction' => 'applications/files/xaction/PhabricatorFileDeleteTransaction.php',
+ 'PhabricatorFileDocumentController' => 'applications/files/controller/PhabricatorFileDocumentController.php',
'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php',
'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php',
'PhabricatorFileEditEngine' => 'applications/files/editor/PhabricatorFileEditEngine.php',
@@ -3140,6 +3141,7 @@
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php',
'PhabricatorHeraldContentSource' => 'applications/herald/contentsource/PhabricatorHeraldContentSource.php',
+ 'PhabricatorHexdumpDocumentEngine' => 'applications/files/document/PhabricatorHexdumpDocumentEngine.php',
'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php',
'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php',
'PhabricatorHomeConstants' => 'applications/home/constants/PhabricatorHomeConstants.php',
@@ -8590,6 +8592,7 @@
'PhabricatorFileDataController' => 'PhabricatorFileController',
'PhabricatorFileDeleteController' => 'PhabricatorFileController',
'PhabricatorFileDeleteTransaction' => 'PhabricatorFileTransactionType',
+ 'PhabricatorFileDocumentController' => 'PhabricatorFileController',
'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
'PhabricatorFileEditController' => 'PhabricatorFileController',
'PhabricatorFileEditEngine' => 'PhabricatorEditEngine',
@@ -8747,6 +8750,7 @@
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
'PhabricatorHeraldApplication' => 'PhabricatorApplication',
'PhabricatorHeraldContentSource' => 'PhabricatorContentSource',
+ 'PhabricatorHexdumpDocumentEngine' => 'PhabricatorDocumentEngine',
'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorHomeApplication' => 'PhabricatorApplication',
'PhabricatorHomeConstants' => 'PhabricatorHomeController',
diff --git a/src/applications/files/application/PhabricatorFilesApplication.php b/src/applications/files/application/PhabricatorFilesApplication.php
--- a/src/applications/files/application/PhabricatorFilesApplication.php
+++ b/src/applications/files/application/PhabricatorFilesApplication.php
@@ -89,6 +89,8 @@
'iconset/(?P<key>[^/]+)/' => array(
'select/' => 'PhabricatorFileIconSetSelectController',
),
+ 'document/(?P<engineKey>[^/]+)/(?P<phid>[^/]+)/'
+ => 'PhabricatorFileDocumentController',
) + $this->getResourceSubroutes(),
);
}
diff --git a/src/applications/files/controller/PhabricatorFileDocumentController.php b/src/applications/files/controller/PhabricatorFileDocumentController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/files/controller/PhabricatorFileDocumentController.php
@@ -0,0 +1,113 @@
+<?php
+
+final class PhabricatorFileDocumentController
+ extends PhabricatorFileController {
+
+ private $file;
+ private $engine;
+ private $ref;
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
+
+ $file_phid = $request->getURIData('phid');
+
+ $file = id(new PhabricatorFileQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($file_phid))
+ ->executeOne();
+ if (!$file) {
+ return $this->newErrorResponse(
+ pht(
+ 'This file ("%s") does not exist or could not be loaded.',
+ $file_phid));
+ }
+ $this->file = $file;
+
+ $ref = id(new PhabricatorDocumentRef())
+ ->setFile($file);
+ $this->ref = $ref;
+
+ $engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref);
+ $engine_key = $request->getURIData('engineKey');
+ if (!isset($engines[$engine_key])) {
+ return $this->newErrorResponse(
+ pht(
+ 'The engine ("%s") is unknown, or unable to render this document.',
+ $engine_key));
+ }
+ $engine = $engines[$engine_key];
+ $this->engine = $engine;
+
+ try {
+ $content = $engine->newDocument($ref);
+ } catch (Exception $ex) {
+ return $this->newErrorResponse($ex->getMessage());
+ }
+
+ return $this->newContentResponse($content);
+ }
+
+ private function newErrorResponse($message) {
+ $container = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'document-engine-error',
+ ),
+ array(
+ id(new PHUIIconView())
+ ->setIcon('fa-exclamation-triangle red'),
+ ' ',
+ $message,
+ ));
+
+ return $this->newContentResponse($container);
+ }
+
+
+ private function newContentResponse($content) {
+ $viewer = $this->getViewer();
+ $request = $this->getRequest();
+
+ $file = $this->file;
+ $engine = $this->engine;
+ $ref = $this->ref;
+
+ if ($request->isAjax()) {
+ return id(new AphrontAjaxResponse())
+ ->setContent(
+ array(
+ 'markup' => hsprintf('%s', $content),
+ ));
+ }
+
+ $crumbs = $this->buildApplicationCrumbs();
+ if ($file) {
+ $crumbs->addTextCrumb($file->getMonogram(), $file->getInfoURI());
+ }
+
+ $label = $engine->getViewAsLabel($ref);
+ if ($label) {
+ $crumbs->addTextCrumb($label);
+ }
+
+ $crumbs->setBorder(true);
+
+ $content_frame = id(new PHUIObjectBoxView())
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+ ->appendChild($content);
+
+ $page_frame = id(new PHUITwoColumnView())
+ ->setFooter($content_frame);
+
+ return $this->newPage()
+ ->setCrumbs($crumbs)
+ ->setTitle(
+ array(
+ $ref->getName(),
+ pht('Standalone'),
+ ))
+ ->appendChild($page_frame);
+ }
+
+}
diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php
--- a/src/applications/files/controller/PhabricatorFileInfoController.php
+++ b/src/applications/files/controller/PhabricatorFileInfoController.php
@@ -404,50 +404,70 @@
private function newFileContent(PhabricatorFile $file) {
$viewer = $this->getViewer();
- $engines = PhabricatorDocumentEngine::getAllEngines();
$ref = id(new PhabricatorDocumentRef())
->setFile($file);
- foreach ($engines as $key => $engine) {
- $engine = id(clone $engine)
- ->setViewer($viewer);
+ $engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref);
+ $engine = head($engines);
- if (!$engine->canRenderDocument($ref)) {
- unset($engines[$key]);
+ $content = $engine->newDocument($ref);
+
+ $icon = $engine->newDocumentIcon($ref);
+
+ $views = array();
+ foreach ($engines as $candidate_engine) {
+ $label = $candidate_engine->getViewAsLabel($ref);
+ if ($label === null) {
continue;
}
- $engines[$key] = $engine;
- }
+ $view_icon = $candidate_engine->getViewAsIconIcon($ref);
- if (!$engines) {
- throw new Exception(pht('No engine can render this document.'));
+ $views[] = array(
+ 'viewKey' => $candidate_engine->getDocumentEngineKey(),
+ 'icon' => $view_icon,
+ 'name' => $label,
+ 'engineURI' => $candidate_engine->getRenderURI($ref),
+ );
}
- $vectors = array();
- foreach ($engines as $key => $usable_engine) {
- $vectors[$key] = $usable_engine->newSortVector($ref);
- }
- $vectors = msortv($vectors, 'getSelf');
+ Javelin::initBehavior('document-engine');
- $engine = $engines[head_key($vectors)];
+ $viewport_id = celerity_generate_unique_node_id();
- $content = $engine->newDocument($ref);
- if (!$content) {
- return null;
- }
+ $viewport = phutil_tag(
+ 'div',
+ array(
+ 'id' => $viewport_id,
+ ),
+ $content);
- $icon = $engine->newDocumentIcon($ref);
+ $meta = array(
+ 'viewportID' => $viewport_id,
+ 'viewKey' => $engine->getDocumentEngineKey(),
+ 'views' => $views,
+ 'standaloneURI' => $engine->getRenderURI($ref),
+ );
+
+ $view_button = id(new PHUIButtonView())
+ ->setTag('a')
+ ->setText(pht('View Options'))
+ ->setIcon('fa-file-image-o')
+ ->setColor(PHUIButtonView::GREY)
+ ->setMetadata($meta)
+ ->setDropdown(true)
+ ->addSigil('document-engine-view-dropdown');
$header = id(new PHUIHeaderView())
->setHeaderIcon($icon)
- ->setHeader($ref->getName());
+ ->setHeader($ref->getName())
+ ->addActionLink($view_button);
return id(new PHUIObjectBoxView())
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setHeader($header)
- ->appendChild($content);
+ ->appendChild($viewport);
}
}
diff --git a/src/applications/files/document/PhabricatorAudioDocumentEngine.php b/src/applications/files/document/PhabricatorAudioDocumentEngine.php
--- a/src/applications/files/document/PhabricatorAudioDocumentEngine.php
+++ b/src/applications/files/document/PhabricatorAudioDocumentEngine.php
@@ -5,6 +5,10 @@
const ENGINEKEY = 'audio';
+ public function getViewAsLabel(PhabricatorDocumentRef $ref) {
+ return pht('View as Audio');
+ }
+
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-file-sound-o';
}
diff --git a/src/applications/files/document/PhabricatorDocumentEngine.php b/src/applications/files/document/PhabricatorDocumentEngine.php
--- a/src/applications/files/document/PhabricatorDocumentEngine.php
+++ b/src/applications/files/document/PhabricatorDocumentEngine.php
@@ -59,4 +59,52 @@
return 2000;
}
+ abstract public function getViewAsLabel(PhabricatorDocumentRef $ref);
+
+ public function getViewAsIconIcon(PhabricatorDocumentRef $ref) {
+ return $this->getDocumentIconIcon($ref);
+ }
+
+ public function getRenderURI(PhabricatorDocumentRef $ref) {
+ $file = $ref->getFile();
+ if (!$file) {
+ throw new PhutilMethodNotImplementedException();
+ }
+
+ $engine_key = $this->getDocumentEngineKey();
+ $file_phid = $file->getPHID();
+
+ return "/file/document/{$engine_key}/{$file_phid}/";
+ }
+
+ final public static function getEnginesForRef(
+ PhabricatorUser $viewer,
+ PhabricatorDocumentRef $ref) {
+ $engines = self::getAllEngines();
+
+ foreach ($engines as $key => $engine) {
+ $engine = id(clone $engine)
+ ->setViewer($viewer);
+
+ if (!$engine->canRenderDocument($ref)) {
+ unset($engines[$key]);
+ continue;
+ }
+
+ $engines[$key] = $engine;
+ }
+
+ if (!$engines) {
+ throw new Exception(pht('No content engine can render this document.'));
+ }
+
+ $vectors = array();
+ foreach ($engines as $key => $usable_engine) {
+ $vectors[$key] = $usable_engine->newSortVector($ref);
+ }
+ $vectors = msortv($vectors, 'getSelf');
+
+ return array_select_keys($engines, array_keys($vectors));
+ }
+
}
diff --git a/src/applications/files/document/PhabricatorDocumentRef.php b/src/applications/files/document/PhabricatorDocumentRef.php
--- a/src/applications/files/document/PhabricatorDocumentRef.php
+++ b/src/applications/files/document/PhabricatorDocumentRef.php
@@ -68,6 +68,14 @@
return null;
}
+ public function loadData() {
+ if ($this->file) {
+ return $this->file->loadFileData();
+ }
+
+ throw new PhutilMethodNotImplementedException();
+ }
+
public function hasAnyMimeType(array $candidate_types) {
$mime_full = $this->getMimeType();
$mime_parts = explode(';', $mime_full);
diff --git a/src/applications/files/document/PhabricatorHexdumpDocumentEngine.php b/src/applications/files/document/PhabricatorHexdumpDocumentEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/files/document/PhabricatorHexdumpDocumentEngine.php
@@ -0,0 +1,83 @@
+<?php
+
+final class PhabricatorHexdumpDocumentEngine
+ extends PhabricatorDocumentEngine {
+
+ const ENGINEKEY = 'hexdump';
+
+ public function getViewAsLabel(PhabricatorDocumentRef $ref) {
+ return pht('View as Hexdump');
+ }
+
+ protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
+ return 'fa-microchip';
+ }
+
+ protected function getContentScore() {
+ return 500;
+ }
+
+ protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
+ return true;
+ }
+
+ protected function newDocumentContent(PhabricatorDocumentRef $ref) {
+ $content = $ref->loadData();
+
+ $output = array();
+ $offset = 0;
+
+ $lines = str_split($content, 16);
+ foreach ($lines as $line) {
+ $output[] = sprintf(
+ '%08x %- 23s %- 23s %- 16s',
+ $offset,
+ $this->renderHex(substr($line, 0, 8)),
+ $this->renderHex(substr($line, 8)),
+ $this->renderBytes($line));
+
+ $offset += 16;
+ }
+
+ $output = implode("\n", $output);
+
+ $container = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'document-engine-hexdump PhabricatorMonospaced',
+ ),
+ $output);
+
+ return $container;
+ }
+
+ private function renderHex($bytes) {
+ $length = strlen($bytes);
+
+ $output = array();
+ for ($ii = 0; $ii < $length; $ii++) {
+ $output[] = sprintf('%02x', ord($bytes[$ii]));
+ }
+
+ return implode(' ', $output);
+ }
+
+ private function renderBytes($bytes) {
+ $length = strlen($bytes);
+
+ $output = array();
+ for ($ii = 0; $ii < $length; $ii++) {
+ $chr = $bytes[$ii];
+ $ord = ord($chr);
+
+ if ($ord < 0x20 || $ord >= 0x7F) {
+ $chr = '.';
+ }
+
+ $output[] = $chr;
+ }
+
+ return implode('', $output);
+ }
+
+}
diff --git a/src/applications/files/document/PhabricatorImageDocumentEngine.php b/src/applications/files/document/PhabricatorImageDocumentEngine.php
--- a/src/applications/files/document/PhabricatorImageDocumentEngine.php
+++ b/src/applications/files/document/PhabricatorImageDocumentEngine.php
@@ -5,6 +5,10 @@
const ENGINEKEY = 'image';
+ public function getViewAsLabel(PhabricatorDocumentRef $ref) {
+ return pht('View as Image');
+ }
+
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-file-image-o';
}
diff --git a/src/applications/files/document/PhabricatorVideoDocumentEngine.php b/src/applications/files/document/PhabricatorVideoDocumentEngine.php
--- a/src/applications/files/document/PhabricatorVideoDocumentEngine.php
+++ b/src/applications/files/document/PhabricatorVideoDocumentEngine.php
@@ -5,6 +5,10 @@
const ENGINEKEY = 'video';
+ public function getViewAsLabel(PhabricatorDocumentRef $ref) {
+ return pht('View as Video');
+ }
+
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-film';
}
diff --git a/src/applications/files/document/PhabricatorVoidDocumentEngine.php b/src/applications/files/document/PhabricatorVoidDocumentEngine.php
--- a/src/applications/files/document/PhabricatorVoidDocumentEngine.php
+++ b/src/applications/files/document/PhabricatorVoidDocumentEngine.php
@@ -5,6 +5,10 @@
const ENGINEKEY = 'void';
+ public function getViewAsLabel(PhabricatorDocumentRef $ref) {
+ return null;
+ }
+
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
return 'fa-file';
}
diff --git a/webroot/rsrc/css/phui/phui-property-list-view.css b/webroot/rsrc/css/phui/phui-property-list-view.css
--- a/webroot/rsrc/css/phui/phui-property-list-view.css
+++ b/webroot/rsrc/css/phui/phui-property-list-view.css
@@ -231,3 +231,14 @@
text-align: center;
color: {$greytext};
}
+
+.document-engine-error {
+ margin: 20px auto;
+ text-align: center;
+ color: {$redtext};
+}
+
+.document-engine-hexdump {
+ margin: 20px;
+ white-space: pre;
+}
diff --git a/webroot/rsrc/js/application/files/behavior-document-engine.js b/webroot/rsrc/js/application/files/behavior-document-engine.js
new file mode 100644
--- /dev/null
+++ b/webroot/rsrc/js/application/files/behavior-document-engine.js
@@ -0,0 +1,67 @@
+/**
+ * @provides javelin-behavior-document-engine
+ * @requires javelin-behavior
+ * javelin-dom
+ * javelin-stratcom
+ */
+
+JX.behavior('document-engine', function() {
+
+ function onmenu(e) {
+ var node = e.getNode('document-engine-view-dropdown');
+ var data = JX.Stratcom.getData(node);
+
+ if (data.menu) {
+ return;
+ }
+
+ e.prevent();
+
+ var menu = new JX.PHUIXDropdownMenu(node);
+ var list = new JX.PHUIXActionListView();
+
+ var view;
+ for (var ii = 0; ii < data.views.length; ii++) {
+ var spec = data.views[ii];
+
+ view = new JX.PHUIXActionView()
+ .setName(spec.name)
+ .setIcon(spec.icon)
+ .setHref(spec.engineURI);
+
+ view.setHandler(JX.bind(null, function(spec, e) {
+ if (!e.isNormalClick()) {
+ return;
+ }
+
+ e.prevent();
+ menu.close();
+
+ onview(data, spec);
+ }, spec));
+
+ list.addItem(view);
+ }
+
+ menu.setContent(list.getNode());
+
+ data.menu = menu;
+ menu.open();
+ }
+
+ function onview(data, spec) {
+ var handler = JX.bind(null, onrender, data);
+
+ new JX.Request(spec.engineURI, handler)
+ .send();
+ }
+
+ function onrender(data, r) {
+ var viewport = JX.$(data.viewportID);
+
+ JX.DOM.setContent(viewport, JX.$H(r.markup));
+ }
+
+ JX.Stratcom.listen('click', 'document-engine-view-dropdown', onmenu);
+
+});

File Metadata

Mime Type
text/plain
Expires
Fri, Nov 22, 9:37 PM (19 h, 47 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6772962
Default Alt Text
D19238.diff (20 KB)

Event Timeline