diff --git a/Assets/File.php b/Assets/File.php
index 6a5a6d2e5..e8325eb1a 100644
--- a/Assets/File.php
+++ b/Assets/File.php
@@ -15,7 +15,8 @@ use SilverStripe\Forms\DatetimeField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\HeaderField;
use SilverStripe\Forms\HiddenField;
-use SilverStripe\Forms\HTMLReadonlyField;
+use SilverStripe\Forms\Tab;
+use SilverStripe\Forms\TabSet;
use SilverStripe\Forms\LiteralField;
use SilverStripe\Forms\ReadonlyField;
use SilverStripe\Forms\TextField;
@@ -472,36 +473,36 @@ class File extends DataObject implements ShortcodeHandler, AssetContainer, Thumb
public function getCMSFields() {
$path = '/' . dirname($this->getFilename());
- $fields = FieldList::create([
- HeaderField::create('TitleHeader', $this->Title, 1),
- LiteralField::create("ImageFull", $this->PreviewThumbnail()),
- TextField::create("Name", $this->fieldLabel('Filename')),
- ReadonlyField::create(
- "Path",
- _t('AssetTableField.PATH', 'Path'),
- (($path !== '/.') ? $path : '') . '/'
- )
- ]);
+ $width = (int)Image::config()->get('asset_preview_width');
+ $previewLink = Convert::raw2att($this->ScaleMaxWidth($width)->getIcon());
+ $image = "";
- if ($this->getIsImage()) {
- $fields->push(ReadonlyField::create(
- "DisplaySize",
- _t('AssetTableField.SIZE', "File size"),
- sprintf('%spx, %s', $this->getDimensions(), $this->getSize())
- ));
- $fields->push(HTMLReadonlyField::create(
- 'ClickableURL',
- _t('AssetTableField.URL','URL'),
- sprintf('%s', $this->Link(), $this->Link())
- ));
- }
- $fields->push(HiddenField::create('ID', $this->ID));
+ $content = Tab::create('Main',
+ HeaderField::create('TitleHeader', $this->Title, 1)
+ ->addExtraClass('editor__heading'),
+ LiteralField::create("IconFull", $image)
+ ->addExtraClass('editor__file-preview'),
+ TabSet::create('Editor',
+ Tab::create('Details',
+ TextField::create("Title", $this->fieldLabel('Title')),
+ TextField::create("Name", $this->fieldLabel('Filename')),
+ ReadonlyField::create(
+ "Path",
+ _t('AssetTableField.PATH', 'Path'),
+ (($path !== '/.') ? $path : '') . '/'
+ )
+ ),
+ Tab::create('Usage',
+ DatetimeField::create(
+ "LastEdited",
+ _t('AssetTableField.LASTEDIT', 'Last changed')
+ )->setReadonly(true)
+ )
+ ),
+ HiddenField::create('ID', $this->ID)
+ );
- $fields->insertBefore('Name', TextField::create("Title", $this->fieldLabel('Title')));
- $fields->push(DatetimeField::create(
- "LastEdited",
- _t('AssetTableField.LASTEDIT', 'Last changed')
- )->setReadonly(true));
+ $fields = FieldList::create(TabSet::create('Root', $content));
$this->extend('updateCMSFields', $fields);
diff --git a/Assets/Folder.php b/Assets/Folder.php
index c5a61d6c1..bcbd460d8 100644
--- a/Assets/Folder.php
+++ b/Assets/Folder.php
@@ -11,6 +11,8 @@ use SilverStripe\Forms\TextField;
use SilverStripe\ORM\DataList;
use SilverStripe\ORM\ValidationResult;
use SilverStripe\ORM\Versioning\Versioned;
+use SilverStripe\Forms\Tab;
+use SilverStripe\Forms\TabSet;
/**
* Represents a logical folder, which may be used to organise assets
@@ -194,12 +196,24 @@ class Folder extends File {
// Don't show readonly path until we can implement parent folder selection,
// it's too confusing when readonly (makes sense for files only).
- $fields = FieldList::create([
- HeaderField::create('TitleHeader', $this->Title, 1),
- LiteralField::create("ImageFull", $this->PreviewThumbnail()),
- TextField::create("Name", $this->fieldLabel('Filename')),
+ $width = (int)Image::config()->get('asset_preview_width');
+ $previewLink = Convert::raw2att($this->ScaleMaxWidth($width)->getIcon());
+ $image = "";
+
+ $content = Tab::create('Main',
+ HeaderField::create('TitleHeader', $this->Title, 1)
+ ->addExtraClass('editor__heading'),
+ LiteralField::create("IconFull", $image)
+ ->addExtraClass('editor__file-preview'),
+ TabSet::create('Editor',
+ Tab::create('Details',
+ TextField::create("Name", $this->fieldLabel('Filename'))
+ )
+ ),
HiddenField::create('ID', $this->ID)
- ]);
+ );
+
+ $fields = FieldList::create(TabSet::create('Root', $content));
$this->extend('updateCMSFields', $fields);
diff --git a/Assets/Image.php b/Assets/Image.php
index 309ea8506..f6a64bfec 100644
--- a/Assets/Image.php
+++ b/Assets/Image.php
@@ -3,9 +3,18 @@
namespace SilverStripe\Assets;
use SilverStripe\Core\Convert;
-use SilverStripe\Forms\ReadonlyField;
+use SilverStripe\Forms\HTMLReadonlyField;
+use SilverStripe\Forms\LiteralField;
use SilverStripe\View\Parsers\ShortcodeParser;
use SilverStripe\View\Parsers\ShortcodeHandler;
+use SilverStripe\Forms\Tab;
+use SilverStripe\Forms\HeaderField;
+use SilverStripe\Forms\TabSet;
+use SilverStripe\Forms\TextField;
+use SilverStripe\Forms\DatetimeField;
+use SilverStripe\Forms\ReadonlyField;
+use SilverStripe\Forms\HiddenField;
+use SilverStripe\Forms\FieldList;
/**
* Represents an Image
@@ -36,11 +45,64 @@ class Image extends File implements ShortcodeHandler {
}
public function getCMSFields() {
- $fields = parent::getCMSFields();
- $fields->insertAfter(
- 'LastEdited',
- new ReadonlyField("Dimensions", _t('AssetTableField.DIM','Dimensions') . ':')
+ $path = '/' . dirname($this->getFilename());
+
+ $width = (int)Image::config()->get('asset_preview_width');
+ $height = (int)Image::config()->get('asset_preview_height');
+ $previewLink = Convert::raw2att($this
+ ->FitMax($width, $height)
+ ->PreviewLink()
);
+ $image = "";
+
+ $link = $this->Link();
+
+ $content = Tab::create('Main',
+ HeaderField::create('TitleHeader', $this->Title, 1)
+ ->addExtraClass('editor__heading'),
+ LiteralField::create("ImageFull", $image)
+ ->addExtraClass('editor__file-preview'),
+ TabSet::create('Editor',
+ Tab::create('Details',
+ TextField::create("Title", $this->fieldLabel('Title')),
+ TextField::create("Name", $this->fieldLabel('Filename')),
+ ReadonlyField::create(
+ "Path",
+ _t('AssetTableField.PATH', 'Path'),
+ (($path !== '/.') ? $path : '') . '/'
+ ),
+ HTMLReadonlyField::create(
+ 'ClickableURL',
+ _t('AssetTableField.URL','URL'),
+ sprintf('%s',
+ 'icon font-icon-link editor__url-icon', $link, $link)
+ )
+ ),
+ Tab::create('Usage',
+ DatetimeField::create(
+ "LastEdited",
+ _t('AssetTableField.LASTEDIT', 'Last changed')
+ )->setReadonly(true)
+ )
+ ),
+ HiddenField::create('ID', $this->ID)
+ );
+
+ if ($dimensions = $this->getDimensions()) {
+ $content->insertAfter(
+ 'TitleHeader',
+ LiteralField::create(
+ "DisplaySize",
+ sprintf('
%spx, %s
',
+ $dimensions, $this->getSize())
+ )
+ );
+ }
+
+ $fields = FieldList::create(TabSet::create('Root', $content));
+
+ $this->extend('updateCMSFields', $fields);
+
return $fields;
}
diff --git a/Assets/ImageManipulation.php b/Assets/ImageManipulation.php
index 83a30ed63..050030079 100644
--- a/Assets/ImageManipulation.php
+++ b/Assets/ImageManipulation.php
@@ -156,13 +156,19 @@ trait ImageManipulation {
/**
* The width of an image preview in the Asset section
*
- * This thumbnail is only sized to width.
- *
* @config
* @var int
*/
private static $asset_preview_width = 400;
+ /**
+ * The height of an image preview in the Asset section
+ *
+ * @config
+ * @var int
+ */
+ private static $asset_preview_height = 336;
+
/**
* Fit image to specified dimensions and fill leftover space with a solid colour (default white). Use in templates with $Pad.
*
diff --git a/Assets/Storage/DBFile.php b/Assets/Storage/DBFile.php
index aaccd8180..aaed8c935 100644
--- a/Assets/Storage/DBFile.php
+++ b/Assets/Storage/DBFile.php
@@ -12,6 +12,7 @@ use SilverStripe\ORM\ValidationResult;
use SilverStripe\ORM\ValidationException;
use SilverStripe\ORM\FieldType\DBComposite;
use SilverStripe\Security\Permission;
+use SilverStripe\Core\Convert;
/**
* Represents a file reference stored in a database
@@ -528,4 +529,27 @@ class DBFile extends DBComposite implements AssetContainer, Thumbnail {
->getStore()
->canView($this->Filename, $this->Hash);
}
+
+ /**
+ * Generates the URL for this DBFile preview, this is particularly important for images that
+ * have been manipulated e.g. by {@link ImageManipulation}
+ * Use the 'updatePreviewLink' extension point to customise the link.
+ *
+ * @param null $action
+ * @return bool|string
+ */
+ public function PreviewLink($action = null) {
+ // Since AbsoluteURL can whitelist protected assets,
+ // do permission check first
+ if (!$this->failover->canView()) {
+ return false;
+ }
+ if ($this->getIsImage()) {
+ $link = $this->getAbsoluteURL();
+ } else {
+ $link = Convert::raw2att($this->failover->getIcon());
+ }
+ $this->extend('updatePreviewLink', $link, $action);
+ return $link;
+ }
}
diff --git a/Forms/CompositeField.php b/Forms/CompositeField.php
index 99ea7b631..47170e0ef 100644
--- a/Forms/CompositeField.php
+++ b/Forms/CompositeField.php
@@ -76,6 +76,10 @@ class CompositeField extends FormField {
}
$defaults['children'] = $childSchema;
}
+
+ $defaults['data']['tag'] = $this->getTag();
+ $defaults['data']['legend'] = $this->getLegend();
+
return $defaults;
}
@@ -97,6 +101,35 @@ class CompositeField extends FormField {
return $this->children;
}
+ /**
+ * Returns the name (ID) for the element.
+ * If the CompositeField doesn't have a name, but we still want the ID/name to be set.
+ * This code generates the ID from the nested children.
+ *
+ * @todo this is temporary, and should be removed when FormTemplateHelper is updated to handle ID for CompositeFields with no name
+ *
+ * @return String $name
+ */
+ public function getName(){
+ if($this->name) {
+ return $this->name;
+ }
+
+ $fieldList = $this->FieldList();
+ $compositeTitle = '';
+ $count = 0;
+ /** @var FormField $subfield */
+ foreach($fieldList as $subfield){
+ $compositeTitle .= $subfield->getName();
+ if($subfield->getName()) $count++;
+ }
+ /** @skipUpgrade */
+ if($count === 1) {
+ $compositeTitle .= 'Group';
+ }
+ return preg_replace("/[^a-zA-Z0-9]+/", "", $compositeTitle);
+ }
+
/**
* @param FieldList $children
* @return $this
diff --git a/Forms/FieldGroup.php b/Forms/FieldGroup.php
index d2b98b48f..02ada59d1 100644
--- a/Forms/FieldGroup.php
+++ b/Forms/FieldGroup.php
@@ -103,6 +103,9 @@ class FieldGroup extends CompositeField {
* Returns the name (ID) for the element.
* In some cases the FieldGroup doesn't have a title, but we still want
* the ID / name to be set. This code, generates the ID from the nested children
+ *
+ * TODO this is temporary, and should be removed when FormTemplateHelper is updated to handle ID
+ * for CompositeFields with no name
*/
public function getName(){
if($this->name) {
@@ -110,17 +113,7 @@ class FieldGroup extends CompositeField {
}
if(!$this->title) {
- $fs = $this->FieldList();
- $compositeTitle = '';
- $count = 0;
- foreach($fs as $subfield){
- /** @var FormField $subfield */
- $compositeTitle .= $subfield->getName();
- if($subfield->getName()) $count++;
- }
- /** @skipUpgrade */
- if($count == 1) $compositeTitle .= 'Group';
- return preg_replace("/[^a-zA-Z0-9]+/", "", $compositeTitle);
+ return parent::getName();
}
return preg_replace("/[^a-zA-Z0-9]+/", "", $this->title);
diff --git a/Forms/GroupedDropdownField.php b/Forms/GroupedDropdownField.php
index 3ecd710dd..7634fc850 100644
--- a/Forms/GroupedDropdownField.php
+++ b/Forms/GroupedDropdownField.php
@@ -53,6 +53,9 @@ use SilverStripe\View\ArrayData;
*/
class GroupedDropdownField extends DropdownField {
+ // TODO remove this when GroupedDropdownField is implemented
+ protected $schemaDataType = 'GroupedDropdownField';
+
/**
* Build a potentially nested fieldgroup
*
diff --git a/Forms/MoneyField.php b/Forms/MoneyField.php
index 8c8c52521..13d4734df 100644
--- a/Forms/MoneyField.php
+++ b/Forms/MoneyField.php
@@ -16,7 +16,8 @@ use SilverStripe\ORM\DataObjectInterface;
*/
class MoneyField extends FormField {
- protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_TEXT;
+ // TODO replace with `FormField::SCHEMA_DATA_TYPE_TEXT` when MoneyField is implemented
+ protected $schemaDataType = 'MoneyField';
/**
* @var string $_locale
diff --git a/Forms/Schema/FormSchema.php b/Forms/Schema/FormSchema.php
index dd50b8716..17bd3b8c7 100644
--- a/Forms/Schema/FormSchema.php
+++ b/Forms/Schema/FormSchema.php
@@ -35,13 +35,13 @@ class FormSchema {
'actions' => []
];
+ /** @var FormField $action */
foreach ($form->Actions() as $action) {
- /** @var FormField $action */
$schema['actions'][] = $action->getSchemaData();
}
+ /** @var FormField $field */
foreach ($form->Fields() as $field) {
- /** @var FormField $field */
$schema['fields'][] = $field->getSchemaData();
}
@@ -81,7 +81,7 @@ class FormSchema {
if ($field instanceof CompositeField) {
$subFields = $field->FieldList();
- array_merge($states, $this->getFieldStates($subFields));
+ $states = array_merge($states, $this->getFieldStates($subFields));
}
}
return $states;
diff --git a/Forms/Tab.php b/Forms/Tab.php
index 3ae27b7a7..e39bcd4b1 100644
--- a/Forms/Tab.php
+++ b/Forms/Tab.php
@@ -21,6 +21,13 @@ use InvalidArgumentException;
*/
class Tab extends CompositeField {
+ /**
+ * Use custom react component
+ *
+ * @var string
+ */
+ protected $schemaComponent = 'TabItem';
+
/**
* @var TabSet
*/
@@ -115,7 +122,9 @@ class Tab extends CompositeField {
}
public function extraClass() {
- return implode(' ', (array)$this->extraClasses);
+ $classes = (array)$this->extraClasses;
+
+ return implode(' ', $classes);
}
public function getAttributes() {
diff --git a/Forms/TabSet.php b/Forms/TabSet.php
index d340201ae..5e9da7190 100644
--- a/Forms/TabSet.php
+++ b/Forms/TabSet.php
@@ -32,6 +32,13 @@ use InvalidArgumentException;
*/
class TabSet extends CompositeField {
+ /**
+ * Use custom react component
+ *
+ * @var string
+ */
+ protected $schemaComponent = 'Tabs';
+
/**
* @var TabSet
*/
diff --git a/ORM/Versioning/ChangeSet.php b/ORM/Versioning/ChangeSet.php
index 922af2f02..d57bac358 100644
--- a/ORM/Versioning/ChangeSet.php
+++ b/ORM/Versioning/ChangeSet.php
@@ -369,11 +369,13 @@ class ChangeSet extends DataObject {
}
public function getCMSFields() {
- $fields = new FieldList();
- $fields->push(TextField::create('Name', $this->fieldLabel('Name')));
- if($this->isInDB()) {
- $fields->push(ReadonlyField::create('State', $this->fieldLabel('State')));
- }
+ $fields = parent::getCMSFields();
+
+ $fields->removeByName('OwnerID');
+ $fields->removeByName('Changes');
+
+ $fields->dataFieldByName('State')->setReadonly(true);
+
$this->extend('updateCMSFields', $fields);
return $fields;
}
diff --git a/admin/client/src/boot/index.js b/admin/client/src/boot/index.js
index 980467641..309165a91 100644
--- a/admin/client/src/boot/index.js
+++ b/admin/client/src/boot/index.js
@@ -20,6 +20,9 @@ import PopoverField from 'components/PopoverField/PopoverField';
import HeaderField from 'components/HeaderField/HeaderField';
import LiteralField from 'components/LiteralField/LiteralField';
import HtmlReadonlyField from 'components/HtmlReadonlyField/HtmlReadonlyField';
+import CompositeField from 'components/CompositeField/CompositeField';
+import Tabs from 'components/Tabs/Tabs';
+import TabItem from 'components/Tabs/TabItem';
import { routerReducer } from 'react-router-redux';
// Sections
@@ -43,6 +46,9 @@ function appBoot() {
injector.register('HeaderField', HeaderField);
injector.register('LiteralField', LiteralField);
injector.register('HtmlReadonlyField', HtmlReadonlyField);
+ injector.register('CompositeField', CompositeField);
+ injector.register('Tabs', Tabs);
+ injector.register('TabItem', TabItem);
injector.register('FormAction', FormAction);
const initialState = {};
diff --git a/admin/client/src/components/CompositeField/CompositeField.js b/admin/client/src/components/CompositeField/CompositeField.js
new file mode 100644
index 000000000..da1449d2c
--- /dev/null
+++ b/admin/client/src/components/CompositeField/CompositeField.js
@@ -0,0 +1,36 @@
+import React from 'react';
+import SilverStripeComponent from 'lib/SilverStripeComponent';
+
+class CompositeField extends SilverStripeComponent {
+ getLegend() {
+ return (
+ this.props.data.tag === 'fieldset' &&
+ this.props.data.legend &&
+
+ );
+ }
+
+ render() {
+ const legend = this.getLegend();
+ const Tag = this.props.data.tag;
+
+ return (
+
+ {legend}
+ {this.props.children}
+
+ );
+ }
+}
+
+CompositeField.propTypes = {
+ tag: React.PropTypes.string,
+ legend: React.PropTypes.string,
+ extraClass: React.PropTypes.string,
+};
+
+CompositeField.defaultProps = {
+ tag: 'div',
+};
+
+export default CompositeField;
diff --git a/admin/client/src/components/CompositeField/README.md b/admin/client/src/components/CompositeField/README.md
new file mode 100644
index 000000000..7e96dbe8a
--- /dev/null
+++ b/admin/client/src/components/CompositeField/README.md
@@ -0,0 +1,18 @@
+# CompositeField
+
+For containing groups of fields in a container element.
+
+## Example
+
+```
+
+
+
+
+```
+
+## Properties
+
+ * `tag` (string): The element type the composite field should use in HTML.
+ * `legend` (boolean): A label/legend for the group of fields contained.
+ * `extraClass` (string): Extra classes the CompositeField should have.
diff --git a/admin/client/src/components/FieldHolder/FieldHolder.js b/admin/client/src/components/FieldHolder/FieldHolder.js
index 68e0fb867..579370954 100644
--- a/admin/client/src/components/FieldHolder/FieldHolder.js
+++ b/admin/client/src/components/FieldHolder/FieldHolder.js
@@ -22,7 +22,7 @@ function fieldHolder(Field) {
return (
{labelText &&
-
diff --git a/tests/forms/FormSchemaTest.php b/tests/forms/FormSchemaTest.php
index 9b400e667..8dc2a87cb 100644
--- a/tests/forms/FormSchemaTest.php
+++ b/tests/forms/FormSchemaTest.php
@@ -286,6 +286,8 @@ class FormSchemaTest extends SapphireTest {
'data' => [
'popoverTitle' => null,
'placement' => 'bottom',
+ 'tag' => 'div',
+ 'legend' => null,
],
'children' => [
[