id, $quotedTitle)) $quoteIndex = $quoteIndex . (!empty($quoteIndex) ? '、' : '') . $item->index; if(!empty($item->children)) { $childrenResult = $this->getQuotedText($item->children, $quotedTitle, $quoteIndex); if($childrenResult) return $childrenResult; } } return $quoteIndex; } /** * @param mixed[] $items * @param int $parentID */ private function buildMenuTree($items, $parentID = 0) { global $config; if(empty($items)) $items = $this->modules; if(empty($items)) return array(); $activeKey = $this->prop('activeKey'); $toggleNonNodeShow = $this->prop('toggleNonNodeShow'); $hidden = $this->prop('hidden'); $sortTree = $this->prop('sortable') || $this->prop('onSort'); $wizard = $this->prop('wizard'); $hiddenModelType = in_array($wizard->model, $config->thinkwizard->hiddenMenuModel); $parentItems = array(); $questionItems = array(); if($hiddenModelType) $questionItems = array_values($items); foreach($items as $setting) { if(!is_object($setting)) continue; $options = !empty($setting->options) && is_string($setting->options) ? json_decode($setting->options) : array(); $quotedTitle = !empty($options->quoteTitle) ? explode(',', $options->quoteTitle) : null; $quotedText = ''; /* 给引用其他问题的多选题添加标签。Add tags to multiple-choice questions that reference other questions. */ if($quotedTitle) { $quotedIndex = $this->getQuotedText($this->modules, $quotedTitle); $quotedText = sprintf($this->lang->thinkstep->treeLabel, $quotedIndex); } $itemQuestionIndex = 0; foreach ($questionItems as $index => $questionItem) { if($questionItem->id == $setting->id) $itemQuestionIndex = $index; } if(!empty($itemQuestionIndex) && $hiddenModelType) $preStep = $questionItems[$itemQuestionIndex - 1]; $canView = common::hasPriv('thinkstep', 'view'); $unClickable = $toggleNonNodeShow && $setting->id != $activeKey && $setting->type != 'node' && json_decode($setting->answer) == null; $preStepAnswer = !empty($preStep) && !empty($preStep->answer) ? json_decode($preStep->answer, true) : array(); $preStepResult = !empty($preStepAnswer) ? $preStepAnswer['result'] : array(); $preHasAnswer = (!empty($preStep) && $preStep->type == 'transition') ? $preStepAnswer : $preStepResult; $hiddenStep = $unClickable || (!empty($preStep) && empty($preHasAnswer)); $item = array( 'key' => $setting->id, 'text' => (isset($setting->index) ? ($setting->index . '. ') : '') . $setting->title, 'subtitle' => (!empty($quotedText) && !in_array($wizard->model, $config->thinkwizard->hiddenMenuModel) && $this->prop('showAction')) ? array('html' => "$quotedText") : null, 'hint' => $unClickable ? $this->lang->thinkrun->error->unanswered :$setting->title, 'url' => $unClickable || !$canView ? '' : $setting->url, 'data-id' => $setting->id, 'data-type' => $setting->type, 'data-parent' => $setting->parent, 'data-order' => $setting->order, 'data-level' => $setting->grade, 'selected' => $setting->id == $activeKey, 'disabled' => $unClickable, 'actions' => $this->prop('showAction') ? $this->getActions($setting) : null, 'data-wizard' => $setting->wizard, 'class' => $hiddenStep && $hidden ? 'hidden' : '' ); if($sortTree) $item['trailingIcon'] = 'move muted cursor-move'; $children = zget($setting, 'children', array()); if(!empty($children)) { $children = $this->buildMenuTree($children, $setting->id); $item['items'] = $children; } $parentItems[] = $item; } return $parentItems; } private function setMenuTreeProps() { global $app, $lang; $this->lang = $lang; $this->modules = $this->prop('modules'); $app->loadLang('thinkstep'); $untitledLangs = array('transition' => $lang->thinkstep->untitled . $lang->thinkstep->transition, 'question' => $lang->thinkstep->untitled . $lang->thinkstep->question); jsVar('untitledLangs', $untitledLangs); $this->setProp('items', $this->buildMenuTree(array(), 0)); } private function getActions($item) { $canCreate = common::hasPriv('thinkstep', 'create'); $canEdit = common::hasPriv('thinkstep', 'edit'); $canDelete = common::hasPriv('thinkstep', 'delete'); if(!$canCreate && !$canEdit && !$canDelete) return array(); $actions = array(); $moreBtn = $this->getOperateItems($item); $actions[] = array( 'key' => 'more', 'icon' => 'ellipsis-v', 'type' => 'dropdown', 'caret' => false, 'dropdown' => array( 'placement' => 'bottom-start', 'items' => $moreBtn ) ); return $actions; } private function getOperateItems($item) { global $config; $wizard = $this->prop('wizard'); $canAddChild = true; $showQuestionOfNode = true; $hiddenModelType = in_array($wizard->model, $config->thinkwizard->hiddenMenuModel); $previewCanActions = !$hiddenModelType || ($hiddenModelType && $item->type == 'transition'); $from = ''; if($hiddenModelType) $from = strtolower($wizard->type); if(!empty($item->children)) { foreach($item->children as $child) { if($canAddChild && $child->type == 'question') $canAddChild = false; if($showQuestionOfNode && $child->type == 'node') $showQuestionOfNode = false; } } if($hiddenModelType) $canAddChild = false; $canCreate = common::hasPriv('thinkstep', 'create'); $canEdit = common::hasPriv('thinkstep', 'edit'); $canDelete = common::hasPriv('thinkstep', 'delete'); $canLink = common::hasPriv('thinkstep', 'link'); $parentID = $item->type != 'node' ? $item->parent : $item->id; $confirmTips = empty($item->link) ? $this->lang->thinkstep->deleteTips[$item->type] : array('message' => $this->lang->thinkstep->tips->deleteLinkStep, 'icon' => 'icon-exclamation-sign', 'iconClass' => 'warning-pale rounded-full icon-2x', 'size' => 'sm'); $menus = array(); $transitionAction = array(); if($canCreate) { if($item->type == 'node' && !$hiddenModelType) $menus[] = array( 'key' => 'addNode', 'icon' => 'add-chapter', 'text' => $this->lang->thinkstep->actions['sameNode'], 'onClick' => jsRaw("() => addNode({$item->id}, 'same')") ); if($item->grade != 3 && $item->type == 'node' && $canAddChild) $menus[] = array( 'key' => 'addNode', 'icon' => 'add-sub-chapter', 'text' => $this->lang->thinkstep->actions['childNode'], 'onClick' => jsRaw("() => addNode({$item->id}, 'child')") ); $transitionAction[] = $previewCanActions ? array('type' => 'divider') : null; $transitionAction[] = array( 'key' => 'transition', 'icon' => 'transition', 'text' => $this->lang->thinkstep->createStep . $this->lang->thinkstep->actions['transition'], 'onClick' => jsRaw("() => addQuestion({$item->id}, {$parentID}, 'transition')"), ); } $marketID = $this->prop('marketID'); $itemHasQuoted = empty($item->hasQuoted) || $item->hasQuoted == 0; $deleteItem = (!$item->existNotNode && $itemHasQuoted) ? array( 'key' => 'deleteNode', 'icon' => 'trash', 'text' => $this->lang->thinkstep->actions['delete'], 'innerClass' => 'ajax-submit', 'data-url' => createLink('thinkstep', 'delete', "marketID={$marketID}&stepID={$item->id}&from={$from}"), 'data-confirm' => $confirmTips, ) : array( 'key' => 'deleteNode', 'icon' => 'trash', 'text' => $this->lang->thinkstep->actions['delete'], 'innerClass' => 'text-gray opacity-50', 'hint' => $item->existNotNode ? $this->lang->thinkstep->cannotDeleteNode : $this->lang->thinkstep->cannotDeleteQuestion, ); $linkItem = ($canLink && $item->type === 'question') ? array( 'key' => 'linkNode', 'icon' => 'link', 'text' => $this->lang->thinkstep->actions['link'], 'data-url' => createLink('thinkstep', 'link', "marketID={$marketID}&stepID={$item->id}"), 'data-toggle' => 'modal', 'data-dismiss' => 'modal', 'data-size' => 'sm' ) : array( 'key' => 'linkNode', 'icon' => 'link', 'text' => $this->lang->thinkstep->actions['link'], 'innerClass' => 'text-gray opacity-50', 'hint' => $this->lang->thinkstep->tips->linkBlocks ); $menus = array_merge($menus, array( ($canEdit && $previewCanActions) ? array( 'key' => 'editNode', 'icon' => 'edit', 'text' => $this->lang->thinkstep->actions['edit'], 'url' => createLink('thinkstep', 'edit', "marketID={$marketID}&stepID={$item->id}&from={$from}") ) : null, ($canDelete && $previewCanActions) ? $deleteItem : null, in_array($wizard->model, $config->thinkwizard->venn) && $item->type == 'question' && $canLink ? $linkItem : null ), $transitionAction); $canAddQuestions = ($showQuestionOfNode && $item->type == 'node') || $item->hasSameQuestion || $item->type == 'question'; if($canCreate && !$hiddenModelType && $canAddQuestions) $menus = array_merge($menus, array( array('type' => 'divider'), $this->buildMenuItem('radio', 'radio', $this->lang->thinkstep->createStep . $this->lang->thinkstep->actions['radio'], $item, $parentID, 'radio'), $this->buildMenuItem('checkbox', 'checkbox', $this->lang->thinkstep->createStep . $this->lang->thinkstep->actions['checkbox'], $item, $parentID, 'checkbox'), $this->buildMenuItem('input', 'input', $this->lang->thinkstep->createStep . $this->lang->thinkstep->actions['input'], $item, $parentID, 'input'), $this->buildMenuItem('tableInput', 'multi-input', $this->lang->thinkstep->createStep . $this->lang->thinkstep->actions['tableInput'], $item, $parentID, 'tableInput'), $this->buildMenuItem('multicolumn', 'multi-input', $this->lang->thinkstep->createStep . $this->lang->thinkstep->actions['multicolumn'], $item, $parentID, 'multicolumn'), )); return $menus; } /** * @param object $item * @param string $key * @param string $icon * @param string $text * @param int $parentID */ private function buildMenuItem($key, $icon, $text, $item, $parentID) { return array( 'key' => $key, 'icon' => $icon, 'text' => $text, 'onClick' => jsRaw("() => addQuestion({$item->id}, {$parentID}, 'question', '{$key}')"), ); } private function buildActions() { return btn(set::type('ghost'), setClass('text-gray absolute top-2 right-3 z-10 toggle-btn'), set::icon('fold-all'), on::click('toggleQuestionShow')); } protected function build() { $this->setMenuTreeProps(); $treeProps = $this->props->pick(array('items', 'activeClass', 'activeIcon', 'activeKey', 'onClickItem', 'defaultNestedShow', 'changeActiveKey', 'isDropdownMenu', 'checkbox', 'checkOnClick', 'onCheck', 'sortable', 'onSort')); $isInSidebar = $this->parent instanceof sidebar; $treeType = (!empty($treeProps['onSort']) || !empty($treeProps['sortable'])) ? 'sortableTree' : 'tree'; list($marketID, $from) = $this->prop(array('marketID', 'from')); return array ( div ( setClass('think-node-menu rounded bg-white col bg-canvas pb-3 h-full no-morph'), setData(array('marketID' => $marketID, 'from' => $from ?? '')), zui::$treeType ( set::_id('thinkNodeMenu'), setClass('pl-4'), set::_tag('menu'), set::defaultNestedShow(true), set::hover(true), set::toggleIcons(array('expanded' => 'caret-down cursor-pointer', 'collapsed' => 'caret-right cursor-pointer')), set::className('tree-lines bg-canvas col flex-auto scrollbar-hover scrollbar-thin overflow-y-auto overflow-x-hidden'), set($treeProps) ), $isInSidebar ? array ( $this->buildActions(), row ( setClass('w-full h-10 justify-end p-1 absolute bottom-0 right-0 pr-4 z-10 bg-canvas'), btn ( set::type('ghost'), set::size('sm'), set::icon('menu-arrow-left text-gray'), set::hint($this->lang->collapse), on::click('hideSidebar') ) ), h::js("$('#mainContainer').addClass('has-sidebar');$('#mainContainer .sidebar').addClass('relative');") ) : null ), ); } }