Move "Macros" to a first-class application

Summary:
This is mostly to unblock D3547.

  - Move "Macros" to a first-class application called "Macros".
  - After D3547, this application will also house "Memes" (macros with text on them).
  - This will also make them easier to find; the top navigational query I field is "where are image macros?" nowadays, since it's not intuitive they're part of files.
  - This makes some of the UI mobile-aware but doesn't set the `device` flag yet, since there are still some missing pieces.
  - I'll separate storage out and continue modernizing the UI as we unblock and integrate D3547.

Test Plan: Created, edited and deleted macros. Viewed files.

Reviewers: btrahan, vrana, teisenbe

Reviewed By: vrana

CC: aran

Maniphest Tasks: T175

Differential Revision: https://secure.phabricator.com/D3572
This commit is contained in:
epriestley
2012-10-01 14:04:03 -07:00
parent a6b6a9766b
commit 92ff9c092b
13 changed files with 155 additions and 73 deletions

View File

@@ -0,0 +1,47 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorApplicationMacro extends PhabricatorApplication {
public function getBaseURI() {
return '/macro/';
}
public function getShortDescription() {
return 'Image Macros and Memes';
}
public function getAutospriteName() {
return 'macro';
}
public function getTitleGlyph() {
return "\xE2\x9A\x98";
}
public function getRoutes() {
return array(
'/macro/' => array(
'' => 'PhabricatorMacroListController',
'edit/(?:(?P<id>\d+)/)?' => 'PhabricatorMacroEditController',
'delete/(?P<id>\d+)/' => 'PhabricatorMacroDeleteController',
),
);
}
}

View File

@@ -0,0 +1,37 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
abstract class PhabricatorMacroController
extends PhabricatorController {
protected function buildSideNavView(PhabricatorFileImageMacro $macro = null) {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
$nav->addLabel('Create');
$nav->addFilter('edit', 'Create Macro');
$nav->addSpacer();
$nav->addLabel('Macros');
$nav->addFilter('', 'All Macros');
return $nav;
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorMacroDeleteController
extends PhabricatorMacroController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$macro = id(new PhabricatorFileImageMacro())->load($this->id);
if (!$macro) {
return new Aphront404Response();
}
$request = $this->getRequest();
if ($request->isDialogFormPost()) {
$macro->delete();
return id(new AphrontRedirectResponse())->setURI(
$this->getApplicationURI());
}
$dialog = new AphrontDialogView();
$dialog
->setUser($request->getUser())
->setTitle('Really delete macro?')
->appendChild(
'<p>Really delete the much-beloved image macro "'.
phutil_escape_html($macro->getName()).'"? It will be sorely missed.'.
'</p>')
->setSubmitURI($this->getApplicationURI('/delete/'.$this->id.'/'))
->addSubmitButton('Delete')
->addCancelButton($this->getApplicationURI());
return id(new AphrontDialogResponse())->setDialog($dialog);
}
}

View File

@@ -0,0 +1,132 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorMacroEditController
extends PhabricatorMacroController {
private $id;
public function willProcessRequest(array $data) {
$this->id = idx($data, 'id');
}
public function processRequest() {
if ($this->id) {
$macro = id(new PhabricatorFileImageMacro())->load($this->id);
if (!$macro) {
return new Aphront404Response();
}
} else {
$macro = new PhabricatorFileImageMacro();
}
$errors = array();
$e_name = true;
$request = $this->getRequest();
$user = $request->getUser();
if ($request->isFormPost()) {
$macro->setName($request->getStr('name'));
if (!strlen($macro->getName())) {
$errors[] = 'Macro name is required.';
$e_name = 'Required';
} else if (!preg_match('/^[a-z0-9_-]{3,}$/', $macro->getName())) {
$errors[] = 'Macro must be at least three characters long and contain '.
'only lowercase letters, digits, hyphen and underscore.';
$e_name = 'Invalid';
} else {
$e_name = null;
}
if (!$errors) {
$file = PhabricatorFile::newFromPHPUpload(
idx($_FILES, 'file'),
array(
'name' => $request->getStr('name'),
'authorPHID' => $user->getPHID(),
));
$macro->setFilePHID($file->getPHID());
try {
$macro->save();
return id(new AphrontRedirectResponse())->setURI(
$this->getApplicationURI());
} catch (AphrontQueryDuplicateKeyException $ex) {
$errors[] = 'Macro name is not unique!';
$e_name = 'Duplicate';
}
}
}
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle('Form Errors');
$error_view->setErrors($errors);
} else {
$error_view = null;
}
$form = new AphrontFormView();
$form->setAction($this->getApplicationURI('/edit/'));
$form->setUser($request->getUser());
$form
->setEncType('multipart/form-data')
->appendChild(
id(new AphrontFormTextControl())
->setLabel('Name')
->setName('name')
->setValue($macro->getName())
->setCaption('This word or phrase will be replaced with the image.')
->setError($e_name))
->appendChild(
id(new AphrontFormFileControl())
->setLabel('File')
->setName('file')
->setError(true))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Save Image Macro')
->addCancelButton($this->getApplicationURI()));
$panel = new AphrontPanelView();
if ($macro->getID()) {
$title = 'Edit Image Macro';
} else {
$title = 'Create Image Macro';
}
$panel->setHeader($title);
$panel->appendChild($form);
$panel->setWidth(AphrontPanelView::WIDTH_FULL);
$nav = $this->buildSideNavView($macro);
$nav->selectFilter('#', 'edit');
$nav->appendChild($error_view);
$nav->appendChild($panel);
return $this->buildApplicationPage(
$nav,
array(
'title' => $title,
));
}
}

View File

@@ -0,0 +1,154 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorMacroListController
extends PhabricatorMacroController {
public function processRequest() {
$request = $this->getRequest();
$macro_table = new PhabricatorFileImageMacro();
if ($request->getStr('name') !== null) {
$macros = $macro_table->loadAllWhere(
'name LIKE %~',
$request->getStr('name'));
} else {
$pager = new AphrontPagerView();
$pager->setOffset($request->getInt('page'));
$macros = $macro_table->loadAllWhere(
'1 = 1 ORDER BY id DESC LIMIT %d, %d',
$pager->getOffset(),
$pager->getPageSize());
// Get an exact count since the size here is reasonably going to be a few
// thousand at most in any reasonable case.
$count = queryfx_one(
$macro_table->establishConnection('r'),
'SELECT COUNT(*) N FROM %T',
$macro_table->getTableName());
$count = $count['N'];
$pager->setCount($count);
$pager->setURI($request->getRequestURI(), 'page');
}
$file_phids = mpull($macros, 'getFilePHID');
$files = array();
if ($file_phids) {
$files = id(new PhabricatorFile())->loadAllWhere(
"phid IN (%Ls)",
$file_phids);
$author_phids = mpull($files, 'getAuthorPHID', 'getPHID');
$handles = $this->loadViewerHandles($author_phids);
}
$files_map = mpull($files, null, 'getPHID');
$rows = array();
foreach ($macros as $macro) {
$file_phid = $macro->getFilePHID();
$file = idx($files_map, $file_phid);
$author_link = isset($author_phids[$file_phid])
? $handles[$author_phids[$file_phid]]->renderLink()
: null;
$rows[] = array(
phutil_render_tag(
'a',
array(
'href' => $this->getApplicationURI('/edit/'.$macro->getID().'/'),
),
phutil_escape_html($macro->getName())),
$author_link,
phutil_render_tag(
'a',
array(
'href' => $file ? $file->getBestURI() : null,
'target' => '_blank',
),
phutil_render_tag(
'img',
array(
'src' => $file ? $file->getBestURI() : null,
))),
javelin_render_tag(
'a',
array(
'href' => $this->getApplicationURI('/delete/'.$macro->getID().'/'),
'sigil' => 'workflow',
'class' => 'grey small button',
),
'Delete'),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Name',
'Author',
'Image',
'',
));
$table->setColumnClasses(
array(
'pri',
'',
'wide thumb',
'action',
));
$filter_form = id(new AphrontFormView())
->setMethod('GET')
->setUser($request->getUser())
->appendChild(
id(new AphrontFormTextControl())
->setName('name')
->setLabel('Name')
->setValue($request->getStr('name')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue('Filter Image Macros'));
$filter_view = new AphrontListFilterView();
$filter_view->appendChild($filter_form);
$panel = new AphrontPanelView();
$panel->appendChild($table);
$panel->setHeader('Image Macros');
if ($request->getStr('name') === null) {
$panel->appendChild($pager);
}
$nav = $this->buildSideNavView();
$nav->selectFilter('/');
$nav->appendChild($filter_view);
$nav->appendChild($panel);
return $this->buildApplicationPage(
$nav,
array(
'title' => 'Image Macros',
));
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorFileImageMacro extends PhabricatorFileDAO {
protected $filePHID;
protected $name;
public function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
) + parent::getConfiguration();
}
static public function newFromImageURI($uri, $file_name, $image_macro_name) {
$file = PhabricatorFile::newFromFileDownload($uri, $file_name);
if (!$file) {
return null;
}
$image_macro = new PhabricatorFileImageMacro();
$image_macro->setName($image_macro_name);
$image_macro->setFilePHID($file->getPHID());
$image_macro->save();
return $image_macro;
}
}