= e(trans('graker.photoalbums::lang.errors.return_to_albums')) ?>
+ + \ No newline at end of file diff --git a/server/plugins/graker/photoalbums/controllers/albums/index.htm b/server/plugins/graker/photoalbums/controllers/albums/index.htm new file mode 100644 index 0000000..766877d --- /dev/null +++ b/server/plugins/graker/photoalbums/controllers/albums/index.htm @@ -0,0 +1,2 @@ + += $this->listRender() ?> diff --git a/server/plugins/graker/photoalbums/controllers/albums/preview.htm b/server/plugins/graker/photoalbums/controllers/albums/preview.htm new file mode 100644 index 0000000..3e8a61f --- /dev/null +++ b/server/plugins/graker/photoalbums/controllers/albums/preview.htm @@ -0,0 +1,19 @@ + += e(trans('graker.photoalbums::lang.errors.return_to_albums')) ?>
+ + \ No newline at end of file diff --git a/server/plugins/graker/photoalbums/controllers/albums/update.htm b/server/plugins/graker/photoalbums/controllers/albums/update.htm new file mode 100644 index 0000000..48bb5e5 --- /dev/null +++ b/server/plugins/graker/photoalbums/controllers/albums/update.htm @@ -0,0 +1,60 @@ + += e(trans('backend::lang.errors.return_to_albums')) ?>
+ + \ No newline at end of file diff --git a/server/plugins/graker/photoalbums/controllers/photos/_list_toolbar.htm b/server/plugins/graker/photoalbums/controllers/photos/_list_toolbar.htm new file mode 100644 index 0000000..5c3952a --- /dev/null +++ b/server/plugins/graker/photoalbums/controllers/photos/_list_toolbar.htm @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/server/plugins/graker/photoalbums/controllers/photos/config_form.yaml b/server/plugins/graker/photoalbums/controllers/photos/config_form.yaml new file mode 100644 index 0000000..c0d4c5f --- /dev/null +++ b/server/plugins/graker/photoalbums/controllers/photos/config_form.yaml @@ -0,0 +1,31 @@ +# =================================== +# Form Behavior Config +# =================================== + +# Record name +name: graker.photoalbums::lang.plugin.photo + +# Model Form Field configuration +form: $/graker/photoalbums/models/photo/fields.yaml + +# Model Class name +modelClass: Graker\PhotoAlbums\Models\Photo + +# Default redirect location +defaultRedirect: graker/photoalbums/photos + +# Create page +create: + title: graker.photoalbums::lang.plugin.create_photo + redirect: graker/photoalbums/photos/update/:id + redirectClose: graker/photoalbums/photos + +# Update page +update: + title: graker.photoalbums::lang.plugin.edit_photo + redirect: graker/photoalbums/photos + redirectClose: graker/photoalbums/photos + +# Preview page +preview: + title: graker.photoalbums::lang.plugin.preview_photo \ No newline at end of file diff --git a/server/plugins/graker/photoalbums/controllers/photos/config_list.yaml b/server/plugins/graker/photoalbums/controllers/photos/config_list.yaml new file mode 100644 index 0000000..10dc71d --- /dev/null +++ b/server/plugins/graker/photoalbums/controllers/photos/config_list.yaml @@ -0,0 +1,36 @@ +# =================================== +# List Behavior Config +# =================================== + +# Model List Column configuration +list: $/graker/photoalbums/models/photo/columns.yaml + +# Model Class name +modelClass: Graker\PhotoAlbums\Models\Photo + +# List Title +title: graker.photoalbums::lang.plugin.manage_photos + +# Link URL for each record +recordUrl: graker/photoalbums/photos/update/:id + +# Message to display if the list is empty +noRecordsMessage: backend::lang.list.no_records + +# Records to display per page +recordsPerPage: 20 + +# Displays the list column set up button +showSetup: true + +# Displays the sorting link on each column +showSorting: true + +# Toolbar widget configuration +toolbar: + # Partial for toolbar buttons + buttons: list_toolbar + + # Search widget configuration + search: + prompt: backend::lang.list.search_prompt diff --git a/server/plugins/graker/photoalbums/controllers/photos/create.htm b/server/plugins/graker/photoalbums/controllers/photos/create.htm new file mode 100644 index 0000000..2552f54 --- /dev/null +++ b/server/plugins/graker/photoalbums/controllers/photos/create.htm @@ -0,0 +1,48 @@ + += e(trans('backend::lang.errors.return_to_photos')) ?>
+ + \ No newline at end of file diff --git a/server/plugins/graker/photoalbums/controllers/photos/index.htm b/server/plugins/graker/photoalbums/controllers/photos/index.htm new file mode 100644 index 0000000..766877d --- /dev/null +++ b/server/plugins/graker/photoalbums/controllers/photos/index.htm @@ -0,0 +1,2 @@ + += $this->listRender() ?> diff --git a/server/plugins/graker/photoalbums/controllers/photos/preview.htm b/server/plugins/graker/photoalbums/controllers/photos/preview.htm new file mode 100644 index 0000000..ef07d97 --- /dev/null +++ b/server/plugins/graker/photoalbums/controllers/photos/preview.htm @@ -0,0 +1,19 @@ + += e(trans('backend::lang.errors.return_to_photos')) ?>
+ + \ No newline at end of file diff --git a/server/plugins/graker/photoalbums/controllers/photos/update.htm b/server/plugins/graker/photoalbums/controllers/photos/update.htm new file mode 100644 index 0000000..2a77e41 --- /dev/null +++ b/server/plugins/graker/photoalbums/controllers/photos/update.htm @@ -0,0 +1,56 @@ + += e(trans('graker.photoalbums::lang.errors.return_to_photos')) ?>
+ + \ No newline at end of file diff --git a/server/plugins/graker/photoalbums/controllers/reorder/_records.htm b/server/plugins/graker/photoalbums/controllers/reorder/_records.htm new file mode 100644 index 0000000..02e57ed --- /dev/null +++ b/server/plugins/graker/photoalbums/controllers/reorder/_records.htm @@ -0,0 +1,11 @@ + + += Lang::get('backend::lang.reorder.no_records') ?>
+ += e(trans('graker.photoalbums::lang.errors.return_to_albums')) ?>
+ \ No newline at end of file diff --git a/server/plugins/graker/photoalbums/controllers/upload/_form.htm b/server/plugins/graker/photoalbums/controllers/upload/_form.htm new file mode 100644 index 0000000..22d53b0 --- /dev/null +++ b/server/plugins/graker/photoalbums/controllers/upload/_form.htm @@ -0,0 +1,51 @@ + += e(trans('graker.photoalbums::lang.errors.return_to_albums')) ?>
+ + diff --git a/server/plugins/graker/photoalbums/lang/en/lang.php b/server/plugins/graker/photoalbums/lang/en/lang.php new file mode 100644 index 0000000..a19d0ed --- /dev/null +++ b/server/plugins/graker/photoalbums/lang/en/lang.php @@ -0,0 +1,111 @@ + [ + 'name' => 'Photo Albums', + 'description' => 'Create, display and manage galleries of photos arranged in albums.', + 'settings_description' => 'Photo Albums plugin settings', + 'tab' => 'Photo Albums', + 'manage_albums' => 'Manage photo albums', + 'access_permission' => 'Access Settings', + 'upload_photos' => 'Upload photos', + 'new_album' => 'New album', + 'create_album' => 'Create album', + 'edit_album' => 'Edit album', + 'preview_album' => 'Preview album', + 'creating_album' => 'Creating album...', + 'saving_album' => 'Saving album...', + 'deleting_album' => 'Deleting album...', + 'list_title' => 'Manage albums', + 'album' => 'Album', + 'albums' => 'Albums', + 'manage_photos' => 'Manage photos', + 'new_photo' => 'New photo', + 'create_photo' => 'Create photo', + 'edit_photo' => 'Edit photo', + 'preview_photo' => 'Preview photo', + 'creating_photo' => 'Creating photo...', + 'saving_photo' => 'Saving photo...', + 'deleting_photo' => 'Deleting photo...', + 'photo' => 'Photo', + 'photos' => 'Photos', + 'photo_description' => 'Description', + 'set_front_button' => 'Set as front', + 'reorder_button' => 'Reorder photos', + 'bool_positive' => 'Yes', + 'reorder_title' => 'Reorder album :name', + 'reorder' => 'Reorder', + 'saving_upload' => 'Saving upload...', + 'upload_photos_title' => 'Upload multiple photos', + 'album_to_upload' => 'Album to upload to', + 'save_upload' => 'Save upload', + 'title_label' => 'Title', + 'title_placeholder_album' => 'Album title', + 'title_placeholder_photo' => 'Photo title', + 'created_label' => 'Created', + 'updated_label' => 'Updated', + 'slug_label' => 'Slug', + 'slug_description' => 'URL slug parameter', + 'slug_placeholder_album' => 'album-title', + 'description_label' => 'Description', + 'front_label' => 'Front', + 'code_label' => 'Code', + 'code_description' => 'Type in default markdown to use for photo insert. There are two placeholders: %id% and %title%, they will be replaced with photo id and photo title automatically.', + 'selecting_photo' => 'Selecting photo', + 'insert' => 'Insert', + 'not_selected' => 'Not selected', + 'back_to_albums' => 'Back to albums', + 'all_photo_albums' => 'All Photo Albums', + 'all_photos' => 'All Photos', + ], + 'errors' => [ + 'album_not_found' => 'Album not found!', + 'cant_find_selected' => 'Can\'t find selected photo!', + 'not_this_album' => 'Selected photo doesn\'t belong to this album!', + 'return_to_albums' => 'Return to albums list', + 'return_to_photos' => 'Return to photos list', + 'no_file' => 'No file in request', + 'invalid_file' => 'File :name is not valid.', + 'thumb_width_error' => 'Thumb width must be a number', + 'thumb_height_error' => 'Thumb height must be a number', + 'photos_on_page_error' => 'Photos on page value must be a number', + 'albums_on_page_error' => 'Albums on page value must be a number', + 'photos_count_error' => 'Photos count must be a number', + 'cache_lifetime_error' => 'Cache lifetime must be a number', + 'no_albums' => 'You don\'t have any albums yet.', + ], + 'messages' => [ + 'set_front' => 'Are you sure to set this photo as front for the album?', + 'delete' => 'Do you really want to delete this album?', + 'delete_photo' => 'Do you really want to delete this photo?', + 'photos_saved' => 'Photos are saved!', + ], + 'components' => [ + 'photo_description' => 'Single photo component', + 'album_description' => 'Component to output one photo album with all its photos.', + 'photo_page_label' => 'Photo page', + 'photo_page_description' => 'Page used to display a single photo', + 'thumb_mode_label' => 'Thumb mode', + 'thumb_mode_description' => 'Mode of thumb generation', + 'thumb_width_label' => 'Thumb width', + 'thumb_width_description' => 'Width of the thumb to be generated', + 'thumb_height_label' => 'Thumb height', + 'thumb_height_description' => 'Height of the thumb to be generated', + 'photos_on_page_label' => 'Photos on page', + 'photos_on_page_description' => 'Amount of photos on one page (to use in pagination)', + 'albums_on_page_label' => 'Albums on page', + 'albums_on_page_description' => 'Amount of albums on one page (to use in pagination)', + 'albums_list' => 'Albums list', + 'albums_list_description' => 'Lists all photo albums on site', + 'album_page_label' => 'Album page', + 'album_page_description' => 'Page used to display photo albums', + 'id_label' => 'ID', + 'id_description' => 'Photo id parameter', + 'random_photos' => 'Random Photos', + 'random_photos_description' => 'Output predefined number of random photos', + 'photos_count_label' => 'Photos to output', + 'photos_count_description' => 'Amount of random photos to output', + 'cache_lifetime_label' => 'Cache lifetime', + 'cache_lifetime_description' => 'Number of minutes selected photos are stored in cache. 0 for no caching.', + ], +]; diff --git a/server/plugins/graker/photoalbums/models/Album.php b/server/plugins/graker/photoalbums/models/Album.php new file mode 100644 index 0000000..00ef6ab --- /dev/null +++ b/server/plugins/graker/photoalbums/models/Album.php @@ -0,0 +1,119 @@ + 'required', + 'slug' => ['required', 'regex:/^[a-z0-9\/\:_\-\*\[\]\+\?\|]*$/i', 'unique:graker_photoalbums_albums'], + ]; + + /** + * @var array Relations + */ + public $hasMany = [ + 'photos' => [ + 'Graker\PhotoAlbums\Models\Photo', + 'order' => 'sort_order desc', + ] + ]; + public $belongsTo = [ + 'user' => ['Backend\Models\User'], + 'front' => ['Graker\PhotoAlbums\Models\Photo'], + ]; + + + /** + * + * This relation allows us to eager-load 1 latest photo per album + * + * @return mixed + */ + public function latestPhoto() { + return $this->hasOne('Graker\PhotoAlbums\Models\Photo')->latest(); + } + + + /** + * + * This relation allows us to count photos + * + * @return mixed + */ + public function photosCount() { + return $this->hasOne('Graker\PhotoAlbums\Models\Photo') + ->selectRaw('album_id, count(*) as aggregate') + ->orderBy('album_id') + ->groupBy('album_id'); + } + + + /** + * + * Getter for photos count + * + * @return int + */ + public function getPhotosCountAttribute() { + // if relation is not loaded already, let's do it first + if (!array_key_exists('photosCount', $this->relations)) { + $this->load('photosCount'); + } + $related = $this->getRelation('photosCount'); + + return ($related) ? (int) $related->aggregate : 0; + } + + + /** + * + * Returns image file of photo set as album front or image in the latest photo of the album + * + * @return File + */ + public function getImage() { + if ($this->front) { + return $this->front->image; + } + + if ($this->latestPhoto) { + return $this->latestPhoto->image; + } + + return NULL; + } + + + /** + * + * Sets and returns url for this model using provided page name and controller + * For now we expose just id and slug for URL parameters + * + * @param string $pageName + * @param CMS\Classes\Controller $controller + * @return string + */ + public function setUrl($pageName, $controller) { + $params = [ + 'id' => $this->id, + 'slug' => $this->slug, + ]; + + return $this->url = $controller->pageUrl($pageName, $params); + } + +} diff --git a/server/plugins/graker/photoalbums/models/Photo.php b/server/plugins/graker/photoalbums/models/Photo.php new file mode 100644 index 0000000..95e4ff8 --- /dev/null +++ b/server/plugins/graker/photoalbums/models/Photo.php @@ -0,0 +1,122 @@ + 'required', + ]; + + /** + * @var array of fillable fields to use in mass assignment + */ + protected $fillable = [ + 'title', 'description', + ]; + + /** + * @var array Relations + */ + public $belongsTo = [ + 'user' => ['Backend\Models\User'], + 'album' => ['Graker\PhotoAlbums\Models\Album'], + ]; + public $attachOne = [ + 'image' => ['System\Models\File'], + ]; + + + /** + * + * Returns next photo or NULL if this is the last in the album + * + * @return Photo + */ + public function nextPhoto() { + $next = NULL; + $current_found = FALSE; + + foreach ($this->album->photos as $photo) { + if ($current_found) { + // previous iteration was current photo, so we found the next one + $next = $photo; + break; + } + if ($photo->id == $this->id) { + $current_found = TRUE; + } + } + + return $next; + } + + + /** + * + * Returns previous photo or NULL if this is the first in the album + * + * @return Photo + */ + public function previousPhoto() { + $previous = NULL; + + foreach ($this->album->photos as $photo) { + if ($photo->id == $this->id) { + // found current photo + break; + } else { + $previous = $photo; + } + } + + return $previous; + } + + + /** + * + * Sets and returns url for this model using provided page name and controller + * For now we expose photo id and album's slug + * + * @param string $pageName + * @param CMS\Classes\Controller $controller + * @return string + */ + public function setUrl($pageName, $controller) { + $params = [ + 'id' => $this->id, + 'album_slug' => $this->album->slug, + ]; + + return $this->url = $controller->pageUrl($pageName, $params); + } + + + /** + * beforeDelete() event + * Using it to delete attached + */ + public function beforeDelete() { + if ($this->image) { + $this->image->delete(); + } + } + +} diff --git a/server/plugins/graker/photoalbums/models/Settings.php b/server/plugins/graker/photoalbums/models/Settings.php new file mode 100644 index 0000000..74e319a --- /dev/null +++ b/server/plugins/graker/photoalbums/models/Settings.php @@ -0,0 +1,25 @@ + ++ + + +
+ ++ + + +
+ +Hello world!
', 10, 'Hello...
'), + array('Hello world!
', 30, 'Hello world!
'), + array('Hello World! blah blah blah
', 20, 'Hello World!...
'), + array('OneTwo Three', 10, 'OneTwo...'), + array('Hello world!
', 10, 'Hello...
'), + array('Hello world!
', 20, 'Hello world!
'), + ); + } + + /** + * @dataProvider provider + */ + public function testTruncate($htmlString, $length, $expected) + { + $truncatedText = $this->truncateService->truncate($htmlString, $length); + $this->assertEquals($expected, $truncatedText); + } + +} diff --git a/server/plugins/rainlab/builder/LICENCE.md b/server/plugins/rainlab/builder/LICENCE.md new file mode 100644 index 0000000..d68943e --- /dev/null +++ b/server/plugins/rainlab/builder/LICENCE.md @@ -0,0 +1,19 @@ +# MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/server/plugins/rainlab/builder/Plugin.php b/server/plugins/rainlab/builder/Plugin.php new file mode 100644 index 0000000..ed484ab --- /dev/null +++ b/server/plugins/rainlab/builder/Plugin.php @@ -0,0 +1,148 @@ + 'rainlab.builder::lang.plugin.name', + 'description' => 'rainlab.builder::lang.plugin.description', + 'author' => 'Alexey Bobkov, Samuel Georges', + 'icon' => 'icon-wrench', + 'homepage' => 'https://github.com/rainlab/builder-plugin' + ]; + } + + public function registerComponents() + { + return [ + 'RainLab\Builder\Components\RecordList' => 'builderList', + 'RainLab\Builder\Components\RecordDetails' => 'builderDetails' + ]; + } + + public function registerPermissions() + { + return [ + 'rainlab.builder.manage_plugins' => [ + 'tab' => 'rainlab.builder::lang.plugin.name', + 'label' => 'rainlab.builder::lang.plugin.manage_plugins'] + ]; + } + + public function registerNavigation() + { + return [ + 'builder' => [ + 'label' => 'rainlab.builder::lang.plugin.name', + 'url' => Backend::url('rainlab/builder'), + 'icon' => 'icon-wrench', + 'iconSvg' => 'plugins/rainlab/builder/assets/images/builder-icon.svg', + 'permissions' => ['rainlab.builder.manage_plugins'], + 'order' => 400, + + 'sideMenu' => [ + 'database' => [ + 'label' => 'rainlab.builder::lang.database.menu_label', + 'icon' => 'icon-hdd-o', + 'url' => 'javascript:;', + 'attributes' => ['data-menu-item'=>'database'], + 'permissions' => ['rainlab.builder.manage_plugins'] + ], + 'models' => [ + 'label' => 'rainlab.builder::lang.model.menu_label', + 'icon' => 'icon-random', + 'url' => 'javascript:;', + 'attributes' => ['data-menu-item'=>'models'], + 'permissions' => ['rainlab.builder.manage_plugins'] + ], + 'permissions' => [ + 'label' => 'rainlab.builder::lang.permission.menu_label', + 'icon' => 'icon-unlock-alt', + 'url' => '#', + 'attributes' => ['data-no-side-panel'=>'true', 'data-builder-command'=>'permission:cmdOpenPermissions', 'data-menu-item'=>'permissions'], + 'permissions' => ['rainlab.builder.manage_plugins'] + ], + 'menus' => [ + 'label' => 'rainlab.builder::lang.menu.menu_label', + 'icon' => 'icon-location-arrow', + 'url' => 'javascript:;', + 'attributes' => ['data-no-side-panel'=>'true', 'data-builder-command'=>'menus:cmdOpenMenus', 'data-menu-item'=>'menus'], + 'permissions' => ['rainlab.builder.manage_plugins'] + ], + 'controllers' => [ + 'label' => 'rainlab.builder::lang.controller.menu_label', + 'icon' => 'icon-asterisk', + 'url' => 'javascript:;', + 'attributes' => ['data-menu-item'=>'controllers'], + 'permissions' => ['rainlab.builder.manage_plugins'] + ], + 'versions' => [ + 'label' => 'rainlab.builder::lang.version.menu_label', + 'icon' => 'icon-code-fork', + 'url' => 'javascript:;', + 'attributes' => ['data-menu-item'=>'version'], + 'permissions' => ['rainlab.builder.manage_plugins'] + ], + 'localization' => [ + 'label' => 'rainlab.builder::lang.localization.menu_label', + 'icon' => 'icon-globe', + 'url' => 'javascript:;', + 'attributes' => ['data-menu-item'=>'localization'], + 'permissions' => ['rainlab.builder.manage_plugins'] + ] + ] + + ] + ]; + } + + public function registerSettings() + { + return [ + 'config' => [ + 'label' => 'Builder', + 'icon' => 'icon-wrench', + 'description' => 'Set your author name and namespace for plugin creation.', + 'class' => 'RainLab\Builder\Models\Settings', + 'permissions' => ['rainlab.builder.manage_plugins'], + 'order' => 600 + ] + ]; + } + + public function boot() + { + Event::listen('pages.builder.registerControls', function($controlLibrary) { + new StandardControlsRegistry($controlLibrary); + }); + + Event::listen('pages.builder.registerControllerBehaviors', function($behaviorLibrary) { + new StandardBehaviorsRegistry($behaviorLibrary); + }); + + // Register reserved keyword validation + Validator::resolver(function ($translator, $data, $rules, $messages, $customAttributes) { + return new ReservedValidator($translator, $data, $rules, $messages, $customAttributes); + }); + } + + public function register() + { + /* + * Register asset bundles + */ + CombineAssets::registerCallback(function ($combiner) { + $combiner->registerBundle('$/rainlab/builder/assets/js/build.js'); + }); + } +} diff --git a/server/plugins/rainlab/builder/assets/css/builder.css b/server/plugins/rainlab/builder/assets/css/builder.css new file mode 100644 index 0000000..516239c --- /dev/null +++ b/server/plugins/rainlab/builder/assets/css/builder.css @@ -0,0 +1,1209 @@ +.builder-building-area { + background: white; +} +.builder-building-area ul.builder-control-list { + padding: 20px; + margin-bottom: 0; + list-style: none; +} +.builder-building-area ul.builder-control-list:before, +.builder-building-area ul.builder-control-list:after { + content: " "; + display: table; +} +.builder-building-area ul.builder-control-list:after { + clear: both; +} +.builder-building-area ul.builder-control-list > li.control { + position: relative; + margin-bottom: 20px; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.builder-building-area ul.builder-control-list > li.control[data-unknown] { + cursor: default; +} +.builder-building-area ul.builder-control-list > li.control.placeholder, +.builder-building-area ul.builder-control-list > li.control.loading-control { + padding: 10px 12px; + position: relative; + text-align: center; + border: 2px dotted #dde0e2; + margin-top: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + color: #dae0e0; +} +.builder-building-area ul.builder-control-list > li.control.placeholder i, +.builder-building-area ul.builder-control-list > li.control.loading-control i { + margin-right: 8px; +} +.builder-building-area ul.builder-control-list > li.control.clear-row { + display: none; + margin-bottom: 0; +} +.builder-building-area ul.builder-control-list > li.control.loading-control { + border-color: #bdc3c7; + text-align: left; +} +.builder-building-area ul.builder-control-list > li.control.updating-control:after, +.builder-building-area ul.builder-control-list > li.control.loading-control:before { + background-image: url(../../../../../modules/system/assets/ui/images/loader-transparent.svg); + background-size: 15px 15px; + background-position: 50% 50%; + display: inline-block; + width: 15px; + height: 15px; + content: ' '; + margin-right: 13px; + position: relative; + top: 2px; + -webkit-animation: spin 1s linear infinite; + animation: spin 1s linear infinite; +} +.builder-building-area ul.builder-control-list > li.control.loading-control:after { + content: attr(data-builder-loading-text); + display: inline-block; +} +.builder-building-area ul.builder-control-list > li.control.updating-control:after { + position: absolute; + right: -8px; + top: 5px; +} +.builder-building-area ul.builder-control-list > li.control.updating-control:before { + content: ''; + position: absolute; + right: 0; + top: 0; + width: 25px; + height: 25px; + background: rgba(127, 127, 127, 0.1); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.builder-building-area ul.builder-control-list > li.control.drag-over { + color: #2581b8; + border-color: #2581b8; +} +.builder-building-area ul.builder-control-list > li.control.span-full { + width: 100%; + float: left; +} +.builder-building-area ul.builder-control-list > li.control.span-left { + float: left; + width: 48.5%; + clear: left; +} +.builder-building-area ul.builder-control-list > li.control.span-right { + float: right; + width: 48.5%; + clear: right; +} +.builder-building-area ul.builder-control-list > li.control.span-right + li.clear-row { + display: block; + clear: both; +} +.builder-building-area ul.builder-control-list > li.control > div.remove-control { + display: none; +} +.builder-building-area ul.builder-control-list > li.control:not(.placeholder):not(.loading-control):not(.updating-control):hover > div.remove-control { + font-family: sans-serif; + display: block; + position: absolute; + right: 0; + top: 0; + cursor: pointer; + width: 21px; + height: 21px; + padding-left: 6px; + font-size: 16px; + font-weight: bold; + line-height: 21px; + -webkit-border-radius: 20px; + -moz-border-radius: 20px; + border-radius: 20px; + background: #ecf0f1; + color: #95a5a6!important; +} +.builder-building-area ul.builder-control-list > li.control:not(.placeholder):not(.loading-control):not(.updating-control):hover > div.remove-control:hover { + color: white!important; + background: #c03f31; +} +.builder-building-area ul.builder-control-list > li.control:not(.placeholder):not(.loading-control):not(.updating-control):hover[data-control-type=hint] > div.remove-control, +.builder-building-area ul.builder-control-list > li.control:not(.placeholder):not(.loading-control):not(.updating-control):hover[data-control-type=partial] > div.remove-control { + top: 12px; + right: 12px; +} +.builder-building-area ul.builder-control-list > li.control[data-control-type=hint].updating-control:before, +.builder-building-area ul.builder-control-list > li.control[data-control-type=partial].updating-control:before { + right: 12px; + top: 7; +} +.builder-building-area ul.builder-control-list > li.control[data-control-type=hint].updating-control:after, +.builder-building-area ul.builder-control-list > li.control[data-control-type=partial].updating-control:after { + right: 4px; + top: 13px; +} +.builder-building-area ul.builder-control-list > li.control > .control-wrapper, +.builder-building-area ul.builder-control-list > li.control > .control-static-contents { + position: relative; + -webkit-transition: margin 0.1s; + transition: margin 0.1s; +} +.builder-building-area ul.builder-control-list > li.placeholder:hover, +.builder-building-area ul.builder-control-list > li.placeholder.popover-highlight, +.builder-building-area ul.builder-control-list > li.placeholder.control-palette-open { + background-color: #2581b8 !important; + color: white!important; + border-style: solid; + border-color: #2581b8; +} +.builder-building-area ul.builder-control-list > li.control:not(.placeholder):not(.loading-control):not([data-unknown]):hover > .control-wrapper *, +.builder-building-area ul.builder-control-list > li.control.inspector-open:not(.placeholder):not(.loading-control) > .control-wrapper * { + color: #2581b8 !important; +} +.builder-building-area ul.builder-control-list > li.control.drag-over:not(.placeholder):before { + position: absolute; + content: ''; + top: 0; + left: 0; + width: 10px; + height: 100%; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + background-color: #2581b8; +} +.builder-building-area ul.builder-control-list > li.control.drag-over:not(.placeholder) > .control-wrapper, +.builder-building-area ul.builder-control-list > li.control.drag-over:not(.placeholder) > .control-static-contents { + margin-left: 20px; + margin-right: -20px; +} +.builder-building-area .control-body.field-disabled, +.builder-building-area .control-body.field-hidden { + opacity: 0.5; + filter: alpha(opacity=50); +} +.builder-building-area .builder-control-label { + margin-bottom: 10px; + color: #555555; + font-size: 14px; + font-weight: 600; +} +.builder-building-area .builder-control-label.required:after { + vertical-align: super; + font-size: 60%; + content: " *"; +} +.builder-building-area .builder-control-label:empty { + margin-bottom: 0; +} +.builder-building-area .builder-control-comment-above { + margin-bottom: 8px; + margin-top: -3px; +} +.builder-building-area .builder-control-comment-below { + margin-top: 6px; +} +.builder-building-area .builder-control-comment-above, +.builder-building-area .builder-control-comment-below { + color: #737373; + font-size: 12px; +} +.builder-building-area .builder-control-comment-above:empty, +.builder-building-area .builder-control-comment-below:empty { + display: none; +} +html.gecko.mac .builder-building-area div[data-root-control-wrapper] { + margin-right: 17px; +} +.builder-building-area .builder-blueprint-control-text, +.builder-building-area .builder-blueprint-control-textarea, +.builder-building-area .builder-blueprint-control-partial, +.builder-building-area .builder-blueprint-control-unknown, +.builder-building-area .builder-blueprint-control-dropdown { + padding: 10px 12px; + border: 2px solid #bdc3c7; + color: #95a5a6; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.builder-building-area .builder-blueprint-control-text i, +.builder-building-area .builder-blueprint-control-textarea i, +.builder-building-area .builder-blueprint-control-partial i, +.builder-building-area .builder-blueprint-control-unknown i, +.builder-building-area .builder-blueprint-control-dropdown i { + margin-right: 5px; +} +.builder-building-area li.control:hover > .control-wrapper .builder-blueprint-control-text, +.builder-building-area li.inspector-open > .control-wrapper .builder-blueprint-control-text, +.builder-building-area li.control:hover > .control-wrapper .builder-blueprint-control-textarea, +.builder-building-area li.inspector-open > .control-wrapper .builder-blueprint-control-textarea, +.builder-building-area li.control:hover > .control-wrapper .builder-blueprint-control-dropdown, +.builder-building-area li.inspector-open > .control-wrapper .builder-blueprint-control-dropdown { + border-color: #2581b8; +} +.builder-building-area li.control:hover > .control-wrapper .builder-blueprint-control-dropdown:before, +.builder-building-area li.inspector-open > .control-wrapper .builder-blueprint-control-dropdown:before { + background-color: #2581b8; +} +.builder-building-area .builder-blueprint-control-textarea.size-tiny { + min-height: 50px; +} +.builder-building-area .builder-blueprint-control-textarea.size-small { + min-height: 100px; +} +.builder-building-area .builder-blueprint-control-textarea.size-large { + min-height: 200px; +} +.builder-building-area .builder-blueprint-control-textarea.size-huge { + min-height: 250px; +} +.builder-building-area .builder-blueprint-control-textarea.size-giant { + min-height: 350px; +} +.builder-building-area .builder-blueprint-control-section { + border-bottom: 1px solid #bdc3c7; + padding-bottom: 4px; +} +.builder-building-area .builder-blueprint-control-section .builder-control-label { + font-size: 16px; + margin-bottom: 6px; +} +.builder-building-area .builder-blueprint-control-unknown { + border-color: #eee; + background: #eee; +} +.builder-building-area .builder-blueprint-control-partial { + border-color: #eee; + background: #eee; +} +.builder-building-area .builder-blueprint-control-dropdown { + position: relative; +} +.builder-building-area .builder-blueprint-control-dropdown:before, +.builder-building-area .builder-blueprint-control-dropdown:after { + position: absolute; + content: ''; +} +.builder-building-area .builder-blueprint-control-dropdown:before { + top: 0; + width: 2px; + background: #bdc3c7; + right: 40px; + height: 100%; +} +.builder-building-area .builder-blueprint-control-dropdown:after { + font-family: FontAwesome; + font-weight: normal; + font-style: normal; + text-decoration: inherit; + -webkit-font-smoothing: antialiased; + *margin-right: .3em; + content: "\f107"; + color: inherit; + right: 15px; + top: 12px; + font-size: 20px; + line-height: 20px; +} +.builder-building-area .builder-blueprint-control-checkbox:before { + float: left; + content: ' '; + border: 2px solid #bdc3c7; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + width: 17px; + height: 17px; + position: relative; + top: 2px; +} +.builder-building-area .builder-blueprint-control-checkbox .builder-control-label { + margin-left: 25px; + font-weight: normal; +} +.builder-building-area .builder-blueprint-control-checkbox .builder-control-comment-below { + margin-left: 25px; +} +.builder-building-area li.control:hover > .control-wrapper .builder-blueprint-control-checkbox:before, +.builder-building-area li.inspector-open > .control-wrapper .builder-blueprint-control-checkbox:before { + border-color: #2581b8; +} +.builder-building-area .builder-blueprint-control-switch { + position: relative; +} +.builder-building-area .builder-blueprint-control-switch:before, +.builder-building-area .builder-blueprint-control-switch:after { + position: absolute; + content: ' '; + -webkit-border-radius: 30px; + -moz-border-radius: 30px; + border-radius: 30px; +} +.builder-building-area .builder-blueprint-control-switch:before { + background-color: #bdc3c7; + width: 34px; + height: 18px; + top: 2px; + left: 2px; +} +.builder-building-area .builder-blueprint-control-switch:after { + background-color: white; + width: 14px; + height: 14px; + top: 4px; + left: 4px; + margin-left: 16px; +} +.builder-building-area .builder-blueprint-control-switch .builder-control-label { + margin-left: 45px; + font-weight: normal; +} +.builder-building-area .builder-blueprint-control-switch .builder-control-comment-below { + margin-left: 45px; +} +.builder-building-area li.control:hover > .control-wrapper .builder-blueprint-control-switch:before, +.builder-building-area li.inspector-open > .control-wrapper .builder-blueprint-control-switch:before { + background-color: #2581b8; +} +.builder-building-area .builder-blueprint-control-repeater-body > .repeater-button { + padding: 8px 13px; + background: #bdc3c7; + color: white; + display: inline-block; + margin-bottom: 10px; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; +} +.builder-building-area ul.builder-control-list > li.control:hover > .control-wrapper > .control-body .builder-blueprint-control-repeater-body > .repeater-button, +.builder-building-area ul.builder-control-list > li.inspector-open > .control-wrapper > .control-body .builder-blueprint-control-repeater-body > .repeater-button { + background: #2581b8; + color: white!important; +} +.builder-building-area ul.builder-control-list > li.control:hover > .control-wrapper > .control-body .builder-blueprint-control-repeater-body > .repeater-button span, +.builder-building-area ul.builder-control-list > li.inspector-open > .control-wrapper > .control-body .builder-blueprint-control-repeater-body > .repeater-button span { + color: white!important; +} +.builder-building-area .builder-blueprint-control-repeater { + position: relative; +} +.builder-building-area .builder-blueprint-control-repeater:before { + content: ''; + position: absolute; + width: 2px; + top: 0; + left: 2px; + height: 100%; + background: #bdc3c7; +} +.builder-building-area .builder-blueprint-control-repeater:after { + content: ''; + position: absolute; + width: 6px; + height: 6px; + top: 14px; + left: 0; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + background: #bdc3c7; +} +.builder-building-area .builder-blueprint-control-repeater > ul.builder-control-list { + padding-right: 0; + padding-bottom: 0; + padding-top: 10px; +} +.builder-building-area li.control:hover > .builder-blueprint-control-repeater:before, +.builder-building-area li.inspector-open > .builder-blueprint-control-repeater:before, +.builder-building-area li.control:hover > .builder-blueprint-control-repeater:after, +.builder-building-area li.inspector-open > .builder-blueprint-control-repeater:after { + background-color: #2581b8; +} +.builder-building-area .builder-blueprint-control-radiolist ul, +.builder-building-area .builder-blueprint-control-checkboxlist ul { + list-style: none; + padding: 0; + color: #95a5a6; +} +.builder-building-area .builder-blueprint-control-radiolist ul li, +.builder-building-area .builder-blueprint-control-checkboxlist ul li { + margin-bottom: 3px; +} +.builder-building-area .builder-blueprint-control-radiolist ul li:last-child, +.builder-building-area .builder-blueprint-control-checkboxlist ul li:last-child { + margin-bottom: 0; +} +.builder-building-area .builder-blueprint-control-radiolist ul li i, +.builder-building-area .builder-blueprint-control-checkboxlist ul li i { + margin-right: 5px; +} +.builder-building-area .builder-blueprint-control-text.fileupload.image { + width: 100px; + height: 100px; + text-align: center; +} +.builder-building-area .builder-blueprint-control-text.fileupload.image i { + line-height: 77px; + margin-right: 0; +} +.builder-controllers-builder-area { + background: white; +} +.builder-controllers-builder-area ul.controller-behavior-list { + padding: 20px; + margin-bottom: 0; + list-style: none; +} +.builder-controllers-builder-area ul.controller-behavior-list:before, +.builder-controllers-builder-area ul.controller-behavior-list:after { + content: " "; + display: table; +} +.builder-controllers-builder-area ul.controller-behavior-list:after { + clear: both; +} +.builder-controllers-builder-area ul.controller-behavior-list li h4 { + text-align: center; + border-bottom: 1px dotted #bdc3c7; + margin: 0 -20px 40px; +} +.builder-controllers-builder-area ul.controller-behavior-list li h4 span { + display: inline-block; + color: white; + margin: 0 auto; + -webkit-border-radius: 8px; + -moz-border-radius: 8px; + border-radius: 8px; + background: #bdc3c7; + padding: 7px 10px; + font-size: 13px; + line-height: 100%; + position: relative; + top: 14px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container { + margin-bottom: 40px; + cursor: pointer; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container:before, +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container:after { + content: " "; + display: table; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container:after { + clear: both; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .list-behavior, +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .reorder-behavior { + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + border: 2px solid #bdc3c7; + padding: 25px 10px 25px 10px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .list-behavior table, +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .reorder-behavior table { + border-collapse: collapse; + width: 100%; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .list-behavior table td, +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .reorder-behavior table td { + padding: 0 15px 15px 15px; + border-right: 1px solid #bdc3c7; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .list-behavior table td:last-child, +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .reorder-behavior table td:last-child { + border-right: none; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .list-behavior table .placeholder, +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .reorder-behavior table .placeholder { + background: #EEF2F4; + height: 25px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .list-behavior table tbody tr:last-child td, +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .reorder-behavior table tbody tr:last-child td { + padding-bottom: 0; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .reorder-behavior table i.icon-bars, +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .reorder-behavior table .placeholder { + float: left; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .reorder-behavior table i.icon-bars { + margin-right: 15px; + color: #D6DDE0; + font-size: 28px; + line-height: 28px; + position: relative; + top: -2px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.form { + padding: 25px 25px 0 25px; + border: 2px solid #bdc3c7; + margin-bottom: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.form:before, +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.form:after { + content: " "; + display: table; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.form:after { + clear: both; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.field.left { + float: left; + width: 48%; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.field.right { + float: right; + width: 45%; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.field div.label { + background: #EEF2F4; + height: 25px; + margin-bottom: 10px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.field div.label.size-3 { + width: 100px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.field div.label.size-5 { + width: 150px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.field div.label.size-2 { + width: 60px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.field div.control { + background: #EEF2F4; + height: 35px; + margin-bottom: 25px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.button { + background: #EEF2F4; + height: 35px; + margin-right: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.button.size-5 { + width: 100px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.button.size-3 { + width: 60px; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container .form-behavior div.button:first-child { + margin-right: 0; +} +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container:hover *, +.builder-controllers-builder-area ul.controller-behavior-list .behavior-container.inspector-open * { + border-color: #2581b8 !important; +} +html.gecko.mac .builder-controllers-builder-area ul.controller-behavior-list { + padding-right: 40px; +} +.builder-tabs > .tabs { + position: relative; +} +.builder-tabs > .tabs .tab-control { + position: absolute; + display: block; +} +.builder-tabs > .tabs .tab-control.inspector-trigger { + font-size: 14px; + padding-left: 5px; + padding-right: 5px; + cursor: pointer; +} +.builder-tabs > .tabs .tab-control.inspector-trigger span { + display: block; + width: 3px; + height: 3px; + margin-bottom: 2px; + background: #95a5a6; +} +.builder-tabs > .tabs .tab-control.inspector-trigger span:last-child { + margin-bottom: 0; +} +.builder-tabs > .tabs .tab-control.inspector-trigger:hover span, +.builder-tabs > .tabs .tab-control.inspector-trigger.inspector-open span { + background: #0181b9; +} +.builder-tabs > .tabs .tab-control.inspector-trigger.global { + top: 5px; + right: 15px; +} +.builder-tabs > .tabs > ul.tabs { + margin: 0; + list-style: none; + font-size: 0; + white-space: nowrap; + overflow: hidden; + position: relative; +} +.builder-tabs > .tabs > ul.tabs > li { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + display: inline-block; + font-size: 13px; + white-space: nowrap; + position: relative; + cursor: pointer; +} +.builder-tabs > .tabs > ul.tabs > li > div.tab-container { + position: relative; + color: #bdc3c7!important; +} +.builder-tabs > .tabs > ul.tabs > li > div.tab-container > div { + -webkit-transition: padding 0.1s; + transition: padding 0.1s; + position: relative; +} +.builder-tabs > .tabs > ul.tabs > li:hover > div { + color: #95a5a6!important; +} +.builder-tabs > .tabs > ul.tabs > li .tab-control { + display: none; +} +.builder-tabs > .tabs > ul.tabs > li .tab-control.close-btn { + font-size: 15px; + top: 7px; + right: 18px; + line-height: 15px; + height: 15px; + width: 15px; + text-align: center; + cursor: pointer; + color: #95a5a6; +} +.builder-tabs > .tabs > ul.tabs > li .tab-control.close-btn:hover { + color: #0181b9 !important; +} +.builder-tabs > .tabs > ul.tabs > li .tab-control.inspector-trigger { + right: 34px; + top: 10px; +} +.builder-tabs > .tabs > ul.tabs > li.active > div.tab-container { + color: #95a5a6!important; +} +.builder-tabs > .tabs > ul.tabs > li.active .tab-control { + display: block; +} +.builder-tabs > .tabs > ul.panels { + padding: 0; + list-style: none; +} +.builder-tabs > .tabs > ul.panels > li { + display: none; +} +.builder-tabs > .tabs > ul.panels > li.active { + display: block; +} +.builder-tabs.primary > .tabs > ul.tabs { + padding: 0 20px 0 40px; + height: 31px; +} +.builder-tabs.primary > .tabs > ul.tabs:after { + position: absolute; + content: ''; + display: block; + height: 2px; + left: 0; + bottom: 0; + width: 100%; + background: #bdc3c7; + z-index: 106; +} +.builder-tabs.primary > .tabs > ul.tabs > li { + bottom: -2px; + margin-left: -20px; + z-index: 105; +} +.builder-tabs.primary > .tabs > ul.tabs > li > div.tab-container { + padding: 0 21px 0 21px; + height: 27px; +} +.builder-tabs.primary > .tabs > ul.tabs > li > div.tab-container > div { + padding: 5px 5px 0 5px; + border-top: 2px solid #e5e5e5; +} +.builder-tabs.primary > .tabs > ul.tabs > li > div.tab-container > div > span { + position: relative; + top: -2px; + -webkit-transition: top 0.1s; + transition: top 0.1s; +} +.builder-tabs.primary > .tabs > ul.tabs > li > div.tab-container:before, +.builder-tabs.primary > .tabs > ul.tabs > li > div.tab-container:after { + content: ''; + display: block; + position: absolute; + top: 0; + height: 27px; + width: 21px; + background: transparent url(../images/tab.png) no-repeat; +} +.builder-tabs.primary > .tabs > ul.tabs > li > div.tab-container:before { + left: 0; + background-position: 0 -27px; +} +.builder-tabs.primary > .tabs > ul.tabs > li > div.tab-container:after { + right: 0; + background-position: -75px -27px; +} +.builder-tabs.primary > .tabs > ul.tabs > li.active { + z-index: 107; +} +.builder-tabs.primary > .tabs > ul.tabs > li.active > div.tab-container:before { + background-position: 0 0; +} +.builder-tabs.primary > .tabs > ul.tabs > li.active > div.tab-container:after { + background-position: -75px 0; +} +.builder-tabs.primary > .tabs > ul.tabs > li.active > div.tab-container > div { + padding-right: 30px; + border-top: 2px solid #bdc3c7; +} +.builder-tabs.primary > .tabs > ul.tabs > li.active > div.tab-container > div > span { + top: 0; +} +.builder-tabs.primary > .tabs > ul.tabs > li.active:before { + position: absolute; + content: ''; + display: block; + height: 3px; + left: 0; + bottom: 0; + width: 100%; + background: white; +} +.builder-tabs.primary > .tabs > ul.tabs > li.new-tab { + background: transparent url(../images/tab.png) no-repeat; + background-position: -24px 0; + width: 27px; + height: 22px; + margin-left: -11px; + top: 4px; + position: relative; + cursor: pointer; +} +.builder-tabs.primary > .tabs > ul.tabs > li.new-tab:hover { + background-position: -24px -32px; +} +.builder-tabs.secondary > .tabs ul.tabs { + margin-left: 12px; + padding-left: 0; +} +.builder-tabs.secondary > .tabs ul.tabs > li { + border-right: 1px solid #bdc3c7; + padding-right: 1px; +} +.builder-tabs.secondary > .tabs ul.tabs > li > div.tab-container > div { + padding: 4px 10px; +} +.builder-tabs.secondary > .tabs ul.tabs > li > div.tab-container > div span { + font-size: 14px; +} +.builder-tabs.secondary > .tabs ul.tabs > li .tab-control { + right: 23px; + top: 7px; +} +.builder-tabs.secondary > .tabs ul.tabs > li .tab-control.close-btn { + right: 6px; + top: 5px; +} +.builder-tabs.secondary > .tabs ul.tabs > li.new-tab { + background: transparent; + border: 2px solid #e4e4e4; + width: 27px; + height: 22px; + left: 9px; + top: 7px; + position: relative; + cursor: pointer; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.builder-tabs.secondary > .tabs ul.tabs > li.new-tab:hover { + background-color: #2581b8; + border-color: #2581b8; +} +.builder-tabs.secondary > .tabs ul.tabs > li.active { + padding-right: 10px; +} +.builder-tabs.secondary > .tabs ul.tabs > li.active > div.tab-container > div { + color: #555555; + padding-right: 30px; +} +html.gecko .builder-tabs.primary > .tabs > ul.tabs > li { + bottom: -3px; +} +html.gecko .builder-tabs.primary > .tabs > ul.tabs > li > div.tab-container > div { + padding-top: 5px; +} +.builder-menu-editor { + background: white; +} +.builder-menu-editor .builder-menu-editor-workspace { + padding: 30px; +} +.builder-menu-editor ul.builder-menu { + font-size: 0; + padding: 0; + cursor: pointer; +} +.builder-menu-editor ul.builder-menu > li { + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} +.builder-menu-editor ul.builder-menu > li div.item-container:hover, +.builder-menu-editor ul.builder-menu > li.inspector-open > div.item-container { + background: #2581b8 !important; + color: white!important; +} +.builder-menu-editor ul.builder-menu > li div.item-container:hover a, +.builder-menu-editor ul.builder-menu > li.inspector-open > div.item-container a { + color: white!important; +} +.builder-menu-editor ul.builder-menu > li div.item-container { + position: relative; +} +.builder-menu-editor ul.builder-menu > li div.item-container .close-btn { + color: white; + position: absolute; + display: none; + width: 15px; + height: 15px; + right: 5px; + top: 5px; + font-size: 14px; + text-align: center; + line-height: 14px; +} +.builder-menu-editor ul.builder-menu > li div.item-container:hover .close-btn { + display: block; + text-decoration: none; + opacity: 0.5; + filter: alpha(opacity=50); +} +.builder-menu-editor ul.builder-menu > li div.item-container:hover .close-btn:hover { + opacity: 1; + filter: alpha(opacity=100); +} +.builder-menu-editor ul.builder-menu > li.add { + font-size: 16px; + text-align: center; + border: 2px dotted #dde0e2; +} +.builder-menu-editor ul.builder-menu > li.add a { + text-decoration: none; + color: #bdc3c7; +} +.builder-menu-editor ul.builder-menu > li.add span.title { + font-size: 14px; +} +.builder-menu-editor ul.builder-menu > li.add:hover { + border: 2px dotted #2581b8; + background: #2581b8 !important; +} +.builder-menu-editor ul.builder-menu > li.add:hover a { + color: white; +} +.builder-menu-editor ul.builder-menu > li.list-sortable-placeholder { + border: 2px dotted #2581b8; + height: 10px; + background: transparent; +} +.builder-menu-editor ul.builder-menu.builder-main-menu > li { + display: inline-block; + vertical-align: top; +} +.builder-menu-editor ul.builder-menu.builder-main-menu > li.item { + margin: 0 20px 20px 0; +} +.builder-menu-editor ul.builder-menu.builder-main-menu > li > div.item-container { + background: #ecf0f1; + color: #708080; + padding: 20px 25px; + height: 64px; + white-space: nowrap; +} +.builder-menu-editor ul.builder-menu.builder-main-menu > li > div.item-container i { + font-size: 24px; + margin-right: 10px; +} +.builder-menu-editor ul.builder-menu.builder-main-menu > li > div.item-container span.title { + font-size: 14px; + line-height: 100%; + position: relative; + top: -3px; +} +.builder-menu-editor ul.builder-menu.builder-main-menu > li.add { + height: 64px; +} +.builder-menu-editor ul.builder-menu.builder-main-menu > li.add a { + padding: 20px 15px; + height: 60px; + display: block; +} +.builder-menu-editor ul.builder-menu.builder-main-menu > li.add a i { + margin-right: 5px; +} +.builder-menu-editor ul.builder-menu.builder-main-menu > li.add a span { + position: relative; + top: -1px; +} +.builder-menu-editor ul.builder-menu.builder-submenu { + margin-top: 1px; +} +.builder-menu-editor ul.builder-menu.builder-submenu > li { + display: block; + width: 120px; +} +.builder-menu-editor ul.builder-menu.builder-submenu > li i { + display: block; + margin-bottom: 7px; +} +.builder-menu-editor ul.builder-menu.builder-submenu > li span.title { + display: block; + font-size: 12px; +} +.builder-menu-editor ul.builder-menu.builder-submenu > li.item { + margin: 0 0 1px 0; +} +.builder-menu-editor ul.builder-menu.builder-submenu > li > div.item-container { + background: #f3f5f5; + color: #94a5a6; + padding: 18px 13px; + text-align: center; +} +.builder-menu-editor ul.builder-menu.builder-submenu > li > div.item-container i { + font-size: 24px; +} +.builder-menu-editor ul.builder-menu.builder-submenu > li.add { + margin-top: 20px; +} +.builder-menu-editor ul.builder-menu.builder-submenu > li.add a { + padding: 10px 20px; + display: block; +} +.localization-input-container input[type=text].string-editor { + padding-right: 20px!important; +} +.localization-input-container .localization-trigger { + position: absolute; + display: none; + width: 10px; + height: 10px; + font-size: 14px; + color: #95a5a6; + outline: none; +} +.localization-input-container .localization-trigger:hover, +.localization-input-container .localization-trigger:active, +.localization-input-container .localization-trigger:focus { + color: #2581b8; + text-decoration: none; +} +table.inspector-fields td.active .localization-input-container .localization-trigger, +table.data td.active .localization-input-container .localization-trigger { + display: block; +} +table.data td.active .localization-input-container .localization-trigger { + top: 5px!important; + right: 7px!important; +} +.control-table td[data-column-type=builderLocalization] input[type=text] { + padding-right: 20px!important; +} +.control-table td[data-column-type=builderLocalization] input[type=text] { + width: 100%; + height: 100%; + display: block; + outline: none; + border: none; + padding: 6px 10px 6px; +} +html.chrome .control-table td[data-column-type=builderLocalization] input[type=text] { + padding: 6px 10px 7px!important; +} +html.safari .control-table td[data-column-type=builderLocalization] input[type=text], +html.gecko .control-table td[data-column-type=builderLocalization] input[type=text] { + padding: 5px 10px 5px; +} +.autocomplete.dropdown-menu.table-widget-autocomplete.localization li a { + white-space: normal; + word-wrap: break-word; +} +table.data td[data-column-type=builderLocalization] .loading-indicator-container.size-small .loading-indicator { + padding-bottom: 0!important; +} +table.data td[data-column-type=builderLocalization] .loading-indicator-container.size-small .loading-indicator span { + left: auto; + right: 6px; +} +.control-filelist ul li.group.model > h4 a:after { + content: "\f074"; + top: 10px; +} +.control-filelist ul li.group.form > h4 a:after { + content: "\f14a"; +} +.control-filelist ul li.group.list > h4 a:after { + content: "\f00b"; + top: 10px; +} +.control-filelist ul li.group > ul > li.group > ul > li > a { + padding-left: 73px; + margin-left: -20px; +} +.control-filelist ul li.with-icon span.title, +.control-filelist ul li.with-icon span.description { + padding-left: 22px; +} +.control-filelist ul li.with-icon i.list-icon { + position: absolute; + left: 20px; + top: 12px; + color: #405261; +} +.control-filelist ul li.with-icon i.list-icon.mute { + color: #8f8f8f; +} +.control-filelist ul li.with-icon i.list-icon.icon-check-square { + color: #8da85e; +} +html.gecko .control-filelist ul li.group { + margin-right: 10px; +} +.builder-inspector-container { + width: 350px; + border-left: 1px solid #d9d9d9; +} +.builder-inspector-container:empty { + display: none!important; +} +form.hide-secondary-tabs div.control-tabs.secondary-tabs ul.nav.nav-tabs { + display: none; +} +.form-group.size-quarter { + width: 23.5%; +} +.form-group.size-three-quarter { + width: 73.5%; +} +form[data-entity=database] div.field-datatable { + position: absolute; + width: 100%; + height: 100%; +} +form[data-entity=database] div.field-datatable div[data-control=table] { + position: absolute; + width: 100%; + height: 100%; +} +form[data-entity=database] div.field-datatable div[data-control=table] div.table-container { + position: absolute; + width: 100%; + height: 100%; +} +form[data-entity=database] div.field-datatable div[data-control=table] div.table-container div.control-scrollbar { + top: 70px; + bottom: 0; + position: absolute; + max-height: none!important; + height: auto!important; +} +div.control-table .toolbar a.builder-custom-table-button:before { + line-height: 17px; + font-size: 21px; + color: #323e50; + margin-right: 5px; + top: 3px; + opacity: 1; + filter: alpha(opacity=100); +} +.control-tabs.auxiliary-tabs { + background: white; +} +.control-tabs.auxiliary-tabs > ul.nav-tabs, +.control-tabs.auxiliary-tabs > div > ul.nav-tabs { + padding-left: 20px; + padding-bottom: 2px; + background: white; + position: relative; +} +.control-tabs.auxiliary-tabs > ul.nav-tabs:before, +.control-tabs.auxiliary-tabs > div > ul.nav-tabs:before { + content: ' '; + display: block; + position: absolute; + width: 100%; + height: 1px; + background: #95a5a6; + top: 0; + left: 0; +} +.control-tabs.auxiliary-tabs > ul.nav-tabs > li, +.control-tabs.auxiliary-tabs > div > ul.nav-tabs > li { + margin-right: 2px; +} +.control-tabs.auxiliary-tabs > ul.nav-tabs > li > a, +.control-tabs.auxiliary-tabs > div > ul.nav-tabs > li > a { + background: white; + color: #bdc3c7; + border-left: 1px solid #ecf0f1!important; + border-right: 1px solid #ecf0f1!important; + border-bottom: 1px solid #ecf0f1!important; + padding: 4px 10px; + line-height: 100%; + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} +.control-tabs.auxiliary-tabs > ul.nav-tabs > li > a > span.title > span, +.control-tabs.auxiliary-tabs > div > ul.nav-tabs > li > a > span.title > span { + margin-bottom: 0; + font-size: 13px; + height: auto; +} +.control-tabs.auxiliary-tabs > ul.nav-tabs > li.active, +.control-tabs.auxiliary-tabs > div > ul.nav-tabs > li.active { + top: 0; +} +.control-tabs.auxiliary-tabs > ul.nav-tabs > li.active:before, +.control-tabs.auxiliary-tabs > div > ul.nav-tabs > li.active:before { + content: ' '; + display: block; + position: absolute; + width: 100%; + height: 1px; + background: white; + top: 0; + left: 0; + top: -1px; +} +.control-tabs.auxiliary-tabs > ul.nav-tabs > li.active a, +.control-tabs.auxiliary-tabs > div > ul.nav-tabs > li.active a { + padding-top: 5px; + border-left: 1px solid #95a5a6!important; + border-right: 1px solid #95a5a6!important; + border-bottom: 1px solid #95a5a6!important; + color: #95a5a6; +} +.control-tabs.auxiliary-tabs > div.tab-content > .tab-pane { + background: white; +} diff --git a/server/plugins/rainlab/builder/assets/images/builder-icon.svg b/server/plugins/rainlab/builder/assets/images/builder-icon.svg new file mode 100644 index 0000000..99c5de6 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/images/builder-icon.svg @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/images/tab.png b/server/plugins/rainlab/builder/assets/images/tab.png new file mode 100644 index 0000000..9227f65 Binary files /dev/null and b/server/plugins/rainlab/builder/assets/images/tab.png differ diff --git a/server/plugins/rainlab/builder/assets/js/build-min.js b/server/plugins/rainlab/builder/assets/js/build-min.js new file mode 100644 index 0000000..49f2352 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/build-min.js @@ -0,0 +1,809 @@ + ++function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +var Base=$.oc.foundation.base,BaseProto=Base.prototype +var DataRegistry=function(){this.data={} +this.requestCache={} +this.callbackCache={} +Base.call(this)} +DataRegistry.prototype.set=function(plugin,type,subtype,data,params){this.storeData(plugin,type,subtype,data) +if(type=='localization'&&!subtype){this.localizationUpdated(plugin,params)}} +DataRegistry.prototype.get=function($formElement,plugin,type,subtype,callback){if(this.data[plugin]===undefined||this.data[plugin][type]===undefined||this.data[plugin][type][subtype]===undefined||this.isCacheObsolete(this.data[plugin][type][subtype].timestamp)){return this.loadDataFromServer($formElement,plugin,type,subtype,callback)} +callback(this.data[plugin][type][subtype].data)} +DataRegistry.prototype.makeCacheKey=function(plugin,type,subtype){var key=plugin+'-'+type +if(subtype){key+='-'+subtype} +return key} +DataRegistry.prototype.isCacheObsolete=function(timestamp){return(Date.now()-timestamp)>60000*5} +DataRegistry.prototype.loadDataFromServer=function($formElement,plugin,type,subtype,callback){var self=this,cacheKey=this.makeCacheKey(plugin,type,subtype) +if(this.requestCache[cacheKey]===undefined){this.requestCache[cacheKey]=$formElement.request('onPluginDataRegistryGetData',{data:{registry_plugin_code:plugin,registry_data_type:type,registry_data_subtype:subtype}}).done(function(data){if(data.registryData===undefined){throw new Error('Invalid data registry response.')} +self.storeData(plugin,type,subtype,data.registryData) +self.applyCallbacks(cacheKey,data.registryData) +self.requestCache[cacheKey]=undefined})} +this.addCallbackToQueue(callback,cacheKey) +return this.requestCache[cacheKey]} +DataRegistry.prototype.addCallbackToQueue=function(callback,key){if(this.callbackCache[key]===undefined){this.callbackCache[key]=[]} +this.callbackCache[key].push(callback)} +DataRegistry.prototype.applyCallbacks=function(key,registryData){if(this.callbackCache[key]===undefined){return} +for(var i=this.callbackCache[key].length-1;i>=0;i--){this.callbackCache[key][i](registryData);} +delete this.callbackCache[key]} +DataRegistry.prototype.storeData=function(plugin,type,subtype,data){if(this.data[plugin]===undefined){this.data[plugin]={}} +if(this.data[plugin][type]===undefined){this.data[plugin][type]={}} +var dataItem={timestamp:Date.now(),data:data} +this.data[plugin][type][subtype]=dataItem} +DataRegistry.prototype.clearCache=function(plugin,type){if(this.data[plugin]===undefined){return} +if(this.data[plugin][type]===undefined){return} +this.data[plugin][type]=undefined} +DataRegistry.prototype.getLocalizationString=function($formElement,plugin,key,callback){this.get($formElement,plugin,'localization',null,function(data){if(data[key]!==undefined){callback(data[key]) +return} +callback(key)})} +DataRegistry.prototype.localizationUpdated=function(plugin,params){$.oc.builder.localizationInput.updatePluginInputs(plugin) +if(params===undefined||!params.suppressLanguageEditorUpdate){$.oc.builder.indexController.entityControllers.localization.languageUpdated(plugin)} +$.oc.builder.indexController.entityControllers.localization.updateOnScreenStrings(plugin)} +$.oc.builder.dataRegistry=new DataRegistry()}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +if($.oc.builder.entityControllers===undefined) +$.oc.builder.entityControllers={} +var Base=$.oc.foundation.base,BaseProto=Base.prototype +var EntityBase=function(typeName,indexController){if(typeName===undefined){throw new Error('The Builder entity type name should be set in the base constructor call.')} +if(indexController===undefined){throw new Error('The Builder index controller should be set when creating an entity controller.')} +this.typeName=typeName +this.indexController=indexController +Base.call(this)} +EntityBase.prototype=Object.create(BaseProto) +EntityBase.prototype.constructor=EntityBase +EntityBase.prototype.registerHandlers=function(){} +EntityBase.prototype.invokeCommand=function(command,ev){if(/^cmd[a-zA-Z0-9]+$/.test(command)){if(this[command]!==undefined){this[command].apply(this,[ev])} +else{throw new Error('Unknown command: '+command)}} +else{throw new Error('Invalid command: '+command)}} +EntityBase.prototype.newTabId=function(){return this.typeName+Math.random()} +EntityBase.prototype.makeTabId=function(objectName){return this.typeName+'-'+objectName} +EntityBase.prototype.getMasterTabsActivePane=function(){return this.indexController.getMasterTabActivePane()} +EntityBase.prototype.getMasterTabsObject=function(){return this.indexController.masterTabsObj} +EntityBase.prototype.getSelectedPlugin=function(){var activeItem=$('#PluginList-pluginList-plugin-list > ul > li.active') +return activeItem.data('id')} +EntityBase.prototype.getIndexController=function(){return this.indexController} +EntityBase.prototype.updateMasterTabIdAndTitle=function($tabPane,responseData){var tabsObject=this.getMasterTabsObject() +tabsObject.updateIdentifier($tabPane,responseData.tabId) +tabsObject.updateTitle($tabPane,responseData.tabTitle)} +EntityBase.prototype.unhideFormDeleteButton=function($tabPane){$('[data-control=delete-button]',$tabPane).removeClass('hide')} +EntityBase.prototype.forceCloseTab=function($tabPane){$tabPane.trigger('close.oc.tab',[{force:true}])} +EntityBase.prototype.unmodifyTab=function($tabPane){this.indexController.unchangeTab($tabPane)} +$.oc.builder.entityControllers.base=EntityBase;}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +if($.oc.builder.entityControllers===undefined) +$.oc.builder.entityControllers={} +var Base=$.oc.builder.entityControllers.base,BaseProto=Base.prototype +var Plugin=function(indexController){Base.call(this,'plugin',indexController) +this.popupZIndex=5050} +Plugin.prototype=Object.create(BaseProto) +Plugin.prototype.constructor=Plugin +Plugin.prototype.cmdMakePluginActive=function(ev){var $target=$(ev.currentTarget),selectedPluginCode=$target.data('pluginCode') +this.makePluginActive(selectedPluginCode)} +Plugin.prototype.cmdCreatePlugin=function(ev){var $target=$(ev.currentTarget) +$target.one('shown.oc.popup',this.proxy(this.onPluginPopupShown)) +$target.popup({handler:'onPluginLoadPopup',zIndex:this.popupZIndex})} +Plugin.prototype.cmdApplyPluginSettings=function(ev){var $form=$(ev.currentTarget),self=this +$.oc.stripeLoadIndicator.show() +$form.request('onPluginSave').always($.oc.builder.indexController.hideStripeIndicatorProxy).done(function(data){$form.trigger('close.oc.popup') +self.applyPluginSettingsDone(data)})} +Plugin.prototype.cmdEditPluginSettings=function(ev){var $target=$(ev.currentTarget) +$target.one('shown.oc.popup',this.proxy(this.onPluginPopupShown)) +$target.popup({handler:'onPluginLoadPopup',zIndex:this.popupZIndex,extraData:{pluginCode:$target.data('pluginCode')}})} +Plugin.prototype.onPluginPopupShown=function(ev,button,popup){$(popup).find('input[name=name]').focus()} +Plugin.prototype.applyPluginSettingsDone=function(data){if(data.responseData!==undefined&&data.responseData.isNewPlugin!==undefined){this.makePluginActive(data.responseData.pluginCode,true)}} +Plugin.prototype.makePluginActive=function(pluginCode,updatePluginList){var $form=$('#builder-plugin-selector-panel form').first() +$.oc.stripeLoadIndicator.show() +$form.request('onPluginSetActive',{data:{pluginCode:pluginCode,updatePluginList:(updatePluginList?1:0)}}).always($.oc.builder.indexController.hideStripeIndicatorProxy).done(this.proxy(this.makePluginActiveDone))} +Plugin.prototype.makePluginActiveDone=function(data){var pluginCode=data.responseData.pluginCode +$('#builder-plugin-selector-panel [data-control=filelist]').fileList('markActive',pluginCode)} +$.oc.builder.entityControllers.plugin=Plugin;}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +if($.oc.builder.entityControllers===undefined) +$.oc.builder.entityControllers={} +var Base=$.oc.builder.entityControllers.base,BaseProto=Base.prototype +var DatabaseTable=function(indexController){Base.call(this,'databaseTable',indexController)} +DatabaseTable.prototype=Object.create(BaseProto) +DatabaseTable.prototype.constructor=DatabaseTable +DatabaseTable.prototype.cmdCreateTable=function(ev){var result=this.indexController.openOrLoadMasterTab($(ev.target),'onDatabaseTableCreateOrOpen',this.newTabId()) +if(result!==false){result.done(this.proxy(this.onTableLoaded,this))}} +DatabaseTable.prototype.cmdOpenTable=function(ev){var table=$(ev.currentTarget).data('id'),result=this.indexController.openOrLoadMasterTab($(ev.target),'onDatabaseTableCreateOrOpen',this.makeTabId(table),{table_name:table}) +if(result!==false){result.done(this.proxy(this.onTableLoaded,this))}} +DatabaseTable.prototype.cmdSaveTable=function(ev){var $target=$(ev.currentTarget) +if(!this.validateTable($target)){return} +var data={'columns':this.getTableData($target)} +$target.popup({extraData:data,handler:'onDatabaseTableValidateAndShowPopup'})} +DatabaseTable.prototype.cmdSaveMigration=function(ev){var $target=$(ev.currentTarget) +$.oc.stripeLoadIndicator.show() +$target.request('onDatabaseTableMigrationApply').always($.oc.builder.indexController.hideStripeIndicatorProxy).done(this.proxy(this.saveMigrationDone))} +DatabaseTable.prototype.cmdDeleteTable=function(ev){var $target=$(ev.currentTarget) +$.oc.confirm($target.data('confirm'),this.proxy(this.deleteConfirmed))} +DatabaseTable.prototype.cmdUnModifyForm=function(){var $masterTabPane=this.getMasterTabsActivePane() +this.unmodifyTab($masterTabPane)} +DatabaseTable.prototype.cmdAddTimestamps=function(ev){var $target=$(ev.currentTarget),added=this.addTimeStampColumns($target,['created_at','updated_at']) +if(!added){alert($target.closest('form').attr('data-lang-timestamps-exist'))}} +DatabaseTable.prototype.cmdAddSoftDelete=function(ev){var $target=$(ev.currentTarget),added=this.addTimeStampColumns($target,['deleted_at']) +if(!added){alert($target.closest('form').attr('data-lang-soft-deleting-exist'))}} +DatabaseTable.prototype.onTableCellChanged=function(ev,column,value,rowIndex){var $target=$(ev.target) +if($target.data('alias')!='columns'){return} +if($target.closest('form').data('entity')!='database'){return} +var updatedRow={} +if(column=='auto_increment'&&value){updatedRow.unsigned=1 +updatedRow.primary_key=1} +if(column=='unsigned'&&!value){updatedRow.auto_increment=0} +if(column=='primary_key'&&value){updatedRow.allow_null=0} +if(column=='allow_null'&&value){updatedRow.primary_key=0} +if(column=='primary_key'&&!value){updatedRow.auto_increment=0} +$target.table('setRowValues',rowIndex,updatedRow)} +DatabaseTable.prototype.onTableLoaded=function(){$(document).trigger('render') +var $masterTabPane=this.getMasterTabsActivePane(),$form=$masterTabPane.find('form'),$toolbar=$masterTabPane.find('div[data-control=table] div.toolbar'),$button=$('') +$button.text($form.attr('data-lang-add-timestamps'));$toolbar.append($button) +$button=$('') +$button.text($form.attr('data-lang-add-soft-delete'));$toolbar.append($button)} +DatabaseTable.prototype.registerHandlers=function(){this.indexController.$masterTabs.on('oc.tableCellChanged',this.proxy(this.onTableCellChanged))} +DatabaseTable.prototype.validateTable=function($target){var tableObj=this.getTableControlObject($target) +tableObj.unfocusTable() +return tableObj.validate()} +DatabaseTable.prototype.getTableData=function($target){var tableObj=this.getTableControlObject($target) +return tableObj.dataSource.getAllData()} +DatabaseTable.prototype.getTableControlObject=function($target){var $form=$target.closest('form'),$table=$form.find('[data-control=table]'),tableObj=$table.data('oc.table') +if(!tableObj){throw new Error('Table object is not found on the database table tab')} +return tableObj} +DatabaseTable.prototype.saveMigrationDone=function(data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +$('#builderTableMigrationPopup').trigger('close.oc.popup') +var $masterTabPane=this.getMasterTabsActivePane(),tabsObject=this.getMasterTabsObject() +if(data.builderResponseData.operation!='delete'){$masterTabPane.find('input[name=table_name]').val(data.builderResponseData.builderObjectName) +this.updateMasterTabIdAndTitle($masterTabPane,data.builderResponseData) +this.unhideFormDeleteButton($masterTabPane) +this.getTableList().fileList('markActive',data.builderResponseData.tabId) +this.getIndexController().unchangeTab($masterTabPane)} +else{this.forceCloseTab($masterTabPane)} +$.oc.builder.dataRegistry.clearCache(data.builderResponseData.pluginCode,'model-columns')} +DatabaseTable.prototype.getTableList=function(){return $('#layout-side-panel form[data-content-id=database] [data-control=filelist]')} +DatabaseTable.prototype.deleteConfirmed=function(){var $masterTabPane=this.getMasterTabsActivePane() +$masterTabPane.find('form').popup({handler:'onDatabaseTableShowDeletePopup'})} +DatabaseTable.prototype.getColumnNames=function($target){var tableObj=this.getTableControlObject($target) +tableObj.unfocusTable() +var data=this.getTableData($target),result=[] +for(var index in data){if(data[index].name!==undefined){result.push($.trim(data[index].name))}} +return result} +DatabaseTable.prototype.addTimeStampColumns=function($target,columns) +{var existingColumns=this.getColumnNames($target),added=false +for(var index in columns){var column=columns[index] +if($.inArray(column,existingColumns)==-1){this.addTimeStampColumn($target,column) +added=true}} +if(added){$target.trigger('change')} +return added} +DatabaseTable.prototype.addTimeStampColumn=function($target,column){var tableObj=this.getTableControlObject($target),currentData=this.getTableData($target),rowData={name:column,type:'timestamp','default':null,allow_null:true} +tableObj.addRecord('bottom',true) +tableObj.setRowValues(currentData.length-1,rowData) +tableObj.addRecord('bottom',false) +tableObj.deleteRecord()} +$.oc.builder.entityControllers.databaseTable=DatabaseTable;}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +if($.oc.builder.entityControllers===undefined) +$.oc.builder.entityControllers={} +var Base=$.oc.builder.entityControllers.base,BaseProto=Base.prototype +var Model=function(indexController){Base.call(this,'model',indexController)} +Model.prototype=Object.create(BaseProto) +Model.prototype.constructor=Model +Model.prototype.cmdCreateModel=function(ev){var $target=$(ev.currentTarget) +$target.one('shown.oc.popup',this.proxy(this.onModelPopupShown)) +$target.popup({handler:'onModelLoadPopup'})} +Model.prototype.cmdApplyModelSettings=function(ev){var $form=$(ev.currentTarget),self=this +$.oc.stripeLoadIndicator.show() +$form.request('onModelSave').always($.oc.builder.indexController.hideStripeIndicatorProxy).done(function(data){$form.trigger('close.oc.popup') +self.applyModelSettingsDone(data)})} +Model.prototype.onModelPopupShown=function(ev,button,popup){$(popup).find('input[name=className]').focus()} +Model.prototype.applyModelSettingsDone=function(data){if(data.builderResponseData.registryData!==undefined){var registryData=data.builderResponseData.registryData +$.oc.builder.dataRegistry.set(registryData.pluginCode,'model-classes',null,registryData.models)}} +$.oc.builder.entityControllers.model=Model;}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +if($.oc.builder.entityControllers===undefined) +$.oc.builder.entityControllers={} +var Base=$.oc.builder.entityControllers.base,BaseProto=Base.prototype +var ModelForm=function(indexController){Base.call(this,'modelForm',indexController)} +ModelForm.prototype=Object.create(BaseProto) +ModelForm.prototype.constructor=ModelForm +ModelForm.prototype.cmdCreateForm=function(ev){var $link=$(ev.currentTarget),data={model_class:$link.data('modelClass')} +this.indexController.openOrLoadMasterTab($link,'onModelFormCreateOrOpen',this.newTabId(),data)} +ModelForm.prototype.cmdSaveForm=function(ev){var $target=$(ev.currentTarget),$form=$target.closest('form'),$rootContainer=$('[data-root-control-wrapper] > [data-control-container]',$form),$inspectorContainer=$form.find('.inspector-container'),controls=$.oc.builder.formbuilder.domToPropertyJson.convert($rootContainer.get(0)) +if(!$.oc.inspector.manager.applyValuesFromContainer($inspectorContainer)){return} +if(controls===false){$.oc.flashMsg({'text':$.oc.builder.formbuilder.domToPropertyJson.getLastError(),'class':'error','interval':5}) +return} +var data={controls:controls} +$target.request('onModelFormSave',{data:data}).done(this.proxy(this.saveFormDone))} +ModelForm.prototype.cmdOpenForm=function(ev){var form=$(ev.currentTarget).data('form'),model=$(ev.currentTarget).data('modelClass') +this.indexController.openOrLoadMasterTab($(ev.target),'onModelFormCreateOrOpen',this.makeTabId(model+'-'+form),{file_name:form,model_class:model})} +ModelForm.prototype.cmdDeleteForm=function(ev){var $target=$(ev.currentTarget) +$.oc.confirm($target.data('confirm'),this.proxy(this.deleteConfirmed))} +ModelForm.prototype.cmdAddControl=function(ev){$.oc.builder.formbuilder.controlPalette.addControl(ev)} +ModelForm.prototype.cmdUndockControlPalette=function(ev){$.oc.builder.formbuilder.controlPalette.undockFromContainer(ev)} +ModelForm.prototype.cmdDockControlPalette=function(ev){$.oc.builder.formbuilder.controlPalette.dockToContainer(ev)} +ModelForm.prototype.cmdCloseControlPalette=function(ev){$.oc.builder.formbuilder.controlPalette.closeInContainer(ev)} +ModelForm.prototype.saveFormDone=function(data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +var $masterTabPane=this.getMasterTabsActivePane() +$masterTabPane.find('input[name=file_name]').val(data.builderResponseData.builderObjectName) +this.updateMasterTabIdAndTitle($masterTabPane,data.builderResponseData) +this.unhideFormDeleteButton($masterTabPane) +this.getModelList().fileList('markActive',data.builderResponseData.tabId) +this.getIndexController().unchangeTab($masterTabPane) +this.updateDataRegistry(data)} +ModelForm.prototype.updateDataRegistry=function(data){if(data.builderResponseData.registryData!==undefined){var registryData=data.builderResponseData.registryData +$.oc.builder.dataRegistry.set(registryData.pluginCode,'model-forms',registryData.modelClass,registryData.forms)}} +ModelForm.prototype.deleteConfirmed=function(){var $masterTabPane=this.getMasterTabsActivePane(),$form=$masterTabPane.find('form') +$.oc.stripeLoadIndicator.show() +$form.request('onModelFormDelete').always($.oc.builder.indexController.hideStripeIndicatorProxy).done(this.proxy(this.deleteDone))} +ModelForm.prototype.deleteDone=function(data){var $masterTabPane=this.getMasterTabsActivePane() +this.getIndexController().unchangeTab($masterTabPane) +this.forceCloseTab($masterTabPane) +this.updateDataRegistry(data)} +ModelForm.prototype.getModelList=function(){return $('#layout-side-panel form[data-content-id=models] [data-control=filelist]')} +$.oc.builder.entityControllers.modelForm=ModelForm;}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +if($.oc.builder.entityControllers===undefined) +$.oc.builder.entityControllers={} +var Base=$.oc.builder.entityControllers.base,BaseProto=Base.prototype +var ModelList=function(indexController){this.cachedModelFieldsPromises={} +Base.call(this,'modelList',indexController)} +ModelList.prototype=Object.create(BaseProto) +ModelList.prototype.constructor=ModelList +ModelList.prototype.registerHandlers=function(){$(document).on('autocompleteitems.oc.table','form[data-sub-entity="model-list"] [data-control=table]',this.proxy(this.onAutocompleteItems))} +ModelList.prototype.cmdCreateList=function(ev){var $link=$(ev.currentTarget),data={model_class:$link.data('modelClass')} +var result=this.indexController.openOrLoadMasterTab($link,'onModelListCreateOrOpen',this.newTabId(),data) +if(result!==false){result.done(this.proxy(this.onListLoaded,this))}} +ModelList.prototype.cmdSaveList=function(ev){var $target=$(ev.currentTarget),$form=$target.closest('form') +if(!this.validateTable($target)){return} +$target.request('onModelListSave',{data:{columns:this.getTableData($target)}}).done(this.proxy(this.saveListDone))} +ModelList.prototype.cmdOpenList=function(ev){var list=$(ev.currentTarget).data('list'),model=$(ev.currentTarget).data('modelClass') +var result=this.indexController.openOrLoadMasterTab($(ev.target),'onModelListCreateOrOpen',this.makeTabId(model+'-'+list),{file_name:list,model_class:model}) +if(result!==false){result.done(this.proxy(this.onListLoaded,this))}} +ModelList.prototype.cmdDeleteList=function(ev){var $target=$(ev.currentTarget) +$.oc.confirm($target.data('confirm'),this.proxy(this.deleteConfirmed))} +ModelList.prototype.cmdAddDatabaseColumns=function(ev){var $target=$(ev.currentTarget) +$.oc.stripeLoadIndicator.show() +$target.request('onModelListLoadDatabaseColumns').done(this.proxy(this.databaseColumnsLoaded)).always($.oc.builder.indexController.hideStripeIndicatorProxy)} +ModelList.prototype.saveListDone=function(data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +var $masterTabPane=this.getMasterTabsActivePane() +$masterTabPane.find('input[name=file_name]').val(data.builderResponseData.builderObjectName) +this.updateMasterTabIdAndTitle($masterTabPane,data.builderResponseData) +this.unhideFormDeleteButton($masterTabPane) +this.getModelList().fileList('markActive',data.builderResponseData.tabId) +this.getIndexController().unchangeTab($masterTabPane) +this.updateDataRegistry(data)} +ModelList.prototype.deleteConfirmed=function(){var $masterTabPane=this.getMasterTabsActivePane(),$form=$masterTabPane.find('form') +$.oc.stripeLoadIndicator.show() +$form.request('onModelListDelete').always($.oc.builder.indexController.hideStripeIndicatorProxy).done(this.proxy(this.deleteDone))} +ModelList.prototype.deleteDone=function(data){var $masterTabPane=this.getMasterTabsActivePane() +this.getIndexController().unchangeTab($masterTabPane) +this.forceCloseTab($masterTabPane) +this.updateDataRegistry(data)} +ModelList.prototype.getTableControlObject=function($target){var $form=$target.closest('form'),$table=$form.find('[data-control=table]'),tableObj=$table.data('oc.table') +if(!tableObj){throw new Error('Table object is not found on the model list tab')} +return tableObj} +ModelList.prototype.getModelList=function(){return $('#layout-side-panel form[data-content-id=models] [data-control=filelist]')} +ModelList.prototype.validateTable=function($target){var tableObj=this.getTableControlObject($target) +tableObj.unfocusTable() +return tableObj.validate()} +ModelList.prototype.getTableData=function($target){var tableObj=this.getTableControlObject($target) +return tableObj.dataSource.getAllData()} +ModelList.prototype.loadModelFields=function(table,callback){var $form=$(table).closest('form'),modelClass=$form.find('input[name=model_class]').val(),cachedFields=$form.data('oc.model-field-cache') +if(cachedFields!==undefined){callback(cachedFields) +return} +if(this.cachedModelFieldsPromises[modelClass]===undefined){this.cachedModelFieldsPromises[modelClass]=$form.request('onModelFormGetModelFields',{data:{'as_plain_list':1}})} +if(callback===undefined){return} +this.cachedModelFieldsPromises[modelClass].done(function(data){$form.data('oc.model-field-cache',data.responseData.options) +callback(data.responseData.options)})} +ModelList.prototype.updateDataRegistry=function(data){if(data.builderResponseData.registryData!==undefined){var registryData=data.builderResponseData.registryData +$.oc.builder.dataRegistry.set(registryData.pluginCode,'model-lists',registryData.modelClass,registryData.lists) +$.oc.builder.dataRegistry.clearCache(registryData.pluginCode,'plugin-lists')}} +ModelList.prototype.databaseColumnsLoaded=function(data){if(!$.isArray(data.responseData.columns)){alert('Invalid server response')} +var $masterTabPane=this.getMasterTabsActivePane(),$form=$masterTabPane.find('form'),existingColumns=this.getColumnNames($form),columnsAdded=false +for(var i in data.responseData.columns){var column=data.responseData.columns[i],type=this.mapType(column.type) +if($.inArray(column.name,existingColumns)!==-1){continue} +this.addColumn($form,column.name,type) +columnsAdded=true} +if(!columnsAdded){alert($form.attr('data-lang-all-database-columns-exist'))} +else{$form.trigger('change')}} +ModelList.prototype.mapType=function(type){switch(type){case'integer':return'number' +case'timestamp':return'datetime' +default:return'text'}} +ModelList.prototype.addColumn=function($target,column,type){var tableObj=this.getTableControlObject($target),currentData=this.getTableData($target),rowData={field:column,label:column,type:type} +tableObj.addRecord('bottom',true) +tableObj.setRowValues(currentData.length-1,rowData) +tableObj.addRecord('bottom',false) +tableObj.deleteRecord()} +ModelList.prototype.getColumnNames=function($target){var tableObj=this.getTableControlObject($target) +tableObj.unfocusTable() +var data=this.getTableData($target),result=[] +for(var index in data){if(data[index].field!==undefined){result.push($.trim(data[index].field))}} +return result} +ModelList.prototype.onAutocompleteItems=function(ev,data){if(data.columnConfiguration.fillFrom==='model-fields'){ev.preventDefault() +this.loadModelFields(ev.target,data.callback) +return false}} +ModelList.prototype.onListLoaded=function(){$(document).trigger('render') +var $masterTabPane=this.getMasterTabsActivePane(),$form=$masterTabPane.find('form'),$toolbar=$masterTabPane.find('div[data-control=table] div.toolbar'),$button=$('') +$button.text($form.attr('data-lang-add-database-columns'));$toolbar.append($button)} +$.oc.builder.entityControllers.modelList=ModelList;}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +if($.oc.builder.entityControllers===undefined) +$.oc.builder.entityControllers={} +var Base=$.oc.builder.entityControllers.base,BaseProto=Base.prototype +var Permission=function(indexController){Base.call(this,'permissions',indexController)} +Permission.prototype=Object.create(BaseProto) +Permission.prototype.constructor=Permission +Permission.prototype.registerHandlers=function(){this.indexController.$masterTabs.on('oc.tableNewRow',this.proxy(this.onTableRowCreated))} +Permission.prototype.cmdOpenPermissions=function(ev){var currentPlugin=this.getSelectedPlugin() +if(!currentPlugin){alert('Please select a plugin first') +return} +this.indexController.openOrLoadMasterTab($(ev.target),'onPermissionsOpen',this.makeTabId(currentPlugin))} +Permission.prototype.cmdSavePermissions=function(ev){var $target=$(ev.currentTarget),$form=$target.closest('form') +if(!this.validateTable($target)){return} +$target.request('onPermissionsSave',{data:{permissions:this.getTableData($target)}}).done(this.proxy(this.savePermissionsDone))} +Permission.prototype.getTableControlObject=function($target){var $form=$target.closest('form'),$table=$form.find('[data-control=table]'),tableObj=$table.data('oc.table') +if(!tableObj){throw new Error('Table object is not found on permissions tab')} +return tableObj} +Permission.prototype.validateTable=function($target){var tableObj=this.getTableControlObject($target) +tableObj.unfocusTable() +return tableObj.validate()} +Permission.prototype.getTableData=function($target){var tableObj=this.getTableControlObject($target) +return tableObj.dataSource.getAllData()} +Permission.prototype.savePermissionsDone=function(data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +var $masterTabPane=this.getMasterTabsActivePane() +this.getIndexController().unchangeTab($masterTabPane) +$.oc.builder.dataRegistry.clearCache(data.builderResponseData.pluginCode,'permissions')} +Permission.prototype.onTableRowCreated=function(ev,recordData){var $target=$(ev.target) +if($target.data('alias')!='permissions'){return} +var $form=$target.closest('form') +if($form.data('entity')!='permissions'){return} +var pluginCode=$form.find('input[name=plugin_code]').val() +recordData.permission=pluginCode.toLowerCase()+'.';} +$.oc.builder.entityControllers.permission=Permission;}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +if($.oc.builder.entityControllers===undefined) +$.oc.builder.entityControllers={} +var Base=$.oc.builder.entityControllers.base,BaseProto=Base.prototype +var Menus=function(indexController){Base.call(this,'menus',indexController)} +Menus.prototype=Object.create(BaseProto) +Menus.prototype.constructor=Menus +Menus.prototype.cmdOpenMenus=function(ev){var currentPlugin=this.getSelectedPlugin() +if(!currentPlugin){alert('Please select a plugin first') +return} +this.indexController.openOrLoadMasterTab($(ev.target),'onMenusOpen',this.makeTabId(currentPlugin))} +Menus.prototype.cmdSaveMenus=function(ev){var $target=$(ev.currentTarget),$form=$target.closest('form'),$inspectorContainer=$form.find('.inspector-container') +if(!$.oc.inspector.manager.applyValuesFromContainer($inspectorContainer)){return} +var menus=$.oc.builder.menubuilder.controller.getJson($form.get(0)) +$target.request('onMenusSave',{data:{menus:menus}}).done(this.proxy(this.saveMenusDone))} +Menus.prototype.cmdAddMainMenuItem=function(ev){$.oc.builder.menubuilder.controller.addMainMenuItem(ev)} +Menus.prototype.cmdAddSideMenuItem=function(ev){$.oc.builder.menubuilder.controller.addSideMenuItem(ev)} +Menus.prototype.cmdDeleteMenuItem=function(ev){$.oc.builder.menubuilder.controller.deleteMenuItem(ev)} +Menus.prototype.saveMenusDone=function(data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +var $masterTabPane=this.getMasterTabsActivePane() +this.getIndexController().unchangeTab($masterTabPane)} +$.oc.builder.entityControllers.menus=Menus;}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +if($.oc.builder.entityControllers===undefined) +$.oc.builder.entityControllers={} +var Base=$.oc.builder.entityControllers.base,BaseProto=Base.prototype +var Version=function(indexController){Base.call(this,'version',indexController) +this.hiddenHints={}} +Version.prototype=Object.create(BaseProto) +Version.prototype.constructor=Version +Version.prototype.cmdCreateVersion=function(ev){var $link=$(ev.currentTarget),versionType=$link.data('versionType') +this.indexController.openOrLoadMasterTab($link,'onVersionCreateOrOpen',this.newTabId(),{version_type:versionType})} +Version.prototype.cmdSaveVersion=function(ev){var $target=$(ev.currentTarget),$form=$target.closest('form') +$target.request('onVersionSave').done(this.proxy(this.saveVersionDone))} +Version.prototype.cmdOpenVersion=function(ev){var versionNumber=$(ev.currentTarget).data('id'),pluginCode=$(ev.currentTarget).data('pluginCode') +this.indexController.openOrLoadMasterTab($(ev.target),'onVersionCreateOrOpen',this.makeTabId(pluginCode+'-'+versionNumber),{original_version:versionNumber})} +Version.prototype.cmdDeleteVersion=function(ev){var $target=$(ev.currentTarget) +$.oc.confirm($target.data('confirm'),this.proxy(this.deleteConfirmed))} +Version.prototype.cmdApplyVersion=function(ev){var $target=$(ev.currentTarget),$pane=$target.closest('div.tab-pane'),self=this +this.showHintPopup($pane,'builder-version-apply',function(){$target.request('onVersionApply').done(self.proxy(self.applyVersionDone))})} +Version.prototype.cmdRollbackVersion=function(ev){var $target=$(ev.currentTarget),$pane=$target.closest('div.tab-pane'),self=this +this.showHintPopup($pane,'builder-version-rollback',function(){$target.request('onVersionRollback').done(self.proxy(self.rollbackVersionDone))})} +Version.prototype.saveVersionDone=function(data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +var $masterTabPane=this.getMasterTabsActivePane() +this.updateUiAfterSave($masterTabPane,data) +if(!data.builderResponseData.isApplied){this.showSavedNotAppliedHint($masterTabPane)}} +Version.prototype.showSavedNotAppliedHint=function($masterTabPane){this.showHintPopup($masterTabPane,'builder-version-save-unapplied')} +Version.prototype.showHintPopup=function($masterTabPane,code,callback){if(this.getDontShowHintAgain(code,$masterTabPane)){if(callback){callback.apply(this)} +return} +$masterTabPane.one('hide.oc.popup',this.proxy(this.onHintPopupHide)) +if(callback){$masterTabPane.one('shown.oc.popup',function(ev,$element,$modal){$modal.find('form').one('submit',function(ev){callback.apply(this) +ev.preventDefault() +$(ev.target).trigger('close.oc.popup') +return false})})} +$masterTabPane.popup({content:this.getPopupContent($masterTabPane,code)})} +Version.prototype.onHintPopupHide=function(ev,$element,$modal){var cbValue=$modal.find('input[type=checkbox][name=dont_show_again]').is(':checked'),code=$modal.find('input[type=hidden][name=hint_code]').val() +$modal.find('form').off('submit') +if(!cbValue){return} +var $form=this.getMasterTabsActivePane().find('form[data-entity="versions"]') +$form.request('onHideBackendHint',{data:{name:code}}) +this.setDontShowHintAgain(code)} +Version.prototype.setDontShowHintAgain=function(code){this.hiddenHints[code]=true} +Version.prototype.getDontShowHintAgain=function(code,$pane){if(this.hiddenHints[code]!==undefined){return this.hiddenHints[code]} +return $pane.find('input[type=hidden][data-hint-hidden="'+code+'"]').val()=="true"} +Version.prototype.getPopupContent=function($pane,code){var template=$pane.find('script[data-version-hint-template="'+code+'"]') +if(template.length===0){throw new Error('Version popup template not found: '+code)} +return template.html()} +Version.prototype.updateUiAfterSave=function($masterTabPane,data){$masterTabPane.find('input[name=original_version]').val(data.builderResponseData.savedVersion) +this.updateMasterTabIdAndTitle($masterTabPane,data.builderResponseData) +this.unhideFormDeleteButton($masterTabPane) +this.getVersionList().fileList('markActive',data.builderResponseData.tabId) +this.getIndexController().unchangeTab($masterTabPane)} +Version.prototype.deleteConfirmed=function(){var $masterTabPane=this.getMasterTabsActivePane(),$form=$masterTabPane.find('form') +$.oc.stripeLoadIndicator.show() +$form.request('onVersionDelete').always($.oc.builder.indexController.hideStripeIndicatorProxy).done(this.proxy(this.deleteDone))} +Version.prototype.deleteDone=function(){var $masterTabPane=this.getMasterTabsActivePane() +this.getIndexController().unchangeTab($masterTabPane) +this.forceCloseTab($masterTabPane)} +Version.prototype.applyVersionDone=function(data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +var $masterTabPane=this.getMasterTabsActivePane() +this.updateUiAfterSave($masterTabPane,data) +this.updateVersionsButtons()} +Version.prototype.rollbackVersionDone=function(data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +var $masterTabPane=this.getMasterTabsActivePane() +this.updateUiAfterSave($masterTabPane,data) +this.updateVersionsButtons()} +Version.prototype.getVersionList=function(){return $('#layout-side-panel form[data-content-id=version] [data-control=filelist]')} +Version.prototype.updateVersionsButtons=function(){var tabsObject=this.getMasterTabsObject(),$tabs=tabsObject.$tabsContainer.find('> li'),$versionList=this.getVersionList() +for(var i=$tabs.length-1;i>=0;i--){var $tab=$($tabs[i]),tabId=$tab.data('tabId') +if(!tabId||String(tabId).length==0){continue} +var $versionLi=$versionList.find('li[data-id="'+tabId+'"]') +if(!$versionLi.length){continue} +var isApplied=$versionLi.data('applied'),$pane=tabsObject.findPaneFromTab($tab) +if(isApplied){$pane.find('[data-builder-command="version:cmdApplyVersion"]').addClass('hide') +$pane.find('[data-builder-command="version:cmdRollbackVersion"]').removeClass('hide')} +else{$pane.find('[data-builder-command="version:cmdApplyVersion"]').removeClass('hide') +$pane.find('[data-builder-command="version:cmdRollbackVersion"]').addClass('hide')}}} +$.oc.builder.entityControllers.version=Version;}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +if($.oc.builder.entityControllers===undefined) +$.oc.builder.entityControllers={} +var Base=$.oc.builder.entityControllers.base,BaseProto=Base.prototype +var Localization=function(indexController){Base.call(this,'localization',indexController)} +Localization.prototype=Object.create(BaseProto) +Localization.prototype.constructor=Localization +Localization.prototype.cmdCreateLanguage=function(ev){this.indexController.openOrLoadMasterTab($(ev.target),'onLanguageCreateOrOpen',this.newTabId())} +Localization.prototype.cmdOpenLanguage=function(ev){var language=$(ev.currentTarget).data('id'),pluginCode=$(ev.currentTarget).data('pluginCode') +this.indexController.openOrLoadMasterTab($(ev.target),'onLanguageCreateOrOpen',this.makeTabId(pluginCode+'-'+language),{original_language:language})} +Localization.prototype.cmdSaveLanguage=function(ev){var $target=$(ev.currentTarget),$form=$target.closest('form') +$target.request('onLanguageSave').done(this.proxy(this.saveLanguageDone))} +Localization.prototype.cmdDeleteLanguage=function(ev){var $target=$(ev.currentTarget) +$.oc.confirm($target.data('confirm'),this.proxy(this.deleteConfirmed))} +Localization.prototype.cmdCopyMissingStrings=function(ev){var $form=$(ev.currentTarget),language=$form.find('select[name=language]').val(),$masterTabPane=this.getMasterTabsActivePane() +$form.trigger('close.oc.popup') +$.oc.stripeLoadIndicator.show() +$masterTabPane.find('form').request('onLanguageCopyStringsFrom',{data:{copy_from:language}}).always($.oc.builder.indexController.hideStripeIndicatorProxy).done(this.proxy(this.copyStringsFromDone))} +Localization.prototype.languageUpdated=function(plugin){var languageForm=this.findDefaultLanguageForm(plugin) +if(!languageForm){return} +var $languageForm=$(languageForm) +if(!$languageForm.hasClass('oc-data-changed')){this.updateLanguageFromServer($languageForm)} +else{this.mergeLanguageFromServer($languageForm)}} +Localization.prototype.updateOnScreenStrings=function(plugin){var stringElements=document.body.querySelectorAll('span[data-localization-key][data-plugin="'+plugin+'"]') +$.oc.builder.dataRegistry.get($('#builder-plugin-selector-panel form'),plugin,'localization',null,function(data){for(var i=stringElements.length-1;i>=0;i--){var stringElement=stringElements[i],stringKey=stringElement.getAttribute('data-localization-key') +if(data[stringKey]!==undefined){stringElement.textContent=data[stringKey]} +else{stringElement.textContent=stringKey}}})} +Localization.prototype.saveLanguageDone=function(data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +var $masterTabPane=this.getMasterTabsActivePane() +$masterTabPane.find('input[name=original_language]').val(data.builderResponseData.language) +this.updateMasterTabIdAndTitle($masterTabPane,data.builderResponseData) +this.unhideFormDeleteButton($masterTabPane) +this.getLanguageList().fileList('markActive',data.builderResponseData.tabId) +this.getIndexController().unchangeTab($masterTabPane) +if(data.builderResponseData.registryData!==undefined){var registryData=data.builderResponseData.registryData +$.oc.builder.dataRegistry.set(registryData.pluginCode,'localization',null,registryData.strings,{suppressLanguageEditorUpdate:true}) +$.oc.builder.dataRegistry.set(registryData.pluginCode,'localization','sections',registryData.sections)}} +Localization.prototype.getLanguageList=function(){return $('#layout-side-panel form[data-content-id=localization] [data-control=filelist]')} +Localization.prototype.getCodeEditor=function($tab){return $tab.find('div[data-field-name=strings] div[data-control=codeeditor]').data('oc.codeEditor').editor} +Localization.prototype.deleteConfirmed=function(){var $masterTabPane=this.getMasterTabsActivePane(),$form=$masterTabPane.find('form') +$.oc.stripeLoadIndicator.show() +$form.request('onLanguageDelete').always($.oc.builder.indexController.hideStripeIndicatorProxy).done(this.proxy(this.deleteDone))} +Localization.prototype.deleteDone=function(){var $masterTabPane=this.getMasterTabsActivePane() +this.getIndexController().unchangeTab($masterTabPane) +this.forceCloseTab($masterTabPane)} +Localization.prototype.copyStringsFromDone=function(data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +var responseData=data.builderResponseData,$masterTabPane=this.getMasterTabsActivePane(),$form=$masterTabPane.find('form'),codeEditor=this.getCodeEditor($masterTabPane),newStringMessage=$form.data('newStringMessage'),mismatchMessage=$form.data('structureMismatch') +codeEditor.getSession().setValue(responseData.strings) +var annotations=[] +for(var i=responseData.updatedLines.length-1;i>=0;i--){var line=responseData.updatedLines[i] +annotations.push({row:line,column:0,text:newStringMessage,type:'warning'})} +codeEditor.getSession().setAnnotations(annotations) +if(responseData.mismatch){$.oc.alert(mismatchMessage)}} +Localization.prototype.findDefaultLanguageForm=function(plugin){var forms=document.body.querySelectorAll('form[data-entity=localization]') +for(var i=forms.length-1;i>=0;i--){var form=forms[i],pluginInput=form.querySelector('input[name=plugin_code]'),languageInput=form.querySelector('input[name=original_language]') +if(!pluginInput||pluginInput.value!=plugin){continue} +if(!languageInput){continue} +if(form.getAttribute('data-default-language')==languageInput.value){return form}} +return null} +Localization.prototype.updateLanguageFromServer=function($languageForm){var self=this +$languageForm.request('onLanguageGetStrings').done(function(data){self.updateLanguageFromServerDone($languageForm,data)})} +Localization.prototype.updateLanguageFromServerDone=function($languageForm,data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +var responseData=data.builderResponseData,$tabPane=$languageForm.closest('.tab-pane'),codeEditor=this.getCodeEditor($tabPane) +if(!responseData.strings){return} +codeEditor.getSession().setValue(responseData.strings) +this.unmodifyTab($tabPane)} +Localization.prototype.mergeLanguageFromServer=function($languageForm){var language=$languageForm.find('input[name=original_language]').val(),self=this +$languageForm.request('onLanguageCopyStringsFrom',{data:{copy_from:language}}).done(function(data){self.mergeLanguageFromServerDone($languageForm,data)})} +Localization.prototype.mergeLanguageFromServerDone=function($languageForm,data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +var responseData=data.builderResponseData,$tabPane=$languageForm.closest('.tab-pane'),codeEditor=this.getCodeEditor($tabPane) +codeEditor.getSession().setValue(responseData.strings) +codeEditor.getSession().setAnnotations([])} +$.oc.builder.entityControllers.localization=Localization;}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +if($.oc.builder.entityControllers===undefined) +$.oc.builder.entityControllers={} +var Base=$.oc.builder.entityControllers.base,BaseProto=Base.prototype +var Controller=function(indexController){Base.call(this,'controller',indexController)} +Controller.prototype=Object.create(BaseProto) +Controller.prototype.constructor=Controller +Controller.prototype.cmdCreateController=function(ev){var $form=$(ev.currentTarget),self=this,pluginCode=$form.data('pluginCode'),behaviorsSelected=$form.find('input[name="behaviors[]"]:checked').length,promise=null +if(behaviorsSelected){promise=this.indexController.openOrLoadMasterTab($form,'onControllerCreate',this.makeTabId(pluginCode+'-new-controller'),{})} +else{promise=$form.request('onControllerCreate')} +promise.done(function(data){$form.trigger('close.oc.popup') +self.updateDataRegistry(data)}).always($.oc.builder.indexController.hideStripeIndicatorProxy)} +Controller.prototype.cmdOpenController=function(ev){var controller=$(ev.currentTarget).data('id'),pluginCode=$(ev.currentTarget).data('pluginCode') +this.indexController.openOrLoadMasterTab($(ev.target),'onControllerOpen',this.makeTabId(pluginCode+'-'+controller),{controller:controller})} +Controller.prototype.cmdSaveController=function(ev){var $target=$(ev.currentTarget),$form=$target.closest('form'),$inspectorContainer=$form.find('.inspector-container') +if(!$.oc.inspector.manager.applyValuesFromContainer($inspectorContainer)){return} +$target.request('onControllerSave').done(this.proxy(this.saveControllerDone))} +Controller.prototype.saveControllerDone=function(data){if(data['builderResponseData']===undefined){throw new Error('Invalid response data')} +var $masterTabPane=this.getMasterTabsActivePane() +this.getIndexController().unchangeTab($masterTabPane)} +Controller.prototype.updateDataRegistry=function(data){if(data.builderResponseData.registryData!==undefined){var registryData=data.builderResponseData.registryData +$.oc.builder.dataRegistry.set(registryData.pluginCode,'controller-urls',null,registryData.urls)}} +Controller.prototype.getControllerList=function(){return $('#layout-side-panel form[data-content-id=controller] [data-control=filelist]')} +$.oc.builder.entityControllers.controller=Controller;}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +var Base=$.oc.foundation.base,BaseProto=Base.prototype +var Builder=function(){Base.call(this) +this.$masterTabs=null +this.masterTabsObj=null +this.hideStripeIndicatorProxy=null +this.entityControllers={} +this.init()} +Builder.prototype=Object.create(BaseProto) +Builder.prototype.constructor=Builder +Builder.prototype.dispose=function(){BaseProto.dispose.call(this)} +Builder.prototype.openOrLoadMasterTab=function($form,serverHandlerName,tabId,data){if(this.masterTabsObj.goTo(tabId)) +return false +var requestData=data===undefined?{}:data +$.oc.stripeLoadIndicator.show() +var promise=$form.request(serverHandlerName,{data:requestData}).done(this.proxy(this.addMasterTab)).always(this.hideStripeIndicatorProxy) +return promise} +Builder.prototype.getMasterTabActivePane=function(){return this.$masterTabs.find('> .tab-content > .tab-pane.active')} +Builder.prototype.unchangeTab=function($pane){$pane.find('form').trigger('unchange.oc.changeMonitor')} +Builder.prototype.triggerCommand=function(command,ev){var commandParts=command.split(':') +if(commandParts.length===2){var entity=commandParts[0],commandToExecute=commandParts[1] +if(this.entityControllers[entity]===undefined){throw new Error('Unknown entity type: '+entity)} +this.entityControllers[entity].invokeCommand(commandToExecute,ev)}} +Builder.prototype.init=function(){this.$masterTabs=$('#builder-master-tabs') +this.$sidePanel=$('#builder-side-panel') +this.masterTabsObj=this.$masterTabs.data('oc.tab') +this.hideStripeIndicatorProxy=this.proxy(this.hideStripeIndicator) +new $.oc.tabFormExpandControls(this.$masterTabs) +this.createEntityControllers() +this.registerHandlers()} +Builder.prototype.createEntityControllers=function(){for(var controller in $.oc.builder.entityControllers){if(controller=="base"){continue} +this.entityControllers[controller]=new $.oc.builder.entityControllers[controller](this)}} +Builder.prototype.registerHandlers=function(){$(document).on('click','[data-builder-command]',this.proxy(this.onCommand)) +$(document).on('submit','[data-builder-command]',this.proxy(this.onCommand)) +this.$masterTabs.on('changed.oc.changeMonitor',this.proxy(this.onFormChanged)) +this.$masterTabs.on('unchanged.oc.changeMonitor',this.proxy(this.onFormUnchanged)) +this.$masterTabs.on('shown.bs.tab',this.proxy(this.onTabShown)) +this.$masterTabs.on('afterAllClosed.oc.tab',this.proxy(this.onAllTabsClosed)) +this.$masterTabs.on('closed.oc.tab',this.proxy(this.onTabClosed)) +this.$masterTabs.on('autocompleteitems.oc.inspector',this.proxy(this.onDataRegistryItems)) +this.$masterTabs.on('dropdownoptions.oc.inspector',this.proxy(this.onDataRegistryItems)) +for(var controller in this.entityControllers){if(this.entityControllers[controller].registerHandlers!==undefined){this.entityControllers[controller].registerHandlers()}}} +Builder.prototype.hideStripeIndicator=function(){$.oc.stripeLoadIndicator.hide()} +Builder.prototype.addMasterTab=function(data){this.masterTabsObj.addTab(data.tabTitle,data.tab,data.tabId,'oc-'+data.tabIcon) +if(data.isNewRecord){var $masterTabPane=this.getMasterTabActivePane() +$masterTabPane.find('form').one('ready.oc.changeMonitor',this.proxy(this.onChangeMonitorReady))}} +Builder.prototype.updateModifiedCounter=function(){var counters={database:{menu:'database',count:0},models:{menu:'models',count:0},permissions:{menu:'permissions',count:0},menus:{menu:'menus',count:0},versions:{menu:'versions',count:0},localization:{menu:'localization',count:0},controller:{menu:'controllers',count:0}} +$('> div.tab-content > div.tab-pane[data-modified] > form',this.$masterTabs).each(function(){var entity=$(this).data('entity') +counters[entity].count++}) +$.each(counters,function(type,data){$.oc.sideNav.setCounter('builder/'+data.menu,data.count);})} +Builder.prototype.getFormPluginCode=function(formElement){var $form=$(formElement).closest('form'),$input=$form.find('input[name="plugin_code"]'),code=$input.val() +if(!code){throw new Error('Plugin code input is not found in the form.')} +return code} +Builder.prototype.setPageTitle=function(title){$.oc.layout.setPageTitle(title.length?(title+' | '):title)} +Builder.prototype.getFileLists=function(){return $('[data-control=filelist]',this.$sidePanel)} +Builder.prototype.dataToInspectorArray=function(data){var result=[] +for(var key in data){var item={title:data[key],value:key} +result.push(item)} +return result} +Builder.prototype.onCommand=function(ev){if(ev.currentTarget.tagName=='FORM'&&ev.type=='click'){return} +var command=$(ev.currentTarget).data('builderCommand') +this.triggerCommand(command,ev) +var $target=$(ev.currentTarget) +if(ev.currentTarget.tagName==='A'&&$target.attr('role')=='menuitem'&&$target.attr('href')=='javascript:;'){return} +ev.preventDefault() +return false} +Builder.prototype.onFormChanged=function(ev){$('.form-tabless-fields',ev.target).trigger('modified.oc.tab') +this.updateModifiedCounter()} +Builder.prototype.onFormUnchanged=function(ev){$('.form-tabless-fields',ev.target).trigger('unmodified.oc.tab') +this.updateModifiedCounter()} +Builder.prototype.onTabShown=function(ev){var $tabControl=$(ev.target).closest('[data-control=tab]') +if($tabControl.attr('id')!=this.$masterTabs.attr('id')){return} +var dataId=$(ev.target).closest('li').attr('data-tab-id'),title=$(ev.target).attr('title') +if(title){this.setPageTitle(title)} +this.getFileLists().fileList('markActive',dataId) +$(window).trigger('resize')} +Builder.prototype.onAllTabsClosed=function(ev){this.setPageTitle('') +this.getFileLists().fileList('markActive',null)} +Builder.prototype.onTabClosed=function(ev,tab,pane){$(pane).find('form').off('ready.oc.changeMonitor',this.proxy(this.onChangeMonitorReady)) +this.updateModifiedCounter()} +Builder.prototype.onChangeMonitorReady=function(ev){$(ev.target).trigger('change')} +Builder.prototype.onDataRegistryItems=function(ev,data){var self=this +if(data.propertyDefinition.fillFrom=='model-classes'||data.propertyDefinition.fillFrom=='model-forms'||data.propertyDefinition.fillFrom=='model-lists'||data.propertyDefinition.fillFrom=='controller-urls'||data.propertyDefinition.fillFrom=='model-columns'||data.propertyDefinition.fillFrom=='plugin-lists'||data.propertyDefinition.fillFrom=='permissions'){ev.preventDefault() +var subtype=null,subtypeProperty=data.propertyDefinition.subtypeFrom +if(subtypeProperty!==undefined){subtype=data.values[subtypeProperty]} +$.oc.builder.dataRegistry.get($(ev.target),this.getFormPluginCode(ev.target),data.propertyDefinition.fillFrom,subtype,function(response){data.callback({options:self.dataToInspectorArray(response)})})}} +$(document).ready(function(){$.oc.builder.indexController=new Builder()})}(window.jQuery);+function($){"use strict";if($.oc.builder===undefined) +$.oc.builder={} +var Base=$.oc.foundation.base,BaseProto=Base.prototype +var LocalizationInput=function(input,form,options){this.input=input +this.form=form +this.options=$.extend({},LocalizationInput.DEFAULTS,options) +this.disposed=false +this.initialized=false +this.newStringPopupMarkup=null +Base.call(this) +this.init()} +LocalizationInput.prototype=Object.create(BaseProto) +LocalizationInput.prototype.constructor=LocalizationInput +LocalizationInput.prototype.dispose=function(){this.unregisterHandlers() +this.form=null +this.options.beforePopupShowCallback=null +this.options.afterPopupHideCallback=null +this.options=null +this.disposed=true +this.newStringPopupMarkup=null +if(this.initialized){$(this.input).autocomplete('destroy')} +$(this.input).removeData('localization-input') +this.input=null +BaseProto.dispose.call(this)} +LocalizationInput.prototype.init=function(){if(!this.options.plugin){throw new Error('The options.plugin value should be set in the localization input object.')} +var $input=$(this.input) +$input.data('localization-input',this) +$input.attr('data-builder-localization-input','true') +$input.attr('data-builder-localization-plugin',this.options.plugin) +this.getContainer().addClass('localization-input-container') +this.registerHandlers() +this.loadDataAndBuild()} +LocalizationInput.prototype.buildAddLink=function(){var $container=this.getContainer() +if($container.find('a.localization-trigger').length>0){return} +var trigger=document.createElement('a') +trigger.setAttribute('class','oc-icon-plus localization-trigger') +trigger.setAttribute('href','#') +var pos=$container.position() +$(trigger).css({top:pos.top+4,right:7}) +$container.append(trigger)} +LocalizationInput.prototype.loadDataAndBuild=function(){this.showLoadingIndicator() +var result=$.oc.builder.dataRegistry.get(this.form,this.options.plugin,'localization',null,this.proxy(this.dataLoaded)),self=this +if(result){result.always(function(){self.hideLoadingIndicator()})}} +LocalizationInput.prototype.reload=function(){$.oc.builder.dataRegistry.get(this.form,this.options.plugin,'localization',null,this.proxy(this.dataLoaded))} +LocalizationInput.prototype.dataLoaded=function(data){if(this.disposed){return} +var $input=$(this.input),autocomplete=$input.data('autocomplete') +if(!autocomplete){this.hideLoadingIndicator() +var autocompleteOptions={source:this.preprocessData(data),matchWidth:true} +autocompleteOptions=$.extend(autocompleteOptions,this.options.autocompleteOptions) +$(this.input).autocomplete(autocompleteOptions) +this.initialized=true} +else{autocomplete.source=this.preprocessData(data)}} +LocalizationInput.prototype.preprocessData=function(data){var dataClone=$.extend({},data) +for(var key in dataClone){dataClone[key]=key+' - '+dataClone[key]} +return dataClone} +LocalizationInput.prototype.getContainer=function(){return $(this.input).closest('.autocomplete-container')} +LocalizationInput.prototype.showLoadingIndicator=function(){var $container=this.getContainer() +$container.addClass('loading-indicator-container size-small') +$container.loadIndicator()} +LocalizationInput.prototype.hideLoadingIndicator=function(){var $container=this.getContainer() +$container.loadIndicator('hide') +$container.loadIndicator('destroy') +$container.removeClass('loading-indicator-container')} +LocalizationInput.prototype.loadAndShowPopup=function(){if(this.newStringPopupMarkup===null){$.oc.stripeLoadIndicator.show() +$(this.input).request('onLanguageLoadAddStringForm').done(this.proxy(this.popupMarkupLoaded)).always(function(){$.oc.stripeLoadIndicator.hide()})} +else{this.showPopup()}} +LocalizationInput.prototype.popupMarkupLoaded=function(responseData){this.newStringPopupMarkup=responseData.markup +this.showPopup()} +LocalizationInput.prototype.showPopup=function(){var $input=$(this.input) +$input.popup({content:this.newStringPopupMarkup}) +var $content=$input.data('oc.popup').$content,$keyInput=$content.find('#language_string_key') +$.oc.builder.dataRegistry.get(this.form,this.options.plugin,'localization','sections',function(data){$keyInput.autocomplete({source:data,matchWidth:true})}) +$content.find('form').on('submit',this.proxy(this.onSubmitPopupForm))} +LocalizationInput.prototype.stringCreated=function(data){if(data.localizationData===undefined||data.registryData===undefined){throw new Error('Invalid server response.')} +var $input=$(this.input) +$input.val(data.localizationData.key) +$.oc.builder.dataRegistry.set(this.options.plugin,'localization',null,data.registryData.strings) +$.oc.builder.dataRegistry.set(this.options.plugin,'localization','sections',data.registryData.sections) +$input.data('oc.popup').hide() +$input.trigger('change')} +LocalizationInput.prototype.onSubmitPopupForm=function(ev){var $form=$(ev.target) +$.oc.stripeLoadIndicator.show() +$form.request('onLanguageCreateString',{data:{plugin_code:this.options.plugin}}).done(this.proxy(this.stringCreated)).always(function(){$.oc.stripeLoadIndicator.hide()}) +ev.preventDefault() +return false} +LocalizationInput.prototype.onPopupHidden=function(ev,link,popup){$(popup).find('#language_string_key').autocomplete('destroy') +$(popup).find('form').on('submit',this.proxy(this.onSubmitPopupForm)) +if(this.options.afterPopupHideCallback){this.options.afterPopupHideCallback()}} +LocalizationInput.updatePluginInputs=function(plugin){var inputs=document.body.querySelectorAll('input[data-builder-localization-input][data-builder-localization-plugin="'+plugin+'"]') +for(var i=inputs.length-1;i>=0;i--){$(inputs[i]).data('localization-input').reload()}} +LocalizationInput.prototype.unregisterHandlers=function(){this.input.removeEventListener('focus',this.proxy(this.onInputFocus)) +this.getContainer().off('click','a.localization-trigger',this.proxy(this.onTriggerClick)) +$(this.input).off('hidden.oc.popup',this.proxy(this.onPopupHidden))} +LocalizationInput.prototype.registerHandlers=function(){this.input.addEventListener('focus',this.proxy(this.onInputFocus)) +this.getContainer().on('click','a.localization-trigger',this.proxy(this.onTriggerClick)) +$(this.input).on('hidden.oc.popup',this.proxy(this.onPopupHidden))} +LocalizationInput.prototype.onInputFocus=function(){this.buildAddLink()} +LocalizationInput.prototype.onTriggerClick=function(ev){if(this.options.beforePopupShowCallback){this.options.beforePopupShowCallback()} +this.loadAndShowPopup() +ev.preventDefault() +return false} +LocalizationInput.DEFAULTS={plugin:null,autocompleteOptions:{},beforePopupShowCallback:null,afterPopupHideCallback:null} +$.oc.builder.localizationInput=LocalizationInput}(window.jQuery);+function($){"use strict";var Base=$.oc.inspector.propertyEditors.string,BaseProto=Base.prototype +var LocalizationEditor=function(inspector,propertyDefinition,containerCell,group){this.localizationInput=null +Base.call(this,inspector,propertyDefinition,containerCell,group)} +LocalizationEditor.prototype=Object.create(BaseProto) +LocalizationEditor.prototype.constructor=Base +LocalizationEditor.prototype.dispose=function(){this.removeLocalizationInput() +BaseProto.dispose.call(this)} +LocalizationEditor.prototype.build=function(){var container=document.createElement('div'),editor=document.createElement('input'),placeholder=this.propertyDefinition.placeholder!==undefined?this.propertyDefinition.placeholder:'',value=this.inspector.getPropertyValue(this.propertyDefinition.property) +editor.setAttribute('type','text') +editor.setAttribute('class','string-editor') +editor.setAttribute('placeholder',placeholder) +container.setAttribute('class','autocomplete-container') +if(value===undefined){value=this.propertyDefinition.default} +if(value===undefined){value=''} +editor.value=value +$.oc.foundation.element.addClass(this.containerCell,'text autocomplete') +container.appendChild(editor) +this.containerCell.appendChild(container) +this.buildLocalizationEditor()} +LocalizationEditor.prototype.buildLocalizationEditor=function(){this.localizationInput=new $.oc.builder.localizationInput(this.getInput(),this.getForm(),{plugin:this.getPluginCode(),beforePopupShowCallback:this.proxy(this.onPopupShown,this),afterPopupHideCallback:this.proxy(this.onPopupHidden,this)})} +LocalizationEditor.prototype.removeLocalizationInput=function(){this.localizationInput.dispose() +this.localizationInput=null} +LocalizationEditor.prototype.supportsExternalParameterEditor=function(){return false} +LocalizationEditor.prototype.registerHandlers=function(){BaseProto.registerHandlers.call(this) +$(this.getInput()).on('change',this.proxy(this.onInputKeyUp))} +LocalizationEditor.prototype.unregisterHandlers=function(){BaseProto.unregisterHandlers.call(this) +$(this.getInput()).off('change',this.proxy(this.onInputKeyUp))} +LocalizationEditor.prototype.getForm=function(){var inspectableElement=this.getRootSurface().getInspectableElement() +if(!inspectableElement){throw new Error('Cannot determine inspectable element in the Builder localization editor.')} +return $(inspectableElement).closest('form')} +LocalizationEditor.prototype.getPluginCode=function(){var $form=this.getForm(),$input=$form.find('input[name=plugin_code]') +if(!$input.length){throw new Error('The input "plugin_code" should be defined in the form in order to use the localization Inspector editor.')} +return $input.val()} +LocalizationEditor.prototype.onPopupShown=function(){this.getRootSurface().popupDisplayed()} +LocalizationEditor.prototype.onPopupHidden=function(){this.getRootSurface().popupHidden()} +$.oc.inspector.propertyEditors.builderLocalization=LocalizationEditor}(window.jQuery);+function($){"use strict";if($.oc.table===undefined) +throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded.");if($.oc.table.processor===undefined) +throw new Error("The $.oc.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");var Base=$.oc.table.processor.string,BaseProto=Base.prototype +var LocalizationProcessor=function(tableObj,columnName,columnConfiguration){this.localizationInput=null +this.popupDisplayed=false +Base.call(this,tableObj,columnName,columnConfiguration)} +LocalizationProcessor.prototype=Object.create(BaseProto) +LocalizationProcessor.prototype.constructor=LocalizationProcessor +LocalizationProcessor.prototype.dispose=function(){this.removeLocalizationInput() +BaseProto.dispose.call(this)} +LocalizationProcessor.prototype.onUnfocus=function(){if(!this.activeCell||this.popupDisplayed) +return +this.removeLocalizationInput() +BaseProto.onUnfocus.call(this)} +LocalizationProcessor.prototype.onBeforePopupShow=function(){this.popupDisplayed=true} +LocalizationProcessor.prototype.onAfterPopupHide=function(){this.popupDisplayed=false} +LocalizationProcessor.prototype.renderCell=function(value,cellContentContainer){BaseProto.renderCell.call(this,value,cellContentContainer)} +LocalizationProcessor.prototype.buildEditor=function(cellElement,cellContentContainer,isClick){BaseProto.buildEditor.call(this,cellElement,cellContentContainer,isClick) +$.oc.foundation.element.addClass(cellContentContainer,'autocomplete-container') +this.buildLocalizationEditor()} +LocalizationProcessor.prototype.buildLocalizationEditor=function(){var input=this.getInput() +this.localizationInput=new $.oc.builder.localizationInput(input,$(input),{plugin:this.getPluginCode(input),beforePopupShowCallback:$.proxy(this.onBeforePopupShow,this),afterPopupHideCallback:$.proxy(this.onAfterPopupHide,this),autocompleteOptions:{menu:' ',bodyContainer:true}})} +LocalizationProcessor.prototype.getInput=function(){if(!this.activeCell){return null} +return this.activeCell.querySelector('.string-input')} +LocalizationProcessor.prototype.getPluginCode=function(input){var $form=$(input).closest('form'),$input=$form.find('input[name=plugin_code]') +if(!$input.length){throw new Error('The input "plugin_code" should be defined in the form in order to use the localization table processor.')} +return $input.val()} +LocalizationProcessor.prototype.removeLocalizationInput=function(){if(!this.localizationInput){return} +this.localizationInput.dispose() +this.localizationInput=null} +$.oc.table.processor.builderLocalization=LocalizationProcessor;}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/build.js b/server/plugins/rainlab/builder/assets/js/build.js new file mode 100644 index 0000000..965eba0 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/build.js @@ -0,0 +1,20 @@ +/* + +=require builder.dataregistry.js +=require builder.index.entity.base.js +=require builder.index.entity.plugin.js +=require builder.index.entity.databasetable.js +=require builder.index.entity.model.js +=require builder.index.entity.modelform.js +=require builder.index.entity.modellist.js +=require builder.index.entity.permission.js +=require builder.index.entity.menus.js +=require builder.index.entity.version.js +=require builder.index.entity.localization.js +=require builder.index.entity.controller.js +=require builder.index.js +=require builder.localizationinput.js +=require builder.inspector.editor.localization.js +=require builder.table.processor.localization.js + +*/ diff --git a/server/plugins/rainlab/builder/assets/js/builder.dataregistry.js b/server/plugins/rainlab/builder/assets/js/builder.dataregistry.js new file mode 100644 index 0000000..bb39ba4 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.dataregistry.js @@ -0,0 +1,170 @@ +/* + * Builder client-side plugin data registry + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + var Base = $.oc.foundation.base, + BaseProto = Base.prototype + + var DataRegistry = function() { + this.data = {} + this.requestCache = {} + this.callbackCache = {} + + Base.call(this) + } + + /* + * Example: + * $.oc.builder.dataRegistry.set('rainlab.blog', 'model.forms', 'Categories', formsArray) + * $.oc.builder.dataRegistry.set('rainlab.blog', 'localization', null, stringsArray) // The registry contains only default language + */ + DataRegistry.prototype.set = function(plugin, type, subtype, data, params) { + this.storeData(plugin, type, subtype, data) + + if (type == 'localization' && !subtype) { + this.localizationUpdated(plugin, params) + } + } + + /* + * Example: + * $.oc.builder.dataRegistry.get($form, 'rainlab.blog', 'model.forms', 'Categories', function(data){ ... }) + */ + DataRegistry.prototype.get = function($formElement, plugin, type, subtype, callback) { + if (this.data[plugin] === undefined + || this.data[plugin][type] === undefined + || this.data[plugin][type][subtype] === undefined + || this.isCacheObsolete(this.data[plugin][type][subtype].timestamp)) { + + return this.loadDataFromServer($formElement, plugin, type, subtype, callback) + } + + callback(this.data[plugin][type][subtype].data) + } + + // INTERNAL METHODS + // ============================ + + DataRegistry.prototype.makeCacheKey = function(plugin, type, subtype) { + var key = plugin + '-' + type + + if (subtype) { + key += '-' + subtype + } + + return key + } + + DataRegistry.prototype.isCacheObsolete = function(timestamp) { + return (Date.now() - timestamp) > 60000*5 // 5 minutes cache TTL + } + + DataRegistry.prototype.loadDataFromServer = function($formElement, plugin, type, subtype, callback) { + var self = this, + cacheKey = this.makeCacheKey(plugin, type, subtype) + + if (this.requestCache[cacheKey] === undefined) { + this.requestCache[cacheKey] = $formElement.request('onPluginDataRegistryGetData', { + data: { + registry_plugin_code: plugin, + registry_data_type: type, + registry_data_subtype: subtype + } + }).done( + function(data) { + if (data.registryData === undefined) { + throw new Error('Invalid data registry response.') + } + + self.storeData(plugin, type, subtype, data.registryData) + self.applyCallbacks(cacheKey, data.registryData) + + self.requestCache[cacheKey] = undefined + } + ) + } + + this.addCallbackToQueue(callback, cacheKey) + + return this.requestCache[cacheKey] + } + + DataRegistry.prototype.addCallbackToQueue = function(callback, key) { + if (this.callbackCache[key] === undefined) { + this.callbackCache[key] = [] + } + + this.callbackCache[key].push(callback) + } + + DataRegistry.prototype.applyCallbacks = function(key, registryData) { + if (this.callbackCache[key] === undefined) { + return + } + + for (var i=this.callbackCache[key].length-1; i>=0; i--) { + this.callbackCache[key][i](registryData); + } + + delete this.callbackCache[key] + } + + DataRegistry.prototype.storeData = function(plugin, type, subtype, data) { + if (this.data[plugin] === undefined) { + this.data[plugin] = {} + } + + if (this.data[plugin][type] === undefined) { + this.data[plugin][type] = {} + } + + var dataItem = { + timestamp: Date.now(), + data: data + } + + this.data[plugin][type][subtype] = dataItem + } + + DataRegistry.prototype.clearCache = function(plugin, type) { + if (this.data[plugin] === undefined) { + return + } + + if (this.data[plugin][type] === undefined) { + return + } + + this.data[plugin][type] = undefined + } + + // LOCALIZATION-SPECIFIC METHODS + // ============================ + + DataRegistry.prototype.getLocalizationString = function($formElement, plugin, key, callback) { + this.get($formElement, plugin, 'localization', null, function(data){ + if (data[key] !== undefined) { + callback(data[key]) + return + } + + callback(key) + }) + } + + DataRegistry.prototype.localizationUpdated = function(plugin, params) { + $.oc.builder.localizationInput.updatePluginInputs(plugin) + + if (params === undefined || !params.suppressLanguageEditorUpdate) { + $.oc.builder.indexController.entityControllers.localization.languageUpdated(plugin) + } + + $.oc.builder.indexController.entityControllers.localization.updateOnScreenStrings(plugin) + } + + $.oc.builder.dataRegistry = new DataRegistry() +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.index.entity.base.js b/server/plugins/rainlab/builder/assets/js/builder.index.entity.base.js new file mode 100644 index 0000000..38ff0a4 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.index.entity.base.js @@ -0,0 +1,100 @@ +/* + * Base class for Builder Index entity controllers + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + if ($.oc.builder.entityControllers === undefined) + $.oc.builder.entityControllers = {} + + var Base = $.oc.foundation.base, + BaseProto = Base.prototype + + var EntityBase = function(typeName, indexController) { + if (typeName === undefined) { + throw new Error('The Builder entity type name should be set in the base constructor call.') + } + + if (indexController === undefined) { + throw new Error('The Builder index controller should be set when creating an entity controller.') + } + + // The type name is used mostly for referring to + // DOM objects. + this.typeName = typeName + + this.indexController = indexController + + Base.call(this) + } + + EntityBase.prototype = Object.create(BaseProto) + EntityBase.prototype.constructor = EntityBase + + EntityBase.prototype.registerHandlers = function() { + + } + + EntityBase.prototype.invokeCommand = function(command, ev) { + if (/^cmd[a-zA-Z0-9]+$/.test(command)) { + if (this[command] !== undefined) { + this[command].apply(this, [ev]) + } + else { + throw new Error('Unknown command: '+command) + } + } + else { + throw new Error('Invalid command: '+command) + } + } + + EntityBase.prototype.newTabId = function() { + return this.typeName + Math.random() + } + + EntityBase.prototype.makeTabId = function(objectName) { + return this.typeName + '-' + objectName + } + + EntityBase.prototype.getMasterTabsActivePane = function() { + return this.indexController.getMasterTabActivePane() + } + + EntityBase.prototype.getMasterTabsObject = function() { + return this.indexController.masterTabsObj + } + + EntityBase.prototype.getSelectedPlugin = function() { + var activeItem = $('#PluginList-pluginList-plugin-list > ul > li.active') + + return activeItem.data('id') + } + + EntityBase.prototype.getIndexController = function() { + return this.indexController + } + + EntityBase.prototype.updateMasterTabIdAndTitle = function($tabPane, responseData) { + var tabsObject = this.getMasterTabsObject() + + tabsObject.updateIdentifier($tabPane, responseData.tabId) + tabsObject.updateTitle($tabPane, responseData.tabTitle) + } + + EntityBase.prototype.unhideFormDeleteButton = function($tabPane) { + $('[data-control=delete-button]', $tabPane).removeClass('hide') + } + + EntityBase.prototype.forceCloseTab = function($tabPane) { + $tabPane.trigger('close.oc.tab', [{force: true}]) + } + + EntityBase.prototype.unmodifyTab = function($tabPane) { + this.indexController.unchangeTab($tabPane) + } + + $.oc.builder.entityControllers.base = EntityBase; +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.index.entity.controller.js b/server/plugins/rainlab/builder/assets/js/builder.index.entity.controller.js new file mode 100644 index 0000000..1cb12e3 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.index.entity.controller.js @@ -0,0 +1,109 @@ +/* + * Builder Index controller Controller entity controller + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + if ($.oc.builder.entityControllers === undefined) + $.oc.builder.entityControllers = {} + + var Base = $.oc.builder.entityControllers.base, + BaseProto = Base.prototype + + var Controller = function(indexController) { + Base.call(this, 'controller', indexController) + } + + Controller.prototype = Object.create(BaseProto) + Controller.prototype.constructor = Controller + + // PUBLIC METHODS + // ============================ + + Controller.prototype.cmdCreateController = function(ev) { + var $form = $(ev.currentTarget), + self = this, + pluginCode = $form.data('pluginCode'), + behaviorsSelected = $form.find('input[name="behaviors[]"]:checked').length, + promise = null + + // If behaviors were selected, open a new tab after the + // controller is saved. Otherwise just update the controller + // list. + if (behaviorsSelected) { + promise = this.indexController.openOrLoadMasterTab( + $form, + 'onControllerCreate', + this.makeTabId(pluginCode+'-new-controller'), + {} + ) + } + else { + promise = $form.request('onControllerCreate') + } + + promise.done(function(data){ + $form.trigger('close.oc.popup') + self.updateDataRegistry(data) + }).always($.oc.builder.indexController.hideStripeIndicatorProxy) + } + + Controller.prototype.cmdOpenController = function(ev) { + var controller = $(ev.currentTarget).data('id'), + pluginCode = $(ev.currentTarget).data('pluginCode') + + this.indexController.openOrLoadMasterTab($(ev.target), 'onControllerOpen', this.makeTabId(pluginCode+'-'+controller), { + controller: controller + }) + } + + Controller.prototype.cmdSaveController = function(ev) { + var $target = $(ev.currentTarget), + $form = $target.closest('form'), + $inspectorContainer = $form.find('.inspector-container') + + if (!$.oc.inspector.manager.applyValuesFromContainer($inspectorContainer)) { + return + } + + $target.request('onControllerSave').done( + this.proxy(this.saveControllerDone) + ) + } + + // EVENT HANDLERS + // ============================ + + // INTERNAL METHODS + // ============================ + + Controller.prototype.saveControllerDone = function(data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + var $masterTabPane = this.getMasterTabsActivePane() + + this.getIndexController().unchangeTab($masterTabPane) + } + + Controller.prototype.updateDataRegistry = function(data) { + if (data.builderResponseData.registryData !== undefined) { + var registryData = data.builderResponseData.registryData + + $.oc.builder.dataRegistry.set(registryData.pluginCode, 'controller-urls', null, registryData.urls) + } + } + + Controller.prototype.getControllerList = function() { + return $('#layout-side-panel form[data-content-id=controller] [data-control=filelist]') + } + + // REGISTRATION + // ============================ + + $.oc.builder.entityControllers.controller = Controller; + +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.index.entity.databasetable.js b/server/plugins/rainlab/builder/assets/js/builder.index.entity.databasetable.js new file mode 100644 index 0000000..0802fb7 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.index.entity.databasetable.js @@ -0,0 +1,303 @@ +/* + * Builder Index controller Database Table entity controller + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + if ($.oc.builder.entityControllers === undefined) + $.oc.builder.entityControllers = {} + + var Base = $.oc.builder.entityControllers.base, + BaseProto = Base.prototype + + var DatabaseTable = function(indexController) { + Base.call(this, 'databaseTable', indexController) + } + + DatabaseTable.prototype = Object.create(BaseProto) + DatabaseTable.prototype.constructor = DatabaseTable + + // PUBLIC METHODS + // ============================ + + DatabaseTable.prototype.cmdCreateTable = function(ev) { + var result = this.indexController.openOrLoadMasterTab($(ev.target), 'onDatabaseTableCreateOrOpen', this.newTabId()) + + if (result !== false) { + result.done(this.proxy(this.onTableLoaded, this)) + } + } + + DatabaseTable.prototype.cmdOpenTable = function(ev) { + var table = $(ev.currentTarget).data('id'), + result = this.indexController.openOrLoadMasterTab($(ev.target), 'onDatabaseTableCreateOrOpen', this.makeTabId(table), { + table_name: table + }) + + if (result !== false) { + result.done(this.proxy(this.onTableLoaded, this)) + } + } + + DatabaseTable.prototype.cmdSaveTable = function(ev) { + var $target = $(ev.currentTarget) + + // The process of saving a database table: + // - validate client-side + // - validate columns on the server + // - display a popup asking to enter the migration text + // - generate the migration on the server and execute it + // - drop the form modified flag + + if (!this.validateTable($target)) { + return + } + + var data = { + 'columns': this.getTableData($target) + } + + $target.popup({ + extraData: data, + handler: 'onDatabaseTableValidateAndShowPopup' + }) + } + + DatabaseTable.prototype.cmdSaveMigration = function(ev) { + var $target = $(ev.currentTarget) + + $.oc.stripeLoadIndicator.show() + $target.request('onDatabaseTableMigrationApply').always( + $.oc.builder.indexController.hideStripeIndicatorProxy + ).done( + this.proxy(this.saveMigrationDone) + ) + } + + DatabaseTable.prototype.cmdDeleteTable = function(ev) { + var $target = $(ev.currentTarget) + $.oc.confirm($target.data('confirm'), this.proxy(this.deleteConfirmed)) + } + + DatabaseTable.prototype.cmdUnModifyForm = function() { + var $masterTabPane = this.getMasterTabsActivePane() + this.unmodifyTab($masterTabPane) + } + + DatabaseTable.prototype.cmdAddTimestamps = function(ev) { + var $target = $(ev.currentTarget), + added = this.addTimeStampColumns($target, ['created_at', 'updated_at']) + + if (!added) { + alert($target.closest('form').attr('data-lang-timestamps-exist')) + } + } + + DatabaseTable.prototype.cmdAddSoftDelete = function(ev) { + var $target = $(ev.currentTarget), + added = this.addTimeStampColumns($target, ['deleted_at']) + + if (!added) { + alert($target.closest('form').attr('data-lang-soft-deleting-exist')) + } + } + + // EVENT HANDLERS + // ============================ + + DatabaseTable.prototype.onTableCellChanged = function(ev, column, value, rowIndex) { + var $target = $(ev.target) + + if ($target.data('alias') != 'columns') { + return + } + + if ($target.closest('form').data('entity') != 'database') { + return + } + + // Some migration-related rules are enforced here: + // + // 1. Checking Autoincrement checkbox automatically checks the Unsigned checkbox (this corresponds to the + // logic internally implemented in Laravel schema builder) and PK + // 2. Unchecking Unsigned unchecks Autoincrement + // 3. Checking the PK column unchecks Nullable + // 4. Checking Nullable unchecks PK + // 6. Unchecking the PK unchecks Autoincrement + + var updatedRow = {} + + if (column == 'auto_increment' && value) { + updatedRow.unsigned = 1 + updatedRow.primary_key = 1 + } + + if (column == 'unsigned' && !value) { + updatedRow.auto_increment = 0 + } + + if (column == 'primary_key' && value) { + updatedRow.allow_null = 0 + } + + if (column == 'allow_null' && value) { + updatedRow.primary_key = 0 + } + + if (column == 'primary_key' && !value) { + updatedRow.auto_increment = 0 + } + + $target.table('setRowValues', rowIndex, updatedRow) + } + + DatabaseTable.prototype.onTableLoaded = function() { + $(document).trigger('render') + + var $masterTabPane = this.getMasterTabsActivePane(), + $form = $masterTabPane.find('form'), + $toolbar = $masterTabPane.find('div[data-control=table] div.toolbar'), + $button = $('') + + $button.text($form.attr('data-lang-add-timestamps')); + $toolbar.append($button) + + $button = $('') + $button.text($form.attr('data-lang-add-soft-delete')); + $toolbar.append($button) + } + + // INTERNAL METHODS + // ============================ + + DatabaseTable.prototype.registerHandlers = function() { + this.indexController.$masterTabs.on('oc.tableCellChanged', this.proxy(this.onTableCellChanged)) + } + + DatabaseTable.prototype.validateTable = function($target) { + var tableObj = this.getTableControlObject($target) + + tableObj.unfocusTable() + return tableObj.validate() + } + + DatabaseTable.prototype.getTableData = function($target) { + var tableObj = this.getTableControlObject($target) + + return tableObj.dataSource.getAllData() + } + + DatabaseTable.prototype.getTableControlObject = function($target) { + var $form = $target.closest('form'), + $table = $form.find('[data-control=table]'), + tableObj = $table.data('oc.table') + + if (!tableObj) { + throw new Error('Table object is not found on the database table tab') + } + + return tableObj + } + + DatabaseTable.prototype.saveMigrationDone = function(data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + $('#builderTableMigrationPopup').trigger('close.oc.popup') + + var $masterTabPane = this.getMasterTabsActivePane(), + tabsObject = this.getMasterTabsObject() + + if (data.builderResponseData.operation != 'delete') { + $masterTabPane.find('input[name=table_name]').val(data.builderResponseData.builderObjectName) + this.updateMasterTabIdAndTitle($masterTabPane, data.builderResponseData) + this.unhideFormDeleteButton($masterTabPane) + + this.getTableList().fileList('markActive', data.builderResponseData.tabId) + this.getIndexController().unchangeTab($masterTabPane) + } + else { + this.forceCloseTab($masterTabPane) + } + + $.oc.builder.dataRegistry.clearCache(data.builderResponseData.pluginCode, 'model-columns') + } + + DatabaseTable.prototype.getTableList = function() { + return $('#layout-side-panel form[data-content-id=database] [data-control=filelist]') + } + + DatabaseTable.prototype.deleteConfirmed = function() { + var $masterTabPane = this.getMasterTabsActivePane() + + $masterTabPane.find('form').popup({ + handler: 'onDatabaseTableShowDeletePopup' + }) + } + + DatabaseTable.prototype.getColumnNames = function($target) { + var tableObj = this.getTableControlObject($target) + + tableObj.unfocusTable() + + var data = this.getTableData($target), + result = [] + + for (var index in data) { + if (data[index].name !== undefined) { + result.push($.trim(data[index].name)) + } + } + + return result + } + + DatabaseTable.prototype.addTimeStampColumns = function($target, columns) + { + var existingColumns = this.getColumnNames($target), + added = false + + for (var index in columns) { + var column = columns[index] + + if ($.inArray(column, existingColumns) == -1) { + this.addTimeStampColumn($target, column) + added = true + } + } + + if (added) { + $target.trigger('change') + } + + return added + } + + DatabaseTable.prototype.addTimeStampColumn = function($target, column) { + var tableObj = this.getTableControlObject($target), + currentData = this.getTableData($target), + rowData = { + name: column, + type: 'timestamp', + 'default': null, + allow_null: true // Simplifies the case when a timestamp is added to a table with data + } + + tableObj.addRecord('bottom', true) + tableObj.setRowValues(currentData.length-1, rowData) + + // Forces the table to apply values + // from the data source + tableObj.addRecord('bottom', false) + tableObj.deleteRecord() + } + + // REGISTRATION + // ============================ + + $.oc.builder.entityControllers.databaseTable = DatabaseTable; + +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.index.entity.localization.js b/server/plugins/rainlab/builder/assets/js/builder.index.entity.localization.js new file mode 100644 index 0000000..40f92fe --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.index.entity.localization.js @@ -0,0 +1,282 @@ +/* + * Builder Index controller Localization entity controller + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + if ($.oc.builder.entityControllers === undefined) + $.oc.builder.entityControllers = {} + + var Base = $.oc.builder.entityControllers.base, + BaseProto = Base.prototype + + var Localization = function(indexController) { + Base.call(this, 'localization', indexController) + } + + Localization.prototype = Object.create(BaseProto) + Localization.prototype.constructor = Localization + + // PUBLIC METHODS + // ============================ + + Localization.prototype.cmdCreateLanguage = function(ev) { + this.indexController.openOrLoadMasterTab($(ev.target), 'onLanguageCreateOrOpen', this.newTabId()) + } + + Localization.prototype.cmdOpenLanguage = function(ev) { + var language = $(ev.currentTarget).data('id'), + pluginCode = $(ev.currentTarget).data('pluginCode') + + this.indexController.openOrLoadMasterTab($(ev.target), 'onLanguageCreateOrOpen', this.makeTabId(pluginCode+'-'+language), { + original_language: language + }) + } + + Localization.prototype.cmdSaveLanguage = function(ev) { + var $target = $(ev.currentTarget), + $form = $target.closest('form') + + $target.request('onLanguageSave').done( + this.proxy(this.saveLanguageDone) + ) + } + + Localization.prototype.cmdDeleteLanguage = function(ev) { + var $target = $(ev.currentTarget) + $.oc.confirm($target.data('confirm'), this.proxy(this.deleteConfirmed)) + } + + Localization.prototype.cmdCopyMissingStrings = function(ev) { + var $form = $(ev.currentTarget), + language = $form.find('select[name=language]').val(), + $masterTabPane = this.getMasterTabsActivePane() + + $form.trigger('close.oc.popup') + + $.oc.stripeLoadIndicator.show() + $masterTabPane.find('form').request('onLanguageCopyStringsFrom', { + data: { + copy_from: language + } + }).always( + $.oc.builder.indexController.hideStripeIndicatorProxy + ).done( + this.proxy(this.copyStringsFromDone) + ) + } + + // EVENT HANDLERS + // ============================ + + // INTERNAL BUILDER API + // ============================ + + Localization.prototype.languageUpdated = function(plugin) { + var languageForm = this.findDefaultLanguageForm(plugin) + + if (!languageForm) { + return + } + + var $languageForm = $(languageForm) + + if (!$languageForm.hasClass('oc-data-changed')) { + this.updateLanguageFromServer($languageForm) + } + else { + // If there are changes - merge language from server + // in the background. As this operation is not 100% + // reliable, it could be a good idea to display a + // warning when the user navigates to the tab. + + this.mergeLanguageFromServer($languageForm) + } + } + + Localization.prototype.updateOnScreenStrings = function(plugin) { + var stringElements = document.body.querySelectorAll('span[data-localization-key][data-plugin="'+plugin+'"]') + + $.oc.builder.dataRegistry.get($('#builder-plugin-selector-panel form'), plugin, 'localization', null, function(data){ + for (var i=stringElements.length-1; i>=0; i--) { + var stringElement = stringElements[i], + stringKey = stringElement.getAttribute('data-localization-key') + + if (data[stringKey] !== undefined) { + stringElement.textContent = data[stringKey] + } + else { + stringElement.textContent = stringKey + } + } + }) + } + + // INTERNAL METHODS + // ============================ + + Localization.prototype.saveLanguageDone = function(data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + var $masterTabPane = this.getMasterTabsActivePane() + + $masterTabPane.find('input[name=original_language]').val(data.builderResponseData.language) + this.updateMasterTabIdAndTitle($masterTabPane, data.builderResponseData) + this.unhideFormDeleteButton($masterTabPane) + + this.getLanguageList().fileList('markActive', data.builderResponseData.tabId) + this.getIndexController().unchangeTab($masterTabPane) + + if (data.builderResponseData.registryData !== undefined) { + var registryData = data.builderResponseData.registryData + + $.oc.builder.dataRegistry.set(registryData.pluginCode, 'localization', null, registryData.strings, {suppressLanguageEditorUpdate: true}) + $.oc.builder.dataRegistry.set(registryData.pluginCode, 'localization', 'sections', registryData.sections) + } + } + + Localization.prototype.getLanguageList = function() { + return $('#layout-side-panel form[data-content-id=localization] [data-control=filelist]') + } + + Localization.prototype.getCodeEditor = function($tab) { + return $tab.find('div[data-field-name=strings] div[data-control=codeeditor]').data('oc.codeEditor').editor + } + + Localization.prototype.deleteConfirmed = function() { + var $masterTabPane = this.getMasterTabsActivePane(), + $form = $masterTabPane.find('form') + + $.oc.stripeLoadIndicator.show() + $form.request('onLanguageDelete').always( + $.oc.builder.indexController.hideStripeIndicatorProxy + ).done( + this.proxy(this.deleteDone) + ) + } + + Localization.prototype.deleteDone = function() { + var $masterTabPane = this.getMasterTabsActivePane() + + this.getIndexController().unchangeTab($masterTabPane) + this.forceCloseTab($masterTabPane) + } + + Localization.prototype.copyStringsFromDone = function(data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + var responseData = data.builderResponseData, + $masterTabPane = this.getMasterTabsActivePane(), + $form = $masterTabPane.find('form'), + codeEditor = this.getCodeEditor($masterTabPane), + newStringMessage = $form.data('newStringMessage'), + mismatchMessage = $form.data('structureMismatch') + + codeEditor.getSession().setValue(responseData.strings) + + var annotations = [] + for (var i=responseData.updatedLines.length-1; i>=0; i--) { + var line = responseData.updatedLines[i] + + annotations.push({ + row: line, + column: 0, + text: newStringMessage, + type: 'warning' + }) + } + + codeEditor.getSession().setAnnotations(annotations) + + if (responseData.mismatch) { + $.oc.alert(mismatchMessage) + } + } + + Localization.prototype.findDefaultLanguageForm = function(plugin) { + var forms = document.body.querySelectorAll('form[data-entity=localization]') + + for (var i=forms.length-1; i>=0; i--) { + var form = forms[i], + pluginInput = form.querySelector('input[name=plugin_code]'), + languageInput = form.querySelector('input[name=original_language]') + + if (!pluginInput || pluginInput.value != plugin) { + continue + } + + if (!languageInput) { + continue + } + + if (form.getAttribute('data-default-language') == languageInput.value) { + return form + } + } + + return null + } + + Localization.prototype.updateLanguageFromServer = function($languageForm) { + var self = this + + $languageForm.request('onLanguageGetStrings').done(function(data) { + self.updateLanguageFromServerDone($languageForm, data) + }) + } + + Localization.prototype.updateLanguageFromServerDone = function($languageForm, data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + var responseData = data.builderResponseData, + $tabPane = $languageForm.closest('.tab-pane'), + codeEditor = this.getCodeEditor($tabPane) + + if (!responseData.strings) { + return + } + + codeEditor.getSession().setValue(responseData.strings) + this.unmodifyTab($tabPane) + } + + Localization.prototype.mergeLanguageFromServer = function($languageForm) { + var language = $languageForm.find('input[name=original_language]').val(), + self = this + + $languageForm.request('onLanguageCopyStringsFrom', { + data: { + copy_from: language + } + }).done(function(data) { + self.mergeLanguageFromServerDone($languageForm, data) + }) + } + + Localization.prototype.mergeLanguageFromServerDone = function($languageForm, data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + var responseData = data.builderResponseData, + $tabPane = $languageForm.closest('.tab-pane'), + codeEditor = this.getCodeEditor($tabPane) + + codeEditor.getSession().setValue(responseData.strings) + codeEditor.getSession().setAnnotations([]) + } + + // REGISTRATION + // ============================ + + $.oc.builder.entityControllers.localization = Localization; + +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.index.entity.menus.js b/server/plugins/rainlab/builder/assets/js/builder.index.entity.menus.js new file mode 100644 index 0000000..5330bfb --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.index.entity.menus.js @@ -0,0 +1,86 @@ +/* + * Builder Index controller Menus entity controller + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + if ($.oc.builder.entityControllers === undefined) + $.oc.builder.entityControllers = {} + + var Base = $.oc.builder.entityControllers.base, + BaseProto = Base.prototype + + var Menus = function(indexController) { + Base.call(this, 'menus', indexController) + } + + Menus.prototype = Object.create(BaseProto) + Menus.prototype.constructor = Menus + + // PUBLIC METHODS + // ============================ + + Menus.prototype.cmdOpenMenus = function(ev) { + var currentPlugin = this.getSelectedPlugin() + + if (!currentPlugin) { + alert('Please select a plugin first') + return + } + + this.indexController.openOrLoadMasterTab($(ev.target), 'onMenusOpen', this.makeTabId(currentPlugin)) + } + + Menus.prototype.cmdSaveMenus = function(ev) { + var $target = $(ev.currentTarget), + $form = $target.closest('form'), + $inspectorContainer = $form.find('.inspector-container') + + if (!$.oc.inspector.manager.applyValuesFromContainer($inspectorContainer)) { + return + } + + var menus = $.oc.builder.menubuilder.controller.getJson($form.get(0)) + + $target.request('onMenusSave', { + data: { + menus: menus + } + }).done( + this.proxy(this.saveMenusDone) + ) + } + + Menus.prototype.cmdAddMainMenuItem = function(ev) { + $.oc.builder.menubuilder.controller.addMainMenuItem(ev) + } + + Menus.prototype.cmdAddSideMenuItem = function(ev) { + $.oc.builder.menubuilder.controller.addSideMenuItem(ev) + } + + Menus.prototype.cmdDeleteMenuItem = function(ev) { + $.oc.builder.menubuilder.controller.deleteMenuItem(ev) + } + + // INTERNAL METHODS + // ============================ + + Menus.prototype.saveMenusDone = function(data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + var $masterTabPane = this.getMasterTabsActivePane() + + this.getIndexController().unchangeTab($masterTabPane) + } + + // REGISTRATION + // ============================ + + $.oc.builder.entityControllers.menus = Menus; + +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.index.entity.model.js b/server/plugins/rainlab/builder/assets/js/builder.index.entity.model.js new file mode 100644 index 0000000..10a1cd9 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.index.entity.model.js @@ -0,0 +1,72 @@ +/* + * Builder Index controller Model entity controller + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + if ($.oc.builder.entityControllers === undefined) + $.oc.builder.entityControllers = {} + + var Base = $.oc.builder.entityControllers.base, + BaseProto = Base.prototype + + var Model = function(indexController) { + Base.call(this, 'model', indexController) + } + + Model.prototype = Object.create(BaseProto) + Model.prototype.constructor = Model + + // PUBLIC METHODS + // ============================ + + Model.prototype.cmdCreateModel = function(ev) { + var $target = $(ev.currentTarget) + + $target.one('shown.oc.popup', this.proxy(this.onModelPopupShown)) + + $target.popup({ + handler: 'onModelLoadPopup' + }) + } + + Model.prototype.cmdApplyModelSettings = function(ev) { + var $form = $(ev.currentTarget), + self = this + + $.oc.stripeLoadIndicator.show() + $form.request('onModelSave').always( + $.oc.builder.indexController.hideStripeIndicatorProxy + ).done(function(data){ + $form.trigger('close.oc.popup') + + self.applyModelSettingsDone(data) + }) + } + + // EVENT HANDLERS + // ============================ + + Model.prototype.onModelPopupShown = function(ev, button, popup) { + $(popup).find('input[name=className]').focus() + } + + // INTERNAL METHODS + // ============================ + + Model.prototype.applyModelSettingsDone = function(data) { + if (data.builderResponseData.registryData !== undefined) { + var registryData = data.builderResponseData.registryData + + $.oc.builder.dataRegistry.set(registryData.pluginCode, 'model-classes', null, registryData.models) + } + } + + // REGISTRATION + // ============================ + + $.oc.builder.entityControllers.model = Model; + +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.index.entity.modelform.js b/server/plugins/rainlab/builder/assets/js/builder.index.entity.modelform.js new file mode 100644 index 0000000..1361845 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.index.entity.modelform.js @@ -0,0 +1,155 @@ +/* + * Builder Index controller Model Form entity controller + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + if ($.oc.builder.entityControllers === undefined) + $.oc.builder.entityControllers = {} + + var Base = $.oc.builder.entityControllers.base, + BaseProto = Base.prototype + + var ModelForm = function(indexController) { + Base.call(this, 'modelForm', indexController) + } + + ModelForm.prototype = Object.create(BaseProto) + ModelForm.prototype.constructor = ModelForm + + // PUBLIC METHODS + // ============================ + + ModelForm.prototype.cmdCreateForm = function(ev) { + var $link = $(ev.currentTarget), + data = { + model_class: $link.data('modelClass') + } + + this.indexController.openOrLoadMasterTab($link, 'onModelFormCreateOrOpen', this.newTabId(), data) + } + + ModelForm.prototype.cmdSaveForm = function(ev) { + var $target = $(ev.currentTarget), + $form = $target.closest('form'), + $rootContainer = $('[data-root-control-wrapper] > [data-control-container]', $form), + $inspectorContainer = $form.find('.inspector-container'), + controls = $.oc.builder.formbuilder.domToPropertyJson.convert($rootContainer.get(0)) + + if (!$.oc.inspector.manager.applyValuesFromContainer($inspectorContainer)) { + return + } + + if (controls === false) { + $.oc.flashMsg({ + 'text': $.oc.builder.formbuilder.domToPropertyJson.getLastError(), + 'class': 'error', + 'interval': 5 + }) + + return + } + + var data = { + controls: controls + } + + $target.request('onModelFormSave', { + data: data + }).done( + this.proxy(this.saveFormDone) + ) + } + + ModelForm.prototype.cmdOpenForm = function(ev) { + var form = $(ev.currentTarget).data('form'), + model = $(ev.currentTarget).data('modelClass') + + this.indexController.openOrLoadMasterTab($(ev.target), 'onModelFormCreateOrOpen', this.makeTabId(model+'-'+form), { + file_name: form, + model_class: model + }) + } + + ModelForm.prototype.cmdDeleteForm = function(ev) { + var $target = $(ev.currentTarget) + $.oc.confirm($target.data('confirm'), this.proxy(this.deleteConfirmed)) + } + + ModelForm.prototype.cmdAddControl = function(ev) { + $.oc.builder.formbuilder.controlPalette.addControl(ev) + } + + ModelForm.prototype.cmdUndockControlPalette = function(ev) { + $.oc.builder.formbuilder.controlPalette.undockFromContainer(ev) + } + + ModelForm.prototype.cmdDockControlPalette = function(ev) { + $.oc.builder.formbuilder.controlPalette.dockToContainer(ev) + } + + ModelForm.prototype.cmdCloseControlPalette = function(ev) { + $.oc.builder.formbuilder.controlPalette.closeInContainer(ev) + } + + // INTERNAL METHODS + // ============================ + + ModelForm.prototype.saveFormDone = function(data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + var $masterTabPane = this.getMasterTabsActivePane() + + $masterTabPane.find('input[name=file_name]').val(data.builderResponseData.builderObjectName) + this.updateMasterTabIdAndTitle($masterTabPane, data.builderResponseData) + this.unhideFormDeleteButton($masterTabPane) + + this.getModelList().fileList('markActive', data.builderResponseData.tabId) + this.getIndexController().unchangeTab($masterTabPane) + + this.updateDataRegistry(data) + } + + ModelForm.prototype.updateDataRegistry = function(data) { + if (data.builderResponseData.registryData !== undefined) { + var registryData = data.builderResponseData.registryData + + $.oc.builder.dataRegistry.set(registryData.pluginCode, 'model-forms', registryData.modelClass, registryData.forms) + } + } + + ModelForm.prototype.deleteConfirmed = function() { + var $masterTabPane = this.getMasterTabsActivePane(), + $form = $masterTabPane.find('form') + + $.oc.stripeLoadIndicator.show() + $form.request('onModelFormDelete').always( + $.oc.builder.indexController.hideStripeIndicatorProxy + ).done( + this.proxy(this.deleteDone) + ) + } + + ModelForm.prototype.deleteDone = function(data) { + var $masterTabPane = this.getMasterTabsActivePane() + + this.getIndexController().unchangeTab($masterTabPane) + this.forceCloseTab($masterTabPane) + + this.updateDataRegistry(data) + } + + ModelForm.prototype.getModelList = function() { + return $('#layout-side-panel form[data-content-id=models] [data-control=filelist]') + } + + // REGISTRATION + // ============================ + + $.oc.builder.entityControllers.modelForm = ModelForm; + +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.index.entity.modellist.js b/server/plugins/rainlab/builder/assets/js/builder.index.entity.modellist.js new file mode 100644 index 0000000..7f8be68 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.index.entity.modellist.js @@ -0,0 +1,304 @@ +/* + * Builder Index controller Model List entity controller + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + if ($.oc.builder.entityControllers === undefined) + $.oc.builder.entityControllers = {} + + var Base = $.oc.builder.entityControllers.base, + BaseProto = Base.prototype + + var ModelList = function(indexController) { + this.cachedModelFieldsPromises = {} + + Base.call(this, 'modelList', indexController) + } + + ModelList.prototype = Object.create(BaseProto) + ModelList.prototype.constructor = ModelList + + ModelList.prototype.registerHandlers = function() { + $(document).on('autocompleteitems.oc.table', 'form[data-sub-entity="model-list"] [data-control=table]', this.proxy(this.onAutocompleteItems)) + } + + // PUBLIC METHODS + // ============================ + + ModelList.prototype.cmdCreateList = function(ev) { + var $link = $(ev.currentTarget), + data = { + model_class: $link.data('modelClass') + } + + var result = this.indexController.openOrLoadMasterTab($link, 'onModelListCreateOrOpen', this.newTabId(), data) + + if (result !== false) { + result.done(this.proxy(this.onListLoaded, this)) + } + } + + ModelList.prototype.cmdSaveList = function(ev) { + var $target = $(ev.currentTarget), + $form = $target.closest('form') + + if (!this.validateTable($target)) { + return + } + + $target.request('onModelListSave', { + data: { + columns: this.getTableData($target) + } + }).done( + this.proxy(this.saveListDone) + ) + } + + ModelList.prototype.cmdOpenList = function(ev) { + var list = $(ev.currentTarget).data('list'), + model = $(ev.currentTarget).data('modelClass') + + var result = this.indexController.openOrLoadMasterTab($(ev.target), 'onModelListCreateOrOpen', this.makeTabId(model+'-'+list), { + file_name: list, + model_class: model + }) + + if (result !== false) { + result.done(this.proxy(this.onListLoaded, this)) + } + } + + ModelList.prototype.cmdDeleteList = function(ev) { + var $target = $(ev.currentTarget) + $.oc.confirm($target.data('confirm'), this.proxy(this.deleteConfirmed)) + } + + ModelList.prototype.cmdAddDatabaseColumns = function(ev) { + var $target = $(ev.currentTarget) + + $.oc.stripeLoadIndicator.show() + $target.request('onModelListLoadDatabaseColumns').done( + this.proxy(this.databaseColumnsLoaded) + ).always( + $.oc.builder.indexController.hideStripeIndicatorProxy + ) + } + + // INTERNAL METHODS + // ============================ + + ModelList.prototype.saveListDone = function(data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + var $masterTabPane = this.getMasterTabsActivePane() + + $masterTabPane.find('input[name=file_name]').val(data.builderResponseData.builderObjectName) + this.updateMasterTabIdAndTitle($masterTabPane, data.builderResponseData) + this.unhideFormDeleteButton($masterTabPane) + + this.getModelList().fileList('markActive', data.builderResponseData.tabId) + this.getIndexController().unchangeTab($masterTabPane) + + this.updateDataRegistry(data) + } + + ModelList.prototype.deleteConfirmed = function() { + var $masterTabPane = this.getMasterTabsActivePane(), + $form = $masterTabPane.find('form') + + $.oc.stripeLoadIndicator.show() + $form.request('onModelListDelete').always( + $.oc.builder.indexController.hideStripeIndicatorProxy + ).done( + this.proxy(this.deleteDone) + ) + } + + ModelList.prototype.deleteDone = function(data) { + var $masterTabPane = this.getMasterTabsActivePane() + + this.getIndexController().unchangeTab($masterTabPane) + this.forceCloseTab($masterTabPane) + + this.updateDataRegistry(data) + } + + ModelList.prototype.getTableControlObject = function($target) { + var $form = $target.closest('form'), + $table = $form.find('[data-control=table]'), + tableObj = $table.data('oc.table') + + if (!tableObj) { + throw new Error('Table object is not found on the model list tab') + } + + return tableObj + } + + ModelList.prototype.getModelList = function() { + return $('#layout-side-panel form[data-content-id=models] [data-control=filelist]') + } + + ModelList.prototype.validateTable = function($target) { + var tableObj = this.getTableControlObject($target) + + tableObj.unfocusTable() + return tableObj.validate() + } + + ModelList.prototype.getTableData = function($target) { + var tableObj = this.getTableControlObject($target) + + return tableObj.dataSource.getAllData() + } + + ModelList.prototype.loadModelFields = function(table, callback) { + var $form = $(table).closest('form'), + modelClass = $form.find('input[name=model_class]').val(), + cachedFields = $form.data('oc.model-field-cache') + + if (cachedFields !== undefined) { + callback(cachedFields) + + return + } + + if (this.cachedModelFieldsPromises[modelClass] === undefined) { + this.cachedModelFieldsPromises[modelClass] = $form.request('onModelFormGetModelFields', { + data: { + 'as_plain_list': 1 + } + }) + } + + if (callback === undefined) { + return + } + + this.cachedModelFieldsPromises[modelClass].done(function(data){ + $form.data('oc.model-field-cache', data.responseData.options) + + callback(data.responseData.options) + }) + } + + ModelList.prototype.updateDataRegistry = function(data) { + if (data.builderResponseData.registryData !== undefined) { + var registryData = data.builderResponseData.registryData + + $.oc.builder.dataRegistry.set(registryData.pluginCode, 'model-lists', registryData.modelClass, registryData.lists) + + $.oc.builder.dataRegistry.clearCache(registryData.pluginCode, 'plugin-lists') + } + } + + ModelList.prototype.databaseColumnsLoaded = function(data) { + if (!$.isArray(data.responseData.columns)) { + alert('Invalid server response') + } + + var $masterTabPane = this.getMasterTabsActivePane(), + $form = $masterTabPane.find('form'), + existingColumns = this.getColumnNames($form), + columnsAdded = false + + for (var i in data.responseData.columns) { + var column = data.responseData.columns[i], + type = this.mapType(column.type) + + if ($.inArray(column.name, existingColumns) !== -1) { + continue + } + + this.addColumn($form, column.name, type) + columnsAdded = true + } + + if (!columnsAdded) { + alert($form.attr('data-lang-all-database-columns-exist')) + } + else { + $form.trigger('change') + } + } + + ModelList.prototype.mapType = function(type) { + switch (type) { + case 'integer' : return 'number' + case 'timestamp' : return 'datetime' + default: return 'text' + } + } + + ModelList.prototype.addColumn = function($target, column, type) { + var tableObj = this.getTableControlObject($target), + currentData = this.getTableData($target), + rowData = { + field: column, + label: column, + type: type + } + + tableObj.addRecord('bottom', true) + tableObj.setRowValues(currentData.length-1, rowData) + + // Forces the table to apply values + // from the data source + tableObj.addRecord('bottom', false) + tableObj.deleteRecord() + } + + ModelList.prototype.getColumnNames = function($target) { + var tableObj = this.getTableControlObject($target) + + tableObj.unfocusTable() + + var data = this.getTableData($target), + result = [] + + for (var index in data) { + if (data[index].field !== undefined) { + result.push($.trim(data[index].field)) + } + } + + return result + } + + // EVENT HANDLERS + // ============================ + + ModelList.prototype.onAutocompleteItems = function(ev, data) { + if (data.columnConfiguration.fillFrom === 'model-fields') { + ev.preventDefault() + + this.loadModelFields(ev.target, data.callback) + + return false + } + } + + ModelList.prototype.onListLoaded = function() { + $(document).trigger('render') + + var $masterTabPane = this.getMasterTabsActivePane(), + $form = $masterTabPane.find('form'), + $toolbar = $masterTabPane.find('div[data-control=table] div.toolbar'), + $button = $('') + + $button.text($form.attr('data-lang-add-database-columns')); + $toolbar.append($button) + } + + // REGISTRATION + // ============================ + + $.oc.builder.entityControllers.modelList = ModelList; + +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.index.entity.permission.js b/server/plugins/rainlab/builder/assets/js/builder.index.entity.permission.js new file mode 100644 index 0000000..a74732b --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.index.entity.permission.js @@ -0,0 +1,122 @@ +/* + * Builder Index controller Permission entity controller + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + if ($.oc.builder.entityControllers === undefined) + $.oc.builder.entityControllers = {} + + var Base = $.oc.builder.entityControllers.base, + BaseProto = Base.prototype + + var Permission = function(indexController) { + Base.call(this, 'permissions', indexController) + } + + Permission.prototype = Object.create(BaseProto) + Permission.prototype.constructor = Permission + + Permission.prototype.registerHandlers = function() { + this.indexController.$masterTabs.on('oc.tableNewRow', this.proxy(this.onTableRowCreated)) + } + + // PUBLIC METHODS + // ============================ + + Permission.prototype.cmdOpenPermissions = function(ev) { + var currentPlugin = this.getSelectedPlugin() + + if (!currentPlugin) { + alert('Please select a plugin first') + return + } + + this.indexController.openOrLoadMasterTab($(ev.target), 'onPermissionsOpen', this.makeTabId(currentPlugin)) + } + + Permission.prototype.cmdSavePermissions = function(ev) { + var $target = $(ev.currentTarget), + $form = $target.closest('form') + + if (!this.validateTable($target)) { + return + } + + $target.request('onPermissionsSave', { + data: { + permissions: this.getTableData($target) + } + }).done( + this.proxy(this.savePermissionsDone) + ) + } + + // INTERNAL METHODS + // ============================ + + Permission.prototype.getTableControlObject = function($target) { + var $form = $target.closest('form'), + $table = $form.find('[data-control=table]'), + tableObj = $table.data('oc.table') + + if (!tableObj) { + throw new Error('Table object is not found on permissions tab') + } + + return tableObj + } + + Permission.prototype.validateTable = function($target) { + var tableObj = this.getTableControlObject($target) + + tableObj.unfocusTable() + return tableObj.validate() + } + + Permission.prototype.getTableData = function($target) { + var tableObj = this.getTableControlObject($target) + + return tableObj.dataSource.getAllData() + } + + Permission.prototype.savePermissionsDone = function(data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + var $masterTabPane = this.getMasterTabsActivePane() + + this.getIndexController().unchangeTab($masterTabPane) + $.oc.builder.dataRegistry.clearCache(data.builderResponseData.pluginCode, 'permissions') + } + + // EVENT HANDLERS + // ============================ + + Permission.prototype.onTableRowCreated = function(ev, recordData) { + var $target = $(ev.target) + + if ($target.data('alias') != 'permissions') { + return + } + + var $form = $target.closest('form') + + if ($form.data('entity') != 'permissions') { + return + } + + var pluginCode = $form.find('input[name=plugin_code]').val() + + recordData.permission = pluginCode.toLowerCase() + '.'; + } + + // REGISTRATION + // ============================ + + $.oc.builder.entityControllers.permission = Permission; + +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.index.entity.plugin.js b/server/plugins/rainlab/builder/assets/js/builder.index.entity.plugin.js new file mode 100644 index 0000000..7468e3e --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.index.entity.plugin.js @@ -0,0 +1,116 @@ +/* + * Builder Index controller Plugin entity controller + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + if ($.oc.builder.entityControllers === undefined) + $.oc.builder.entityControllers = {} + + var Base = $.oc.builder.entityControllers.base, + BaseProto = Base.prototype + + var Plugin = function(indexController) { + Base.call(this, 'plugin', indexController) + + this.popupZIndex = 5050 // This popup should be above the flyout overlay, which z-index is 5000 + } + + Plugin.prototype = Object.create(BaseProto) + Plugin.prototype.constructor = Plugin + + // PUBLIC METHODS + // ============================ + + Plugin.prototype.cmdMakePluginActive = function(ev) { + var $target = $(ev.currentTarget), + selectedPluginCode = $target.data('pluginCode') + + this.makePluginActive(selectedPluginCode) + } + + Plugin.prototype.cmdCreatePlugin = function(ev) { + var $target = $(ev.currentTarget) + + $target.one('shown.oc.popup', this.proxy(this.onPluginPopupShown)) + + $target.popup({ + handler: 'onPluginLoadPopup', + zIndex: this.popupZIndex + }) + } + + Plugin.prototype.cmdApplyPluginSettings = function(ev) { + var $form = $(ev.currentTarget), + self = this + + $.oc.stripeLoadIndicator.show() + $form.request('onPluginSave').always( + $.oc.builder.indexController.hideStripeIndicatorProxy + ).done(function(data){ + $form.trigger('close.oc.popup') + + self.applyPluginSettingsDone(data) + }) + } + + Plugin.prototype.cmdEditPluginSettings = function(ev) { + var $target = $(ev.currentTarget) + + $target.one('shown.oc.popup', this.proxy(this.onPluginPopupShown)) + + $target.popup({ + handler: 'onPluginLoadPopup', + zIndex: this.popupZIndex, + extraData: { + pluginCode: $target.data('pluginCode') + } + }) + } + + // EVENT HANDLERS + // ============================ + + Plugin.prototype.onPluginPopupShown = function(ev, button, popup) { + $(popup).find('input[name=name]').focus() + } + + // INTERNAL METHODS + // ============================ + + Plugin.prototype.applyPluginSettingsDone = function(data) { + if (data.responseData !== undefined && data.responseData.isNewPlugin !== undefined) { + this.makePluginActive(data.responseData.pluginCode, true) + } + } + + Plugin.prototype.makePluginActive = function(pluginCode, updatePluginList) { + var $form = $('#builder-plugin-selector-panel form').first() + + $.oc.stripeLoadIndicator.show() + $form.request('onPluginSetActive', { + data: { + pluginCode: pluginCode, + updatePluginList: (updatePluginList ? 1 : 0) + } + }).always( + $.oc.builder.indexController.hideStripeIndicatorProxy + ).done( + this.proxy(this.makePluginActiveDone) + ) + } + + Plugin.prototype.makePluginActiveDone = function(data) { + var pluginCode = data.responseData.pluginCode + + $('#builder-plugin-selector-panel [data-control=filelist]').fileList('markActive', pluginCode) + } + + // REGISTRATION + // ============================ + + $.oc.builder.entityControllers.plugin = Plugin; + +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.index.entity.version.js b/server/plugins/rainlab/builder/assets/js/builder.index.entity.version.js new file mode 100644 index 0000000..f63a278 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.index.entity.version.js @@ -0,0 +1,272 @@ +/* + * Builder Index controller Version entity controller + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + if ($.oc.builder.entityControllers === undefined) + $.oc.builder.entityControllers = {} + + var Base = $.oc.builder.entityControllers.base, + BaseProto = Base.prototype + + var Version = function(indexController) { + Base.call(this, 'version', indexController) + + this.hiddenHints = {} + } + + Version.prototype = Object.create(BaseProto) + Version.prototype.constructor = Version + + // PUBLIC METHODS + // ============================ + + Version.prototype.cmdCreateVersion = function(ev) { + var $link = $(ev.currentTarget), + versionType = $link.data('versionType') + + this.indexController.openOrLoadMasterTab($link, 'onVersionCreateOrOpen', this.newTabId(), { + version_type: versionType + }) + } + + Version.prototype.cmdSaveVersion = function(ev) { + var $target = $(ev.currentTarget), + $form = $target.closest('form') + + $target.request('onVersionSave').done( + this.proxy(this.saveVersionDone) + ) + } + + Version.prototype.cmdOpenVersion = function(ev) { + var versionNumber = $(ev.currentTarget).data('id'), + pluginCode = $(ev.currentTarget).data('pluginCode') + + this.indexController.openOrLoadMasterTab($(ev.target), 'onVersionCreateOrOpen', this.makeTabId(pluginCode+'-'+versionNumber), { + original_version: versionNumber + }) + } + + Version.prototype.cmdDeleteVersion = function(ev) { + var $target = $(ev.currentTarget) + $.oc.confirm($target.data('confirm'), this.proxy(this.deleteConfirmed)) + } + + Version.prototype.cmdApplyVersion = function(ev) { + var $target = $(ev.currentTarget), + $pane = $target.closest('div.tab-pane'), + self = this + + this.showHintPopup($pane, 'builder-version-apply', function(){ + $target.request('onVersionApply').done( + self.proxy(self.applyVersionDone) + ) + }) + } + + Version.prototype.cmdRollbackVersion = function(ev) { + var $target = $(ev.currentTarget), + $pane = $target.closest('div.tab-pane'), + self = this + + + this.showHintPopup($pane, 'builder-version-rollback', function(){ + $target.request('onVersionRollback').done( + self.proxy(self.rollbackVersionDone) + ) + }) + } + + // INTERNAL METHODS + // ============================ + + Version.prototype.saveVersionDone = function(data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + var $masterTabPane = this.getMasterTabsActivePane() + this.updateUiAfterSave($masterTabPane, data) + + if (!data.builderResponseData.isApplied) { + this.showSavedNotAppliedHint($masterTabPane) + } + } + + Version.prototype.showSavedNotAppliedHint = function($masterTabPane) { + this.showHintPopup($masterTabPane, 'builder-version-save-unapplied') + } + + Version.prototype.showHintPopup = function($masterTabPane, code, callback) { + if (this.getDontShowHintAgain(code, $masterTabPane)) { + if (callback) { + callback.apply(this) + } + + return + } + + $masterTabPane.one('hide.oc.popup', this.proxy(this.onHintPopupHide)) + + if (callback) { + $masterTabPane.one('shown.oc.popup', function(ev, $element, $modal) { + $modal.find('form').one('submit', function(ev) { + callback.apply(this) + ev.preventDefault() + + $(ev.target).trigger('close.oc.popup') + + return false + }) + }) + } + + $masterTabPane.popup({ + content: this.getPopupContent($masterTabPane, code) + }) + } + + Version.prototype.onHintPopupHide = function(ev, $element, $modal) { + var cbValue = $modal.find('input[type=checkbox][name=dont_show_again]').is(':checked'), + code = $modal.find('input[type=hidden][name=hint_code]').val() + + $modal.find('form').off('submit') + + if (!cbValue) { + return + } + + var $form = this.getMasterTabsActivePane().find('form[data-entity="versions"]') + + $form.request('onHideBackendHint', { + data: { + name: code + } + }) + + this.setDontShowHintAgain(code) + } + + Version.prototype.setDontShowHintAgain = function(code) { + this.hiddenHints[code] = true + } + + Version.prototype.getDontShowHintAgain = function(code, $pane) { + if (this.hiddenHints[code] !== undefined) { + return this.hiddenHints[code] + } + + return $pane.find('input[type=hidden][data-hint-hidden="'+code+'"]').val() == "true" + } + + Version.prototype.getPopupContent = function($pane, code) { + var template = $pane.find('script[data-version-hint-template="'+code+'"]') + + if (template.length === 0) { + throw new Error('Version popup template not found: '+code) + } + + return template.html() + } + + Version.prototype.updateUiAfterSave = function($masterTabPane, data) { + $masterTabPane.find('input[name=original_version]').val(data.builderResponseData.savedVersion) + this.updateMasterTabIdAndTitle($masterTabPane, data.builderResponseData) + this.unhideFormDeleteButton($masterTabPane) + + this.getVersionList().fileList('markActive', data.builderResponseData.tabId) + this.getIndexController().unchangeTab($masterTabPane) + } + + Version.prototype.deleteConfirmed = function() { + var $masterTabPane = this.getMasterTabsActivePane(), + $form = $masterTabPane.find('form') + + $.oc.stripeLoadIndicator.show() + $form.request('onVersionDelete').always( + $.oc.builder.indexController.hideStripeIndicatorProxy + ).done( + this.proxy(this.deleteDone) + ) + } + + Version.prototype.deleteDone = function() { + var $masterTabPane = this.getMasterTabsActivePane() + + this.getIndexController().unchangeTab($masterTabPane) + this.forceCloseTab($masterTabPane) + } + + Version.prototype.applyVersionDone = function(data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + var $masterTabPane = this.getMasterTabsActivePane() + + this.updateUiAfterSave($masterTabPane, data) + + this.updateVersionsButtons() + } + + Version.prototype.rollbackVersionDone = function(data) { + if (data['builderResponseData'] === undefined) { + throw new Error('Invalid response data') + } + + var $masterTabPane = this.getMasterTabsActivePane() + + this.updateUiAfterSave($masterTabPane, data) + + this.updateVersionsButtons() + } + + Version.prototype.getVersionList = function() { + return $('#layout-side-panel form[data-content-id=version] [data-control=filelist]') + } + + Version.prototype.updateVersionsButtons = function() { + var tabsObject = this.getMasterTabsObject(), + $tabs = tabsObject.$tabsContainer.find('> li'), + $versionList = this.getVersionList() + + // Find all version tabs and update Apply and Rollback buttons + // basing on the version statuses in the version list. + for (var i=$tabs.length-1; i>=0; i--) { + var $tab = $($tabs[i]), + tabId = $tab.data('tabId') + + if (!tabId || String(tabId).length == 0) { + continue + } + + var $versionLi = $versionList.find('li[data-id="'+tabId+'"]') + if (!$versionLi.length) { + continue + } + + var isApplied = $versionLi.data('applied'), + $pane = tabsObject.findPaneFromTab($tab) + + if (isApplied) { + $pane.find('[data-builder-command="version:cmdApplyVersion"]').addClass('hide') + $pane.find('[data-builder-command="version:cmdRollbackVersion"]').removeClass('hide') + } + else { + $pane.find('[data-builder-command="version:cmdApplyVersion"]').removeClass('hide') + $pane.find('[data-builder-command="version:cmdRollbackVersion"]').addClass('hide') + } + } + + } + + // REGISTRATION + // ============================ + + $.oc.builder.entityControllers.version = Version; + +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.index.js b/server/plugins/rainlab/builder/assets/js/builder.index.js new file mode 100644 index 0000000..c73c6dc --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.index.js @@ -0,0 +1,294 @@ +/* + * Builder client-side Index page controller + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + var Base = $.oc.foundation.base, + BaseProto = Base.prototype + + var Builder = function() { + Base.call(this) + + this.$masterTabs = null + this.masterTabsObj = null + this.hideStripeIndicatorProxy = null + this.entityControllers = {} + + this.init() + } + + Builder.prototype = Object.create(BaseProto) + Builder.prototype.constructor = Builder + + Builder.prototype.dispose = function() { + // We don't really care about disposing the + // index controller, as it's used only once + // and always exists during the page life. + BaseProto.dispose.call(this) + } + + // PUBLIC METHODS + // ============================ + + Builder.prototype.openOrLoadMasterTab = function($form, serverHandlerName, tabId, data) { + if (this.masterTabsObj.goTo(tabId)) + return false + + var requestData = data === undefined ? {} : data + + $.oc.stripeLoadIndicator.show() + var promise = $form.request( + serverHandlerName, + { data: requestData } + ) + .done(this.proxy(this.addMasterTab)) + .always( + this.hideStripeIndicatorProxy + ) + + return promise + } + + Builder.prototype.getMasterTabActivePane = function() { + return this.$masterTabs.find('> .tab-content > .tab-pane.active') + } + + Builder.prototype.unchangeTab = function($pane) { + $pane.find('form').trigger('unchange.oc.changeMonitor') + } + + Builder.prototype.triggerCommand = function(command, ev) { + var commandParts = command.split(':') + + if (commandParts.length === 2) { + var entity = commandParts[0], + commandToExecute = commandParts[1] + + if (this.entityControllers[entity] === undefined) { + throw new Error('Unknown entity type: ' + entity) + } + + this.entityControllers[entity].invokeCommand(commandToExecute, ev) + } + } + + // INTERNAL METHODS + // ============================ + + Builder.prototype.init = function() { + this.$masterTabs = $('#builder-master-tabs') + this.$sidePanel = $('#builder-side-panel') + + this.masterTabsObj = this.$masterTabs.data('oc.tab') + this.hideStripeIndicatorProxy = this.proxy(this.hideStripeIndicator) + new $.oc.tabFormExpandControls(this.$masterTabs) + + this.createEntityControllers() + this.registerHandlers() + } + + Builder.prototype.createEntityControllers = function() { + for (var controller in $.oc.builder.entityControllers) { + if (controller == "base") { + continue + } + + this.entityControllers[controller] = new $.oc.builder.entityControllers[controller](this) + } + } + + Builder.prototype.registerHandlers = function() { + $(document).on('click', '[data-builder-command]', this.proxy(this.onCommand)) + $(document).on('submit', '[data-builder-command]', this.proxy(this.onCommand)) + + this.$masterTabs.on('changed.oc.changeMonitor', this.proxy(this.onFormChanged)) + this.$masterTabs.on('unchanged.oc.changeMonitor', this.proxy(this.onFormUnchanged)) + this.$masterTabs.on('shown.bs.tab', this.proxy(this.onTabShown)) + this.$masterTabs.on('afterAllClosed.oc.tab', this.proxy(this.onAllTabsClosed)) + this.$masterTabs.on('closed.oc.tab', this.proxy(this.onTabClosed)) + this.$masterTabs.on('autocompleteitems.oc.inspector', this.proxy(this.onDataRegistryItems)) + this.$masterTabs.on('dropdownoptions.oc.inspector', this.proxy(this.onDataRegistryItems)) + + for (var controller in this.entityControllers) { + if (this.entityControllers[controller].registerHandlers !== undefined) { + this.entityControllers[controller].registerHandlers() + } + } + } + + Builder.prototype.hideStripeIndicator = function() { + $.oc.stripeLoadIndicator.hide() + } + + Builder.prototype.addMasterTab = function(data) { + this.masterTabsObj.addTab(data.tabTitle, data.tab, data.tabId, 'oc-' + data.tabIcon) + + if (data.isNewRecord) { + var $masterTabPane = this.getMasterTabActivePane() + + $masterTabPane.find('form').one('ready.oc.changeMonitor', this.proxy(this.onChangeMonitorReady)) + } + } + + Builder.prototype.updateModifiedCounter = function() { + var counters = { + database: { menu: 'database', count: 0 }, + models: { menu: 'models', count: 0 }, + permissions: { menu: 'permissions', count: 0 }, + menus: { menu: 'menus', count: 0 }, + versions: { menu: 'versions', count: 0 }, + localization: { menu: 'localization', count: 0 }, + controller: { menu: 'controllers', count: 0 } + } + + $('> div.tab-content > div.tab-pane[data-modified] > form', this.$masterTabs).each(function(){ + var entity = $(this).data('entity') + counters[entity].count++ + }) + + $.each(counters, function(type, data){ + $.oc.sideNav.setCounter('builder/' + data.menu, data.count); + }) + } + + Builder.prototype.getFormPluginCode = function(formElement) { + var $form = $(formElement).closest('form'), + $input = $form.find('input[name="plugin_code"]'), + code = $input.val() + + if (!code) { + throw new Error('Plugin code input is not found in the form.') + } + + return code + } + + Builder.prototype.setPageTitle = function(title) { + $.oc.layout.setPageTitle(title.length ? (title + ' | ') : title) + } + + Builder.prototype.getFileLists = function() { + return $('[data-control=filelist]', this.$sidePanel) + } + + Builder.prototype.dataToInspectorArray = function(data) { + var result = [] + + for (var key in data) { + var item = { + title: data[key], + value: key + } + result.push(item) + } + + return result + } + + // EVENT HANDLERS + // ============================ + + Builder.prototype.onCommand = function(ev) { + if (ev.currentTarget.tagName == 'FORM' && ev.type == 'click') { + // The form elements could have data-builder-command attribute, + // but for them we only handle the submit event and ignore clicks. + + return + } + + var command = $(ev.currentTarget).data('builderCommand') + this.triggerCommand(command, ev) + + // Prevent default for everything except drop-down menu items + // + var $target = $(ev.currentTarget) + if (ev.currentTarget.tagName === 'A' && $target.attr('role') == 'menuitem' && $target.attr('href') == 'javascript:;') { + return + } + + ev.preventDefault() + return false + } + + Builder.prototype.onFormChanged = function(ev) { + $('.form-tabless-fields', ev.target).trigger('modified.oc.tab') + this.updateModifiedCounter() + } + + Builder.prototype.onFormUnchanged = function(ev) { + $('.form-tabless-fields', ev.target).trigger('unmodified.oc.tab') + this.updateModifiedCounter() + } + + Builder.prototype.onTabShown = function(ev) { + var $tabControl = $(ev.target).closest('[data-control=tab]') + + if ($tabControl.attr('id') != this.$masterTabs.attr('id')) { + return + } + + var dataId = $(ev.target).closest('li').attr('data-tab-id'), + title = $(ev.target).attr('title') + + if (title) { + this.setPageTitle(title) + } + + this.getFileLists().fileList('markActive', dataId) + + $(window).trigger('resize') + } + + Builder.prototype.onAllTabsClosed = function(ev) { + this.setPageTitle('') + this.getFileLists().fileList('markActive', null) + } + + Builder.prototype.onTabClosed = function(ev, tab, pane) { + $(pane).find('form').off('ready.oc.changeMonitor', this.proxy(this.onChangeMonitorReady)) + + this.updateModifiedCounter() + } + + Builder.prototype.onChangeMonitorReady = function(ev) { + $(ev.target).trigger('change') + } + + Builder.prototype.onDataRegistryItems = function(ev, data) { + var self = this + + if (data.propertyDefinition.fillFrom == 'model-classes' || + data.propertyDefinition.fillFrom == 'model-forms' || + data.propertyDefinition.fillFrom == 'model-lists' || + data.propertyDefinition.fillFrom == 'controller-urls' || + data.propertyDefinition.fillFrom == 'model-columns' || + data.propertyDefinition.fillFrom == 'plugin-lists' || + data.propertyDefinition.fillFrom == 'permissions') { + ev.preventDefault() + + var subtype = null, + subtypeProperty = data.propertyDefinition.subtypeFrom + + if (subtypeProperty !== undefined) { + subtype = data.values[subtypeProperty] + } + + $.oc.builder.dataRegistry.get($(ev.target), this.getFormPluginCode(ev.target), data.propertyDefinition.fillFrom, subtype, function(response){ + data.callback({ + options: self.dataToInspectorArray(response) + }) + }) + } + } + + // INITIALIZATION + // ============================ + + $(document).ready(function(){ + $.oc.builder.indexController = new Builder() + }) + +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.inspector.editor.localization.js b/server/plugins/rainlab/builder/assets/js/builder.inspector.editor.localization.js new file mode 100644 index 0000000..1dfc831 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.inspector.editor.localization.js @@ -0,0 +1,114 @@ +/* + * Inspector localization editor class. + */ ++function ($) { "use strict"; + + var Base = $.oc.inspector.propertyEditors.string, + BaseProto = Base.prototype + + var LocalizationEditor = function(inspector, propertyDefinition, containerCell, group) { + this.localizationInput = null + + Base.call(this, inspector, propertyDefinition, containerCell, group) + } + + LocalizationEditor.prototype = Object.create(BaseProto) + LocalizationEditor.prototype.constructor = Base + + LocalizationEditor.prototype.dispose = function() { + this.removeLocalizationInput() + + BaseProto.dispose.call(this) + } + + LocalizationEditor.prototype.build = function() { + var container = document.createElement('div'), + editor = document.createElement('input'), + placeholder = this.propertyDefinition.placeholder !== undefined ? this.propertyDefinition.placeholder : '', + value = this.inspector.getPropertyValue(this.propertyDefinition.property) + + editor.setAttribute('type', 'text') + editor.setAttribute('class', 'string-editor') + editor.setAttribute('placeholder', placeholder) + + container.setAttribute('class', 'autocomplete-container') + + if (value === undefined) { + value = this.propertyDefinition.default + } + + if (value === undefined) { + value = '' + } + + editor.value = value + + $.oc.foundation.element.addClass(this.containerCell, 'text autocomplete') + + container.appendChild(editor) + this.containerCell.appendChild(container) + + this.buildLocalizationEditor() + } + + LocalizationEditor.prototype.buildLocalizationEditor = function() { + this.localizationInput = new $.oc.builder.localizationInput(this.getInput(), this.getForm(), { + plugin: this.getPluginCode(), + beforePopupShowCallback: this.proxy(this.onPopupShown, this), + afterPopupHideCallback: this.proxy(this.onPopupHidden, this) + }) + } + + LocalizationEditor.prototype.removeLocalizationInput = function() { + this.localizationInput.dispose() + + this.localizationInput = null + } + + LocalizationEditor.prototype.supportsExternalParameterEditor = function() { + return false + } + + LocalizationEditor.prototype.registerHandlers = function() { + BaseProto.registerHandlers.call(this) + + $(this.getInput()).on('change', this.proxy(this.onInputKeyUp)) + } + + LocalizationEditor.prototype.unregisterHandlers = function() { + BaseProto.unregisterHandlers.call(this) + + $(this.getInput()).off('change', this.proxy(this.onInputKeyUp)) + } + + LocalizationEditor.prototype.getForm = function() { + var inspectableElement = this.getRootSurface().getInspectableElement() + + if (!inspectableElement) { + throw new Error('Cannot determine inspectable element in the Builder localization editor.') + } + + return $(inspectableElement).closest('form') + } + + LocalizationEditor.prototype.getPluginCode = function() { + var $form = this.getForm(), + $input = $form.find('input[name=plugin_code]') + + if (!$input.length) { + throw new Error('The input "plugin_code" should be defined in the form in order to use the localization Inspector editor.') + } + + return $input.val() + } + + LocalizationEditor.prototype.onPopupShown = function() { + this.getRootSurface().popupDisplayed() + } + + LocalizationEditor.prototype.onPopupHidden = function() { + this.getRootSurface().popupHidden() + } + + $.oc.inspector.propertyEditors.builderLocalization = LocalizationEditor +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.localizationinput.js b/server/plugins/rainlab/builder/assets/js/builder.localizationinput.js new file mode 100644 index 0000000..c2ec839 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.localizationinput.js @@ -0,0 +1,299 @@ +/* + * Builder localization input control + */ ++function ($) { "use strict"; + + if ($.oc.builder === undefined) + $.oc.builder = {} + + var Base = $.oc.foundation.base, + BaseProto = Base.prototype + + var LocalizationInput = function(input, form, options) { + this.input = input + this.form = form + this.options = $.extend({}, LocalizationInput.DEFAULTS, options) + this.disposed = false + this.initialized = false + this.newStringPopupMarkup = null + + Base.call(this) + + this.init() + } + + LocalizationInput.prototype = Object.create(BaseProto) + LocalizationInput.prototype.constructor = LocalizationInput + + LocalizationInput.prototype.dispose = function() { + this.unregisterHandlers() + + this.form = null + this.options.beforePopupShowCallback = null + this.options.afterPopupHideCallback = null + this.options = null + this.disposed = true + this.newStringPopupMarkup = null + + if (this.initialized) { + $(this.input).autocomplete('destroy') + } + + $(this.input).removeData('localization-input') + + this.input = null + + BaseProto.dispose.call(this) + } + + LocalizationInput.prototype.init = function() { + if (!this.options.plugin) { + throw new Error('The options.plugin value should be set in the localization input object.') + } + + var $input = $(this.input) + + $input.data('localization-input', this) + $input.attr('data-builder-localization-input', 'true') + $input.attr('data-builder-localization-plugin', this.options.plugin) + + this.getContainer().addClass('localization-input-container') + + this.registerHandlers() + this.loadDataAndBuild() + } + + LocalizationInput.prototype.buildAddLink = function() { + var $container = this.getContainer() + + if ($container.find('a.localization-trigger').length > 0) { + return + } + + var trigger = document.createElement('a') + + trigger.setAttribute('class', 'oc-icon-plus localization-trigger') + trigger.setAttribute('href', '#') + + var pos = $container.position() + $(trigger).css({ + top: pos.top + 4, + right: 7 + }) + + $container.append(trigger) + } + + LocalizationInput.prototype.loadDataAndBuild = function() { + this.showLoadingIndicator() + + var result = $.oc.builder.dataRegistry.get(this.form, this.options.plugin, 'localization', null, this.proxy(this.dataLoaded)), + self = this + + if (result) { + result.always(function(){ + self.hideLoadingIndicator() + }) + } + } + + LocalizationInput.prototype.reload = function() { + $.oc.builder.dataRegistry.get(this.form, this.options.plugin, 'localization', null, this.proxy(this.dataLoaded)) + } + + LocalizationInput.prototype.dataLoaded = function(data) { + if (this.disposed) { + return + } + + var $input = $(this.input), + autocomplete = $input.data('autocomplete') + + if (!autocomplete) { + this.hideLoadingIndicator() + + var autocompleteOptions = { + source: this.preprocessData(data), + matchWidth: true + } + + autocompleteOptions = $.extend(autocompleteOptions, this.options.autocompleteOptions) + + $(this.input).autocomplete(autocompleteOptions) + + this.initialized = true + } + else { + autocomplete.source = this.preprocessData(data) + } + } + + LocalizationInput.prototype.preprocessData = function(data) { + var dataClone = $.extend({}, data) + + for (var key in dataClone) { + dataClone[key] = key + ' - ' + dataClone[key] + } + + return dataClone + } + + LocalizationInput.prototype.getContainer = function() { + return $(this.input).closest('.autocomplete-container') + } + + LocalizationInput.prototype.showLoadingIndicator = function() { + var $container = this.getContainer() + + $container.addClass('loading-indicator-container size-small') + $container.loadIndicator() + } + + LocalizationInput.prototype.hideLoadingIndicator = function() { + var $container = this.getContainer() + + $container.loadIndicator('hide') + $container.loadIndicator('destroy') + + $container.removeClass('loading-indicator-container') + } + + // POPUP + // ============================ + + LocalizationInput.prototype.loadAndShowPopup = function() { + if (this.newStringPopupMarkup === null) { + $.oc.stripeLoadIndicator.show() + $(this.input).request('onLanguageLoadAddStringForm') + .done( + this.proxy(this.popupMarkupLoaded) + ).always(function(){ + $.oc.stripeLoadIndicator.hide() + }) + } + else { + this.showPopup() + } + } + + LocalizationInput.prototype.popupMarkupLoaded = function(responseData) { + this.newStringPopupMarkup = responseData.markup + + this.showPopup() + } + + LocalizationInput.prototype.showPopup = function() { + var $input = $(this.input) + + $input.popup({ + content: this.newStringPopupMarkup + }) + + var $content = $input.data('oc.popup').$content, + $keyInput = $content.find('#language_string_key') + + $.oc.builder.dataRegistry.get(this.form, this.options.plugin, 'localization', 'sections', function(data){ + $keyInput.autocomplete({ + source: data, + matchWidth: true + }) + }) + + $content.find('form').on('submit', this.proxy(this.onSubmitPopupForm)) + } + + LocalizationInput.prototype.stringCreated = function(data) { + if (data.localizationData === undefined || data.registryData === undefined) { + throw new Error('Invalid server response.') + } + + var $input = $(this.input) + + $input.val(data.localizationData.key) + + $.oc.builder.dataRegistry.set(this.options.plugin, 'localization', null, data.registryData.strings) + $.oc.builder.dataRegistry.set(this.options.plugin, 'localization', 'sections', data.registryData.sections) + + $input.data('oc.popup').hide() + + $input.trigger('change') + } + + LocalizationInput.prototype.onSubmitPopupForm = function(ev) { + var $form = $(ev.target) + + $.oc.stripeLoadIndicator.show() + $form.request('onLanguageCreateString', { + data: { + plugin_code: this.options.plugin + } + }) + .done( + this.proxy(this.stringCreated) + ).always(function(){ + $.oc.stripeLoadIndicator.hide() + }) + + ev.preventDefault() + return false + } + + LocalizationInput.prototype.onPopupHidden = function(ev, link, popup) { + $(popup).find('#language_string_key').autocomplete('destroy') + $(popup).find('form').on('submit', this.proxy(this.onSubmitPopupForm)) + + if (this.options.afterPopupHideCallback) { + this.options.afterPopupHideCallback() + } + } + + LocalizationInput.updatePluginInputs = function(plugin) { + var inputs = document.body.querySelectorAll('input[data-builder-localization-input][data-builder-localization-plugin="'+plugin+'"]') + + for (var i=inputs.length-1; i>=0; i--) { + $(inputs[i]).data('localization-input').reload() + } + } + + // EVENT HANDLERS + // ============================ + + LocalizationInput.prototype.unregisterHandlers = function() { + this.input.removeEventListener('focus', this.proxy(this.onInputFocus)) + + this.getContainer().off('click', 'a.localization-trigger', this.proxy(this.onTriggerClick)) + $(this.input).off('hidden.oc.popup', this.proxy(this.onPopupHidden)) + } + + LocalizationInput.prototype.registerHandlers = function() { + this.input.addEventListener('focus', this.proxy(this.onInputFocus)) + + this.getContainer().on('click', 'a.localization-trigger', this.proxy(this.onTriggerClick)) + $(this.input).on('hidden.oc.popup', this.proxy(this.onPopupHidden)) + } + + LocalizationInput.prototype.onInputFocus = function() { + this.buildAddLink() + } + + LocalizationInput.prototype.onTriggerClick = function(ev) { + if (this.options.beforePopupShowCallback) { + this.options.beforePopupShowCallback() + } + + this.loadAndShowPopup() + + ev.preventDefault() + return false + } + + LocalizationInput.DEFAULTS = { + plugin: null, + autocompleteOptions: {}, + beforePopupShowCallback: null, + afterPopupHideCallback: null + } + + $.oc.builder.localizationInput = LocalizationInput + +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/js/builder.table.processor.localization.js b/server/plugins/rainlab/builder/assets/js/builder.table.processor.localization.js new file mode 100644 index 0000000..f54a89e --- /dev/null +++ b/server/plugins/rainlab/builder/assets/js/builder.table.processor.localization.js @@ -0,0 +1,126 @@ +/* + * Localiztion cell processor for the table control. + */ + ++function ($) { "use strict"; + + // NAMESPACE CHECK + // ============================ + + if ($.oc.table === undefined) + throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded."); + + if ($.oc.table.processor === undefined) + throw new Error("The $.oc.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded."); + + // CLASS DEFINITION + // ============================ + + var Base = $.oc.table.processor.string, + BaseProto = Base.prototype + + var LocalizationProcessor = function(tableObj, columnName, columnConfiguration) { + // + // State properties + // + + this.localizationInput = null + this.popupDisplayed = false + + // + // Parent constructor + // + + Base.call(this, tableObj, columnName, columnConfiguration) + } + + LocalizationProcessor.prototype = Object.create(BaseProto) + LocalizationProcessor.prototype.constructor = LocalizationProcessor + + LocalizationProcessor.prototype.dispose = function() { + this.removeLocalizationInput() + + BaseProto.dispose.call(this) + } + + /* + * Forces the processor to hide the editor when the user navigates + * away from the cell. Processors can update the sell value in this method. + * Processors must clear the reference to the active cell in this method. + */ + LocalizationProcessor.prototype.onUnfocus = function() { + if (!this.activeCell || this.popupDisplayed) + return + + this.removeLocalizationInput() + + BaseProto.onUnfocus.call(this) + } + + LocalizationProcessor.prototype.onBeforePopupShow = function() { + this.popupDisplayed = true + } + + LocalizationProcessor.prototype.onAfterPopupHide = function() { + this.popupDisplayed = false + } + + /* + * Renders the cell in the normal (no edit) mode + */ + LocalizationProcessor.prototype.renderCell = function(value, cellContentContainer) { + BaseProto.renderCell.call(this, value, cellContentContainer) + } + + LocalizationProcessor.prototype.buildEditor = function(cellElement, cellContentContainer, isClick) { + BaseProto.buildEditor.call(this, cellElement, cellContentContainer, isClick) + + $.oc.foundation.element.addClass(cellContentContainer, 'autocomplete-container') + this.buildLocalizationEditor() + } + + LocalizationProcessor.prototype.buildLocalizationEditor = function() { + var input = this.getInput() + + this.localizationInput = new $.oc.builder.localizationInput(input, $(input), { + plugin: this.getPluginCode(input), + beforePopupShowCallback: $.proxy(this.onBeforePopupShow, this), + afterPopupHideCallback: $.proxy(this.onAfterPopupHide, this), + autocompleteOptions: { + menu: ' ', + bodyContainer: true + } + }) + } + + LocalizationProcessor.prototype.getInput = function() { + if (!this.activeCell) { + return null + } + + return this.activeCell.querySelector('.string-input') + } + + LocalizationProcessor.prototype.getPluginCode = function(input) { + var $form = $(input).closest('form'), + $input = $form.find('input[name=plugin_code]') + + if (!$input.length) { + throw new Error('The input "plugin_code" should be defined in the form in order to use the localization table processor.') + } + + return $input.val() + } + + LocalizationProcessor.prototype.removeLocalizationInput = function() { + if (!this.localizationInput) { + return + } + + this.localizationInput.dispose() + + this.localizationInput = null + } + + $.oc.table.processor.builderLocalization = LocalizationProcessor; +}(window.jQuery); \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/less/behaviors.less b/server/plugins/rainlab/builder/assets/less/behaviors.less new file mode 100644 index 0000000..d06e292 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/less/behaviors.less @@ -0,0 +1,167 @@ +.builder-controllers-builder-area { + background: white; + + ul.controller-behavior-list { + .clearfix(); + + padding: 20px; + margin-bottom: 0; + list-style: none; + + li { + h4 { + text-align: center; + border-bottom: 1px dotted @builder-control-border-color; + margin: 0 -20px 40px; + + span { + display: inline-block; + color: white; + margin: 0 auto; + .border-radius(8px); + background: @builder-control-border-color; + padding: 7px 10px; + font-size: 13px; + line-height: 100%; + position: relative; + top: 14px; + } + } + } + + .behavior-container { + margin-bottom: 40px; + .clearfix(); + cursor: pointer; + + .list-behavior, .reorder-behavior { + .border-radius(4px); + border: 2px solid @builder-control-border-color; + padding: 25px 10px 25px 10px; + + table { + border-collapse: collapse; + width: 100%; + + td { + padding: 0 15px 15px 15px; + + border-right: 1px solid @builder-control-border-color; + + &:last-child { + border-right: none; + } + } + + .placeholder { + background: #EEF2F4; + height: 25px; + } + + tbody tr:last-child td { + padding-bottom: 0; + } + } + } + + .reorder-behavior { + table { + i.icon-bars, .placeholder { + float: left; + } + + i.icon-bars { + margin-right: 15px; + color: #D6DDE0; + font-size: 28px; + line-height: 28px; + position: relative; + top: -2px; + } + } + } + + .form-behavior { + div.form { + .clearfix(); + + padding: 25px 25px 0 25px; + border: 2px solid @builder-control-border-color; + margin-bottom: 20px; + .border-radius(4px); + } + + div.field { + &.left { + float: left; + width: 48%; + } + + &.right { + float: right; + width: 45%; + } + + div.label { + background: #EEF2F4; + height: 25px; + margin-bottom: 10px; + + &.size-3 { + width: 100px; + } + + &.size-5 { + width: 150px; + } + + &.size-2 { + width: 60px; + } + } + + div.control { + background: #EEF2F4; + height: 35px; + margin-bottom: 25px; + } + } + + div.button { + background: #EEF2F4; + height: 35px; + margin-right: 20px; + .border-radius(4px); + + &.size-5 { + width: 100px; + } + + &.size-3 { + width: 60px; + } + + &:first-child { + margin-right: 0; + } + } + } + + &:hover, &.inspector-open { + * { + border-color: @builder-hover-color!important; + } + } + } + } +} + +// Fix for the Mac firefox + +html.gecko.mac { + .builder-controllers-builder-area { + ul.controller-behavior-list { + padding-right: 40px; + } + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/less/builder.less b/server/plugins/rainlab/builder/assets/less/builder.less new file mode 100644 index 0000000..45e8ba5 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/less/builder.less @@ -0,0 +1,198 @@ +@import "../../../../../modules/backend/assets/less/core/boot.less"; + +@builder-control-border-color: #bdc3c7; +@builder-control-text-color: #95a5a6; +@builder-hover-color: #2581b8; + +@import "buildingarea.less"; +@import "controlblueprint.less"; +@import "behaviors.less"; +@import "tabs.less"; +@import "menus.less"; +@import "localization.less"; + +.control-filelist ul li.group.model > h4 a:after { + content: @random; + top: 10px; +} + +.control-filelist ul li.group.form > h4 a:after { + content: @check-square; +} + +.control-filelist ul li.group.list > h4 a:after { + content: @th-list; + top: 10px; +} + +.control-filelist ul li.group > ul > li.group > ul > li > a { + padding-left: 73px; + margin-left: -20px; +} + +.control-filelist ul li.with-icon { + span.title, span.description { + padding-left: 22px; + } + + i.list-icon { + position: absolute; + left: 20px; + top: 12px; + color: #405261; + + &.mute { + color: #8f8f8f; + } + + &.icon-check-square { + color: #8da85e; + } + } +} + +html.gecko .control-filelist ul li.group { + margin-right: 10px; +} + +.builder-inspector-container { + width: 350px; + border-left: 1px solid #d9d9d9; + + &:empty { + display: none!important; + } +} + +form.hide-secondary-tabs { + div.control-tabs.secondary-tabs { + ul.nav.nav-tabs { + display: none; + } + } +} + +.form-group { + &.size-quarter { + width: 23.5%; + } + + &.size-three-quarter { + width: 73.5%; + } +} + +// Full height database columns table + +form[data-entity=database] { + div.field-datatable { + position: absolute; + width: 100%; + height: 100%; + + div[data-control=table] { + position: absolute; + width: 100%; + height: 100%; + + div.table-container { + position: absolute; + width: 100%; + height: 100%; + + div.control-scrollbar { + top: 70px; + bottom: 0; + position: absolute; + max-height: none!important; + height: auto!important; + } + } + } + } +} + +// Custom buttons for the toolbar + +div.control-table .toolbar a.builder-custom-table-button { + &:before { + line-height: 17px; + font-size: 21px; + color: #323e50; + margin-right: 5px; + top: 3px; + .opacity(1); + } +} + +// TODO: Move the aux tab styles to the tab.less + +.control-tabs { + &.auxiliary-tabs { + background: white; + + .border-top(@color) { + content: ' '; + display: block; + position: absolute; + width: 100%; + height: 1px; + background: @color; + top: 0; + left: 0; + } + + > ul.nav-tabs, > div > ul.nav-tabs { + padding-left: 20px; + padding-bottom: 2px; + background: white; + position: relative; + + &:before { + .border-top(#95a5a6); + } + + > li { + margin-right: 2px; + + > a { + background: white; + color: #bdc3c7; + border-left: 1px solid #ecf0f1!important; + border-right: 1px solid #ecf0f1!important; + border-bottom: 1px solid #ecf0f1!important; + padding: 4px 10px; + line-height: 100%; + .border-radius(0 0 4px 4px); + + > span.title > span { + margin-bottom: 0; + font-size: 13px; + height: auto; + } + } + + &.active{ + top: 0; + + &:before { + .border-top(white); + top: -1px; + } + + a { + padding-top: 5px; + border-left: 1px solid #95a5a6!important; + border-right: 1px solid #95a5a6!important; + border-bottom: 1px solid #95a5a6!important; + color: #95a5a6; + } + } + } + } + + > div.tab-content > .tab-pane { + background: white; + } + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/less/buildingarea.less b/server/plugins/rainlab/builder/assets/less/buildingarea.less new file mode 100644 index 0000000..96ea040 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/less/buildingarea.less @@ -0,0 +1,251 @@ +.builder-building-area { + background: white; + + ul.builder-control-list { + .clearfix(); + + padding: 20px; + margin-bottom: 0; + list-style: none; + > li.control { + position: relative; + margin-bottom: 20px; + cursor: pointer; + .user-select(none); + + &[data-unknown] { + cursor: default; + } + + &.placeholder, &.loading-control { + padding: 10px 12px; + position: relative; + text-align: center; + border: 2px dotted #dde0e2; + margin-top: 20px; + .border-radius(4px); + color: #dae0e0; + + i { + margin-right: 8px; + } + } + + &.clear-row { + display: none; + margin-bottom: 0; + } + + &.loading-control { + border-color: #bdc3c7; + text-align: left; + } + + &.updating-control:after, + &.loading-control:before { + background-image:url(../../../../../modules/system/assets/ui/images/loader-transparent.svg); + background-size: 15px 15px; + background-position: 50% 50%; + display: inline-block; + width: 15px; + height: 15px; + content: ' '; + margin-right: 13px; + position: relative; + top: 2px; + .animation(spin 1s linear infinite); + } + + &.loading-control:after { + content: attr(data-builder-loading-text); + display: inline-block; + } + + &.updating-control:after { + position: absolute; + right: -8px; + top: 5px; + } + + &.updating-control:before { + content: ''; + position: absolute; + right: 0; + top: 0; + width: 25px; + height: 25px; + background: rgba(127, 127, 127, 0.1); + .border-radius(4px); + } + + &.drag-over { + color: @builder-hover-color; + border-color: @builder-hover-color; + } + + &.span-full { + width: 100%; + float: left; + } + + &.span-left { + float: left; + width: 48.5%; + clear: left; + } + + &.span-right { + float: right; + width: 48.5%; + clear: right; + } + + &.span-right + li.clear-row { + display: block; + clear: both; + } + + > div.remove-control { + display: none; + } + + &:not(.placeholder):not(.loading-control):not(.updating-control):hover > { + > div.remove-control { + font-family: sans-serif; + display: block; + position: absolute; + right: 0; + top: 0; + cursor: pointer; + width: 21px; + height: 21px; + padding-left: 6px; + font-size: 16px; + font-weight: bold; + line-height: 21px; + .border-radius(20px); + background: #ecf0f1; + color: #95a5a6 !important; + + &:hover { + color: white !important; + background: #c03f31; + } + } + + &[data-control-type=hint], &[data-control-type=partial] { + > div.remove-control { + top: 12px; + right: 12px; + } + } + } + + &[data-control-type=hint], &[data-control-type=partial] { + &.updating-control { + &:before { + right: 12px; + top: 7; + } + + &:after { + right: 4px; + top: 13px; + } + } + } + + > .control-wrapper, + > .control-static-contents { + position: relative; + .transition(margin 0.1s); + } + } + + > li.placeholder:hover, + > li.placeholder.popover-highlight, + > li.placeholder.control-palette-open { + background-color: @builder-hover-color!important; + color: white!important; + border-style: solid; + border-color: @builder-hover-color; + } + + > li.control:not(.placeholder):not(.loading-control):not([data-unknown]):hover > .control-wrapper *, + > li.control.inspector-open:not(.placeholder):not(.loading-control) > .control-wrapper * { + color: @builder-hover-color!important; + } + + > li.control.drag-over:not(.placeholder) { + &:before { + position: absolute; + content: ''; + top: 0; + left: 0; + width: 10px; + height: 100%; + .border-radius(5px); + background-color: @builder-hover-color; + } + + > .control-wrapper, + > .control-static-contents { + margin-left: 20px; + margin-right: -20px; + } + } + } + + .control-body { + &.field-disabled, + &.field-hidden { + .opacity(0.5); + } + } + + .builder-control-label { + margin-bottom: 10px; + color: #555555; + font-size: 14px; + font-weight: 600; + + &.required:after { + vertical-align: super; + font-size: 60%; + content: " *"; + } + } + + .builder-control-label:empty { + margin-bottom: 0; + } + + .builder-control-comment-above { + margin-bottom: 8px; + margin-top: -3px; + } + + .builder-control-comment-below { + margin-top: 6px; + } + + .builder-control-comment-above, + .builder-control-comment-below { + color: #737373; + font-size: 12px; + + &:empty { + display: none; + } + } +} + +html.gecko.mac { + // Fixes a quirk in FireFox on mac + + .builder-building-area { + div[data-root-control-wrapper] { + margin-right: 17px; + } + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/less/controlblueprint.less b/server/plugins/rainlab/builder/assets/less/controlblueprint.less new file mode 100644 index 0000000..3f2f786 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/less/controlblueprint.less @@ -0,0 +1,260 @@ +.builder-building-area { + .builder-blueprint-control-text, + .builder-blueprint-control-textarea, + .builder-blueprint-control-partial, + .builder-blueprint-control-unknown, + .builder-blueprint-control-dropdown { + padding: 10px 12px; + border: 2px solid @builder-control-border-color; + color: @builder-control-text-color; + .border-radius(4px); + + i { + margin-right: 5px; + } + } + + li.control:hover, li.inspector-open { + > .control-wrapper { + .builder-blueprint-control-text, + .builder-blueprint-control-textarea, + .builder-blueprint-control-dropdown { + border-color: @builder-hover-color; + } + + .builder-blueprint-control-dropdown:before { + background-color: @builder-hover-color; + } + } + } + + .builder-blueprint-control-textarea { + &.size-tiny { min-height: @size-tiny; } + &.size-small { min-height: @size-small; } + &.size-large { min-height: @size-large; } + &.size-huge { min-height: @size-huge; } + &.size-giant { min-height: @size-giant; } + } + + .builder-blueprint-control-section { + border-bottom: 1px solid @builder-control-border-color; + padding-bottom: 4px; + + .builder-control-label { + font-size: 16px; + margin-bottom: 6px; + } + } + + .builder-blueprint-control-unknown { + border-color: #eee; + background: #eee; + } + + .builder-blueprint-control-partial { + border-color: #eee; + background: #eee; + } + + .builder-blueprint-control-dropdown { + position: relative; + + &:before, &:after { + position: absolute; + content: ''; + } + + &:before { + top: 0; + width: 2px; + background: @builder-control-border-color; + right: 40px; + height: 100%; + } + + &:after { + .icon(@angle-down); + color: inherit; + right: 15px; + top: 12px; + font-size: 20px; + line-height: 20px; + } + } + + .builder-blueprint-control-checkbox { + &:before { + float: left; + content: ' '; + border: 2px solid @builder-control-border-color; + .border-radius(4px); + width: 17px; + height: 17px; + position: relative; + top: 2px; + } + + .builder-control-label { + margin-left: 25px; + font-weight: normal; + } + + .builder-control-comment-below { + margin-left: 25px; + } + } + + li.control:hover, li.inspector-open { + > .control-wrapper { + .builder-blueprint-control-checkbox:before { + border-color: @builder-hover-color; + } + } + } + + .builder-blueprint-control-switch { + position: relative; + + &:before, &:after { + position: absolute; + content: ' '; + .border-radius(30px); + } + + &:before { + background-color: @builder-control-border-color; + + width: 34px; + height: 18px; + top: 2px; + left: 2px; + } + + &:after { + background-color: white; + + width: 14px; + height: 14px; + top: 4px; + left: 4px; + margin-left: 16px; + } + + .builder-control-label { + margin-left: 45px; + font-weight: normal; + } + + .builder-control-comment-below { + margin-left: 45px; + } + } + + li.control:hover, li.inspector-open { + > .control-wrapper { + .builder-blueprint-control-switch:before { + background-color: @builder-hover-color; + } + } + } + + .builder-blueprint-control-repeater-body { + > .repeater-button { + padding: 8px 13px; + background: @builder-control-border-color; + color: white; + display: inline-block; + margin-bottom: 10px; + .border-radius(2px); + } + } + + ul.builder-control-list > li.control:hover, + ul.builder-control-list > li.inspector-open { + > .control-wrapper > .control-body { + .builder-blueprint-control-repeater-body { + > .repeater-button { + background: @builder-hover-color; + color: white!important; + span { + color: white!important; + } + } + } + } + } + + .builder-blueprint-control-repeater { + position: relative; + + &:before { + content: ''; + position: absolute; + width: 2px; + top: 0; + left: 2px; + height: 100%; + background: @builder-control-border-color; + } + + &:after { + content: ''; + position: absolute; + width: 6px; + height: 6px; + top: 14px; + left: 0; + .border-radius(6px); + background: @builder-control-border-color; + } + + > ul.builder-control-list { + padding-right: 0; + padding-bottom: 0; + padding-top: 10px; + } + } + + li.control:hover, li.inspector-open { + > .builder-blueprint-control-repeater { + &:before, &:after { + background-color: @builder-hover-color; + } + } + } + + .builder-blueprint-control-radiolist, + .builder-blueprint-control-checkboxlist { + ul { + list-style: none; + padding: 0; + color: @builder-control-text-color; + + li { + margin-bottom: 3px; + + &:last-child { + margin-bottom: 0; + } + + i { + margin-right: 5px; + } + } + } + } + + .builder-blueprint-control-text { + &.fileupload.image { + width: 100px; + height: 100px; + text-align: center; + + i { + line-height: 77px; + margin-right: 0; + } + } + } + +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/less/localization.less b/server/plugins/rainlab/builder/assets/less/localization.less new file mode 100644 index 0000000..a8637ff --- /dev/null +++ b/server/plugins/rainlab/builder/assets/less/localization.less @@ -0,0 +1,86 @@ +.localization-input-container { + // position: relative; + + input[type=text].string-editor { + padding-right: 20px!important; + } + + .localization-trigger { + position: absolute; + display: none; + width: 10px; + height: 10px; + font-size: 14px; + color: #95a5a6; + + outline: none; + + &:hover, &:active, &:focus { + color: #2581b8; + text-decoration: none; + } + } +} + +table.inspector-fields td.active, +table.data td.active { + .localization-input-container .localization-trigger { + display: block; + } +} + +table.data td.active .localization-input-container .localization-trigger { + top: 5px!important; + right: 7px!important; +} + +.control-table td[data-column-type=builderLocalization] input[type=text] { + padding-right: 20px!important; +} + +.control-table { + td[data-column-type=builderLocalization] { + input[type=text] { + width: 100%; + height: 100%; + display: block; + outline: none; + border: none; + padding: 6px 10px 6px; + } + } +} + +html.chrome { + .control-table { + td[data-column-type=builderLocalization] { + input[type=text] { + padding: 6px 10px 7px!important; + } + } + } +} + +html.safari, html.gecko { + .control-table { + td[data-column-type=builderLocalization] { + input[type=text] { + padding: 5px 10px 5px; + } + } + } +} + +.autocomplete.dropdown-menu.table-widget-autocomplete.localization li a { + white-space: normal; + word-wrap: break-word; +} + +table.data td[data-column-type=builderLocalization] .loading-indicator-container.size-small .loading-indicator { + padding-bottom: 0!important; + + span { + left: auto; + right: 6px; + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/less/menus.less b/server/plugins/rainlab/builder/assets/less/menus.less new file mode 100644 index 0000000..3be02dd --- /dev/null +++ b/server/plugins/rainlab/builder/assets/less/menus.less @@ -0,0 +1,177 @@ +.builder-menu-editor { + background: white; + + .builder-menu-editor-workspace { + padding: 30px; + } + + ul.builder-menu { + font-size: 0; + padding: 0; + cursor: pointer; + + > li { + .border-radius(4px); + + div.item-container:hover, &.inspector-open > div.item-container { + background: @builder-hover-color!important; + color: white!important; + + a { + color: white!important; + } + } + + div.item-container { + position: relative; + + .close-btn { + color: white; + position: absolute; + display: none; + width: 15px; + height: 15px; + right: 5px; + top: 5px; + font-size: 14px; + text-align: center; + line-height: 14px; + } + + &:hover .close-btn { + display: block; + text-decoration: none; + .opacity(0.5); + + &:hover { + .opacity(1); + } + } + } + + &.add { + font-size: 16px; + text-align: center; + border: 2px dotted #dde0e2; + + a { + text-decoration: none; + color: #bdc3c7; + } + + span.title { + font-size: 14px; + } + + &:hover { + border: 2px dotted @builder-hover-color; + background: @builder-hover-color!important; + + a { + color: white; + } + } + } + + &.list-sortable-placeholder { + border: 2px dotted @builder-hover-color; + height: 10px; + background: transparent; + } + } + + &.builder-main-menu { + > li { + display: inline-block; + vertical-align: top; + + &.item { + margin: 0 20px 20px 0; + } + + > div.item-container { + background: #ecf0f1; + color: #708080; + padding: 20px 25px; + height: 64px; + white-space: nowrap; + + i { + font-size: 24px; + margin-right: 10px; + } + + span.title { + font-size: 14px; + line-height: 100%; + position: relative; + top: -3px; + } + } + + &.add { + height: 64px; + + a { + padding: 20px 15px; + height: 60px; + display: block; + + i { + margin-right: 5px; + } + + span { + position: relative; + top: -1px; + } + } + } + } + } + + &.builder-submenu { + margin-top: 1px; + + > li { + display: block; + width: 120px; + + i { + display: block; + margin-bottom: 7px; + } + + span.title { + display: block; + font-size: 12px; + } + + &.item { + margin: 0 0 1px 0; + } + + > div.item-container { + background: #f3f5f5; + color: #94a5a6; + padding: 18px 13px; + text-align: center; + + i { + font-size: 24px; + } + } + + &.add { + margin-top: 20px; + + a { + padding: 10px 20px; + display: block; + } + + } + } + } + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/assets/less/tabs.less b/server/plugins/rainlab/builder/assets/less/tabs.less new file mode 100644 index 0000000..0aa9202 --- /dev/null +++ b/server/plugins/rainlab/builder/assets/less/tabs.less @@ -0,0 +1,307 @@ +.builder-tabs { + > .tabs { + position: relative; + + .tab-control { + position: absolute; + display: block; + + &.inspector-trigger { + font-size: 14px; + padding-left: 5px; + padding-right: 5px; + cursor: pointer; + + span { + display: block; + width: 3px; + height: 3px; + margin-bottom: 2px; + background: #95a5a6; + + &:last-child { + margin-bottom: 0; + } + } + + &:hover, &.inspector-open { + span { + background: @link-color; + } + } + + &.global { + top: 5px; + right: 15px; + } + } + } + + > ul.tabs { + margin: 0; + list-style: none; + font-size: 0; + white-space: nowrap; + overflow: hidden; + position: relative; + + > li { + .user-select(none); + display: inline-block; + font-size: 13px; + white-space: nowrap; + position: relative; + cursor: pointer; + + > div.tab-container { + position: relative; + color: #bdc3c7!important; + + > div { + .transition(padding .1s); + position: relative; + } + } + + &:hover > div { + color: #95a5a6!important; + } + + .tab-control { + display: none; + + &.close-btn { + font-size: 15px; + top: 7px; + right: 18px; + line-height: 15px; + height: 15px; + width: 15px; + text-align: center; + cursor: pointer; + color: #95a5a6; + + &:hover { + color: @link-color!important; + } + } + + &.inspector-trigger{ + right: 34px; + top: 10px; + } + } + + &.active { + > div.tab-container { + color: #95a5a6!important; + } + + .tab-control { + display: block; + } + } + } + } + + > ul.panels { + padding: 0; + list-style: none; + + > li { + display: none; + + &.active { + display: block; + } + } + } + } + + &.primary { + > .tabs { + > ul.tabs { + padding: 0 20px 0 40px; + height: 31px; + + &:after { + position: absolute; + content: ''; + display: block; + height: 2px; + left: 0; + bottom: 0; + width: 100%; + background: #bdc3c7; + z-index: 106; + } + + > li { + bottom: -2px; + margin-left: -20px; + z-index: 105; + + > div.tab-container { + padding: 0 21px 0 21px; + height: 27px; + + > div { + padding: 5px 5px 0 5px; + border-top: 2px solid #e5e5e5; + + > span { + position: relative; + top: -2px; + .transition(top .1s); + } + } + + &:before, &:after { + content: ''; + display: block; + position: absolute; + top: 0; + height: 27px; + width: 21px; + background: transparent url(../images/tab.png) no-repeat; + } + + &:before { + left: 0; + background-position: 0 -27px; + } + + &:after { + right: 0; + background-position: -75px -27px; + } + } + + &.active { + z-index: 107; + + > div.tab-container { + + &:before { + background-position: 0 0; + } + + &:after { + background-position: -75px 0; + } + + > div { + padding-right: 30px; + border-top: 2px solid #bdc3c7; + + > span { + top: 0; + } + } + } + + &:before { + position: absolute; + content: ''; + display: block; + height: 3px; + left: 0; + bottom: 0; + width: 100%; + background: white; + } + } + + &.new-tab { + background: transparent url(../images/tab.png) no-repeat; + background-position: -24px 0; + width: 27px; + height: 22px; + + margin-left: -11px; + top: 4px; + position: relative; + cursor: pointer; + + &:hover { + background-position: -24px -32px; + } + } + } + } + } + } + + &.secondary { + > .tabs { + ul.tabs { + margin-left: 12px; + padding-left: 0; + + > li { + border-right: 1px solid #bdc3c7; + padding-right: 1px; + + > div.tab-container { + > div { + padding: 4px 10px; + + span { + font-size: 14px; + } + } + } + + .tab-control { + right: 23px; + top: 7px; + + &.close-btn { + right: 6px; + top: 5px; + } + } + + &.new-tab { + background: transparent; + border: 2px solid #e4e4e4; + width: 27px; + height: 22px; + left: 9px; + top: 7px; + position: relative; + cursor: pointer; + .border-radius(4px); + + &:hover { + background-color: #2581b8; + border-color: #2581b8; + } + } + + &.active { + padding-right: 10px; + + > div.tab-container { + > div { + color: #555555; + padding-right: 30px; + } + } + } + } + } + } + } +} + +html.gecko { + .builder-tabs.primary > .tabs > ul.tabs > li { + // Fixes a visual glitch in FireFox, noticed in v. 42 on Mac. + // + bottom: -3px; + > div.tab-container > div { + padding-top: 5px; + } + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/behaviors/IndexControllerOperations.php b/server/plugins/rainlab/builder/behaviors/IndexControllerOperations.php new file mode 100644 index 0000000..b87fc4e --- /dev/null +++ b/server/plugins/rainlab/builder/behaviors/IndexControllerOperations.php @@ -0,0 +1,172 @@ +getPluginCode(); + + $options = [ + 'pluginCode' => $pluginCodeObj->toCode() + ]; + + $widget = $this->makeBaseFormWidget($controller, $options); + $this->vars['controller'] = $controller; + + $result = [ + 'tabTitle' => $this->getTabName($widget->model), + 'tabIcon' => 'icon-asterisk', + 'tabId' => $this->getTabId($pluginCodeObj->toCode(), $controller), + 'tab' => $this->makePartial('tab', [ + 'form' => $widget, + 'pluginCode' => $pluginCodeObj->toCode() + ]) + ]; + + return $result; + } + + public function onControllerCreate() + { + $pluginCodeObj = new PluginCode(Request::input('plugin_code')); + + $options = [ + 'pluginCode' => $pluginCodeObj->toCode() + ]; + + $model = $this->loadOrCreateBaseModel(null, $options); + $model->fill($_POST); + $model->save(); + + $this->vars['controller'] = $model->controller; + + $result = $this->controller->widget->controllerList->updateList(); + + if ($model->behaviors) { + // Create a new tab only for controllers + // with behaviors. + + $widget = $this->makeBaseFormWidget($model->controller, $options); + + $tab = [ + 'tabTitle' => $this->getTabName($widget->model), + 'tabIcon' => 'icon-asterisk', + 'tabId' => $this->getTabId($pluginCodeObj->toCode(), $model->controller), + 'tab' => $this->makePartial('tab', [ + 'form' => $widget, + 'pluginCode' => $pluginCodeObj->toCode() + ]) + ]; + + $result = array_merge($result, $tab); + } + + $this->mergeRegistryDataIntoResult($result, $pluginCodeObj); + + return $result; + } + + public function onControllerSave() + { + $controller = Input::get('controller'); + + $model = $this->loadModelFromPost(); + $model->fill($_POST); + $model->save(); + + Flash::success(Lang::get('rainlab.builder::lang.controller.saved')); + + $result['builderResponseData'] = []; + + return $result; + } + + public function onControllerShowCreatePopup() + { + $pluginCodeObj = $this->getPluginCode(); + + $options = [ + 'pluginCode' => $pluginCodeObj->toCode() + ]; + + $this->baseFormConfigFile = '~/plugins/rainlab/builder/classes/controllermodel/new-controller-fields.yaml'; + $widget = $this->makeBaseFormWidget(null, $options); + + return $this->makePartial('create-controller-popup-form', [ + 'form'=>$widget, + 'pluginCode' => $pluginCodeObj->toCode() + ]); + } + + protected function getTabName($model) + { + $pluginName = Lang::get($model->getModelPluginName()); + + return $pluginName.'/'.$model->controller; + } + + protected function getTabId($pluginCode, $controller) + { + return 'controller-'.$pluginCode.'-'.$controller; + } + + protected function loadModelFromPost() + { + $pluginCodeObj = new PluginCode(Request::input('plugin_code')); + $options = [ + 'pluginCode' => $pluginCodeObj->toCode() + ]; + + $controller = Input::get('controller'); + + return $this->loadOrCreateBaseModel($controller, $options); + } + + protected function loadOrCreateBaseModel($controller, $options = []) + { + $model = new ControllerModel(); + + if (isset($options['pluginCode'])) { + $model->setPluginCode($options['pluginCode']); + } + + if (!$controller) { + return $model; + } + + $model->load($controller); + return $model; + } + + protected function mergeRegistryDataIntoResult(&$result, $pluginCodeObj) + { + if (!array_key_exists('builderResponseData', $result)) { + $result['builderResponseData'] = []; + } + + $pluginCode = $pluginCodeObj->toCode(); + $result['builderResponseData']['registryData'] = [ + 'urls' => ControllerModel::getPluginRegistryData($pluginCode, null), + 'pluginCode' => $pluginCode + ]; + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/behaviors/IndexDataRegistry.php b/server/plugins/rainlab/builder/behaviors/IndexDataRegistry.php new file mode 100644 index 0000000..c96ea42 --- /dev/null +++ b/server/plugins/rainlab/builder/behaviors/IndexDataRegistry.php @@ -0,0 +1,66 @@ + $result]; + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/behaviors/IndexDatabaseTableOperations.php b/server/plugins/rainlab/builder/behaviors/IndexDatabaseTableOperations.php new file mode 100644 index 0000000..41d5bb3 --- /dev/null +++ b/server/plugins/rainlab/builder/behaviors/IndexDatabaseTableOperations.php @@ -0,0 +1,198 @@ +getPluginCode(); + + $widget = $this->makeBaseFormWidget($tableName); + $this->vars['tableName'] = $tableName; + + $result = [ + 'tabTitle' => $this->getTabTitle($tableName), + 'tabIcon' => 'icon-hdd-o', + 'tabId' => $this->getTabId($tableName), + 'tab' => $this->makePartial('tab', [ + 'form' => $widget, + 'pluginCode' => $pluginCodeObj->toCode(), + 'tableName' => $tableName + ]) + ]; + + return $result; + } + + public function onDatabaseTableValidateAndShowPopup() + { + $tableName = Input::get('table_name'); + + $model = $this->loadOrCreateBaseModel($tableName); + $model->fill($this->processColumnData($_POST)); + + $pluginCode = Request::input('plugin_code'); + $model->setPluginCode($pluginCode); + $model->validate(); + + $migration = $model->generateCreateOrUpdateMigration(); + + if (!$migration) { + return $this->makePartial('migration-popup-form', [ + 'noChanges' => true + ]); + } + + return $this->makePartial('migration-popup-form', [ + 'form' => $this->makeMigrationFormWidget($migration), + 'operation' => $model->isNewModel() ? 'create' : 'update', + 'table' => $model->name, + 'pluginCode' => $pluginCode + ]); + } + + public function onDatabaseTableMigrationApply() + { + $pluginCode = new PluginCode(Request::input('plugin_code')); + $model = new MigrationModel(); + $model->setPluginCodeObj($pluginCode); + + $model->fill($_POST); + + $operation = Input::get('operation'); + $table = Input::get('table'); + + $model->scriptFileName = 'builder_table_'.$operation.'_'.$table; + $model->makeScriptFileNameUnique(); + + $codeGenerator = new TableMigrationCodeGenerator(); + $model->code = $codeGenerator->wrapMigrationCode($model->scriptFileName, $model->code, $pluginCode); + + try { + $model->save(); + } + catch (Exception $ex) { + throw new ApplicationException($ex->getMessage()); + } + + $result = $this->controller->widget->databaseTabelList->updateList(); + + $result = array_merge( + $result, + $this->controller->widget->versionList->refreshActivePlugin() + ); + + $result['builderResponseData'] = [ + 'builderObjectName'=>$table, + 'tabId' => $this->getTabId($table), + 'tabTitle' => $table, + 'tableName' => $table, + 'operation' => $operation, + 'pluginCode' => $pluginCode->toCode() + ]; + + return $result; + } + + public function onDatabaseTableShowDeletePopup() + { + $tableName = Input::get('table_name'); + + $model = $this->loadOrCreateBaseModel($tableName); + $pluginCode = Request::input('plugin_code'); + $model->setPluginCode($pluginCode); + + $migration = $model->generateDropMigration(); + + return $this->makePartial('migration-popup-form', [ + 'form' => $this->makeMigrationFormWidget($migration), + 'operation' => 'delete', + 'table' => $model->name, + 'pluginCode' => $pluginCode + ]); + } + + protected function getTabTitle($tableName) + { + if (!strlen($tableName)) { + return Lang::get('rainlab.builder::lang.database.tab_new_table'); + } + + return $tableName; + } + + protected function getTabId($tableName) + { + if (!strlen($tableName)) { + return 'databaseTable-'.uniqid(time()); + } + + return 'databaseTable-'.$tableName; + } + + protected function loadOrCreateBaseModel($tableName, $options = []) + { + $model = new DatabaseTableModel(); + + if (!$tableName) { + $model->name = $this->getPluginCode()->toDatabasePrefix().'_'; + + return $model; + } + + $model->load($tableName); + return $model; + } + + protected function makeMigrationFormWidget($migration) + { + $widgetConfig = $this->makeConfig($this->migrationFormConfigFile); + + $widgetConfig->model = $migration; + $widgetConfig->alias = 'form_migration_'.uniqid(); + + $form = $this->makeWidget('Backend\Widgets\Form', $widgetConfig); + $form->context = FormController::CONTEXT_CREATE; + + return $form; + } + + protected function processColumnData($postData) + { + if (!array_key_exists('columns', $postData)) { + return $postData; + } + + $booleanColumns = ['unsigned', 'allow_null', 'auto_increment', 'primary_key']; + foreach ($postData['columns'] as &$row) { + foreach ($row as $column=>$value) { + if (in_array($column, $booleanColumns) && $value == 'false') { + $row[$column] = false; + } + } + } + + return $postData; + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/behaviors/IndexLocalizationOperations.php b/server/plugins/rainlab/builder/behaviors/IndexLocalizationOperations.php new file mode 100644 index 0000000..ce2de16 --- /dev/null +++ b/server/plugins/rainlab/builder/behaviors/IndexLocalizationOperations.php @@ -0,0 +1,218 @@ +getPluginCode(); + + $options = [ + 'pluginCode' => $pluginCodeObj->toCode() + ]; + + $widget = $this->makeBaseFormWidget($language, $options); + $this->vars['originalLanguage'] = $language; + + if ($widget->model->isNewModel()) { + $widget->model->initContent(); + } + + $result = [ + 'tabTitle' => $this->getTabName($widget->model), + 'tabIcon' => 'icon-globe', + 'tabId' => $this->getTabId($pluginCodeObj->toCode(), $language), + 'isNewRecord' => $widget->model->isNewModel(), + 'tab' => $this->makePartial('tab', [ + 'form' => $widget, + 'pluginCode' => $pluginCodeObj->toCode(), + 'language' => $language, + 'defaultLanguage' => LocalizationModel::getDefaultLanguage() + ]) + ]; + + return $result; + } + + public function onLanguageSave() + { + $model = $this->loadOrCreateLocalizationFromPost(); + $model->fill($_POST); + $model->save(false); + + Flash::success(Lang::get('rainlab.builder::lang.localization.saved')); + $result = $this->controller->widget->languageList->updateList(); + + $result['builderResponseData'] = [ + 'tabId' => $this->getTabId($model->getPluginCodeObj()->toCode(), $model->language), + 'tabTitle' => $this->getTabName($model), + 'language' => $model->language + ]; + + if ($model->language === LocalizationModel::getDefaultLanguage()) { + $pluginCode = $model->getPluginCodeObj()->toCode(); + + $registryData = [ + 'strings' => LocalizationModel::getPluginRegistryData($pluginCode, null), + 'sections' => LocalizationModel::getPluginRegistryData($pluginCode, 'sections'), + 'pluginCode' => $pluginCode + ]; + + $result['builderResponseData']['registryData'] = $registryData; + } + + return $result; + } + + public function onLanguageDelete() + { + $model = $this->loadOrCreateLocalizationFromPost(); + + $model->deleteModel(); + + return $this->controller->widget->languageList->updateList(); + } + + public function onLanguageShowCopyStringsPopup() + { + $pluginCodeObj = new PluginCode(Request::input('plugin_code')); + $language = trim(Input::get('original_language')); + + $languages = LocalizationModel::listPluginLanguages($pluginCodeObj); + + if (strlen($language)) { + $languages = array_diff($languages, [$language]); + } + + return $this->makePartial('copy-strings-popup-form', ['languages'=>$languages]); + } + + public function onLanguageCopyStringsFrom() + { + $sourceLanguage = Request::input('copy_from'); + $destinationText = Request::input('strings'); + + $model = new LocalizationModel(); + $model->setPluginCode(Request::input('plugin_code')); + + $responseData = $model->copyStringsFrom($destinationText, $sourceLanguage); + + return ['builderResponseData' => $responseData]; + } + + public function onLanguageLoadAddStringForm() + { + return [ + 'markup' => $this->makePartial('new-string-popup') + ]; + } + + public function onLanguageCreateString() + { + $stringKey = trim(Request::input('key')); + $stringValue = trim(Request::input('value')); + + $pluginCodeObj = new PluginCode(Request::input('plugin_code')); + $pluginCode = $pluginCodeObj->toCode(); + $options = [ + 'pluginCode' => $pluginCode + ]; + + $defaultLanguage = LocalizationModel::getDefaultLanguage(); + if (LocalizationModel::languageFileExists($pluginCode, $defaultLanguage)) { + $model = $this->loadOrCreateBaseModel($defaultLanguage, $options); + } + else { + $model = LocalizationModel::initModel($pluginCode, $defaultLanguage); + } + + $newStringKey = $model->createStringAndSave($stringKey, $stringValue); + $pluginCode = $pluginCodeObj->toCode(); + + return [ + 'localizationData' => [ + 'key' => $newStringKey, + 'value' => $stringValue + ], + 'registryData' => [ + 'strings' => LocalizationModel::getPluginRegistryData($pluginCode, null), + 'sections' => LocalizationModel::getPluginRegistryData($pluginCode, 'sections') + ] + ]; + } + + public function onLanguageGetStrings() + { + $model = $this->loadOrCreateLocalizationFromPost(); + + return ['builderResponseData' => [ + 'strings' => $model ? $model->strings : null + ]]; + } + + protected function loadOrCreateLocalizationFromPost() + { + $pluginCodeObj = new PluginCode(Request::input('plugin_code')); + $options = [ + 'pluginCode' => $pluginCodeObj->toCode() + ]; + + $originalLanguage = Input::get('original_language'); + + return $this->loadOrCreateBaseModel($originalLanguage, $options); + } + + protected function getTabName($model) + { + $pluginName = Lang::get($model->getModelPluginName()); + + if (!strlen($model->language)) { + return $pluginName.'/'.Lang::get('rainlab.builder::lang.localization.tab_new_language'); + } + + return $pluginName.'/'.$model->language; + } + + protected function getTabId($pluginCode, $language) + { + if (!strlen($language)) { + return 'localization-'.$pluginCode.'-'.uniqid(time()); + } + + return 'localization-'.$pluginCode.'-'.$language; + } + + protected function loadOrCreateBaseModel($language, $options = []) + { + $model = new LocalizationModel(); + + if (isset($options['pluginCode'])) { + $model->setPluginCode($options['pluginCode']); + } + + if (!$language) { + return $model; + } + + $model->load($language); + return $model; + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/behaviors/IndexMenusOperations.php b/server/plugins/rainlab/builder/behaviors/IndexMenusOperations.php new file mode 100644 index 0000000..753e02a --- /dev/null +++ b/server/plugins/rainlab/builder/behaviors/IndexMenusOperations.php @@ -0,0 +1,75 @@ +getPluginCode(); + + $pluginCode = $pluginCodeObj->toCode(); + $widget = $this->makeBaseFormWidget($pluginCode); + + $result = [ + 'tabTitle' => $widget->model->getPluginName().'/'.Lang::get('rainlab.builder::lang.menu.tab'), + 'tabIcon' => 'icon-location-arrow', + 'tabId' => $this->getTabId($pluginCode), + 'tab' => $this->makePartial('tab', [ + 'form' => $widget, + 'pluginCode' => $pluginCodeObj->toCode() + ]) + ]; + + return $result; + } + + public function onMenusSave() + { + $pluginCodeObj = new PluginCode(Request::input('plugin_code')); + + $pluginCode = $pluginCodeObj->toCode(); + $model = $this->loadOrCreateBaseModel($pluginCodeObj->toCode()); + $model->setPluginCodeObj($pluginCodeObj); + $model->fill($_POST); + $model->save(); + + Flash::success(Lang::get('rainlab.builder::lang.menu.saved')); + + $result['builderResponseData'] = [ + 'tabId' => $this->getTabId($pluginCode), + 'tabTitle' => $model->getPluginName().'/'.Lang::get('rainlab.builder::lang.menu.tab'), + ]; + + return $result; + } + + protected function getTabId($pluginCode) + { + return 'menus-'.$pluginCode; + } + + protected function loadOrCreateBaseModel($pluginCode, $options = []) + { + $model = new MenusModel(); + + $model->loadPlugin($pluginCode); + return $model; + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/behaviors/IndexModelFormOperations.php b/server/plugins/rainlab/builder/behaviors/IndexModelFormOperations.php new file mode 100644 index 0000000..c4e01ef --- /dev/null +++ b/server/plugins/rainlab/builder/behaviors/IndexModelFormOperations.php @@ -0,0 +1,186 @@ +alias = 'defaultFormBuilder'; + $formBulder->bindToController(); + } + + public function onModelFormCreateOrOpen() + { + $fileName = Input::get('file_name'); + $modelClass = Input::get('model_class'); + + $pluginCodeObj = $this->getPluginCode(); + + $options = [ + 'pluginCode' => $pluginCodeObj->toCode(), + 'modelClass' => $modelClass + ]; + + $widget = $this->makeBaseFormWidget($fileName, $options); + $this->vars['fileName'] = $fileName; + + $result = [ + 'tabTitle' => $widget->model->getDisplayName(Lang::get('rainlab.builder::lang.form.tab_new_form')), + 'tabIcon' => 'icon-check-square', + 'tabId' => $this->getTabId($modelClass, $fileName), + 'tab' => $this->makePartial('tab', [ + 'form' => $widget, + 'pluginCode' => $pluginCodeObj->toCode(), + 'fileName' => $fileName, + 'modelClass' => $modelClass + ]) + ]; + + return $result; + } + + public function onModelFormSave() + { + $model = $this->loadOrCreateFormFromPost(); + + $model->fill($_POST); + $model->save(); + + $result = $this->controller->widget->modelList->updateList(); + + Flash::success(Lang::get('rainlab.builder::lang.form.saved')); + + $modelClass = Input::get('model_class'); + $result['builderResponseData'] = [ + 'builderObjectName' => $model->fileName, + 'tabId' => $this->getTabId($modelClass, $model->fileName), + 'tabTitle' => $model->getDisplayName(Lang::get('rainlab.builder::lang.form.tab_new_form')) + ]; + + $this->mergeRegistryDataIntoResult($result, $model, $modelClass); + + return $result; + } + + public function onModelFormDelete() + { + $model = $this->loadOrCreateFormFromPost(); + + $model->deleteModel(); + + $result = $this->controller->widget->modelList->updateList(); + + $modelClass = Input::get('model_class'); + $this->mergeRegistryDataIntoResult($result, $model, $modelClass); + + return $result; + } + + public function onModelFormGetModelFields() + { + $columnNames = ModelModel::getModelFields($this->getPluginCode(), Input::get('model_class')); + $asPlainList = Input::get('as_plain_list'); + + $result = []; + foreach ($columnNames as $columnName) { + if (!$asPlainList) { + $result[] = [ + 'title' => $columnName, + 'value' => $columnName + ]; + } + else { + $result[$columnName] = $columnName; + } + } + + return [ + 'responseData' => [ + 'options' => $result + ] + ]; + } + + protected function loadOrCreateFormFromPost() + { + $pluginCode = Request::input('plugin_code'); + $modelClass = Input::get('model_class'); + $fileName = Input::get('file_name'); + + $options = [ + 'pluginCode' => $pluginCode, + 'modelClass' => $modelClass + ]; + + return $this->loadOrCreateBaseModel($fileName, $options); + } + + protected function getTabId($modelClass, $fileName) + { + if (!strlen($fileName)) { + return 'modelForm-'.uniqid(time()); + } + + return 'modelForm-'.$modelClass.'-'.$fileName; + } + + protected function loadOrCreateBaseModel($fileName, $options = []) + { + $model = new ModelFormModel(); + + if (isset($options['pluginCode']) && isset($options['modelClass'])) { + $model->setPluginCode($options['pluginCode']); + $model->setModelClassName($options['modelClass']); + } + + if (!$fileName) { + $model->initDefaults(); + + return $model; + } + + $model->loadForm($fileName); + return $model; + } + + protected function mergeRegistryDataIntoResult(&$result, $model, $modelClass) + { + if (!array_key_exists('builderResponseData', $result)) { + $result['builderResponseData'] = []; + } + + $fullClassName = $model->getPluginCodeObj()->toPluginNamespace().'\\Models\\'.$modelClass; + $pluginCode = $model->getPluginCodeObj()->toCode(); + $result['builderResponseData']['registryData'] = [ + 'forms' => ModelFormModel::getPluginRegistryData($pluginCode, $modelClass), + 'pluginCode' => $pluginCode, + 'modelClass' => $fullClassName + ]; + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/behaviors/IndexModelListOperations.php b/server/plugins/rainlab/builder/behaviors/IndexModelListOperations.php new file mode 100644 index 0000000..daaebcf --- /dev/null +++ b/server/plugins/rainlab/builder/behaviors/IndexModelListOperations.php @@ -0,0 +1,176 @@ +getPluginCode(); + + $options = [ + 'pluginCode' => $pluginCodeObj->toCode(), + 'modelClass' => $modelClass + ]; + + $widget = $this->makeBaseFormWidget($fileName, $options); + $this->vars['fileName'] = $fileName; + + $result = [ + 'tabTitle' => $widget->model->getDisplayName(Lang::get('rainlab.builder::lang.list.tab_new_list')), + 'tabIcon' => 'icon-list', + 'tabId' => $this->getTabId($modelClass, $fileName), + 'tab' => $this->makePartial('tab', [ + 'form' => $widget, + 'pluginCode' => $pluginCodeObj->toCode(), + 'fileName' => $fileName, + 'modelClass' => $modelClass + ]) + ]; + + return $result; + } + + public function onModelListSave() + { + $model = $this->loadOrCreateListFromPost(); + $model->fill($_POST); + $model->save(); + + $result = $this->controller->widget->modelList->updateList(); + + Flash::success(Lang::get('rainlab.builder::lang.list.saved')); + + $modelClass = Input::get('model_class'); + $result['builderResponseData'] = [ + 'builderObjectName' => $model->fileName, + 'tabId' => $this->getTabId($modelClass, $model->fileName), + 'tabTitle' => $model->getDisplayName(Lang::get('rainlab.builder::lang.list.tab_new_list')) + ]; + + $this->mergeRegistryDataIntoResult($result, $model, $modelClass); + + return $result; + } + + public function onModelListDelete() + { + $model = $this->loadOrCreateListFromPost(); + + $model->deleteModel(); + + $result = $this->controller->widget->modelList->updateList(); + + $modelClass = Input::get('model_class'); + $this->mergeRegistryDataIntoResult($result, $model, $modelClass); + + return $result; + } + + public function onModelListGetModelFields() + { + $columnNames = ModelModel::getModelFields($this->getPluginCode(), Input::get('model_class')); + + $result = []; + foreach ($columnNames as $columnName) { + $result[] = [ + 'title' => $columnName, + 'value' => $columnName + ]; + } + + return [ + 'responseData' => [ + 'options' => $result + ] + ]; + } + + public function onModelListLoadDatabaseColumns() + { + $columns = ModelModel::getModelColumnsAndTypes($this->getPluginCode(), Input::get('model_class')); + + return [ + 'responseData' => [ + 'columns' => $columns + ] + ]; + } + + protected function loadOrCreateListFromPost() + { + $pluginCode = Request::input('plugin_code'); + $modelClass = Input::get('model_class'); + $fileName = Input::get('file_name'); + + $options = [ + 'pluginCode' => $pluginCode, + 'modelClass' => $modelClass + ]; + + return $this->loadOrCreateBaseModel($fileName, $options); + } + + protected function getTabId($modelClass, $fileName) + { + if (!strlen($fileName)) { + return 'modelForm-'.uniqid(time()); + } + + return 'modelList-'.$modelClass.'-'.$fileName; + } + + protected function loadOrCreateBaseModel($fileName, $options = []) + { + $model = new ModelListModel(); + + if (isset($options['pluginCode']) && isset($options['modelClass'])) { + $model->setPluginCode($options['pluginCode']); + $model->setModelClassName($options['modelClass']); + } + + if (!$fileName) { + $model->initDefaults(); + + return $model; + } + + $model->loadForm($fileName); + return $model; + } + + protected function mergeRegistryDataIntoResult(&$result, $model, $modelClass) + { + if (!array_key_exists('builderResponseData', $result)) { + $result['builderResponseData'] = []; + } + + $fullClassName = $model->getPluginCodeObj()->toPluginNamespace().'\\Models\\'.$modelClass; + $pluginCode = $model->getPluginCodeObj()->toCode(); + $result['builderResponseData']['registryData'] = [ + 'lists' => ModelListModel::getPluginRegistryData($pluginCode, $modelClass), + 'pluginCode' => $pluginCode, + 'modelClass' => $fullClassName + ]; + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/behaviors/IndexModelOperations.php b/server/plugins/rainlab/builder/behaviors/IndexModelOperations.php new file mode 100644 index 0000000..5d583fb --- /dev/null +++ b/server/plugins/rainlab/builder/behaviors/IndexModelOperations.php @@ -0,0 +1,69 @@ +getPluginCode(); + + try { + $widget = $this->makeBaseFormWidget(null); + $this->vars['form'] = $widget; + $widget->model->setPluginCodeObj($pluginCodeObj); + $this->vars['pluginCode'] = $pluginCodeObj->toCode(); + } + catch (ApplicationException $ex) { + $this->vars['errorMessage'] = $ex->getMessage(); + } + + return $this->makePartial('model-popup-form'); + } + + public function onModelSave() + { + $pluginCode = Request::input('plugin_code'); + + $model = $this->loadOrCreateBaseModel(null); + $model->setPluginCode($pluginCode); + + $model->fill($_POST); + $model->save(); + + $result = $this->controller->widget->modelList->updateList(); + + $builderResponseData = [ + 'registryData' => [ + 'models' => ModelModel::getPluginRegistryData($pluginCode, null), + 'pluginCode' => $pluginCode + ] + ]; + + $result['builderResponseData'] = $builderResponseData; + + return $result; + } + + protected function loadOrCreateBaseModel($className, $options = []) + { + // Editing model is not supported, always return + // a new object. + + return new ModelModel(); + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/behaviors/IndexPermissionsOperations.php b/server/plugins/rainlab/builder/behaviors/IndexPermissionsOperations.php new file mode 100644 index 0000000..dc1d138 --- /dev/null +++ b/server/plugins/rainlab/builder/behaviors/IndexPermissionsOperations.php @@ -0,0 +1,76 @@ +getPluginCode(); + + $pluginCode = $pluginCodeObj->toCode(); + $widget = $this->makeBaseFormWidget($pluginCode); + + $result = [ + 'tabTitle' => Lang::get($widget->model->getPluginName()).'/'.Lang::get('rainlab.builder::lang.permission.tab'), + 'tabIcon' => 'icon-unlock-alt', + 'tabId' => $this->getTabId($pluginCode), + 'tab' => $this->makePartial('tab', [ + 'form' => $widget, + 'pluginCode' => $pluginCodeObj->toCode() + ]) + ]; + + return $result; + } + + public function onPermissionsSave() + { + $pluginCodeObj = new PluginCode(Request::input('plugin_code')); + + $pluginCode = $pluginCodeObj->toCode(); + $model = $this->loadOrCreateBaseModel($pluginCodeObj->toCode()); + $model->setPluginCodeObj($pluginCodeObj); + $model->fill($_POST); + $model->save(); + + Flash::success(Lang::get('rainlab.builder::lang.permission.saved')); + + $result['builderResponseData'] = [ + 'tabId' => $this->getTabId($pluginCode), + 'tabTitle' => $model->getPluginName().'/'.Lang::get('rainlab.builder::lang.permission.tab'), + 'pluginCode' => $pluginCode + ]; + + return $result; + } + + protected function getTabId($pluginCode) + { + return 'permissions-'.$pluginCode; + } + + protected function loadOrCreateBaseModel($pluginCode, $options = []) + { + $model = new PermissionsModel(); + + $model->loadPlugin($pluginCode); + return $model; + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/behaviors/IndexPluginOperations.php b/server/plugins/rainlab/builder/behaviors/IndexPluginOperations.php new file mode 100644 index 0000000..14ccffc --- /dev/null +++ b/server/plugins/rainlab/builder/behaviors/IndexPluginOperations.php @@ -0,0 +1,91 @@ +vars['form'] = $this->makeBaseFormWidget($pluginCode); + $this->vars['pluginCode'] = $pluginCode; + } + catch (ApplicationException $ex) { + $this->vars['errorMessage'] = $ex->getMessage(); + } + + return $this->makePartial('plugin-popup-form'); + } + + public function onPluginSave() + { + $pluginCode = Input::get('pluginCode'); + + $model = $this->loadOrCreateBaseModel($pluginCode); + $model->fill($_POST); + $model->save(); + + if (!$pluginCode) { + $result = []; + + $result['responseData'] = [ + 'pluginCode' => $model->getPluginCode(), + 'isNewPlugin' => 1 + ]; + + return $result; + } else { + $result = []; + + $result['responseData'] = [ + 'pluginCode' => $model->getPluginCode() + ]; + + return array_merge($result, $this->controller->updatePluginList()); + } + } + + public function onPluginSetActive() + { + $pluginCode = Input::get('pluginCode'); + $updatePluginList = Input::get('updatePluginList'); + + $result = $this->controller->setBuilderActivePlugin($pluginCode, false); + + if ($updatePluginList) { + $result = array_merge($result, $this->controller->updatePluginList()); + } + + $result['responseData'] = ['pluginCode'=>$pluginCode]; + + return $result; + } + + protected function loadOrCreateBaseModel($pluginCode, $options = []) + { + $model = new PluginBaseModel(); + + if (!$pluginCode) { + $model->initDefaults(); + return $model; + } + + $model->loadPlugin($pluginCode); + return $model; + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/behaviors/IndexVersionsOperations.php b/server/plugins/rainlab/builder/behaviors/IndexVersionsOperations.php new file mode 100644 index 0000000..d5f7b05 --- /dev/null +++ b/server/plugins/rainlab/builder/behaviors/IndexVersionsOperations.php @@ -0,0 +1,178 @@ +getPluginCode(); + + $options = [ + 'pluginCode' => $pluginCodeObj->toCode() + ]; + + $widget = $this->makeBaseFormWidget($versionNumber, $options); + $this->vars['originalVersion'] = $versionNumber; + + if ($widget->model->isNewModel()) { + $versionType = Input::get('version_type'); + $widget->model->initVersion($versionType); + } + + $result = [ + 'tabTitle' => $this->getTabName($versionNumber, $widget->model), + 'tabIcon' => 'icon-code-fork', + 'tabId' => $this->getTabId($pluginCodeObj->toCode(), $versionNumber), + 'isNewRecord' => $widget->model->isNewModel(), + 'tab' => $this->makePartial('tab', [ + 'form' => $widget, + 'pluginCode' => $pluginCodeObj->toCode(), + 'originalVersion' => $versionNumber + ]) + ]; + + return $result; + } + + public function onVersionSave() + { + $model = $this->loadOrCreateListFromPost(); + $model->fill($_POST); + $model->save(false); + + Flash::success(Lang::get('rainlab.builder::lang.version.saved')); + $result = $this->controller->widget->versionList->updateList(); + + $result['builderResponseData'] = [ + 'tabId' => $this->getTabId($model->getPluginCodeObj()->toCode(), $model->version), + 'tabTitle' => $this->getTabName($model->version, $model), + 'savedVersion' => $model->version, + 'isApplied' => $model->isApplied() + ]; + + return $result; + } + + public function onVersionDelete() + { + $model = $this->loadOrCreateListFromPost(); + + $model->deleteModel(); + + return $this->controller->widget->versionList->updateList(); + } + + public function onVersionApply() + { + // Save the version before applying it + // + $model = $this->loadOrCreateListFromPost(); + $model->fill($_POST); + $model->save(false); + + // Apply the version + // + $model->apply(); + + Flash::success(Lang::get('rainlab.builder::lang.version.applied')); + $result = $this->controller->widget->versionList->updateList(); + + $result['builderResponseData'] = [ + 'tabId' => $this->getTabId($model->getPluginCodeObj()->toCode(), $model->version), + 'tabTitle' => $this->getTabName($model->version, $model), + 'savedVersion' => $model->version + ]; + + return $result; + } + + public function onVersionRollback() + { + // Save the version before rolling it back + // + $model = $this->loadOrCreateListFromPost(); + $model->fill($_POST); + $model->save(false); + + // Rollback the version + // + $model->rollback(); + + Flash::success(Lang::get('rainlab.builder::lang.version.rolled_back')); + $result = $this->controller->widget->versionList->updateList(); + + $result['builderResponseData'] = [ + 'tabId' => $this->getTabId($model->getPluginCodeObj()->toCode(), $model->version), + 'tabTitle' => $this->getTabName($model->version, $model), + 'savedVersion' => $model->version + ]; + + return $result; + } + + protected function loadOrCreateListFromPost() + { + $pluginCodeObj = new PluginCode(Request::input('plugin_code')); + $options = [ + 'pluginCode' => $pluginCodeObj->toCode() + ]; + + $versionNumber = Input::get('original_version'); + + return $this->loadOrCreateBaseModel($versionNumber, $options); + } + + protected function getTabName($version, $model) + { + $pluginName = Lang::get($model->getModelPluginName()); + + if (!strlen($version)) { + return $pluginName.'/'.Lang::get('rainlab.builder::lang.version.tab_new_version'); + } + + return $pluginName.'/v'.$version; + } + + protected function getTabId($pluginCode, $version) + { + if (!strlen($version)) { + return 'version-'.$pluginCode.'-'.uniqid(time()); + } + + return 'version-'.$pluginCode.'-'.$version; + } + + protected function loadOrCreateBaseModel($versionNumber, $options = []) + { + $model = new MigrationModel(); + + if (isset($options['pluginCode'])) { + $model->setPluginCode($options['pluginCode']); + } + + if (!$versionNumber) { + return $model; + } + + $model->load($versionNumber); + return $model; + } +} \ No newline at end of file diff --git a/server/plugins/rainlab/builder/behaviors/indexcontrolleroperations/partials/_create-controller-popup-form.htm b/server/plugins/rainlab/builder/behaviors/indexcontrolleroperations/partials/_create-controller-popup-form.htm new file mode 100644 index 0000000..9e77dc9 --- /dev/null +++ b/server/plugins/rainlab/builder/behaviors/indexcontrolleroperations/partials/_create-controller-popup-form.htm @@ -0,0 +1,26 @@ += Form::open([ + 'data-builder-command'=>'controller:cmdCreateController', + 'data-plugin-code' => $pluginCode +]) ?> += e(trans('rainlab.builder::lang.migration.no_changes_to_save')) ?>
+= e(trans('rainlab.builder::lang.localization.no_languages_to_copy_from')) ?>
+ += e(trans('rainlab.builder::lang.localization.string_key_comment')) ?>
+ + += e($errorMessage) ?>
+ += e($errorMessage) ?>
+ += e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/server/plugins/rainlab/builder/classes/standardbehaviorsregistry/formcontroller/templates/preview.htm.tpl b/server/plugins/rainlab/builder/classes/standardbehaviorsregistry/formcontroller/templates/preview.htm.tpl new file mode 100644 index 0000000..46fef44 --- /dev/null +++ b/server/plugins/rainlab/builder/classes/standardbehaviorsregistry/formcontroller/templates/preview.htm.tpl @@ -0,0 +1,22 @@ + ++ + = e(trans('backend::lang.form.return_to_list')) ?> + +
\ No newline at end of file diff --git a/server/plugins/rainlab/builder/classes/standardbehaviorsregistry/formcontroller/templates/update.htm.tpl b/server/plugins/rainlab/builder/classes/standardbehaviorsregistry/formcontroller/templates/update.htm.tpl new file mode 100644 index 0000000..77f3a5e --- /dev/null +++ b/server/plugins/rainlab/builder/classes/standardbehaviorsregistry/formcontroller/templates/update.htm.tpl @@ -0,0 +1,54 @@ + += e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/server/plugins/rainlab/builder/classes/standardbehaviorsregistry/listcontroller/templates/_list_toolbar.htm.tpl b/server/plugins/rainlab/builder/classes/standardbehaviorsregistry/listcontroller/templates/_list_toolbar.htm.tpl new file mode 100644 index 0000000..b2296d6 --- /dev/null +++ b/server/plugins/rainlab/builder/classes/standardbehaviorsregistry/listcontroller/templates/_list_toolbar.htm.tpl @@ -0,0 +1,23 @@ += e(trans($this->noRecordsMessage)) ?>
+ \ No newline at end of file diff --git a/server/plugins/rainlab/builder/widgets/controllerlist/partials/_toolbar.htm b/server/plugins/rainlab/builder/widgets/controllerlist/partials/_toolbar.htm new file mode 100644 index 0000000..5ce9f57 --- /dev/null +++ b/server/plugins/rainlab/builder/widgets/controllerlist/partials/_toolbar.htm @@ -0,0 +1,25 @@ += e(trans('rainlab.builder::lang.common.select_plugin_first')) ?>
+= e(trans($this->noRecordsMessage)) ?>
+ \ No newline at end of file diff --git a/server/plugins/rainlab/builder/widgets/databasetablelist/partials/_table-list.htm b/server/plugins/rainlab/builder/widgets/databasetablelist/partials/_table-list.htm new file mode 100644 index 0000000..0f711cd --- /dev/null +++ b/server/plugins/rainlab/builder/widgets/databasetablelist/partials/_table-list.htm @@ -0,0 +1,13 @@ += e(trans('rainlab.builder::lang.common.select_plugin_first')) ?>
++ | + | + | + | + |
+ | + | + | + | + |
+ | + | + | + | + |
+ | + | + | + | + |
+ |
+ |
+ |
= e(trans($this->noRecordsMessage)) ?>
+ \ No newline at end of file diff --git a/server/plugins/rainlab/builder/widgets/languagelist/partials/_language-list.htm b/server/plugins/rainlab/builder/widgets/languagelist/partials/_language-list.htm new file mode 100644 index 0000000..ef3df47 --- /dev/null +++ b/server/plugins/rainlab/builder/widgets/languagelist/partials/_language-list.htm @@ -0,0 +1,13 @@ += e(trans('rainlab.builder::lang.common.select_plugin_first')) ?>
+= e(trans($this->noRecordsMessage)) ?>
+ diff --git a/server/plugins/rainlab/builder/widgets/modellist/partials/_model-list.htm b/server/plugins/rainlab/builder/widgets/modellist/partials/_model-list.htm new file mode 100644 index 0000000..d1ab5d9 --- /dev/null +++ b/server/plugins/rainlab/builder/widgets/modellist/partials/_model-list.htm @@ -0,0 +1,14 @@ += e(trans('rainlab.builder::lang.common.select_plugin_first')) ?>
+= e(trans($this->noRecordsMessage)) ?>
+ \ No newline at end of file diff --git a/server/plugins/rainlab/builder/widgets/pluginlist/partials/_plugin-list.htm b/server/plugins/rainlab/builder/widgets/pluginlist/partials/_plugin-list.htm new file mode 100644 index 0000000..eab0976 --- /dev/null +++ b/server/plugins/rainlab/builder/widgets/pluginlist/partials/_plugin-list.htm @@ -0,0 +1,13 @@ += e(trans($this->noRecordsMessage)) ?>
+ \ No newline at end of file diff --git a/server/plugins/rainlab/builder/widgets/versionlist/partials/_toolbar.htm b/server/plugins/rainlab/builder/widgets/versionlist/partials/_toolbar.htm new file mode 100644 index 0000000..3e49d70 --- /dev/null +++ b/server/plugins/rainlab/builder/widgets/versionlist/partials/_toolbar.htm @@ -0,0 +1,34 @@ += e(trans('rainlab.builder::lang.common.select_plugin_first')) ?>
+= e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/server/plugins/studiovx/marcleopold/controllers/indeximage/index.htm b/server/plugins/studiovx/marcleopold/controllers/indeximage/index.htm new file mode 100644 index 0000000..ea43a36 --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/indeximage/index.htm @@ -0,0 +1 @@ += $this->listRender() ?> diff --git a/server/plugins/studiovx/marcleopold/controllers/indeximage/preview.htm b/server/plugins/studiovx/marcleopold/controllers/indeximage/preview.htm new file mode 100644 index 0000000..999d907 --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/indeximage/preview.htm @@ -0,0 +1,22 @@ + ++ + = e(trans('backend::lang.form.return_to_list')) ?> + +
\ No newline at end of file diff --git a/server/plugins/studiovx/marcleopold/controllers/indeximage/reorder.htm b/server/plugins/studiovx/marcleopold/controllers/indeximage/reorder.htm new file mode 100644 index 0000000..d9402c7 --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/indeximage/reorder.htm @@ -0,0 +1,8 @@ + += e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/server/plugins/studiovx/marcleopold/controllers/indextagline/_list_toolbar.htm b/server/plugins/studiovx/marcleopold/controllers/indextagline/_list_toolbar.htm new file mode 100644 index 0000000..acdc987 --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/indextagline/_list_toolbar.htm @@ -0,0 +1,19 @@ += e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/server/plugins/studiovx/marcleopold/controllers/indextagline/index.htm b/server/plugins/studiovx/marcleopold/controllers/indextagline/index.htm new file mode 100644 index 0000000..ea43a36 --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/indextagline/index.htm @@ -0,0 +1 @@ += $this->listRender() ?> diff --git a/server/plugins/studiovx/marcleopold/controllers/indextagline/preview.htm b/server/plugins/studiovx/marcleopold/controllers/indextagline/preview.htm new file mode 100644 index 0000000..9fe059a --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/indextagline/preview.htm @@ -0,0 +1,22 @@ + ++ + = e(trans('backend::lang.form.return_to_list')) ?> + +
\ No newline at end of file diff --git a/server/plugins/studiovx/marcleopold/controllers/indextagline/reorder.htm b/server/plugins/studiovx/marcleopold/controllers/indextagline/reorder.htm new file mode 100644 index 0000000..302103b --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/indextagline/reorder.htm @@ -0,0 +1,8 @@ + += e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/server/plugins/studiovx/marcleopold/controllers/pages/_list_toolbar.htm b/server/plugins/studiovx/marcleopold/controllers/pages/_list_toolbar.htm new file mode 100644 index 0000000..d09c543 --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/pages/_list_toolbar.htm @@ -0,0 +1,18 @@ + diff --git a/server/plugins/studiovx/marcleopold/controllers/pages/config_form.yaml b/server/plugins/studiovx/marcleopold/controllers/pages/config_form.yaml new file mode 100644 index 0000000..b13da04 --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/pages/config_form.yaml @@ -0,0 +1,10 @@ +name: Pages +form: $/studiovx/marcleopold/models/pages/fields.yaml +modelClass: Studiovx\Marcleopold\Models\Pages +defaultRedirect: studiovx/marcleopold/pages +create: + redirect: 'studiovx/marcleopold/pages/update/:id' + redirectClose: studiovx/marcleopold/pages +update: + redirect: studiovx/marcleopold/pages + redirectClose: studiovx/marcleopold/pages diff --git a/server/plugins/studiovx/marcleopold/controllers/pages/config_list.yaml b/server/plugins/studiovx/marcleopold/controllers/pages/config_list.yaml new file mode 100644 index 0000000..5c2684e --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/pages/config_list.yaml @@ -0,0 +1,12 @@ +list: $/studiovx/marcleopold/models/pages/columns.yaml +modelClass: Studiovx\Marcleopold\Models\Pages +title: Pages +noRecordsMessage: 'backend::lang.list.no_records' +showSetup: true +showCheckboxes: true +recordsPerPage: 20 +toolbar: + buttons: list_toolbar + search: + prompt: 'backend::lang.list.search_prompt' +recordUrl: 'studiovx/marcleopold/pages/update/:id' diff --git a/server/plugins/studiovx/marcleopold/controllers/pages/create.htm b/server/plugins/studiovx/marcleopold/controllers/pages/create.htm new file mode 100644 index 0000000..1bfe840 --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/pages/create.htm @@ -0,0 +1,46 @@ + += e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/server/plugins/studiovx/marcleopold/controllers/pages/index.htm b/server/plugins/studiovx/marcleopold/controllers/pages/index.htm new file mode 100644 index 0000000..ea43a36 --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/pages/index.htm @@ -0,0 +1 @@ += $this->listRender() ?> diff --git a/server/plugins/studiovx/marcleopold/controllers/pages/preview.htm b/server/plugins/studiovx/marcleopold/controllers/pages/preview.htm new file mode 100644 index 0000000..53b0e8b --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/pages/preview.htm @@ -0,0 +1,22 @@ + ++ + = e(trans('backend::lang.form.return_to_list')) ?> + +
\ No newline at end of file diff --git a/server/plugins/studiovx/marcleopold/controllers/pages/update.htm b/server/plugins/studiovx/marcleopold/controllers/pages/update.htm new file mode 100644 index 0000000..c3a6e57 --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/pages/update.htm @@ -0,0 +1,54 @@ + += e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/server/plugins/studiovx/marcleopold/controllers/services/_list_toolbar.htm b/server/plugins/studiovx/marcleopold/controllers/services/_list_toolbar.htm new file mode 100644 index 0000000..aaf576f --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/services/_list_toolbar.htm @@ -0,0 +1,19 @@ += e(trans('backend::lang.form.return_to_list')) ?>
+ \ No newline at end of file diff --git a/server/plugins/studiovx/marcleopold/controllers/services/index.htm b/server/plugins/studiovx/marcleopold/controllers/services/index.htm new file mode 100644 index 0000000..ea43a36 --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/services/index.htm @@ -0,0 +1 @@ += $this->listRender() ?> diff --git a/server/plugins/studiovx/marcleopold/controllers/services/preview.htm b/server/plugins/studiovx/marcleopold/controllers/services/preview.htm new file mode 100644 index 0000000..4a2f1f6 --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/services/preview.htm @@ -0,0 +1,22 @@ + ++ + = e(trans('backend::lang.form.return_to_list')) ?> + +
\ No newline at end of file diff --git a/server/plugins/studiovx/marcleopold/controllers/services/reorder.htm b/server/plugins/studiovx/marcleopold/controllers/services/reorder.htm new file mode 100644 index 0000000..761fcbb --- /dev/null +++ b/server/plugins/studiovx/marcleopold/controllers/services/reorder.htm @@ -0,0 +1,8 @@ + +