v1.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. <?php
  2. namespace zin;
  3. require_once dirname(__DIR__) . DS . 'btn' . DS . 'v1.php';
  4. require_once dirname(__DIR__) . DS . 'sidebar' . DS . 'v1.php';
  5. require_once dirname(__DIR__) . DS . 'dropdown' . DS . 'v1.php';
  6. class moduleMenu extends wg
  7. {
  8. /**
  9. * @var mixed[]
  10. */
  11. private static $filterMap = array();
  12. /**
  13. * @var mixed[]
  14. */
  15. protected static $defineProps = array(
  16. 'modules: array',
  17. 'activeKey?: int|string',
  18. 'settingLink?: string',
  19. 'settingApp?: string=""',
  20. 'closeLink: string',
  21. 'showDisplay?: bool=true',
  22. 'allText?: string',
  23. 'title?: string',
  24. 'titleShow?: bool=true',
  25. 'app?: string=""',
  26. 'checkbox?: bool',
  27. 'preserve?: string|bool',
  28. 'tree?: array',
  29. 'checkOnClick?: bool|string',
  30. 'appendSettingItems?: array',
  31. 'onCheck?: function',
  32. 'toggleSidebar?: bool=true',
  33. 'isInModal?: bool=false',
  34. 'onClickItem?: function'
  35. );
  36. /**
  37. * @var mixed[]
  38. */
  39. protected static $defineBlocks = array
  40. (
  41. 'header' => array(),
  42. 'footer' => array()
  43. );
  44. public static function getPageCSS()
  45. {
  46. return <<<'CSS'
  47. .module-menu {max-height: calc(100vh - 79px); display: flex; flex-direction: column; min-height: 32px; --menu-selected-bg: none;}
  48. .module-menu header a:hover > .icon {color: var(--color-primary-600) !important;}
  49. .module-menu .tree-item * {white-space: nowrap;}
  50. .module-menu .tree-item .item-content {color: var(--color-gray-700)}
  51. .module-menu .tree-item > .selected .item-content {color: var(--color-fore)}
  52. .module-menu > .tree.has-nested-items {padding-left: calc(2 * var(--space))}
  53. .has-module-menu-header #mainMenu {padding-left: 180px;}
  54. .module-menu-header.is-fixed {position: absolute; left: 0; top: -44px; width: 160px; height: 32px; border: 1px solid var(--color-border); justify-content: center; padding: 0 24px; border-right: 0;}
  55. .module-menu-header.is-fixed::before,
  56. .module-menu-header.is-fixed::after {content: ''; position: absolute; top: 0; right: -12px; width: 0; height: 0; border-style: solid; border-color: transparent transparent transparent var(--color-border); border-width: 15px 0 15px 12px;}
  57. .module-menu-header.is-fixed::after {right: -11px; border-color: transparent transparent transparent var(--color-canvas);}
  58. .has-module-menu-header.is-sidebar-left-collapsed .module-menu-header.is-fixed {left: var(--gutter-width)}
  59. .module-menu-header.is-fixed .module-title {font-size: var(--font-size-base);}
  60. .module-menu-header.is-fixed > .btn-close {position: absolute; right: 0; font-weight: normal;}
  61. .module-menu-header.is-fixed > .btn-close:not(:hover) {opacity: .5;}
  62. .sidebar > .module-menu-header.is-fixed {display: flex!important;}
  63. .sidebar-left > .module-menu {margin-right: -8px}
  64. .sidebar-left.is-expanded > .module-menu ~ .sidebar-gutter {margin-left: 4px}
  65. .sidebar-right.is-expanded > .module-menu ~ .sidebar-gutter {margin-right: 4px}
  66. .is-expanded > .module-menu ~ .sidebar-gutter > .gutter-toggle {opacity: 0}
  67. .has-module-menu-header .sidebar-left {transition-property: width;}
  68. .has-module-menu-header .module-menu {max-height: calc(100vh - 105px); }
  69. .has-module-menu-header .module-menu > .tree {padding-top: 8px; padding-bottom: 8px;}
  70. CSS;
  71. }
  72. /**
  73. * @var mixed[]
  74. */
  75. private $modules = array();
  76. /**
  77. * @param int|string $parentID
  78. */
  79. private function buildMenuTree($parentID = 0)
  80. {
  81. $children = zget($this->modules, $parentID, []);
  82. if(count($children) === 0) return [];
  83. global $app;
  84. $activeKey = $this->prop('activeKey');
  85. $treeItems = array();
  86. $tab = $this->prop('app') ? $this->prop('app') : $app->tab;
  87. $titleAttrs = array('data-app' => $tab);
  88. if(isInModal()) $titleAttrs['data-load'] = 'modal';
  89. foreach($children as $child)
  90. {
  91. $item = array(
  92. 'key' => $child->id,
  93. 'text' => $child->name,
  94. 'hint' => is_array($child->name) ? (isset($child->name['text']) ? $child->name['text'] : current($child->name)) : $child->name,
  95. 'url' => zget($child, 'url', ''),
  96. 'titleAttrs' => $titleAttrs,
  97. 'contentClass' => 'overflow-x-hidden'
  98. );
  99. if(!empty($child->actions)) $item['actions'] = $child->actions;
  100. $items = $this->buildMenuTree($child->id);
  101. if($items) $item['items'] = $items;
  102. if((string)$child->id === (string)$activeKey || $child->id === 'product-' . $activeKey)
  103. {
  104. $itemKey = $this->prop('checkbox') ? 'checked' : 'selected';
  105. $item[$itemKey] = true;
  106. }
  107. $treeItems[] = $item;
  108. }
  109. return $treeItems;
  110. }
  111. private function setMenuTreeProps()
  112. {
  113. $modules = $this->prop('modules');
  114. if($modules)
  115. {
  116. foreach($modules as $module) $this->modules[$module->parent][] = $module;
  117. }
  118. $this->setProp('items', $this->buildMenuTree());
  119. }
  120. private function getTitle()
  121. {
  122. if($this->prop('title')) return $this->prop('title');
  123. global $lang, $app;
  124. $activeKey = $this->prop('activeKey');
  125. if(empty($activeKey))
  126. {
  127. $allText = $this->prop('allText');
  128. if(empty($allText)) return $lang->all;
  129. return $allText;
  130. }
  131. $modules = $this->prop('modules');
  132. $moduleName = '';
  133. if($modules) array_map(function($module) use(&$moduleName, $activeKey) { if($module->id == $activeKey || $module->id == 'product-' . $activeKey) $moduleName = $module->name; }, $modules);
  134. if(!empty($modules) && empty($moduleName))
  135. {
  136. $module = $app->control->loadModel('tree')->getByID($activeKey);
  137. if($module) $moduleName = $module->name;
  138. }
  139. return $moduleName;
  140. }
  141. /**
  142. * @return \zin\node|mixed[]|null
  143. */
  144. private function buildActions()
  145. {
  146. $settingLink = $this->prop('settingLink');
  147. $showDisplay = $this->prop('showDisplay');
  148. $appendSettingItems = $this->prop('appendSettingItems');
  149. $isInModal = $this->prop('isInModal');
  150. if(!$settingLink && !$showDisplay && !$appendSettingItems) return null;
  151. global $app;
  152. $lang = $app->loadLang('datatable')->datatable;
  153. $items = array();
  154. if($settingLink && common::hasPriv('tree', 'browse'))
  155. {
  156. $tab = $this->prop('settingApp', $app->tab);
  157. $settingText = $this->prop('settingText', $lang->moduleSetting);
  158. if(empty($this->prop('items')))
  159. {
  160. $items[] = btn
  161. (
  162. setClass('m-4 mt-0'),
  163. set::text($settingText),
  164. set::url($settingLink),
  165. set::type('primary-pale'),
  166. setData('app', $tab),
  167. $isInModal ? setData(array('toggle' => 'modal', 'size' => 'md')) : null
  168. );
  169. }
  170. else
  171. {
  172. $items[] = array
  173. (
  174. 'text' => $settingText,
  175. 'url' => $settingLink,
  176. 'data-app' => $tab,
  177. 'data-toggle' => $isInModal ? 'modal' : '',
  178. 'data-size' => $isInModal ? 'md' : ''
  179. );
  180. }
  181. }
  182. if($showDisplay)
  183. {
  184. $datatableId = $app->moduleName . ucfirst($app->methodName);
  185. $currentModule = $app->rawModule;
  186. $currentMethod = $app->rawMethod;
  187. if(empty($this->prop('items')))
  188. {
  189. $items[] = btn
  190. (
  191. setClass('m-4 mt-0'),
  192. set::text($lang->displaySetting),
  193. set::url(createLink('datatable', 'ajaxDisplay', "datatableId=$datatableId&moduleName=$app->moduleName&methodName=$app->methodName&currentModule=$currentModule&currentMethod=$currentMethod")),
  194. set::type('primary-pale'),
  195. setData(array('toggle' => 'modal', 'size' => 'md'))
  196. );
  197. }
  198. else
  199. {
  200. $items[] = array
  201. (
  202. 'text' => $lang->displaySetting,
  203. 'url' => createLink('datatable', 'ajaxDisplay', "datatableId=$datatableId&moduleName=$app->moduleName&methodName=$app->methodName&currentModule=$currentModule&currentMethod=$currentMethod"),
  204. 'data-toggle' => 'modal',
  205. 'data-size' => 'md'
  206. );
  207. }
  208. }
  209. if($appendSettingItems)
  210. {
  211. if(empty($this->prop('items')))
  212. {
  213. foreach($appendSettingItems as $item)
  214. {
  215. $items[] = btn(setClass('m-4 mt-0'), set::type('primary-pale'), set($item));
  216. }
  217. }
  218. else
  219. {
  220. $items = array_merge($items, $appendSettingItems);
  221. }
  222. }
  223. if(empty($items)) return null;
  224. if(empty($this->prop('items'))) return $items;
  225. return new dropdown
  226. (
  227. new btn
  228. (
  229. set::type('ghost'),
  230. set::icon('cog-outline'),
  231. set::size('sm'),
  232. set::caret(false)
  233. ),
  234. set::items($items),
  235. set::placement('top-end')
  236. );
  237. }
  238. private function buildCloseBtn()
  239. {
  240. $closeLink = $this->prop('closeLink');
  241. $tab = $this->prop('app');
  242. $titleAttrs = array();
  243. if($tab) $titleAttrs['app'] = $tab;
  244. if(isInModal()) $titleAttrs['load'] = 'modal';
  245. if(!$closeLink) return null;
  246. $activeKey = $this->prop('activeKey');
  247. if(empty($activeKey)) return null;
  248. return btn
  249. (
  250. setClass('btn-close rounded-full'),
  251. set::icon('close'),
  252. set::url($closeLink),
  253. set::size('sm'),
  254. set::type('ghost'),
  255. $titleAttrs ? setData($titleAttrs) : null
  256. );
  257. }
  258. protected function build()
  259. {
  260. global $app;
  261. $this->setMenuTreeProps();
  262. $title = $this->getTitle();
  263. $userTreeProps = $this->prop('tree');
  264. $treeProps = $this->props->pick(array('items', 'activeClass', 'activeIcon', 'activeKey', 'onClickItem', 'defaultNestedShow', 'changeActiveKey', 'isDropdownMenu', 'checkbox', 'checkOnClick', 'onCheck'));
  265. $preserve = $this->prop('preserve', $app->rawModule . '-' . $app->rawMethod);
  266. $isInSidebar = $this->parent instanceof sidebar;
  267. $titleShow = $this->prop('titleShow');
  268. if(!is_null($this->prop('filterMap'))) static::$filterMap = $this->prop('filterMap');
  269. $header = $titleShow ? h::header
  270. (
  271. setClass('module-menu-header h-10 flex items-center pl-4 flex-none gap-3', $isInSidebar ? 'is-fixed rounded rounded-r-none canvas' : ''),
  272. span
  273. (
  274. setClass('module-title text-lg font-semibold clip'),
  275. $title
  276. ),
  277. $this->buildCloseBtn()
  278. ) : null;
  279. $actions = $this->buildActions();
  280. $hasActionDropdown = $actions && $actions instanceof dropdown;
  281. $hasToggleBtn = $this->prop('toggleSidebar');
  282. return array
  283. (
  284. $isInSidebar ? $header : null,
  285. div
  286. (
  287. setID('moduleMenu'),
  288. setClass('module-menu shadow ring rounded bg-canvas col relative'),
  289. $this->block('header'),
  290. $isInSidebar ? null : $header,
  291. zui::tree
  292. (
  293. set::_tag('menu'),
  294. set::_class('tree tree-lines col flex-auto scrollbar-hover overflow-y-auto overflow-x-hidden pr-2 pl-4'),
  295. set::defaultNestedShow(true),
  296. set::hover(true),
  297. set::lines(true),
  298. set::preserve($preserve),
  299. set::itemActions($this->prop('moduleActions')),
  300. set($treeProps),
  301. set($userTreeProps)
  302. ),
  303. $hasActionDropdown ? null : $actions,
  304. $this->block('footer'),
  305. ($hasActionDropdown || $hasToggleBtn) ? row
  306. (
  307. setClass('justify-end p-1 flex-none'),
  308. $this->prop('createModuleLink') ? btn
  309. (
  310. set::type('ghost'),
  311. set::icon('plus'),
  312. set::size('sm'),
  313. set::caret(false),
  314. set::url($this->prop('createModuleLink')),
  315. set::hint($this->prop('createModuleHint')),
  316. setData(array('toggle' => 'modal', 'size' => 'sm'))
  317. ) : null,
  318. $hasActionDropdown ? $actions : div(),
  319. $hasToggleBtn ? btn
  320. (
  321. set::type('ghost'),
  322. set::size('sm'),
  323. set::icon('menu-arrow-left text-gray'),
  324. set::hint($app->lang->collapse),
  325. on::click()->do('$this.closest(".sidebar").sidebar("toggle");')
  326. ) : null
  327. ) : null,
  328. $isInSidebar && !empty($header) ? on::init()->do('$("#mainContainer").addClass("has-module-menu-header")') : null
  329. ),
  330. );
  331. }
  332. }