diff --git a/app/resources/locales/en_US/field.po b/app/resources/locales/en_US/field.po index 58cb5352..4c8afd54 100644 --- a/app/resources/locales/en_US/field.po +++ b/app/resources/locales/en_US/field.po @@ -29,8 +29,8 @@ # When adding entries to this file, group non-model specific translations at the top, # then model specific translations alphabetically by model. -msgid "action" -msgstr "Action" +msgid "actions" +msgstr "{0,plural,=1{Action} other{Actions}}" msgid "actor" msgstr "Actor" @@ -159,6 +159,9 @@ msgstr "Plugin" msgid "postal_code" msgstr "Postal Code" +msgid "primary" +msgstr "Primary" + msgid "primary_name" msgstr "Primary Name" @@ -243,6 +246,9 @@ msgstr "Value" msgid "verified" msgstr "Verified" +msgid "unverified" +msgstr "Unverified" + msgid "ApiUsers.privileged.desc" msgstr "A privileged API user has full access to the CO. Unprivileged API users may be granted specific permissions where supported." diff --git a/app/resources/locales/en_US/information.po b/app/resources/locales/en_US/information.po index 7e6551f1..4cd7ed3a 100644 --- a/app/resources/locales/en_US/information.po +++ b/app/resources/locales/en_US/information.po @@ -56,3 +56,9 @@ msgstr "There are no records to display." msgid "global.title.none" msgstr "No title" + +msgid "global.value.none" +msgstr "No value" + +msgid "global.visit.link" +msgstr "Visit link" diff --git a/app/resources/locales/en_US/operation.po b/app/resources/locales/en_US/operation.po index e337e64f..fc01b4b6 100644 --- a/app/resources/locales/en_US/operation.po +++ b/app/resources/locales/en_US/operation.po @@ -27,6 +27,9 @@ msgid "activate" msgstr "Activate" +msgid "add" +msgstr "Add" + msgid "add.a" msgstr "Add a New {0}" diff --git a/app/src/Controller/PeopleController.php b/app/src/Controller/PeopleController.php index 4577075c..c1d2e5e1 100644 --- a/app/src/Controller/PeopleController.php +++ b/app/src/Controller/PeopleController.php @@ -76,4 +76,66 @@ public function beforeRender(\Cake\Event\EventInterface $event) { return parent::beforeRender($event); } + + /** + * Person overview / canvas + * + * @since COmanage Registry v5.0.0 + */ + + public function canvas(string $id) { + /* XXX Simply including parent::edit() is nearly all that's required - but we need + to delve a little deeper to get data from underlying relationships. We might send a contains() + override to parent::edit() which would allow this to be more DRY + // Keep the following as an example for now. + parent::edit($id, false); + + // We've just set the vv_supertitle in the parent controller. + // Pass it to the title for use in the breadcrumbs. + $this->set('vv_title', $this->viewBuilder()->getVar('vv_supertitle')); + */ + + /* The following is nearly identical to parent::edit/view */ + $modelsName = $this->name; + $table = $this->$modelsName; + $tableName = $table->getTable(); + $query = $table->findById($id); + + // This contain() directive is the primary deviation from standard edit()/view(). + // We need to drill down to deeper related models for display. + $query = $query->contain([ + 'PrimaryName', + 'Names' => ['Types'], + 'Addresses' => ['Types'], + 'AdHocAttributes', + 'EmailAddresses' => ['Types'], + 'GroupMembers', + 'GroupOwners', + 'Identifiers' => ['Types'], + 'PersonRoles' => ['Cous','Types'], + 'Pronouns', + 'TelephoneNumbers' => ['Types'], + 'Urls' => ['Types'] + ]); + + try { + // Pull the current record + $obj = $query->firstOrFail(); + } catch(\Exception $e) { + // findById throws Cake\Datasource\Exception\RecordNotFoundException + $this->Flash->error($e->getMessage()); + // XXX This redirects to an Exception page because $id is not found. + // XXX A 404 with error would be better. + return $this->generateRedirect((int)$id); + } + + $this->set('vv_obj', $obj); + $this->getPrimaryLink(); + $this->populateAutoViewVars($obj); + + $this->set('vv_title', $table->generateDisplayField($obj)); + $this->set('vv_supertitle', $table->generateDisplayField($obj)); + // Pass the display field also into subtitle for dealing with External IDs + $this->set('vv_subtitle', $table->generateDisplayField($obj)); + } } \ No newline at end of file diff --git a/app/src/Controller/StandardController.php b/app/src/Controller/StandardController.php index a62131ff..e3490a4a 100644 --- a/app/src/Controller/StandardController.php +++ b/app/src/Controller/StandardController.php @@ -331,8 +331,9 @@ public function edit(string $id) { } catch(\Exception $e) { // findById throws Cake\Datasource\Exception\RecordNotFoundException - $this->Flash->error($e->getMessage()); + // XXX This redirects to an Exception page because $id is not found. + // XXX A 404 with error would be better. return $this->generateRedirect((int)$id); } @@ -724,8 +725,9 @@ public function view($id = null) { } catch(\Exception $e) { // findById throws Cake\Datasource\Exception\RecordNotFoundException - $this->Flash->error($e->getMessage()); + // XXX This redirects to an Exception page because $id is not found. + // XXX A 404 with error would be better. return $this->generateRedirect((int)$id); } @@ -742,6 +744,9 @@ public function view($id = null) { // We don't use a trait for this since each table will implement different logic $this->set('vv_title', __d('operation', 'view.ai', $table->generateDisplayField($obj))); + $this->set('vv_supertitle', $table->generateDisplayField($obj)); + // Pass the display field also into subtitle for dealing with External IDs + $this->set('vv_subtitle', $table->generateDisplayField($obj)); } else { // Default view title is the object display field $field = $table->getDisplayField(); diff --git a/app/src/Lib/Traits/PrimaryLinkTrait.php b/app/src/Lib/Traits/PrimaryLinkTrait.php index ec52aa6c..bd02b2b6 100644 --- a/app/src/Lib/Traits/PrimaryLinkTrait.php +++ b/app/src/Lib/Traits/PrimaryLinkTrait.php @@ -46,7 +46,7 @@ trait PrimaryLinkTrait { private $unkeyedActions = ['add', 'index']; // Actions where the primary link can be obtained by looking up the record ID - private $lookupActions = ['delete', 'edit', 'view']; + private $lookupActions = ['delete', 'edit', 'canvas', 'view']; // Where to redirect on add or edit, can be 'self', 'index', or 'primaryLink' private $redirectGoal = 'index'; diff --git a/app/src/Lib/Util/StringUtilities.php b/app/src/Lib/Util/StringUtilities.php index 51a8e9c9..c7056b82 100644 --- a/app/src/Lib/Util/StringUtilities.php +++ b/app/src/Lib/Util/StringUtilities.php @@ -46,7 +46,7 @@ class StringUtilities { public static function columnKey($modelsName, $c, $tz=null, $useCustomClMdlLabel=false): string { if(strpos($c, "_id", strlen($c)-3)) { // Key is of the form field_id, use .ct label instead - $k = $this->foreignKeyToClassName($c); + $k = self::foreignKeyToClassName($c); return __d('controller', $k, [1]); } diff --git a/app/src/Model/Table/PeopleTable.php b/app/src/Model/Table/PeopleTable.php index f495b9ce..4e46ac8c 100644 --- a/app/src/Model/Table/PeopleTable.php +++ b/app/src/Model/Table/PeopleTable.php @@ -150,6 +150,7 @@ public function initialize(array $config): void { 'entity' => [ 'delete' => ['platformAdmin', 'coAdmin'], 'edit' => ['platformAdmin', 'coAdmin'], + 'canvas' => ['platformAdmin', 'coAdmin'], 'view' => ['platformAdmin', 'coAdmin'] ], // Actions that operate over a table (ie: do not require an $id) diff --git a/app/templates/AdHocAttributes/columns.inc b/app/templates/AdHocAttributes/columns.inc index dd2f93b6..4d5aecbe 100644 --- a/app/templates/AdHocAttributes/columns.inc +++ b/app/templates/AdHocAttributes/columns.inc @@ -41,6 +41,6 @@ $bulkActions = [ $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'ad_hoc_attributes' ]; \ No newline at end of file diff --git a/app/templates/AdHocAttributes/fields-nav.inc b/app/templates/AdHocAttributes/fields-nav.inc index 8cddfe10..9b42482f 100644 --- a/app/templates/AdHocAttributes/fields-nav.inc +++ b/app/templates/AdHocAttributes/fields-nav.inc @@ -30,6 +30,6 @@ $topLinks = []; $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'ad_hoc_attributes' ]; \ No newline at end of file diff --git a/app/templates/Addresses/columns.inc b/app/templates/Addresses/columns.inc index 257f4b09..e5c58c12 100644 --- a/app/templates/Addresses/columns.inc +++ b/app/templates/Addresses/columns.inc @@ -45,6 +45,6 @@ $bulkActions = [ $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'addresses' ]; diff --git a/app/templates/Addresses/fields-nav.inc b/app/templates/Addresses/fields-nav.inc index 08574e5a..86686fdf 100644 --- a/app/templates/Addresses/fields-nav.inc +++ b/app/templates/Addresses/fields-nav.inc @@ -30,6 +30,6 @@ $topLinks = []; $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'addresses' ]; \ No newline at end of file diff --git a/app/templates/EmailAddresses/columns.inc b/app/templates/EmailAddresses/columns.inc index da915ca1..4c40977c 100644 --- a/app/templates/EmailAddresses/columns.inc +++ b/app/templates/EmailAddresses/columns.inc @@ -41,6 +41,6 @@ $bulkActions = [ $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'email_addresses' ]; \ No newline at end of file diff --git a/app/templates/EmailAddresses/fields-nav.inc b/app/templates/EmailAddresses/fields-nav.inc index 14b67797..797d856b 100644 --- a/app/templates/EmailAddresses/fields-nav.inc +++ b/app/templates/EmailAddresses/fields-nav.inc @@ -30,6 +30,6 @@ $topLinks = []; $subnav = [ 'name' => 'person', - 'active' => 'attributes', // default for person. 'external identities' are special cased + 'active' => 'person', // default for person. 'external identities' are special cased 'subActive' => 'email_addresses' ]; \ No newline at end of file diff --git a/app/templates/Identifiers/columns.inc b/app/templates/Identifiers/columns.inc index 48c82927..8a27be5d 100644 --- a/app/templates/Identifiers/columns.inc +++ b/app/templates/Identifiers/columns.inc @@ -56,6 +56,6 @@ $bulkActions = [ $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'identifiers' ]; \ No newline at end of file diff --git a/app/templates/Identifiers/fields-nav.inc b/app/templates/Identifiers/fields-nav.inc index bcacd714..17db0dc9 100644 --- a/app/templates/Identifiers/fields-nav.inc +++ b/app/templates/Identifiers/fields-nav.inc @@ -30,6 +30,6 @@ $topLinks = []; $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'identifiers' ]; \ No newline at end of file diff --git a/app/templates/Names/columns.inc b/app/templates/Names/columns.inc index feeda0cd..e142b78a 100644 --- a/app/templates/Names/columns.inc +++ b/app/templates/Names/columns.inc @@ -58,6 +58,6 @@ $bulkActions = [ $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'names' ]; \ No newline at end of file diff --git a/app/templates/Names/fields-nav.inc b/app/templates/Names/fields-nav.inc index d333f883..955afd92 100644 --- a/app/templates/Names/fields-nav.inc +++ b/app/templates/Names/fields-nav.inc @@ -30,6 +30,6 @@ $topLinks = []; $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'names' ]; \ No newline at end of file diff --git a/app/templates/People/canvas.php b/app/templates/People/canvas.php new file mode 100644 index 00000000..2d7397b0 --- /dev/null +++ b/app/templates/People/canvas.php @@ -0,0 +1,172 @@ + 'person', + 'active' => 'canvas' + ]; + + $linkFilter = []; + if(!empty($vv_primary_link) && !empty($this->request->getQuery($vv_primary_link))) { + $linkFilter = [$vv_primary_link => $this->request->getQuery($vv_primary_link)]; + } + + $objId = null; + if(!empty($vv_obj)) { + $objId = $vv_obj->id; + } + + // Person Attributes to display. + $attributes = [ + 'names', + 'email_addresses', + 'identifiers', + 'ad_hoc_attributes', + 'addresses', + 'telephone_numbers', + 'urls', + 'pronouns' + ]; + + // Count the number of widgets that will be displayed + $widgetCount = 0; + foreach($attributes as $attr) { + if(!empty($vv_obj[$attr])) { + $widgetCount++; + } + } + +?> + + + +
+ + +
+ element( + 'mveaJs', + [ + 'htmlId' => 'person-canvas-' . $attr . '-js', + 'parentId' => $objId, + 'mveaType' => $attr, + 'entityType' => 'person' + ] + ); + } + } + ?> +
+ + +
+ +
+ element( + 'mvea', + [ + 'vv_obj' => $vv_obj, + 'mveaType' => $attr, + 'entityType' => 'person' + ] + ); + } + } + ?> +
+ + + +
+
+

+ Html->link( + __d('controller', 'PersonRoles', [99]), + [ 'controller' => 'person_roles', + 'action' => 'index', + '?' => ['person_id' => $objId] + ] + ); ?> +

+
+
    + +
  • +
    + Html->link( + !(empty($role['title'])) ? $role['title'] : __d('information','global.title.none'), + [ 'controller' => 'person_roles', + 'action' => 'edit', + $role['id'] + ], + ['class' => 'row-link'] + ); ?> +
    +
    + + + + + +
    +
    + + + + + + + + + + + + + + + + +
    +
    +
  • + +
+
+
+
+
diff --git a/app/templates/People/columns.inc b/app/templates/People/columns.inc index d29f8d6a..4e001764 100644 --- a/app/templates/People/columns.inc +++ b/app/templates/People/columns.inc @@ -30,6 +30,7 @@ $indexColumns = [ 'type' => 'link', 'model' => 'primary_name', 'field' => 'full_name', + 'action' => 'canvas', // XXX see comments in the controller about sorting on given vs family 'sortable' => 'PrimaryName.family' ], @@ -49,67 +50,57 @@ $rowActions = [ 'controller' => 'names', 'action' => 'index', 'icon' => 'account_box', - 'iconClass' => 'material-icons-outlined', - 'type' => 'tab' + 'iconClass' => 'material-icons-outlined' ], [ 'controller' => 'email_addresses', 'action' => 'index', 'icon' => 'email', - 'iconClass' => 'material-icons-outlined', - 'type' => 'tab' + 'iconClass' => 'material-icons-outlined' ], [ 'controller' => 'identifiers', 'action' => 'index', 'icon' => 'fingerprint', - 'type' => 'tab', 'class' => 'bottom-border' ], [ 'controller' => 'person_roles', 'action' => 'index', - 'icon' => 'emoji_people', - 'type' => 'tab' + 'icon' => 'emoji_people' ], [ 'controller' => 'external_identities', 'action' => 'index', 'icon' => 'system_update_alt', - 'type' => 'tab', 'class' => 'bottom-border' ], [ 'controller' => 'ad_hoc_attributes', 'action' => 'index', 'icon' => 'check_box', - 'iconClass' => 'material-icons-outlined', - 'type' => 'tab' + 'iconClass' => 'material-icons-outlined' ], [ 'controller' => 'addresses', 'action' => 'index', 'icon' => 'contact_mail', - 'iconClass' => 'material-icons-outlined', - 'type' => 'tab' + 'iconClass' => 'material-icons-outlined' ], [ 'controller' => 'history_records', 'action' => 'index', - 'icon' => 'history', - 'type' => 'tab' + 'icon' => 'history' ], [ 'controller' => 'telephone_numbers', 'action' => 'index', - 'icon' => 'phone', - 'type' => 'tab' + 'icon' => 'phone' ], [ 'controller' => 'urls', 'action' => 'index', - 'icon' => 'link', - 'type' => 'tab' + 'icon' => 'link' ] ]; diff --git a/app/templates/People/fields-nav.inc b/app/templates/People/fields-nav.inc index e54aef21..cf904e33 100644 --- a/app/templates/People/fields-nav.inc +++ b/app/templates/People/fields-nav.inc @@ -44,5 +44,6 @@ $topLinks = [ $subnav = [ 'name' => 'person', - 'active' => 'properties' + 'active' => 'person', + 'subActive' => 'properties' ]; \ No newline at end of file diff --git a/app/templates/Pronouns/columns.inc b/app/templates/Pronouns/columns.inc index 3963c2db..58767da5 100644 --- a/app/templates/Pronouns/columns.inc +++ b/app/templates/Pronouns/columns.inc @@ -45,6 +45,6 @@ $bulkActions = [ $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'pronouns' ]; \ No newline at end of file diff --git a/app/templates/Pronouns/fields-nav.inc b/app/templates/Pronouns/fields-nav.inc index ff7b40d0..2ea80858 100644 --- a/app/templates/Pronouns/fields-nav.inc +++ b/app/templates/Pronouns/fields-nav.inc @@ -30,6 +30,6 @@ $topLinks = []; $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'pronouns' ]; \ No newline at end of file diff --git a/app/templates/Standard/add-edit-view.php b/app/templates/Standard/add-edit-view.php index 16800f0c..8925cddf 100644 --- a/app/templates/Standard/add-edit-view.php +++ b/app/templates/Standard/add-edit-view.php @@ -61,34 +61,24 @@ // the fields (which would be more consistent with how Views render...) $flashArgs['vv_banners'] = $banners; } -?> - - - +// If subnavigation is present a supertitle and the subnavigation will be placed above +// the normal page title. The flash messages will be shown up there as well. +if(!empty($subnav)) { + // Include the $flashArgs for the subnavigation element + $subnav['flashArgs'] = $flashArgs; + // Generate the subnavigation title and tabs + print $this->element('subnavigation', $subnav); +} +?>
-

+

id; foreach(($topLinks ?? []) as $t) { - if($vv_permissions[ $t['link']['action'] ]) { + // TODO: fix the following test so that cross-model links can exist in top-links (e.g. History Records index) + //if($vv_permissions[ $t['link']['action'] ]) { // We need to inject $linkFilter, but not overwrite any existing query params if(!empty($t['link']['?'])) { $t['link']['?'] = array_merge($t['link']['?'], $linkFilter); @@ -121,7 +112,7 @@ 'url' => $this->Url->build($t['link']), 'label' => $t['label'], ]; - } + //} } // Delete diff --git a/app/templates/Standard/index.php b/app/templates/Standard/index.php index f13cac03..7b7071c0 100644 --- a/app/templates/Standard/index.php +++ b/app/templates/Standard/index.php @@ -67,26 +67,16 @@ if(!empty($banners)) { $flashArgs['vv_banners'] = $banners; } -?> - - - +// If subnavigation is present a supertitle and the subnavigation will be placed above +// the normal page title. The flash messages will be shown up there as well. +if(!empty($subnav)) { + // Include the $flashArgs for the subnavigation element + $subnav['flashArgs'] = $flashArgs; + // Generate the subnavigation title and tabs + print $this->element('subnavigation', $subnav); +} +?>
@@ -94,7 +84,7 @@

@@ -521,6 +511,8 @@ } elseif ($a == 'view') { $linkClass .= ' row-link-view'; $readOnlyIcon = ' edit_off'; + } else { + $linkClass .= ' row-link-' . $a; } $args = ['class' => $linkClass]; $isFirstLink = false; diff --git a/app/templates/TelephoneNumbers/columns.inc b/app/templates/TelephoneNumbers/columns.inc index 55289a09..1c4f56b1 100644 --- a/app/templates/TelephoneNumbers/columns.inc +++ b/app/templates/TelephoneNumbers/columns.inc @@ -41,6 +41,6 @@ $bulkActions = [ $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'telephone_numbers' ]; \ No newline at end of file diff --git a/app/templates/TelephoneNumbers/fields-nav.inc b/app/templates/TelephoneNumbers/fields-nav.inc index 267ae028..e6f2507e 100644 --- a/app/templates/TelephoneNumbers/fields-nav.inc +++ b/app/templates/TelephoneNumbers/fields-nav.inc @@ -30,6 +30,6 @@ $topLinks = []; $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'telephone_numbers' ]; \ No newline at end of file diff --git a/app/templates/Urls/columns.inc b/app/templates/Urls/columns.inc index 5790bff7..08f46183 100644 --- a/app/templates/Urls/columns.inc +++ b/app/templates/Urls/columns.inc @@ -41,6 +41,6 @@ $bulkActions = [ $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'urls' ]; \ No newline at end of file diff --git a/app/templates/Urls/fields-nav.inc b/app/templates/Urls/fields-nav.inc index 8e2ef8f4..8e0c5364 100644 --- a/app/templates/Urls/fields-nav.inc +++ b/app/templates/Urls/fields-nav.inc @@ -30,6 +30,6 @@ $topLinks = []; $subnav = [ 'name' => 'person', - 'active' => 'attributes', + 'active' => 'person', 'subActive' => 'names' ]; \ No newline at end of file diff --git a/app/templates/element/breadcrumbs.php b/app/templates/element/breadcrumbs.php index 83bcdac4..3492f4fd 100644 --- a/app/templates/element/breadcrumbs.php +++ b/app/templates/element/breadcrumbs.php @@ -221,7 +221,7 @@ // XXX This is initially for api_users:generate, not clear how much this does // or does not generalize. If we start adding more exceptions here, we should // flip the logic and let api_users:generate declare that it wants a link back. - if(!in_array($vv_action, ['add', 'edit', 'index', 'view']) + if(!in_array($vv_action, ['add', 'edit', 'index', 'canvas', 'view']) && !empty($vv_obj->id) && !empty($vv_obj->$vv_display_field)) { $oaction = ($vv_permissions['edit'] diff --git a/app/templates/element/javascript.php b/app/templates/element/javascript.php index 2a5b6eac..acfdf8f7 100644 --- a/app/templates/element/javascript.php +++ b/app/templates/element/javascript.php @@ -161,18 +161,23 @@ placeholder: "-- Select --" }); - // Generic row click handling for div-based rows - $('div.linked-row').click(function(e) { - location.href = $(this).find('a.row-link').attr('href'); - }); - - // Generic row click handling for index-table rows + // Generic row click handling // First capture mouse location to test if we're clicking or drag-selecting (for copy) var mouseDownEvent = null; - $('table.index-table tr').mousedown(function(e) { + $('table.index-table tr, .linked-row').mousedown(function(e) { mouseDownEvent = e; }); - + + // Generic row click handling for div-and li based rows + $('.linked-row').click(function(e) { + url = $(this).find('a.row-link').attr('href'); + if(Math.abs(e.clientX-mouseDownEvent.clientX) < 5 && + Math.abs(e.clientY-mouseDownEvent.clientY < 5)) { + location.href = url; + } + }); + + // Generic row click handling for index-table rows $('table.index-table tr').each(function(e) { url = $(this).find('a.row-link').attr('href'); if(url != undefined && url != '') { diff --git a/app/templates/element/menuAction.php b/app/templates/element/menuAction.php index 18fa989e..ee4549b6 100644 --- a/app/templates/element/menuAction.php +++ b/app/templates/element/menuAction.php @@ -27,9 +27,11 @@ $actionsCount = count($vv_actions); $actionsCountClass = $actionsCount > 0 ? ' actions-count-' . $actionsCount : ''; -$actionsMenuClass = 'field-actions-menu dropdown dropleft' . $actionsCountClass; +$actionsMenuClass = (!empty($vv_actions_class) ? $vv_actions_class : 'field-actions-menu') . ' dropdown dropleft' . $actionsCountClass; $actionsMenuUid = md5($vv_attr_id); -$actionsType = isset($vv_actions_type) ? $vv_actions_type : 'row-actions'; +$actionsType = !empty($vv_actions_type) ? $vv_actions_type : 'row-actions'; +$actionsTitle = !empty($vv_actions_title) ? $vv_actions_title : ''; +$actionsIcon = !empty($vv_actions_icon) ? $vv_actions_icon : 'settings'; ?>
'title' => __d('field', 'action') ); print $this->Html->link( - 'settings', + '' . $actionsIcon . ' ' . $actionsTitle, 'javascript:void(0);', $linkparams ); diff --git a/app/templates/element/mvea.php b/app/templates/element/mvea.php new file mode 100644 index 00000000..c2e2ac78 --- /dev/null +++ b/app/templates/element/mvea.php @@ -0,0 +1,196 @@ + + +
+
+
+

+ Html->link( + __d('controller', $mveaController, [99]), + [ 'controller' => $mveaType, + 'action' => 'index', + '?' => ['person_id' => $vv_obj['id']] + ] + ); ?> +

+
+
    + +
  • +
    + + +
    + + Html->link( + $linkTitle, + [ + 'controller' => $mveaType, + 'action' => 'edit', + $mvea['id'] + ], + [ + 'class' => 'row-link' + ] + ); ?> + + + +
    + + + + +
    + +
    + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + +
    + +
  • + +
+
+
+
+
diff --git a/app/templates/element/mveaJs.php b/app/templates/element/mveaJs.php new file mode 100644 index 00000000..5214ed12 --- /dev/null +++ b/app/templates/element/mveaJs.php @@ -0,0 +1,132 @@ +request->getAttribute('csrfToken'); + +// Create an MVEA component - typically this is represented as a card on a canvas page +?> + + + +
+
+
+

+
+ + +
+
+
+
diff --git a/app/templates/element/subnavigation.php b/app/templates/element/subnavigation.php index 93fb928d..8d23e426 100644 --- a/app/templates/element/subnavigation.php +++ b/app/templates/element/subnavigation.php @@ -31,6 +31,7 @@ use Cake\View\Helper; $linkFilter = []; +$flashArgs = []; $curId = NULL; $curController = $this->request->getParam('controller'); $curAction = $this->request->getParam('action'); @@ -60,304 +61,271 @@ $linkFilter = ['group_id' => $curId]; } } -?> - - - +
+ element('menuAction', $action_args) ?> +
+ + +
+ + + element('flash', $flashArgs); ?> - - - request->getQuery($vv_primary_link); - if($isPersonRole || $isExternalId) { - if($isExternalId && !empty($vv_ei_id)) { - $curId = $vv_ei_id; - } - // We display the role or external identity name above the subnav to show the hierarchy. - // We also use this structure to set the second-level $linkFilter - print '

'; - if(!empty($vv_ei_name)) { - // We have an external identity name - print $vv_ei_name->full_name; - $linkFilter = ['external_identity_id' => $curId]; - } elseif(!empty($vv_person_role)) { - // We have a person role - print $vv_person_role; - $linkFilter = ['person_role_id' => $curId]; - } elseif(!empty($vv_obj)) { - // We are editing/viewing an object - if(!empty($vv_obj->title)) { - print $vv_obj->title; - } elseif (!empty($vv_subtitle)) { - print $vv_subtitle; - } else { - print print __d('information','global.title.none'); - } - // Set up $linkFilter for edit/view on Roles and External Identities - $curId = $vv_obj->id; - if($curController == 'PersonRoles') { - $linkFilter = ['person_role_id' => $curId]; - } - if($curController == 'ExternalIdentities') { - $linkFilter = ['external_identity_id' => $curId]; - } - } else { - // We shouldn't get here, but have a deafult in case. - print __d('information','global.title.none'); - } - print '

'; - } - ?> - + + + +
+

+ id; + if(!empty($vv_obj) && ($curController == 'ExternalIdentityRoles' && ($curAction == 'edit' || $curAction == 'view'))) { + $curId = $vv_obj->id; + } + $linkFilter = ['external_identity_role_id' => $curId]; + + if(!empty($vv_ei_role)) { + print $vv_ei_role; + } elseif(!empty($vv_obj->title)) { + print $vv_obj->title; + } else { + // As above, we shouldn't get here, but have a deafult in case. + print __d('information','global.title.none'); + } + ?> +

+ +
+ + +
+ \ No newline at end of file diff --git a/app/webroot/css/co-base.css b/app/webroot/css/co-base.css index 02adf08b..4e38b78d 100644 --- a/app/webroot/css/co-base.css +++ b/app/webroot/css/co-base.css @@ -617,6 +617,13 @@ ul.form-list li.alert-banner .co-alert { /* SUBNAVIGATION & TABS */ .supertitle { padding: 1em 0; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 1em; +} +.supertitle .person-status-badge { + margin: 0; } #subnavigation nav { padding: 0.5em 0; @@ -1008,15 +1015,51 @@ table.index-table .form-check { :-moz-placeholder { opacity: 0.2; } +/* PERSON CANVAS / CARDS */ +#person-actions { + display: flex; +} +#person-actions .action-menu-toggle { + white-space: nowrap; +} +#person-canvas-attributes, +#person-canvas-attributes-js { + margin-top: 0; + margin-bottom: 2em; +} +#person-canvas-attributes-js .badge { + margin-right: 0.5em; +} +#person-canvas-attributes-js .canvas-url-link { + margin-left: 1em; +} +#person-canvas-roles { + margin-bottom: 2em; +} +h2.card-title { + font-size: 1.2rem; + margin-bottom: 0.5rem; + line-height: 2rem; +} +h2.card-title a { + display: block; + color: var(--cmg-color-gray-002); +} +#main h2.card-title a:hover { + text-decoration: none; +} +.field-data-container .id-col { + text-align: right; + padding-right: 1em; + color: var(--cmg-color-gray-003); + font-size: 0.8em; +} /* DATA LISTS */ -/*** ul.data-list is the main structure for representing data -in which the field value is to the left, and actions on that -value are to the right; this is used for canvases and explorers -where the fields require no (or few) labels. -see also ul.form-list below. ***/ +ul.data-list { + padding-left: 0; +} ul.data-list li { - margin: 0 0 2px !important; - overflow: hidden; + margin: 0 0 2px; padding: 2px 0 3px 0; background-color: var(--cmg-color-lightgray-003); } @@ -1027,11 +1070,7 @@ ul.data-table li { background-color: unset; } .field-data { - float: left; - padding: 4px 0 0 10px; -} -.field-data-alone { - padding: 2px 0 2px 10px; + padding: 0.25em 0.75em; } /* FORMS */ label { @@ -1128,6 +1167,8 @@ ul.form-list li.field-stack .field-info { } ul.form-list textarea { font-size: 0.9em; + width: 100%; + height: 4em; } ul.form-list li.field-stack textarea { margin: 0; @@ -1308,6 +1349,9 @@ ul.form-list .cm-time-picker-vals li { p { margin: 0 0 1em; } +address { + margin-bottom: 0; +} .fieldTitle { font-weight: bold; } @@ -1327,6 +1371,22 @@ p { .vtop { vertical-align: top !important; } +.force-wrap { + overflow-wrap: anywhere; +} +.text-muted-cmg { + color: var(--cmg-color-gray-003) !important; +} +.cm-id-display { + margin-left: 1em; + font-size: 0.9em; +} +.smaller { + font-size: smaller; +} +.invalid-feedback { + font-size: 1em; +} .warn-level-a, .warn-level-a td { background-color: var(--cmg-color-red-001); @@ -1576,28 +1636,29 @@ table.list-mode .read-only-link-container { } /*Bootstrap badge*/ .badge { - font-size: 0.95em; font-weight: normal; height: min-content; margin-top: auto; margin-bottom: auto; + line-height: 1.2em; } -.bg-secondary, -.bg-success, -.bg-danger, -.bg-warning, -.bg-info, -.bg-light, -.bg-dark, -.bg-primary { - border: 1px solid transparent; +.bg-light { + color: var(--cmg-color-gray-004); +} +.bg-danger { + color: var(--cmg-color-white); + background-color: var(--cmg-color-red-005); } .bg-warning { - background-color: var(--cmg-color-yellow-001); + background-color: var(--cmg-color-yellow-001) !important; + color: var(--cmg-color-yellow-002); } .bg-primary { background-color: var(--cmg-color-blue-002); } +.badge.bg-primary { + background-color: var(--cmg-color-blue-002) !important; +} .bg-secondary { background-color: var(--cmg-color-gray-002); } diff --git a/app/webroot/css/co-responsive.css b/app/webroot/css/co-responsive.css index 29232fe2..b957c7f8 100644 --- a/app/webroot/css/co-responsive.css +++ b/app/webroot/css/co-responsive.css @@ -36,6 +36,11 @@ right: unset; transform: translate(-95%,0); } + .supertitle-container { + display: flex; + align-items: center; + justify-content: space-between; + } } /* SMALL */ @@ -94,6 +99,15 @@ ul.form-list li.field-stack textarea { margin: 0.5em 0 0.5em 0.5em; } + /* PERSON CANVAS / CARDS */ + .co-cards .field-data-container { + display: grid; + grid-template-columns: 1fr 1fr; + align-items: center; + } + #person-canvas-roles .field-data-container { + grid-template-columns: 1fr 1fr 2fr auto; + } /* PAGINATION */ #pagination { text-align: left; diff --git a/app/webroot/js/comanage/comanage.js b/app/webroot/js/comanage/comanage.js index a928fa43..3bfa27fa 100644 --- a/app/webroot/js/comanage/comanage.js +++ b/app/webroot/js/comanage/comanage.js @@ -211,3 +211,74 @@ function clearTopSearch(formObj) { } formObj.submit(); } + +/** + * COmanage Registry API AJAX Calls: general function for making an ajax call to Registry API v.2 + * @param url {string} API Url + * @param method {string} HTTP Method (GET, POST, PUT, DELETE) + * @param dataType {string} Data type (json, html) + * @param data {Object} [POST or PUT data in JSON] + * @param successCallback {string} [Name of the callback function for success] + * @param entityId {string} [ID used to identify an entity in the DOM] + * @param failureCallback {string} [Name of the callback function for failure] + * @param alwaysCallback {string} [Name of the callback function for always] + */ +function callRegistryAPI( + url, + method, + dataType, + successCallback= undefined, + failureCallback= undefined, + data = undefined, + alwaysCallback = undefined, + entityId= undefined +) { + var apiUrl = url; + var httpMethod = method; + var dataType = dataType; + var successCallback = successCallback; + var failureCallback = failureCallback; + var data = data; + var alwaysCallback = alwaysCallback; + var entityId = entityId; + + if(data === undefined) { + data = ''; + } + + if(entityId === undefined) { + entityId = ''; + } + + var xhr = $.ajax({ + url: apiUrl, + method: httpMethod, + dataType: dataType, + headers: { + "Authorization": "Basic " + btoa("co_1.xhruser:vkam-9163-jwjp-4732") + }, + data: data, + encode: true + }) + .done(function() { + if(successCallback != undefined) { + successCallback(xhr, entityId); + } else { + return xhr; + } + }) + .fail(function() { + if(failureCallback != undefined) { + failureCallback(xhr, entityId); + } else { + return xhr; + } + }) + .always(function() { + if(alwaysCallback != undefined) { + alwaysCallback(xhr, entityId); + } else { + return xhr; + } + }); +} diff --git a/app/webroot/js/comanage/components/mvea/mvea-item.js b/app/webroot/js/comanage/components/mvea/mvea-item.js new file mode 100644 index 00000000..053c920e --- /dev/null +++ b/app/webroot/js/comanage/components/mvea/mvea-item.js @@ -0,0 +1,122 @@ +/** + * COmanage Registry MVEA Component JavaScript + * + * Portions licensed to the University Corporation for Advanced Internet + * Development, Inc. ("UCAID") under one or more contributor license agreements. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * UCAID licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @link https://www.internet2.edu/comanage COmanage Project + * @package registry + * @since COmanage Registry v5.0.0 + * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ + +export default { + props: { + mvea: Object, + core: Object, + txt: Object + }, + template: ` + +
  • + +
    + {{ this.txt.primary }} + type: {{ this.mvea.type_id }} +
    +
  • + +
  • + +
    + {{ this.txt.unverified }} + type: {{ this.mvea.type_id }} +
    +
  • + +
  • + +
    + + type: {{ this.mvea.type_id }} +
    +
  • + +
  • + +
    + {{ this.mvea.tag }} +
    +
  • + +
  • +
    +
    + {{ this.mvea.room }} {{ this.mvea.street }} + +
    {{ this.mvea.locality }}{{ this.mvea.locality != '' && this.mvea.state != '' ? ', ' : ''}}{{ this.mvea.state }} +
    + +
    {{ this.mvea.postal_code }} {{ this.mvea.country }} +
    +
    +
    +
    + type: {{ this.mvea.type_id }} +
    +
  • + +
  • + +
    + type: {{ this.mvea.type_id }} +
    +
  • + +
  • + +
    + type: {{ this.mvea.type_id }} +
    +
  • + +
  • + +
    + type: {{ this.mvea.type_id }} +
    +
  • + ` +} \ No newline at end of file diff --git a/app/webroot/js/comanage/components/mvea/mveas.js b/app/webroot/js/comanage/components/mvea/mveas.js new file mode 100644 index 00000000..6e60fc27 --- /dev/null +++ b/app/webroot/js/comanage/components/mvea/mveas.js @@ -0,0 +1,83 @@ +/** + * COmanage Registry MVEA Component JavaScript + * + * Portions licensed to the University Corporation for Advanced Internet + * Development, Inc. ("UCAID") under one or more contributor license agreements. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership. + * + * UCAID licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @link https://www.internet2.edu/comanage COmanage Project + * @package registry + * @since COmanage Registry v5.0.0 + * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + */ + +import MveaItem from './mvea-item.js'; + +export default { + props: { + mveas: Object, + core: Object, + txt: Object + }, + components: { + MveaItem + }, + data() { + return { + mveaTypeLookup: '' + } + }, + computed: { + mveaModel: function() { + switch(this.core.mveaType) { + case 'names': + return this.mveas.Names; + break; + case 'email_addresses': + return this.mveas.EmailAddresses; + break; + case 'identifiers': + return this.mveas.Identifiers; + break; + case 'ad_hoc_attributes': + return this.mveas.AdHocAttributes; + break; + case 'addresses': + return this.mveas.Addresses; + break; + case 'telephone_numbers': + return this.mveas.TelephoneNumbers; + break; + case 'urls': + return this.mveas.Urls; + break; + case 'pronouns': + return this.mveas.Pronouns; + break; + } + } + }, + template: ` +
      + + +
    + ` +} \ No newline at end of file