From b1e5bbad1aa35380453c052389762981f8bd7225 Mon Sep 17 00:00:00 2001 From: mokaddem Date: Tue, 24 Aug 2021 10:48:53 +0200 Subject: [PATCH] new: [tag] Started integration of tag plugin with custom helpers - WiP --- composer.json | 1 + src/Application.php | 1 + src/Model/Table/IndividualsTable.php | 4 + src/View/Helper/BootstrapHelper.php | 22 +++-- src/View/Helper/TagHelper.php | 98 +++++++++++++++++++ src/View/Helper/TextColourHelper.php | 21 ++++ templates/Individuals/view.php | 4 + .../SingleViews/Fields/tagsField.php | 16 +++ templates/layout/default.php | 1 - webroot/css/main.css | 19 ++++ webroot/js/bootstrap-helper.js | 11 ++- webroot/js/main.js | 54 ++++++++++ 12 files changed, 243 insertions(+), 9 deletions(-) create mode 100644 src/View/Helper/TagHelper.php create mode 100644 src/View/Helper/TextColourHelper.php create mode 100644 templates/element/genericElements/SingleViews/Fields/tagsField.php diff --git a/composer.json b/composer.json index f0b35aa..13f42e8 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ "cakephp/cakephp": "^4.0", "cakephp/migrations": "^3.0", "cakephp/plugin-installer": "^1.2", + "dereuromark/cakephp-tags": "^1.2", "erusev/parsedown": "^1.7", "mobiledetect/mobiledetectlib": "^2.8" }, diff --git a/src/Application.php b/src/Application.php index 2da0df1..c6529bd 100644 --- a/src/Application.php +++ b/src/Application.php @@ -59,6 +59,7 @@ class Application extends BaseApplication implements AuthenticationServiceProvid $this->addPlugin('DebugKit'); } $this->addPlugin('Authentication'); + $this->addPlugin('Tags'); // Load more plugins here } diff --git a/src/Model/Table/IndividualsTable.php b/src/Model/Table/IndividualsTable.php index d6893f7..81d4c65 100644 --- a/src/Model/Table/IndividualsTable.php +++ b/src/Model/Table/IndividualsTable.php @@ -14,6 +14,10 @@ class IndividualsTable extends AppTable { parent::initialize($config); $this->addBehavior('UUID'); + $this->addBehavior('Tags.Tag', [ + 'taggedCounter' => false, + 'strategy' => 'array', + ]); $this->hasMany( 'Alignments', [ diff --git a/src/View/Helper/BootstrapHelper.php b/src/View/Helper/BootstrapHelper.php index 05ce6fe..83cd7f1 100644 --- a/src/View/Helper/BootstrapHelper.php +++ b/src/View/Helper/BootstrapHelper.php @@ -670,6 +670,7 @@ class BoostrapButton extends BootstrapGeneric { 'class' => [], 'type' => 'button', 'nodeType' => 'button', + 'title' => '', 'params' => [], 'badge' => false ]; @@ -678,7 +679,7 @@ class BoostrapButton extends BootstrapGeneric { function __construct($options) { $this->allowedOptionValues = [ - 'variant' => BootstrapGeneric::$variants, + 'variant' => array_merge(BootstrapGeneric::$variants, ['link', 'text']), 'size' => ['', 'sm', 'lg'], 'type' => ['button', 'submit', 'reset'] ]; @@ -701,11 +702,15 @@ class BoostrapButton extends BootstrapGeneric { $this->bsClasses[] = "btn-{$this->options['variant']}"; } if (!empty($this->options['size'])) { - $this->bsClasses[] = "btn-$this->options['size']"; + $this->bsClasses[] = "btn-{$this->options['size']}"; } if ($this->options['block']) { $this->bsClasses[] = 'btn-block'; } + if ($this->options['variant'] == 'text') { + $this->bsClasses[] = 'p-0'; + $this->bsClasses[] = 'lh-1'; + } } public function button() @@ -718,7 +723,8 @@ class BoostrapButton extends BootstrapGeneric { $html = $this->openNode($this->options['nodeType'], array_merge($this->options['params'], [ 'class' => array_merge($this->options['class'], $this->bsClasses), 'role' => "alert", - 'type' => $this->options['type'] + 'type' => $this->options['type'], + 'title' => h($this->options['title']), ])); $html .= $this->genIcon(); @@ -734,7 +740,8 @@ class BoostrapButton extends BootstrapGeneric { private function genIcon() { return $this->genNode('span', [ - 'class' => ['mr-1', "fa fa-{$this->options['icon']}"], + // 'class' => ['mr-1', "fa fa-{$this->options['icon']}"], + 'class' => ['', "fa fa-{$this->options['icon']}"], ]); } @@ -749,7 +756,8 @@ class BoostrapBadge extends BootstrapGeneric { 'text' => '', 'variant' => 'primary', 'pill' => false, - 'title' => '' + 'title' => '', + 'class' => [], ]; function __construct($options) { @@ -773,11 +781,11 @@ class BoostrapBadge extends BootstrapGeneric { private function genBadge() { $html = $this->genNode('span', [ - 'class' => [ + 'class' => array_merge($this->options['class'], [ 'badge', "badge-{$this->options['variant']}", $this->options['pill'] ? 'badge-pill' : '', - ], + ]), 'title' => $this->options['title'] ], h($this->options['text'])); return $html; diff --git a/src/View/Helper/TagHelper.php b/src/View/Helper/TagHelper.php new file mode 100644 index 0000000..2cca055 --- /dev/null +++ b/src/View/Helper/TagHelper.php @@ -0,0 +1,98 @@ + '#983965', + ]; + + public function control(array $options = []) + { + return $this->Tag->control($options); + } + + public function picker(array $options = []) + { + $optionsHtml = ''; + foreach ($options['allTags'] as $i => $tag) { + $optionsHtml .= $this->Bootstrap->genNode('option', [ + 'value' => h($tag['text']), + 'data-colour' => h($tag['colour']), + ], h($tag['text'])); + } + $html = $this->Bootstrap->genNode('select', [ + 'class' => ['tag-input', 'd-none'], + 'multiple' => '', + ], $optionsHtml); + $html .= $this->Bootstrap->button([ + 'size' => 'sm', + 'icon' => 'plus', + 'variant' => 'secondary', + 'class' => ['badge'], + 'params' => [ + 'onclick' => 'createTagPicker(this)', + ] + ]); + return $html; + } + + public function tags(array $options = []) + { + $tags = !empty($options['tags']) ? $options['tags'] : []; + $html = '
'; + $html .= '
'; + foreach ($tags as $tag) { + if (is_array($tag)) { + $html .= $this->tag($tag); + } else { + $html .= $this->tag([ + 'name' => $tag + ]); + } + } + $html .= '
'; + + if (!empty($options['picker'])) { + $html .= $this->picker($options); + } + $html .= '
'; + return $html; + } + + public function tag(array $tag) + { + $tag['colour'] = !empty($tag['colour']) ? $tag['colour'] : $this->getConfig()['default_colour']; + $textColour = $this->TextColour->getTextColour(h($tag['colour'])); + $deleteButton = $this->Bootstrap->button([ + 'size' => 'sm', + 'icon' => 'times', + 'class' => ['ml-1', 'border-0', "text-${textColour}"], + 'variant' => 'text', + 'title' => __('Delete tag'), + ]); + + $html = $this->Bootstrap->genNode('span', [ + 'class' => [ + 'tag', + 'badge', + 'mx-1', + 'align-middle', + ], + 'title' => h($tag['name']), + 'style' => sprintf('color:%s; background-color:%s', $textColour, h($tag['colour'])), + ], h($tag['name']) . $deleteButton); + return $html; + } +} diff --git a/src/View/Helper/TextColourHelper.php b/src/View/Helper/TextColourHelper.php new file mode 100644 index 0000000..c484486 --- /dev/null +++ b/src/View/Helper/TextColourHelper.php @@ -0,0 +1,21 @@ +element( 'key' => __('Position'), 'path' => 'position' ], + [ + 'key' => __('Tags'), + 'type' => 'tags', + ], [ 'key' => __('Alignments'), 'type' => 'alignment', diff --git a/templates/element/genericElements/SingleViews/Fields/tagsField.php b/templates/element/genericElements/SingleViews/Fields/tagsField.php new file mode 100644 index 0000000..a8b9f88 --- /dev/null +++ b/templates/element/genericElements/SingleViews/Fields/tagsField.php @@ -0,0 +1,16 @@ + 'tlp:red', 'text' => 'tlp:red', 'colour' => 'red'], + ['id' => 'tlp:green', 'text' => 'tlp:green', 'colour' => 'green'], + ['id' => 'tlp:amber', 'text' => 'tlp:amber', 'colour' => '#983965'], + ['id' => 'tlp:white', 'text' => 'tlp:white', 'colour' => 'white'], +]; +$this->loadHelper('Tag'); +echo $this->Tag->tags([ + 'allTags' => $allTags, + 'tags' => $tagList, + 'picker' => true, +]); \ No newline at end of file diff --git a/templates/layout/default.php b/templates/layout/default.php index 1e2cbde..d97b800 100644 --- a/templates/layout/default.php +++ b/templates/layout/default.php @@ -86,6 +86,5 @@ $cakeDescription = 'Cerebrate'; diff --git a/webroot/css/main.css b/webroot/css/main.css index bcf6c0e..de19e15 100644 --- a/webroot/css/main.css +++ b/webroot/css/main.css @@ -115,6 +115,13 @@ .text-black {color:black;} .text-white {color:white;} +.lh-1 { + line-height: 1; +} +.lh-2 { + line-height: 1.5; +} + .link-unstyled, .link-unstyled:link, .link-unstyled:hover { color: inherit; text-decoration: inherit; @@ -145,3 +152,15 @@ input[type="checkbox"]:disabled.change-cursor { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } + +.select2-selection__choice { /* Our app do not have the same font size */ + padding-left: 1.5em !important; +} +.picker-container .select2-selection { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} +.picker-container .picker-action .btn:first-child { + border-top-left-radius: 0 !important; + border-bottom-left-radius: 0 !important; +} \ No newline at end of file diff --git a/webroot/js/bootstrap-helper.js b/webroot/js/bootstrap-helper.js index 61bca3f..2fc230a 100644 --- a/webroot/js/bootstrap-helper.js +++ b/webroot/js/bootstrap-helper.js @@ -1108,4 +1108,13 @@ class HtmlHelper { } return $table } -} \ No newline at end of file + + static tag(options={}) { + const $tag = $('') + .addClass(['tag', 'badge', 'border']) + .css({color: getTextColour(options.colour), 'background-color': options.colour}) + .text(options.text) + + return $tag + } +} diff --git a/webroot/js/main.js b/webroot/js/main.js index d950781..9ff0797 100644 --- a/webroot/js/main.js +++ b/webroot/js/main.js @@ -99,6 +99,60 @@ function syntaxHighlightJson(json, indent) { }); } +function getTextColour(hex) { + if (hex === undefined || hex.length == 0) { + return 'black' + } + hex = hex.slice(1) + var r = parseInt(hex.substring(0,2), 16) + var g = parseInt(hex.substring(2,4), 16) + var b = parseInt(hex.substring(4,6), 16) + var avg = ((2 * r) + b + (3 * g))/6 + if (avg < 128) { + return 'white' + } else { + return 'black' + } +} + +function createTagPicker(clicked) { + + function templateTag(state) { + if (!state.id) { + return state.text; + } + if (state.colour === undefined) { + state.colour = $(state.element).data('colour') + } + return HtmlHelper.tag(state) + } + + const $clicked = $(clicked) + const $container = $clicked.closest('.tag-container') + $('.picker-container').remove() + const $pickerContainer = $('
').addClass(['picker-container', 'd-flex']) + const $select = $container.find('select.tag-input').removeClass('d-none').addClass('flex-grow-1') + const $saveButton = $('').addClass(['btn btn-primary btn-sm', 'align-self-start']) + .append($('').text('Save').prepend($('').addClass('fa fa-save mr-1'))) + const $cancelButton = $('').addClass(['btn btn-secondary btn-sm', 'align-self-start']) + .append($('').text('Cancel').prepend($('').addClass('fa fa-times mr-1'))) + .click(function() { + $select.appendTo($container) + $pickerContainer.remove() + }) + const $buttons = $('').addClass(['picker-action', 'btn-group']).append($saveButton, $cancelButton) + $select.prependTo($pickerContainer) + $pickerContainer.append($buttons) + $container.parent().append($pickerContainer) + $select.select2({ + placeholder: 'Pick a tag', + tags: true, + templateResult: templateTag, + templateSelection: templateTag, + }) +} + + var UI $(document).ready(() => { if (typeof UIFactory !== "undefined") {