Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14077126
D19238.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
20 KB
Referenced Files
None
Subscribers
None
D19238.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D19238: Add an async driver for document rendering and a crude "Hexdump" document engine
Attached
Detach File
Event Timeline
Log In to Comment