diff --git a/css/backoffice/pages/_all.scss b/css/backoffice/pages/_all.scss index 90ffc7679..3a77ca2da 100644 --- a/css/backoffice/pages/_all.scss +++ b/css/backoffice/pages/_all.scss @@ -8,4 +8,5 @@ @import "attachments"; @import "impact-analysis"; @import "audit"; -@import "data-synchro"; \ No newline at end of file +@import "data-synchro"; +@import "datamodel-viewer"; \ No newline at end of file diff --git a/css/backoffice/pages/_datamodel-viewer.scss b/css/backoffice/pages/_datamodel-viewer.scss new file mode 100644 index 000000000..1ddb75c0c --- /dev/null +++ b/css/backoffice/pages/_datamodel-viewer.scss @@ -0,0 +1,124 @@ +$ibo-datamodel-viewer--parent--spacer--padding-y: 0 !default; +$ibo-datamodel-viewer--parent--spacer--padding-x: 8px !default; + +$ibo-datamodel-viewer--attributes-table--first-column--width: 3px !default; + +$ibo-datamodel-viewer--origin-cell--diameter: 8px !default; +$ibo-datamodel-viewer--origin-cell--border-radius: $ibo-border-radius-full !default; + +$ibo-datamodel-viewer--classes-list--height: 100% !default; +$ibo-datamodel-viewer--classes-list--max-width: 350px !default; +$ibo-datamodel-viewer--classes-list--padding-left: 24px !default; + +$ibo-datamodel-viewer--lifecycle--code--color: $ibo-color-grey-700 !default; +$ibo-datamodel-viewer--lifecycle--stimuli--color: $ibo-color-blue-900 !default; +$ibo-datamodel-viewer--lifecycle--attribute-option--color: $ibo-color-pink-900 !default; + +$ibo-datamodel-viewer--schema--rectangle--hover--fill: $ibo-color-grey-400 !default; +$ibo-datamodel-viewer--schema--text--fill: $ibo-color-grey-900 !default; +$ibo-datamodel-viewer--schema--self-referencing--hover--fill: $ibo-datamodel-viewer--schema--rectangle--hover--fill !default; +$ibo-datamodel-viewer--schema--tooltip--fill: $ibo-color-white-100 !default; +$ibo-datamodel-viewer--schema--tooltip--background-color: $ibo-color-grey-900 !default; +$ibo-datamodel-viewer--schema--tooltip--border-color: $ibo-color-grey-700 !default; +$ibo-datamodel-viewer--schema--tooltip--border-radius: $ibo-border-radius-300 !default; + +$ibo-datamodel-viewer--schema--tooltip--icon--font-size: $ibo-font-size-100 !default; +$ibo-datamodel-viewer--schema--tooltip--span--margin: 3px !default; + +$ibo-datamodel-viewer--schema--tooltip-top--border-color: $ibo-color-grey-700 !default; +$ibo-datamodel-viewer--schema--tooltip-top--padding: 3px !default; + + +#ibo-datamodel-viewer{ + display: flex; + flex-direction: row; +} + +.ibo-datamodel-viewer--details{ + flex-grow: 1; + .ibo-panel--title .ibo-panel--title-title .ibo-panel--title-title-subtitle{ + @extend %ibo-font-ral-nor-150; + } +} + +.ibo-datamodel-viewer--parent--spacer{ + padding: $ibo-datamodel-viewer--parent--spacer--padding-y $ibo-datamodel-viewer--parent--spacer--padding-x; +} + +#ibo-datamodel-viewer--attributes-table{ + > tbody tr td:first-child{ + width: $ibo-datamodel-viewer--attributes-table--first-column--width; + } +} + +.ibo-datamodel-viewer--origin-cell{ + vertical-align: middle; + > div { + height: $ibo-datamodel-viewer--origin-cell--diameter; + width: $ibo-datamodel-viewer--origin-cell--diameter; + border-radius: $ibo-datamodel-viewer--origin-cell--border-radius; + } +} + +.ibo-datamodel-viewer--classes-list{ + position: relative; + height: $ibo-datamodel-viewer--classes-list--height; + max-width: $ibo-datamodel-viewer--classes-list--max-width; + padding-left: $ibo-datamodel-viewer--classes-list--padding-left; + overflow-y: scroll; +} + +.ibo-datamodel-viewer--lifecycle--code{ + color: $ibo-datamodel-viewer--lifecycle--code--color; +} + +.ibo-datamodel-viewer--lifecycle--stimuli{ + color: $ibo-datamodel-viewer--lifecycle--stimuli--color; +} + +.ibo-datamodel-viewer--lifecycle--attribute-option{ + color: $ibo-datamodel-viewer--lifecycle--attribute-option--color; +} + + +.dataModelSchema g { + cursor: pointer; +} + +.dataModelSchema g:hover rect:not(.liseret) { + fill: $ibo-datamodel-viewer--schema--rectangle--hover--fill; +} + +.dataModelSchema text { + fill: $ibo-datamodel-viewer--schema--text--fill; + text-anchor: middle; + @extend %ibo-font-ral-nor-100; +} + +#selfreferencing:hover ~ g > .selfattr { + fill: $ibo-datamodel-viewer--schema--self-referencing--hover--fill; +} + +.tooltipD3 { + position: fixed; + text-align: center; + background: $ibo-datamodel-viewer--schema--tooltip--fill; + border: 1px solid $ibo-datamodel-viewer--schema--tooltip--border-color; + border-radius: $ibo-datamodel-viewer--schema--tooltip--border-radius; + pointer-events: none; + fill: $ibo-datamodel-viewer--schema--tooltip--background-color; + @extend %ibo-font-ral-nor-100; + text-anchor: middle; + i { + font-size: $ibo-datamodel-viewer--schema--tooltip--icon--font-size; + } + span { + margin: $ibo-datamodel-viewer--schema--tooltip--span--margin; + } +} + +#tooltipD3_top { + @extend %ibo-font-ral-bol-100; + border-bottom: 1px solid $ibo-datamodel-viewer--schema--tooltip-top--border-color; + padding: $ibo-datamodel-viewer--schema--tooltip-top--padding; +} diff --git a/pages/schema.php b/pages/schema.php index 2c4947d75..5bcd6d447 100644 --- a/pages/schema.php +++ b/pages/schema.php @@ -17,6 +17,14 @@ * You should have received a copy of the GNU Affero General Public License */ +use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory; +use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory; +use Combodo\iTop\Application\UI\Base\Component\Input\Select\Select; +use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockFactory; +use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory; +use Combodo\iTop\Application\UI\Base\Component\Title\Title; +use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentWithSideContent; + require_once('../approot.inc.php'); require_once(APPROOT.'/application/application.inc.php'); @@ -35,16 +43,6 @@ function MakeClassHLink($sClass, $sContext) 'UTF-8')."\">".MetaModel::GetName($sClass)." (".$sClass.")"; } -/** - * Helper for this page -> link to a class - */ -function MakeRelationHLink($sRelCode, $sContext) -{ - $sDesc = MetaModel::GetRelationDescription($sRelCode); - - return "".$sRelCode.""; -} - /** * Helper for the global list and the details of a given class */ @@ -78,6 +76,48 @@ function DisplaySubclasses($oPage, $sClass, $sContext) $oPage->add("\n"); } } +/** + * Helper for the global list and the details of a given class + */ +function GetSubclasses($sClass, $sContext) +{ + + $sHtml = ''; + try{ + $aChildClasses = MetaModel::EnumChildClasses($sClass); + if (count($aChildClasses) != 0) + { + + $sHtml .= ""; + } + } + + catch(Exception $e){ + } + return $sHtml; +} /** * Helper for the lifecycle details of a given class @@ -100,27 +140,31 @@ function DisplayLifecycle($oPage, $sClass) EOF ); - $oPage->add(" "); - $oPage->add_ready_script( - <<SetOnClickJsCode( + <<SetOnClickJsCode( + <<AddUiBlock($oOpenAllButton); + $oPage->AddUiBlock($oCloseAllButton); $oPage->add("

".Dict::S('UI:Schema:LifeCycleTransitions')."

\n"); $oPage->add("
    \n"); foreach ($aStates as $sStateCode => $aStateDef) { $sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode); $sStateDescription = MetaModel::GetStateDescription($sClass, $sStateCode); - $oPage->add("
  • $sStateLabel ($sStateCode) $sStateDescription\n"); + $oPage->add("
  • $sStateLabel ($sStateCode) $sStateDescription\n"); $oPage->add("
      \n"); foreach (MetaModel::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef) { @@ -153,10 +197,10 @@ EOF $sActions = ""; } - $oPage->add("
    • $sStimulusLabel - ($sStimulusCode) - => - $sTargetStateLabel ( $sTargetState ) $sActions
    • \n"); + $oPage->add("
    • $sStimulusLabel + ($sStimulusCode) + + $sTargetStateLabel ( $sTargetState ) $sActions
    • \n"); } $oPage->add("
  • \n"); } @@ -167,7 +211,7 @@ EOF { $sStateLabel = MetaModel::GetStateLabel($sClass, $sStateCode); $sStateDescription = MetaModel::GetStateDescription($sClass, $sStateCode); - $oPage->add("
  • $sStateLabel ($sStateCode) $sStateDescription\n"); + $oPage->add("
  • $sStateLabel ($sStateCode) $sStateDescription\n"); if (count($aStates[$sStateCode]['attribute_list']) > 0) { $oPage->add("
      \n"); @@ -206,7 +250,7 @@ EOF $sOptions = ""; } - $oPage->add("
    • $sAttLabel $sOptions
    • \n"); + $oPage->add("
    • $sAttLabel $sOptions
    • \n"); } $oPage->add("
  • \n"); } @@ -236,67 +280,22 @@ function DisplayTriggers($oPage, $sClass) /** * Display the list of classes from the business model */ -function DisplayClassesList($oPage, $sContext) +function DisplayClassesList($oPage, $oLayout, $sContext) { - $oPage->add("

    ".Dict::S('UI:Schema:Title')."

    \n"); - $oPage->add("
    "); - $oPage->add("
    "); - $oPage->add("
      \n"); - $oPage->add_ready_script( - <<]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "$1"); - return $( "
    • " ).append( val ).appendTo( ul ); - }; - - $("#search-model").on('input', function() { - var search_result = []; - $("#ClassesList").find("li").each(function(){ - if( ! ~$(this).children("a").text().toLowerCase().indexOf($("#search-model").val().toLowerCase())){ - $(this).hide(); - } - else{ - search_result.push($(this)); - } - }); - search_result.forEach(function(e){ - e.show(); - e.find('ul > li').show(); - e.parents().show(); - }); - }); - $("#delDataModelSearch").on ('click', function(){ - $("#search-model").val(""); - $("#search-model").trigger('input'); - }); -JS - - ); + $oLayout->AddSideHtml("
      "); + + $oListSearch = new Select("ibo-datamodel-viewer--class-search"); + $oListSearch->SetName('aa'); // Get all the "root" classes for display $aRootClasses = array(); $aClassLabelAndCodeAsJSON = []; $aClassLabelAsJSON = array(); $aClassCodeAsJSON = array(); + + $oOptionSearch = SelectOptionUIBlockFactory::MakeForSelectOption('', "select option", true); + $oListSearch->AddOption($oOptionSearch->SetDisabled(true)); + foreach (MetaModel::GetClasses() as $sClassName) { if (MetaModel::IsRootClass($sClassName)) @@ -309,6 +308,8 @@ JS } $sLabelClassName = MetaModel::GetName($sClassName); + $oOptionSearch = SelectOptionUIBlockFactory::MakeForSelectOption($sClassName, "$sLabelClassName ($sClassName)", false); + $oListSearch->AddOption($oOptionSearch); //Fetch classes names for autocomplete purpose // - Encode as JSON to escape quotes and other characters array_push ($aClassLabelAndCodeAsJSON, ["value"=>$sClassName,"label"=>"$sLabelClassName ($sClassName)"]); @@ -316,10 +317,35 @@ JS array_push ($aClassCodeAsJSON, ["value"=>$sClassName,"label"=>"$sClassName"]); } usort($aClassLabelAndCodeAsJSON, "Label_sort"); - // - Push to autocomplete - $oPage->add_script("autocompleteClassLabelAndCode=".json_encode($aClassLabelAndCodeAsJSON).";"); - $oPage->add_script("autocompleteClassLabel=".json_encode($aClassLabelAsJSON).";"); - $oPage->add_script("autocompleteClassCode=".json_encode($aClassCodeAsJSON).";"); + $oLayout->AddSideBlock($oListSearch); + $oPage->add_ready_script( + <<AddSideHtml("
        \n"); + $oPage->add_ready_script( + <<add("
      • ".MakeClassHLink($sClassName, $sContext)."\n"); - DisplaySubclasses($oPage, $sClassName, $sContext); - $oPage->add("
      • \n"); + $oLayout->AddSideHtml("
      • ".MakeClassHLink($sClassName, $sContext)."\n"); + $oLayout->AddSideHtml(GetSubclasses($sClassName, $sContext)); + $oLayout->AddSideHtml("
      • \n"); } elseif (MetaModel::IsStandaloneClass($sClassName)) { - $oPage->add("
      • ".MakeClassHLink($sClassName, $sContext)."
      • \n"); + $oLayout->AddSideHtml("
      • ".MakeClassHLink($sClassName, $sContext)."
      • \n"); } } - $oPage->add("
      \n"); - $oPage->add_ready_script('$("#ClassesList").treeview();'); + $oLayout->AddSideHtml("
    \n"); + $oPage->add_ready_script('$("#ibo-datamodel-viewer--classes-list--list").treeview();'); } function Label_sort($building_a, $building_b) { @@ -815,7 +841,6 @@ JS */ function DisplayClassDetails($oPage, $sClass, $sContext) { - DisplayClassHeader($oPage, $sClass); $aParentClasses = array(); foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass) { @@ -823,27 +848,25 @@ function DisplayClassDetails($oPage, $sClass, $sContext) } if (count($aParentClasses) > 0) { - $sParents = implode(' >> ', $aParentClasses)." >> $sClass"; + $sParents = implode(' ', $aParentClasses).' '. MetaModel::GetName($sClass) . '('.$sClass.')'; } else { $sParents = ''; } - $oPage->p("[".Dict::S('UI:Schema:AllClasses')."] $sParents"); + $sClassHierarchy = ("[".Dict::S('UI:Schema:AllClasses')."] $sParents"); - if (MetaModel::HasChildrenClasses($sClass)) + $oEnchancedPanel = PanelUIBlockFactory::MakeEnhancedNeutral(MetaModel::GetName($sClass) . ' ('.$sClass.')' , MetaModel::GetClassIcon($sClass,false)); + $sClassDescritpion = MetaModel::GetClassDescription($sClass); + $oEnchancedPanelSubtitle = $oEnchancedPanel->GetSubTitleBlock(); + $oEnchancedPanelSubtitle->AddHtml($sClassHierarchy . ($sClassDescritpion == "" ? "" : ' - ' . $sClassDescritpion)); + if (MetaModel::IsAbstract($sClass)) { - $oPage->add("
      "); - $oPage->add("
    • ".$sClass."\n"); - DisplaySubclasses($oPage, $sClass, $sContext); - $oPage->add("
    • \n"); - $oPage->add("
    \n"); - $oPage->add_ready_script('$("#ClassHierarchy").treeview();'); + $oEnchancedPanelSubtitle->AddHtml(' - '); } - $oPage->p(''); - $oPage->add(""); - $oPage->AddTabContainer('details'); + $oPage->AddUiBlock($oEnchancedPanel); + $oPage->AddTabContainer('details', '', $oEnchancedPanel); $oPage->SetCurrentTabContainer('details'); // List the attributes of the object $aForwardChangeTracking = MetaModel::GetTrackForwardExternalKeys($sClass); @@ -944,7 +967,7 @@ function DisplayClassDetails($oPage, $sClass, $sContext) $aDetails[] = array( 'code' => ''.$oAttDef->GetLabel().' ('.$oAttDef->GetCode().')', 'type' => ''.$sTypeDict.' ('.$sType.')', - 'origincolor' => '', + 'origincolor' => '
    ', 'origin' => "$sOrigin", 'values' => $sAllowedValues, 'moreinfo' => ''.$sMoreInfo.'', @@ -960,7 +983,17 @@ function DisplayClassDetails($oPage, $sClass, $sContext) 'moreinfo' => array('label' => Dict::S('UI:Schema:MoreInfo'), 'description' => Dict::S('UI:Schema:MoreInfo+')), 'origin' => array('label' => Dict::S('UI:Schema:Origin'), 'description' => Dict::S('UI:Schema:Origin+')), ); - $oPage->table($aConfig, $aDetails); + + $oAttributesPanel = DataTableUIBlockFactory::MakeForStaticData('', $aConfig, $aDetails, 'ibo-datamodel-viewer--attributes-table'); + $sTableId = $oAttributesPanel->GetId(); + $oPage->AddUiBlock($oAttributesPanel); + $oPage->add_script( + <<SetCurrentTab('UI:Schema:RelatedClasses'); DisplayRelatedClassesGraph($oPage, $sClass); - $oPage->SetCurrentTab('UI:Schema:ChildClasses'); - - DisplaySubclasses($oPage, $sClass, $sContext); - + + if (MetaModel::HasChildrenClasses($sClass)) + { + $oPage->SetCurrentTab('UI:Schema:ChildClasses'); + $oPage->add("
      "); + $oPage->add("
    • ".$sClass."\n"); + DisplaySubclasses($oPage, $sClass, $sContext); + $oPage->add("
    • \n"); + $oPage->add("
    \n"); + $oPage->add_ready_script('$("#ClassHierarchy").treeview({collapsed: false,});'); + } + $oPage->SetCurrentTab('UI:Schema:LifeCycle'); DisplayLifecycle($oPage, $sClass); @@ -998,102 +1039,13 @@ EOF } -/** - * Display the dropdown that allow to select the attributes/class display granularity - */ -function DisplayGranularityDisplayer($oPage) -{ - - $oPage->add(" - -
    "); - $sDisplayDropDownValue = htmlentities(appUserPreferences::GetPref('datamodel_viewer_display_granularity', 'labelandcode'), ENT_QUOTES, - "UTF-8"); - -//granularity displayer listener - $oPage->add_ready_script( - <<add("
    "); - $oPage->add("
    ".MetaModel::GetClassIcon($sClass)."
    "); - $oPage->add("

    ".MetaModel::GetName($sClass)." (".$sClass.")

    "); - $oPage->add("
    "); - - //content header - $oPage->add("
    "); - DisplayGranularityDisplayer($oPage); - $oPage->add("
    ".MetaModel::GetClassIcon($sClass)."
    "); - $sClassDescritpion = MetaModel::GetClassDescription($sClass); - - $oPage->add("

    ".MetaModel::GetName($sClass)." (".$sClass.")".($sClassDescritpion == "" ? "" : " - ".$sClassDescritpion)."

    \n"); - if (MetaModel::IsAbstract($sClass)) - { - $oPage->p(Dict::S('UI:Schema:AbstractClass')); - } - //ajuste the size of title - $oPage->add_ready_script("$('#dataModelScrollableDiv').width($('#dataModelScrollableDiv').parent().width());"); - -} - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // MAIN BLOCK // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Display the menu on the left + + $oAppContext = new ApplicationContext(); $sContext = $oAppContext->GetForLink(); if (!empty($sContext)) @@ -1102,24 +1054,23 @@ if (!empty($sContext)) } $operation = utils::ReadParam('operation', ''); +$oLayout = new PageContentWithSideContent(); +$oLayout->AddCSSClass('ibo-datamodel-viewer--side-pane'); $oPage = new iTopWebPage(Dict::S('UI:Schema:Title')); +$oPage->SetContentLayout($oLayout); + $oPage->no_cache(); $oPage->SetBreadCrumbEntry('ui-tool-datamodel', Dict::S('Menu:DataModelMenu'), Dict::S('Menu:DataModelMenu+'), '', 'fas fa-book', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); -$oPage->add_script( - <<add("
    "); -$oPage->add("
    "); -DisplayClassesList($oPage, $sContext); -$oPage->add("
    "); -$oPage->add("
    "); +$oTitle = new Title(Dict::S('UI:Schema:Title')); +$oPage->AddUiBlock($oTitle); +$oLayout->AddSideHtml("
    "); +DisplayClassesList($oPage, $oLayout, $sContext); +$oLayout->AddSideHtml("
    "); +$oPage->add("
    "); +$oPage->add("
    "); switch ($operation) { @@ -1128,53 +1079,12 @@ switch ($operation) //if we want to see class details & class is given then display it, otherwise act default (just show the class list) if ($sClass != '') { - $oPage->add_ready_script( - << li').show(); - e.parents().show(); -}); -//$('#search-model').trigger("input"); - -JS - ); DisplayClassDetails($oPage, $sClass, $sContext); - break; } default: - DisplayGranularityDisplayer($oPage); } $oPage->add("
    "); $oPage->add("
    "); -// TODO 3.0.0 Replace these layout.js call -////split the page in 2 panels -//$oPage->add_init_script( -// <<output(); -?>