diff --git a/rsrc/behavior-diagram-extension.js b/rsrc/behavior-diagram-extension.js index eb99c41..43a70e7 100644 --- a/rsrc/behavior-diagram-extension.js +++ b/rsrc/behavior-diagram-extension.js @@ -1,271 +1,255 @@ /** * @provides javelin-behavior-diagram-extension */ JX.behavior('diagram-extension', function(config) { var phorge_extension = {}; function createIframe() { var iframe = document.createElement('iframe'); var iframeInitializedEvent = new CustomEvent('initReceived'); iframe.setAttribute('title', 'diagrams.net editor'); iframe.setAttribute('frameborder', '0'); - iframe.style.width = '100%'; - iframe.style.height = 'calc(100vh - 91px)'; - iframe.style.marginTop = '30px'; - iframe.style.marginBottom = '-16px'; var receive = function (evt) { if (evt.data.length > 0) { var msg = JSON.parse(evt.data); if (msg.event == 'init') { - if (phorge_extension.diagramBase64) { - - var diagramName = document.querySelector('.diagramName'); - diagramName.style.display = 'inline-block'; - diagramName.querySelector('a').innerText = phorge_extension.diagramName; - if (phorge_extension.diagramVersion != '') { - diagramName.querySelector('.version').innerText = '(#' - + phorge_extension.diagramVersion - + ')'; - } - } - iframe.contentWindow.postMessage(JSON.stringify({ action: 'load', autosave: 1, xmlpng: 'data:image/png;base64,' + phorge_extension.diagramBase64 }), '*'); } else if (msg.event == 'load') { // enable Mathematical Typesettings by default iframe.contentWindow.sb.editorUi.setMathEnabled(true); setupButtonsInMenuToolbar(); } else if (msg.event == 'export') { - saveFlowchart(name, msg.data, iframe); + saveFlowchart(msg.data, iframe); } else if (msg.event == 'save') { iframe.contentWindow.postMessage(JSON.stringify({ action: 'export', format: 'xmlpng', xml: msg.xml, spin: 'Updating page' }), '*'); } } }; window.addEventListener('message', receive); iframe.setAttribute('src', phorge_extension.editor); - document.querySelector('#mainScreen').appendChild(iframe); + document.querySelector('#diagram-editor-wrapper').appendChild(iframe); iframe.contentWindow.addEventListener('DOMContentLoaded', addToolbarResourcesToIframe); iframe.dispatchEvent(iframeInitializedEvent); iframe.contentWindow.RESOURCES_PATH = document.baseURI + 'iframe/resources'; iframe.contentWindow.STENCIL_PATH = document.baseURI + 'iframe/stencils'; iframe.contentWindow.IMAGE_PATH = document.baseURI + 'iframe/images'; iframe.contentWindow.STYLE_PATH = document.baseURI + 'iframe/styles'; iframe.contentWindow.CSS_PATH = document.baseURI + 'iframe/styles'; } function addToolbarResourcesToIframe() { var head = this.document.getElementsByTagName('head')[0]; var script = this.document.createElement('script'); script.src = config.toolbarJs; head.appendChild(script); var styles = this.document.createElement('link'); styles.setAttribute('type', 'text/css'); styles.setAttribute('rel', 'stylesheet'); styles.setAttribute('href', config.toolbarCss); head.appendChild(styles); } function init(diagramName, diagramPHID, diagramVersion, diagramBase64) { var baseURI = document.baseURI; if (diagramName != '' && diagramBase64 != '') { baseURI = baseURI.substr(0, baseURI.length - diagramName.length - 1); if (diagramVersion != '') { baseURI = baseURI.substr(0, baseURI.length - diagramVersion.length - 1); } } phorge_extension.baseURI = baseURI; phorge_extension.csrf = document.querySelector('input[name="__csrf__"]')?.value; phorge_extension.editor = baseURI + '/iframe/?embed=1&spin=0&proto=json&noExitBtn=1'; phorge_extension.name = null; phorge_extension.editor += '&lang=en'; phorge_extension.editor += '&ui=min'; phorge_extension.diagramPHID = diagramPHID; phorge_extension.diagramName = diagramName; phorge_extension.diagramVersion = diagramVersion; phorge_extension.diagramBase64 = diagramBase64; createIframe(); } - function saveFlowchart(name, flowchartData, iframe) { - var diagramID = document.querySelector('.diagramName a').innerText.replace(/^DIAG/, ''); + function saveFlowchart(flowchartData, iframe) { + var diagramID = phorge_extension.diagramName.replace(/^DIAG/, ''); var csrf = document.querySelector('input[name="__csrf__"]')?.value; var data = new URLSearchParams(); data.append('data', flowchartData); data.append('diagramID', diagramID); data.append('__csrf__', csrf); data.append('__form__', '1'); data.append('__ajax__', 'true'); var xmlhttp = new XMLHttpRequest(); xmlhttp.overrideMimeType('application/json'); xmlhttp.open('POST', 'save/', true); xmlhttp.onload = function () { if (xmlhttp.readyState == 4) { var errorMessage = null; try { var result = JSON.parse(xmlhttp.responseText); if (result.Status != 'OK') { errorMessage = result.Error; } else { // make sure we don't show messagebox about redirection in browser iframe.parentNode.removeChild(iframe); if (!phorge_extension.diagramVersion || !phorge_extension.diagramVersion.trim()) { if (!phorge_extension.diagramName || !phorge_extension.diagramName.trim()) { // load new diagram window.location = window.location + '/DIAG' + result.DiagramID; } else { // reload actual page (so versioned diagrams info is also updated) window.location.reload(); } } else { // cut off version id from url var url = document.baseURI .substring(0, document.baseURI .length - phorge_extension.diagramVersion .length - 1 ); // redirect to latest version of diagram window.location = url; } } } catch (exc) { errorMessage = exc.message; } } }; xmlhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded;'); xmlhttp.send(data); } function setupButtonsInMenuToolbar() { var iframe = document.querySelector('iframe'); var btnSave = Array.prototype.slice.call( iframe.contentDocument .querySelector('.geMenubarContainer') .querySelectorAll('button'), 0 ).reverse()[0]; // identify Exit Button btnSave.classList.add('btnSave'); // change layout settings of area where btnSave belongs to so // that the dropdown menu is not hidden under the drawing area btnSave.parentNode.style.position = 'fixed'; // create extra controls var subscribeUnsubscribe = setupSubscriptionButtonInMenuToolbar(iframe, btnSave); var dropdown = setupVersionDropDownInMenuToolbar(iframe, subscribeUnsubscribe); // initialize toolbuttons for (var tb in phorge_extension.toolbtn) { if (phorge_extension.toolbtn.hasOwnProperty(tb)) { phorge_extension.toolbtn[tb].instance.initialize(); } } } function setupVersionDropDownInMenuToolbar(iframe, btnLeft) { // generate 'Select Version' button // create the dropdown element const dropdown = document.createElement('div'); dropdown.classList.add('toolbtn-version-dropdown'); // create the dropdown toggle button const toggle = document.createElement('button'); toggle.classList.add('toolbtn-version-dropdown-toggle'); toggle.textContent = 'Select Version'; toggle.title = 'View previous versions'; dropdown.appendChild(toggle); const iframeContent = document.querySelector('iframe').contentDocument; const menubarContainer = iframeContent.querySelector('.geMenubarContainer'); // create the dropdown menu const menu = document.createElement('div'); menu.classList.add('toolbtn-version-dropdown-menu'); menubarContainer.parentNode.insertBefore(menu, menubarContainer.nextSibling); // create the version list const versionList = document.createElement('ul'); versionList.classList.add('toolbtn-version-list'); menu.appendChild(versionList); // create the prev and next buttons const prevBtn = document.createElement('button'); prevBtn.classList.add('toolbtn-version-prev-btn'); prevBtn.textContent = '<'; const nextBtn = document.createElement('button'); nextBtn.classList.add('toolbtn-version-next-btn'); nextBtn.textContent = '>'; menu.appendChild(prevBtn); menu.appendChild(nextBtn); // place dropdown next to button on the left btnLeft.parentNode.insertBefore(dropdown, btnLeft.nextSibling); return dropdown; } function setupSubscriptionButtonInMenuToolbar(iframe, btnLeft) { // generate 'Subscribe/Unsubscribe' button // create grouping div element const div = document.createElement('div'); div.classList.add('toolbtn-diagram-subscription'); // create subscribe button const btnSubscribe = document.createElement('button'); btnSubscribe.classList.add('subscribe'); btnSubscribe.classList.add('eye'); btnSubscribe.title = 'Subscribe'; div.appendChild(btnSubscribe); // create unsubscribe button const btnUnsubscribe = document.createElement('button'); btnUnsubscribe.classList.add('unsubscribe'); btnUnsubscribe.classList.add('eye'); btnUnsubscribe.title = 'Unsubscribe'; div.appendChild(btnUnsubscribe); // place grouping next to button on the left btnLeft.parentNode.insertBefore(div, btnLeft.nextSibling); return div; } if (config.initParams) { init.apply(undefined, config.initParams); } window.__diagram = phorge_extension; }); diff --git a/rsrc/diagram-extension.css b/rsrc/diagram-extension.css new file mode 100644 index 0000000..f3d44cd --- /dev/null +++ b/rsrc/diagram-extension.css @@ -0,0 +1,35 @@ +/** + * @provides diagram-css-extension + */ + +html, +.diagram-editor-page, +.diagram-editor-page #main-page-frame, +.diagram-editor-page #phabricator-standard-page { + height: 100% +} + +.diagram-editor-page #phabricator-standard-page, +.diagram-editor-page #phabricator-standard-page-body { + display: flex; + flex-direction: column; +} + +.diagram-editor-page #phabricator-standard-page-body { + flex-grow: 1; +} + +.diagram-editor-page .phui-crumbs-view { + padding: 0 8px; +} + +#diagram-editor-wrapper { + flex-grow: 1; + position: relative; +} + +#diagram-editor-wrapper > iframe { + position: absolute; + width: 100%; + height: 100%; +} diff --git a/src/application/DiagramApplication.php b/src/application/DiagramApplication.php index a4c25ea..6d014c0 100644 --- a/src/application/DiagramApplication.php +++ b/src/application/DiagramApplication.php @@ -1,81 +1,81 @@ array( // url to image data "data/(?PPHID-DGVN-[a-z0-9]{20})" . "(?:/(?P[^/]+))?" . "(?:/|\?|$).*" => "DiagramDataController", // version info requests "version/DIAG(?P(\d+))" . "/(?P(\d+))" . "(?:/|\?|$).*" => "DiagramController", // draw.io iframe urls "(?:(?PDIAG(\d+))/(?P\d+)/?)" . "(?Piframe)" . "(?:/|\?|$).*" => "DiagramController", // draw.io iframe urls "(?:(?PDIAG(\d+))/?)?" . "(?Piframe)" . "(?:/|\?|$).*" => "DiagramController", // url with diagram id and version in it "(?PDIAG(\d+))" . "/(?P\d+)" . "(?:/|\?|$).*" => "DiagramController", // url with diagram id in it "(?PDIAG(\d+))" . "(?:/|\?|$).*" => "DiagramController", // url for validating subscription "subscribed/request/" . "(?P[^/]+)/" => 'DiagramController', // other urls ".*" => "DiagramController", ) ); } } diff --git a/src/celerity/map.php b/src/celerity/map.php index 3c0298c..bec5fc3 100644 --- a/src/celerity/map.php +++ b/src/celerity/map.php @@ -1,25 +1,27 @@ array( - 'behavior-diagram-extension.js' => 'adc38334', + 'behavior-diagram-extension.js' => 'a0b36dca', + 'diagram-extension.css' => '7ad39d5d', 'iframe-toolbtn.css' => '35ad6f49', 'iframe-toolbtn.js' => '26d75a35', 'remarkup-image.css' => '42b46bf1', 'remarkup-image.js' => '64e7e9e1', ), 'symbols' => array( + 'diagram-css-extension' => '7ad39d5d', 'diagram-css-iframe-toolbtn' => '35ad6f49', 'diagram-js-iframe-toolbtn' => '26d75a35', 'diagram-remarkup-image-css' => '42b46bf1', 'diagram-remarkup-image-js' => '64e7e9e1', - 'javelin-behavior-diagram-extension' => 'adc38334', + 'javelin-behavior-diagram-extension' => 'a0b36dca', ), 'requires' => array(), 'packages' => array(), ); diff --git a/src/controller/DiagramController.php b/src/controller/DiagramController.php index 41f9d79..3fbe80a 100644 --- a/src/controller/DiagramController.php +++ b/src/controller/DiagramController.php @@ -1,511 +1,470 @@ setRequest($request); // detetermine if GET or POST HTTP call if ($request->isHTTPPost()) { // process POST calls (like save action) return $this->handleHttpPostCall($request); } // determine type of URL by means of DiagramApplication route parameters $diagramid = $request->getURIData('diagramid'); $versioneddiagramid = $request->getURIData('versioneddiagramid'); $version = $request->getURIData('version'); $route = $request->getURIData('route'); $versioninfoDiagramID = $request->getURIData('versioninfodiagram'); $versioninfoPage = $request->getURIData('versioninfopage'); + if (isset($diagramphid) && !empty(trim($diagramphid))) { + // return PNG image data + $diagram = id(new DiagramVersion())->loadByDiagramPHID($diagramphid); + if ($diagram !== null) { + $response = new AphrontFileResponse(); + $response->setMimeType('image/png'); + $response->setContent($diagram->getData()); + return $response; + } + } + if (isset($versioninfoDiagramID) && !empty(trim($versioninfoDiagramID))) { // return diagram version info if (!isset($versioninfoPage) || empty(trim($versioninfoPage))) { // versioninfoPage was dismissed -> initialize to 1 - $versioninfoPage = "1"; + $versioninfoPage = '1'; } $diagramVersions = id(new DiagramVersion())->loadByDiagramID( $versioninfoDiagramID); if ($diagramVersions !== null) { $result = []; $viewer = $request->getViewer(); // determine total count of versions $totalcount = count($diagramVersions); // filter out some of the versions we want to show $pageSize = 10; - $diagramVersions = array_slice($diagramVersions, - ($versioninfoPage - 1) * $pageSize, - $pageSize - ); + $diagramVersions = array_slice( + $diagramVersions, + ($versioninfoPage - 1) * $pageSize, + $pageSize + ); // calculate number of pages $totalpages = ceil($totalcount / $pageSize); // create menu-items foreach ($diagramVersions as $diagramVersion) { $author = $diagramVersion->getAuthorPHID(); $user = id(new PhabricatorPeopleQuery()) ->setViewer($viewer) ->withPHIDs(array( $author )) ->executeOne(); $dateModified = $diagramVersion->getDateModified(); $result[] = array( - "id" => $diagramVersion->getVersion(), - "datetime" => phabricator_datetime($dateModified, $viewer), - "author" => $user->getUsername() + 'id' => $diagramVersion->getVersion(), + 'datetime' => phabricator_datetime($dateModified, $viewer), + 'author' => $user->getUsername() ); } // reply back $response = id(new AphrontJSONResponse())->setAddJSONShield(false) ->setContent(array( - "data" => $result, - "pagecount" => $totalpages, - "nopager" => $totalcount <= $pageSize + 'data' => $result, + 'pagecount' => $totalpages, + 'nopager' => $totalcount <= $pageSize )); return $response; } else { // version info requested for inexistant diagram $response = id(new AphrontJSONResponse())->setAddJSONShield(false) ->setContent(array( - "data" => array(), - "pagecount" => 0 + 'data' => array(), + 'pagecount' => 0 )); return $response; } } $root = ''; $file = rtrim($request->getPath(), '/'); $root = dirname(phutil_get_library_root('diagram')); // determine from which application area the file should be loaded: // 1) Phorge extension source // or 2) drawio source if ($route == 'iframe') { // load from drawio source if ($file == '/diagram/iframe') $file .= '/index.html'; if ($versioneddiagramid != null && $version != null) { $file = preg_replace( - "/^\/diagram\/$versioneddiagramid\/$version" ."iframe\//", - "drawio/src/main/webapp/", + '/^\/diagram\/' . $versioneddiagramid . '\/'. $version . 'iframe\//', + 'drawio/src/main/webapp/', $file); } else { $file = preg_replace("/^\/diagram\/($diagramid\/?)?iframe\//", - "drawio/src/main/webapp/", + 'drawio/src/main/webapp/', $file); } } else { // load from extension source if (rtrim($file, '/') == '/diagram') { - return $this->showApplication($request); + return $this->showApplication(); } if ($versioneddiagramid !== null && $version !== null) { $file = preg_replace( '/^\/diagram\/' . $versioneddiagramid . '\/'. $version . '\/?/', 'data/', $file ); $file = rtrim($file, '/') . '/' . $versioneddiagramid; } else { $file = preg_replace( '/^\/diagram\/(' . $diagramid . '\/)?/', 'data/', $file ); } } // determine full path $path = $root . '/' . $file; if (file_exists($path) == false || is_readable($path) == false) { if (preg_match('/^data\/DIAG(\d+)$/', $file, $matches)) { $diagram_id = (int) $matches[1]; if ($version === null) { $diagramVersion = id(new DiagramVersion())->loadLatestByDiagramID( $diagram_id); } else { $diagramVersion = id(new DiagramVersion())->loadByVersionedDiagramID( $diagram_id, $version); } if ($diagramVersion) { $data = $diagramVersion->getData(); $base64_data = base64_encode($data); $diagram = id(new Diagram())->loadByID($diagram_id); return $this->showApplication( - $request, 'DIAG' . $diagram_id, $diagram->getPHID(), - $version ?? "", + $version ?? '', $base64_data ); } } // Invalid URL $response = id(new Aphront404Response()); return $response; } else { // process Iframe content switch (pathinfo($file, PATHINFO_EXTENSION)) { case 'html': $response = id(new PlainHtmlWebpageResponse()) ->setDisableContentSecurityPolicy(true) ->setFrameable(true) ->setContent(file_get_contents($path)); break; case 'js': $response = new AphrontFileResponse(); $response->setMimeType('application/javascript'); break; case 'css': $response = new AphrontFileResponse(); $response->setMimeType('text/css'); break; case 'txt': $response = new AphrontFileResponse(); $response->setMimeType('text/plain'); break; case 'png': $response = new AphrontFileResponse(); $response->setMimeType('image/png'); break; case 'gif': $response = new AphrontFileResponse(); $response->setMimeType('image/gif'); break; case 'jpg': case 'jpeg': $response = new AphrontFileResponse(); $response->setMimeType('image/jpeg'); break; default: $response = new AphrontFileResponse(); $response->setMimeType('application/octet-stream'); break; } try { $response->setContent(file_get_contents($path)); $response->setCacheDurationInSeconds(60 * 60 * 24 * 30); $response->setLastModified(time()); $response->setCanCDN(true); } catch (Exception $e) { $response->setContent($route); } return $response; } } /** * Compares the draw.io tEXt metadata from 2 PNG base64 strings. * The content looks like this: * * * * * ... * * * * * * * The modified and etag attributes of mxfile will always be different. * They are cut out before the 2 strings are compared. */ public static function equalPngMetaData($base64_1, $base64_2) { $base64 = array($base64_1, $base64_2); $textData = array(); for ($i = 0; $i < 2; $i++) { $data = base64_decode($base64[$i]); $fp = fopen('data://text/plain;base64,' . base64_encode($data), 'rb'); $sig = fread($fp, 8); if ($sig != "\x89PNG\x0d\x0a\x1a\x0a") { fclose($fp); return false; } $textData[$i] = array(); while (!feof($fp)) { try { $chunk = unpack('Nlength/a4type', fread($fp, 8)); } catch (Exception $e) { // invalid base64 data return false; } if ($chunk['type'] == 'IEND') break; if ($chunk['type'] == 'tEXt') { list($key, $val) = explode("\0", fread($fp, $chunk['length'])); if ($key == 'mxfile') { // Decode the URL-encoded XML data $decodedVal = urldecode($val); // Load the XML and remove the modified and etag attributes $xml = simplexml_load_string($decodedVal); unset($xml->attributes()->modified); unset($xml->attributes()->etag); // Save the modified XML as the value $val = $xml->asXML(); } $textData[$i][$key] = $val; fseek($fp, 4, SEEK_CUR); } else { fseek($fp, $chunk['length'] + 4, SEEK_CUR); } } fclose($fp); } if (isset($textData[0]['mxfile']) && isset($textData[1]['mxfile'])) { // Both arrays contain the mxfile key, compare their values return $textData[0]['mxfile'] == $textData[1]['mxfile']; } else { // At least one of the arrays doesn't contain mxfile key, return false return false; } } /** * Processes HTTP POST calls from Diagram application, like 'Save' action */ private function handleHttpPostCall(AphrontRequest $request) { $subscriptionphid = $request->getURIData('subscriptionphid'); if (isset($subscriptionphid) && !empty(trim($subscriptionphid))) { // get list of subscriber for specified diagram phid $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( $subscriptionphid); // verify if viewer is subscriber $viewer = $request->getViewer(); if ($viewer == null) { $isSubscribed = false; } else { $isSubscribed = in_array($viewer->getPHID(),$subscribers); } // reply back $response = id(new AphrontJSONResponse())->setAddJSONShield(false) ->setContent(array( - "subscribed" => $isSubscribed + 'subscribed' => $isSubscribed )); return $response; } - $base64_data = $request->getStr("data"); - $diagram_id = $request->getStr("diagramID"); + $base64_data = $request->getStr('data'); + $diagram_id = $request->getStr('diagramID'); // cut off "data:image/png;base64," $base64_data = substr($base64_data, strpos($base64_data, ',') + 1); - if ($diagram_id != "") { + if ($diagram_id != '') { // check if we are trying to save the same data as the current data $diagram = id(new DiagramVersion())->loadLatestByDiagramID($diagram_id); if ($diagram !== null) { $data = $diagram->getData(); $old_data = base64_encode($data); if (DiagramController::equalPngMetaData($base64_data, $old_data)) { // data hasn't been modified // => do not create new version $response = id(new AphrontJSONResponse())->setAddJSONShield(false) ->setContent(array( - "Status" => "OK", - "DiagramID" => $diagram->getDiagramID(), - "Version" => $diagram->getVersion() + 'Status' => 'OK', + 'DiagramID' => $diagram->getDiagramID(), + 'Version' => $diagram->getVersion() )); return $response; } } } // Set the options for the new file $options = array( 'name' => 'diagram.png', 'viewPolicy' => PhabricatorPolicies::POLICY_USER, 'mime-type' => 'image/png', 'actor' => $this->getViewer(), 'diagramID' => $diagram_id ); try { // Create the new file object $diagram = DiagramVersion::newFromFileData($base64_data, $options); $diagram->publishNewVersion($request, $diagram->getDiagramID()); $response = id(new AphrontJSONResponse())->setAddJSONShield(false) ->setContent(array( - "Status" => "OK", - "DiagramID" => $diagram->getDiagramID(), - "Version" => $diagram->getVersion() + 'Status' => 'OK', + 'DiagramID' => $diagram->getDiagramID(), + 'Version' => $diagram->getVersion() )); return $response; } catch (Exception $e) { $response = id(new AphrontJSONResponse())->setAddJSONShield(false) ->setContent(array( - "Status" => "ERROR", - "Error" => $e->getMessage(), + 'Status' => 'ERROR', + 'Error' => $e->getMessage(), )); return $response; } } /** * Verifies if the given base64 data is draw.io compatible */ public static function isDrawioPngBase64($base64) { $data = base64_decode($base64); $fp = fopen('data://text/plain;base64,' . base64_encode($data), 'rb'); $sig = fread($fp, 8); if ($sig != "\x89PNG\x0d\x0a\x1a\x0a") { fclose($fp); return false; } while (!feof($fp)) { try { $chunk = unpack('Nlength/a4type', fread($fp, 8)); } catch (Exception $e) { // invalid base64 data return false; } if ($chunk['type'] == 'IEND') break; if ($chunk['type'] == 'tEXt') { list($key, $val) = explode("\0", fread($fp, $chunk['length'])); if ($key == 'mxfile') { fclose($fp); return true; } fseek($fp, 4, SEEK_CUR); } else { fseek($fp, $chunk['length'] + 4, SEEK_CUR); } } fclose($fp); return false; } /** * Shows the draw.io application integrated in Phorge's layout */ private function showApplication( - AphrontRequest $request, string $diagramName = '', string $diagramPHID = '', string $diagramVersion = '', string $base64_data = '' ) { - $applicationUrl = "/" . explode("/", $request->getPath())[1]; - $behaviorConfig = array(); $behaviorConfig['initParams'] = array( $diagramName, $diagramPHID, $diagramVersion, $base64_data ); - $behaviorConfig['toolbarCss'] = celerity_get_resource_uri('/iframe-toolbtn.css', 'diagram-resources'); - $behaviorConfig['toolbarJs'] = celerity_get_resource_uri('/iframe-toolbtn.js', 'diagram-resources'); + $behaviorConfig['toolbarCss'] = celerity_get_resource_uri( + '/iframe-toolbtn.css', + 'diagram-resources' + ); + $behaviorConfig['toolbarJs'] = celerity_get_resource_uri( + '/iframe-toolbtn.js', + 'diagram-resources' + ); require_celerity_resource('javelin-behavior'); + require_celerity_resource('diagram-css-extension', 'diagram-resources'); Javelin::initBehavior('diagram-extension', $behaviorConfig, 'diagram-resources'); - $content = phutil_tag( - 'div', - array(), - array( - phutil_tag( - 'div', - array( - 'id' => 'mainScreen', - )), - phutil_tag( - 'div', - array( - 'class' => 'crumbs', - 'style' => 'top:48px;' - . 'margin-left: 4px;' - . 'position: fixed;' - . 'font-weight: bold;' - ), - array( - phutil_tag( - 'a', - array( - 'href' => $applicationUrl - ), - array( - phutil_tag( - 'span', - array( - 'class' => 'phui-font-fa fa-sitemap', - 'style' => 'padding-right:5px;' - )) - )), - phutil_tag( - 'a', - array( - 'href' => $applicationUrl - ), - 'Diagram' - ), - phutil_tag( - 'span', - array( - 'class' => 'diagramName', - 'style' => 'display:none' - ), - array( - phutil_tag( - 'span', - array( - 'style' => 'margin: 5px;' - . 'opacity: .5;' - ), - '>' - ), - phutil_tag( - 'a', - array(), - '' - ), - phutil_tag( - 'span', - array( - 'class' => 'version', - 'style' => 'margin-left: 8px;' - . 'color: #999;'), - ''), - )) - )) - )); - - $view = id(new PhabricatorStandardPageView()) - ->setRequest($request) - ->setController($this) - ->setDeviceReady(true) - ->setTitle("Diagrams") - ->appendChild($content); + $crumbs = $this->buildApplicationCrumbs()->setBorder(true); + if (!$diagramName) { + $crumbs->addTextCrumb('Create Diagram'); + } else { + $crumbs->addTextCrumb( + $diagramName, + $this->getApplicationURI('/' . $diagramName) + ); - $response = id(new AphrontWebpageResponse()) - ->setContent($view->render()); + if ($diagramVersion) { + $crumbs->addTextCrumb('Version ' . $diagramVersion); + } + } - return $response; + return $this->newPage() + ->addClass('diagram-editor-page') + ->setTitle('Diagrams') + ->setCrumbs($crumbs) + ->appendChild(phutil_tag( + 'div', + array( + 'id' => 'diagram-editor-wrapper' + ) + )); } }