mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 14:52:41 +01:00
Add an async driver for document rendering and a crude "Hexdump" document engine
Summary: Depends on D19237. Ref T13105. This adds a (very basic) "Hexdump" engine (mostly just to have a second option to switch to) and a selector for choosing view modes. Test Plan: Viewed some files, switched between audio/video/image/hexdump. Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19238
This commit is contained in:
parent
01f22a8d06
commit
f646153f4d
14 changed files with 411 additions and 32 deletions
|
@ -9,7 +9,7 @@ return array(
|
||||||
'names' => array(
|
'names' => array(
|
||||||
'conpherence.pkg.css' => 'e68cf1fa',
|
'conpherence.pkg.css' => 'e68cf1fa',
|
||||||
'conpherence.pkg.js' => '15191c65',
|
'conpherence.pkg.js' => '15191c65',
|
||||||
'core.pkg.css' => '6a8ba174',
|
'core.pkg.css' => '97dc0e74',
|
||||||
'core.pkg.js' => '8581cd02',
|
'core.pkg.js' => '8581cd02',
|
||||||
'differential.pkg.css' => '113e692c',
|
'differential.pkg.css' => '113e692c',
|
||||||
'differential.pkg.js' => 'f6d809c0',
|
'differential.pkg.js' => 'f6d809c0',
|
||||||
|
@ -168,7 +168,7 @@ return array(
|
||||||
'rsrc/css/phui/phui-object-box.css' => '9cff003c',
|
'rsrc/css/phui/phui-object-box.css' => '9cff003c',
|
||||||
'rsrc/css/phui/phui-pager.css' => 'edcbc226',
|
'rsrc/css/phui/phui-pager.css' => 'edcbc226',
|
||||||
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
|
'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-remarkup-preview.css' => '54a34863',
|
||||||
'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892',
|
'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892',
|
||||||
'rsrc/css/phui/phui-spacing.css' => '042804d6',
|
'rsrc/css/phui/phui-spacing.css' => '042804d6',
|
||||||
|
@ -392,6 +392,7 @@ return array(
|
||||||
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc',
|
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc',
|
||||||
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70',
|
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70',
|
||||||
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
|
'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-icon-composer.js' => '8499b6ab',
|
||||||
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
|
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
|
||||||
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909',
|
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909',
|
||||||
|
@ -606,6 +607,7 @@ return array(
|
||||||
'javelin-behavior-diffusion-jump-to' => '73d09eef',
|
'javelin-behavior-diffusion-jump-to' => '73d09eef',
|
||||||
'javelin-behavior-diffusion-locate-file' => '6d3e1947',
|
'javelin-behavior-diffusion-locate-file' => '6d3e1947',
|
||||||
'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc',
|
'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc',
|
||||||
|
'javelin-behavior-document-engine' => 'f6d6f389',
|
||||||
'javelin-behavior-doorkeeper-tag' => '1db13e70',
|
'javelin-behavior-doorkeeper-tag' => '1db13e70',
|
||||||
'javelin-behavior-drydock-live-operation-status' => '901935ef',
|
'javelin-behavior-drydock-live-operation-status' => '901935ef',
|
||||||
'javelin-behavior-durable-column' => '2ae077e1',
|
'javelin-behavior-durable-column' => '2ae077e1',
|
||||||
|
@ -848,7 +850,7 @@ return array(
|
||||||
'phui-oi-simple-ui-css' => 'a8beebea',
|
'phui-oi-simple-ui-css' => 'a8beebea',
|
||||||
'phui-pager-css' => 'edcbc226',
|
'phui-pager-css' => 'edcbc226',
|
||||||
'phui-pinboard-view-css' => '2495140e',
|
'phui-pinboard-view-css' => '2495140e',
|
||||||
'phui-property-list-view-css' => '79fc3a02',
|
'phui-property-list-view-css' => 'ef864066',
|
||||||
'phui-remarkup-preview-css' => '54a34863',
|
'phui-remarkup-preview-css' => '54a34863',
|
||||||
'phui-segment-bar-view-css' => 'b1d1b892',
|
'phui-segment-bar-view-css' => 'b1d1b892',
|
||||||
'phui-spacing-css' => '042804d6',
|
'phui-spacing-css' => '042804d6',
|
||||||
|
@ -2151,6 +2153,11 @@ return array(
|
||||||
'javelin-util',
|
'javelin-util',
|
||||||
'javelin-reactor',
|
'javelin-reactor',
|
||||||
),
|
),
|
||||||
|
'f6d6f389' => array(
|
||||||
|
'javelin-behavior',
|
||||||
|
'javelin-dom',
|
||||||
|
'javelin-stratcom',
|
||||||
|
),
|
||||||
'f829edb3' => array(
|
'f829edb3' => array(
|
||||||
'javelin-view',
|
'javelin-view',
|
||||||
'javelin-install',
|
'javelin-install',
|
||||||
|
|
|
@ -3001,6 +3001,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php',
|
'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php',
|
||||||
'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php',
|
'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php',
|
||||||
'PhabricatorFileDeleteTransaction' => 'applications/files/xaction/PhabricatorFileDeleteTransaction.php',
|
'PhabricatorFileDeleteTransaction' => 'applications/files/xaction/PhabricatorFileDeleteTransaction.php',
|
||||||
|
'PhabricatorFileDocumentController' => 'applications/files/controller/PhabricatorFileDocumentController.php',
|
||||||
'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php',
|
'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php',
|
||||||
'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php',
|
'PhabricatorFileEditController' => 'applications/files/controller/PhabricatorFileEditController.php',
|
||||||
'PhabricatorFileEditEngine' => 'applications/files/editor/PhabricatorFileEditEngine.php',
|
'PhabricatorFileEditEngine' => 'applications/files/editor/PhabricatorFileEditEngine.php',
|
||||||
|
@ -3140,6 +3141,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
|
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
|
||||||
'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php',
|
'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php',
|
||||||
'PhabricatorHeraldContentSource' => 'applications/herald/contentsource/PhabricatorHeraldContentSource.php',
|
'PhabricatorHeraldContentSource' => 'applications/herald/contentsource/PhabricatorHeraldContentSource.php',
|
||||||
|
'PhabricatorHexdumpDocumentEngine' => 'applications/files/document/PhabricatorHexdumpDocumentEngine.php',
|
||||||
'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php',
|
'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php',
|
||||||
'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php',
|
'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php',
|
||||||
'PhabricatorHomeConstants' => 'applications/home/constants/PhabricatorHomeConstants.php',
|
'PhabricatorHomeConstants' => 'applications/home/constants/PhabricatorHomeConstants.php',
|
||||||
|
@ -8590,6 +8592,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorFileDataController' => 'PhabricatorFileController',
|
'PhabricatorFileDataController' => 'PhabricatorFileController',
|
||||||
'PhabricatorFileDeleteController' => 'PhabricatorFileController',
|
'PhabricatorFileDeleteController' => 'PhabricatorFileController',
|
||||||
'PhabricatorFileDeleteTransaction' => 'PhabricatorFileTransactionType',
|
'PhabricatorFileDeleteTransaction' => 'PhabricatorFileTransactionType',
|
||||||
|
'PhabricatorFileDocumentController' => 'PhabricatorFileController',
|
||||||
'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
|
'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
|
||||||
'PhabricatorFileEditController' => 'PhabricatorFileController',
|
'PhabricatorFileEditController' => 'PhabricatorFileController',
|
||||||
'PhabricatorFileEditEngine' => 'PhabricatorEditEngine',
|
'PhabricatorFileEditEngine' => 'PhabricatorEditEngine',
|
||||||
|
@ -8747,6 +8750,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
|
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
|
||||||
'PhabricatorHeraldApplication' => 'PhabricatorApplication',
|
'PhabricatorHeraldApplication' => 'PhabricatorApplication',
|
||||||
'PhabricatorHeraldContentSource' => 'PhabricatorContentSource',
|
'PhabricatorHeraldContentSource' => 'PhabricatorContentSource',
|
||||||
|
'PhabricatorHexdumpDocumentEngine' => 'PhabricatorDocumentEngine',
|
||||||
'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
|
'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
|
||||||
'PhabricatorHomeApplication' => 'PhabricatorApplication',
|
'PhabricatorHomeApplication' => 'PhabricatorApplication',
|
||||||
'PhabricatorHomeConstants' => 'PhabricatorHomeController',
|
'PhabricatorHomeConstants' => 'PhabricatorHomeController',
|
||||||
|
|
|
@ -89,6 +89,8 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
|
||||||
'iconset/(?P<key>[^/]+)/' => array(
|
'iconset/(?P<key>[^/]+)/' => array(
|
||||||
'select/' => 'PhabricatorFileIconSetSelectController',
|
'select/' => 'PhabricatorFileIconSetSelectController',
|
||||||
),
|
),
|
||||||
|
'document/(?P<engineKey>[^/]+)/(?P<phid>[^/]+)/'
|
||||||
|
=> 'PhabricatorFileDocumentController',
|
||||||
) + $this->getResourceSubroutes(),
|
) + $this->getResourceSubroutes(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -404,50 +404,70 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
|
||||||
|
|
||||||
private function newFileContent(PhabricatorFile $file) {
|
private function newFileContent(PhabricatorFile $file) {
|
||||||
$viewer = $this->getViewer();
|
$viewer = $this->getViewer();
|
||||||
$engines = PhabricatorDocumentEngine::getAllEngines();
|
|
||||||
|
|
||||||
$ref = id(new PhabricatorDocumentRef())
|
$ref = id(new PhabricatorDocumentRef())
|
||||||
->setFile($file);
|
->setFile($file);
|
||||||
|
|
||||||
foreach ($engines as $key => $engine) {
|
$engines = PhabricatorDocumentEngine::getEnginesForRef($viewer, $ref);
|
||||||
$engine = id(clone $engine)
|
$engine = head($engines);
|
||||||
->setViewer($viewer);
|
|
||||||
|
|
||||||
if (!$engine->canRenderDocument($ref)) {
|
|
||||||
unset($engines[$key]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$engines[$key] = $engine;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$engines) {
|
|
||||||
throw new Exception(pht('No engine can render this document.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$vectors = array();
|
|
||||||
foreach ($engines as $key => $usable_engine) {
|
|
||||||
$vectors[$key] = $usable_engine->newSortVector($ref);
|
|
||||||
}
|
|
||||||
$vectors = msortv($vectors, 'getSelf');
|
|
||||||
|
|
||||||
$engine = $engines[head_key($vectors)];
|
|
||||||
|
|
||||||
$content = $engine->newDocument($ref);
|
$content = $engine->newDocument($ref);
|
||||||
if (!$content) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$icon = $engine->newDocumentIcon($ref);
|
$icon = $engine->newDocumentIcon($ref);
|
||||||
|
|
||||||
|
$views = array();
|
||||||
|
foreach ($engines as $candidate_engine) {
|
||||||
|
$label = $candidate_engine->getViewAsLabel($ref);
|
||||||
|
if ($label === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$view_icon = $candidate_engine->getViewAsIconIcon($ref);
|
||||||
|
|
||||||
|
$views[] = array(
|
||||||
|
'viewKey' => $candidate_engine->getDocumentEngineKey(),
|
||||||
|
'icon' => $view_icon,
|
||||||
|
'name' => $label,
|
||||||
|
'engineURI' => $candidate_engine->getRenderURI($ref),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Javelin::initBehavior('document-engine');
|
||||||
|
|
||||||
|
$viewport_id = celerity_generate_unique_node_id();
|
||||||
|
|
||||||
|
$viewport = phutil_tag(
|
||||||
|
'div',
|
||||||
|
array(
|
||||||
|
'id' => $viewport_id,
|
||||||
|
),
|
||||||
|
$content);
|
||||||
|
|
||||||
|
$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())
|
$header = id(new PHUIHeaderView())
|
||||||
->setHeaderIcon($icon)
|
->setHeaderIcon($icon)
|
||||||
->setHeader($ref->getName());
|
->setHeader($ref->getName())
|
||||||
|
->addActionLink($view_button);
|
||||||
|
|
||||||
return id(new PHUIObjectBoxView())
|
return id(new PHUIObjectBoxView())
|
||||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||||
->setHeader($header)
|
->setHeader($header)
|
||||||
->appendChild($content);
|
->appendChild($viewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,10 @@ final class PhabricatorAudioDocumentEngine
|
||||||
|
|
||||||
const ENGINEKEY = 'audio';
|
const ENGINEKEY = 'audio';
|
||||||
|
|
||||||
|
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
|
||||||
|
return pht('View as Audio');
|
||||||
|
}
|
||||||
|
|
||||||
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
|
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
|
||||||
return 'fa-file-sound-o';
|
return 'fa-file-sound-o';
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,4 +59,52 @@ abstract class PhabricatorDocumentEngine
|
||||||
return 2000;
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,14 @@ final class PhabricatorDocumentRef
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function loadData() {
|
||||||
|
if ($this->file) {
|
||||||
|
return $this->file->loadFileData();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PhutilMethodNotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public function hasAnyMimeType(array $candidate_types) {
|
public function hasAnyMimeType(array $candidate_types) {
|
||||||
$mime_full = $this->getMimeType();
|
$mime_full = $this->getMimeType();
|
||||||
$mime_parts = explode(';', $mime_full);
|
$mime_parts = explode(';', $mime_full);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,6 +5,10 @@ final class PhabricatorImageDocumentEngine
|
||||||
|
|
||||||
const ENGINEKEY = 'image';
|
const ENGINEKEY = 'image';
|
||||||
|
|
||||||
|
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
|
||||||
|
return pht('View as Image');
|
||||||
|
}
|
||||||
|
|
||||||
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
|
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
|
||||||
return 'fa-file-image-o';
|
return 'fa-file-image-o';
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,10 @@ final class PhabricatorVideoDocumentEngine
|
||||||
|
|
||||||
const ENGINEKEY = 'video';
|
const ENGINEKEY = 'video';
|
||||||
|
|
||||||
|
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
|
||||||
|
return pht('View as Video');
|
||||||
|
}
|
||||||
|
|
||||||
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
|
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
|
||||||
return 'fa-film';
|
return 'fa-film';
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,10 @@ final class PhabricatorVoidDocumentEngine
|
||||||
|
|
||||||
const ENGINEKEY = 'void';
|
const ENGINEKEY = 'void';
|
||||||
|
|
||||||
|
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
|
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
|
||||||
return 'fa-file';
|
return 'fa-file';
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,3 +231,14 @@ div.phui-property-list-stacked .phui-property-list-properties
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: {$greytext};
|
color: {$greytext};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.document-engine-error {
|
||||||
|
margin: 20px auto;
|
||||||
|
text-align: center;
|
||||||
|
color: {$redtext};
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-engine-hexdump {
|
||||||
|
margin: 20px;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in a new issue