* zin: update zin lib.
This commit is contained in:
@@ -135,7 +135,7 @@ class context extends \zin\utils\dataset
|
||||
*/
|
||||
public static function current(): context
|
||||
{
|
||||
if(empty(static::$map)) static::$map['current'] = new context(null);
|
||||
if(empty(static::$map)) static::$map['current'] = new context();
|
||||
return static::$map['current'];
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,10 @@ function getPageData($name)
|
||||
return zin::getData($name);
|
||||
}
|
||||
|
||||
function data(...$args)
|
||||
function data()
|
||||
{
|
||||
$args = func_get_args();
|
||||
|
||||
if(count($args) >= 2) return setPageData($args[0], $args[1]);
|
||||
return getPageData($args[0]);
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ class dom
|
||||
|
||||
if($item instanceof dom)
|
||||
{
|
||||
$json = $item->wg->toJsonData();
|
||||
$json = $item->wg->toJSON();
|
||||
if(!empty($item->dataGetters))
|
||||
{
|
||||
$output = array();
|
||||
|
||||
+41
-23
@@ -121,8 +121,11 @@ class h extends wg
|
||||
return static::create('input', set('type', 'text'), func_get_args());
|
||||
}
|
||||
|
||||
public static function formHidden($name, $value, ...$args)
|
||||
public static function formHidden(/* $name, $value, ...$args */)
|
||||
{
|
||||
$args = func_get_args();
|
||||
$name = array_shift($args);
|
||||
$value = array_shift($args);
|
||||
return static::create('input', set('type', 'hidden'), set::name($name), set::value($value), $args);
|
||||
}
|
||||
|
||||
@@ -146,10 +149,11 @@ class h extends wg
|
||||
return static::create('input', set('type', 'file'), func_get_args());
|
||||
}
|
||||
|
||||
public static function textarea(...$args)
|
||||
public static function textarea(/* ...$args */)
|
||||
{
|
||||
$args = func_get_args();
|
||||
list($code, $args) = h::splitRawCode($args);
|
||||
return static::create('textarea', $code, ...$args);
|
||||
return static::create('textarea', $code, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,18 +168,26 @@ class h extends wg
|
||||
return html("<!-- $comment -->");
|
||||
}
|
||||
|
||||
public static function importJs($src, ...$args)
|
||||
public static function importJs(/* $src, ...$args */)
|
||||
{
|
||||
return static::create('script', set('src', $src), ...$args);
|
||||
$args = func_get_args();
|
||||
$src = array_shift($args);
|
||||
return static::create('script', set('src', $src), $args);
|
||||
}
|
||||
|
||||
public static function importCss($src, ...$args)
|
||||
public static function importCss(/* $src, ...$args */)
|
||||
{
|
||||
return static::create('link', set('rel', 'stylesheet'), set('href', $src), ...$args);
|
||||
$args = func_get_args();
|
||||
$src = array_shift($args);
|
||||
return static::create('link', set('rel', 'stylesheet'), set('href', $src), $args);
|
||||
}
|
||||
|
||||
public static function import($file, $type = null, ...$args)
|
||||
public static function import(/* $file, $type = null, ...$args */)
|
||||
{
|
||||
$args = array_merge(func_get_args(), array(null, null));
|
||||
$file = array_shift($args);
|
||||
$type = array_shift($args);
|
||||
|
||||
if(is_array($file))
|
||||
{
|
||||
$children = array();
|
||||
@@ -186,40 +198,46 @@ class h extends wg
|
||||
return $children;
|
||||
}
|
||||
if($type === null) $type = pathinfo($file, PATHINFO_EXTENSION);
|
||||
if($type == 'js' || $type == 'cjs') return static::importJs($file, ...$args);
|
||||
if($type == 'css') return static::importCss($file, ...$args);
|
||||
if($type == 'js' || $type == 'cjs') return static::importJs($file, $args);
|
||||
if($type == 'css') return static::importCss($file, $args);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function css(...$args)
|
||||
public static function css(/* ...$args */)
|
||||
{
|
||||
list($code, $args) = h::splitRawCode($args);
|
||||
list($code, $args) = h::splitRawCode(func_get_args());
|
||||
if(empty($code)) return null;
|
||||
return static::create('style', html(implode("\n", $code)), ...$args);
|
||||
return static::create('style', html(implode("\n", $code)), $args);
|
||||
}
|
||||
|
||||
public static function globalJS(...$args)
|
||||
public static function globalJS(/* ...$args */)
|
||||
{
|
||||
list($code, $args) = h::splitRawCode($args);
|
||||
list($code, $args) = h::splitRawCode(func_get_args());
|
||||
if(empty($code)) return null;
|
||||
return static::create('script', html(implode("\n", $code)), ...$args);
|
||||
return static::create('script', html(implode("\n", $code)), $args);
|
||||
}
|
||||
|
||||
public static function js(...$args)
|
||||
public static function js(/* ...$args */)
|
||||
{
|
||||
|
||||
list($code, $args) = h::splitRawCode($args);
|
||||
list($code, $args) = h::splitRawCode(func_get_args());
|
||||
if(empty($code)) return null;
|
||||
return static::create('script', html(h::createJsScopeCode($code)), ...$args);
|
||||
return static::create('script', html(h::createJsScopeCode($code)), $args);
|
||||
}
|
||||
|
||||
public static function jsVar($name, $value, ...$directives)
|
||||
public static function jsVar(/* $name, $value, ...$args */)
|
||||
{
|
||||
return static::js(static::createJsVarCode($name, $value), ...$directives);
|
||||
$args = func_get_args();
|
||||
$name = array_shift($args);
|
||||
$value = array_shift($args);
|
||||
return static::js(static::createJsVarCode($name, $value), $args);
|
||||
}
|
||||
|
||||
public static function jsCall($funcName, ...$args)
|
||||
public static function jsCall(/* $funcName, ...$args */)
|
||||
{
|
||||
$args = func_get_args();
|
||||
$funcName = array_shift($args);
|
||||
|
||||
$funcArgs = [];
|
||||
$directives = [];
|
||||
foreach($args as $arg)
|
||||
@@ -228,7 +246,7 @@ class h extends wg
|
||||
else $funcArgs[] = $arg;
|
||||
}
|
||||
$code = static::createJsCallCode($funcName, $funcArgs);
|
||||
return static::js($code, ...$directives);
|
||||
return static::js($code, $directives);
|
||||
}
|
||||
|
||||
public static function createJsCallCode($func, $args)
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* The portal class file of zin lib.
|
||||
*
|
||||
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
|
||||
* @author Hao Sun <sunhao@easycorp.ltd>
|
||||
* @package zin
|
||||
* @version $Id
|
||||
* @link https://www.zentao.net
|
||||
*/
|
||||
|
||||
namespace zin;
|
||||
|
||||
require_once __DIR__ . DS . 'wg.class.php';
|
||||
|
||||
class portal extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'target:string'
|
||||
);
|
||||
|
||||
public static function __callStatic($name, $args)
|
||||
{
|
||||
return new portal(set('target', $name), $args);
|
||||
}
|
||||
}
|
||||
@@ -48,10 +48,10 @@ class props extends \zin\utils\dataset
|
||||
* @access public
|
||||
* @param array $props - Properties list array
|
||||
*/
|
||||
public function __construct(?array $props = null)
|
||||
public function __construct(array $props = array())
|
||||
{
|
||||
$this->style = new \zin\utils\style();
|
||||
$this->class = new \zin\utils\classlist();
|
||||
$this->style = style::new();
|
||||
$this->class = classlist::new();
|
||||
|
||||
parent::__construct($props);
|
||||
}
|
||||
@@ -208,11 +208,11 @@ class props extends \zin\utils\dataset
|
||||
return implode(' ', $pairs);
|
||||
}
|
||||
|
||||
public function toJsonData(bool $skipEvents = false): array
|
||||
public function toJSON(bool $skipEvents = false): array
|
||||
{
|
||||
$data = $this->data;
|
||||
if(!empty($this->style->data)) $data['style'] = $this->style->data;
|
||||
if(!empty($this->class->list)) $data['class'] = $this->class->toStr();
|
||||
if(!empty($this->class->toJSON())) $data['class'] = $this->class->toStr();
|
||||
|
||||
if($skipEvents)
|
||||
{
|
||||
@@ -228,7 +228,7 @@ class props extends \zin\utils\dataset
|
||||
{
|
||||
if(is_string($skipProps)) $skipProps = explode(',', $skipProps);
|
||||
|
||||
$data = $this->toJsonData();
|
||||
$data = $this->toJSON();
|
||||
foreach($data as $name => $value)
|
||||
{
|
||||
if($value === null || $name[0] === '@' || in_array($name, $skipProps)) unset($data[$name]);
|
||||
@@ -242,7 +242,7 @@ class props extends \zin\utils\dataset
|
||||
{
|
||||
if(is_string($firstListProps)) $firstListProps = explode(',', $firstListProps);
|
||||
|
||||
$data = $this->toJsonData();
|
||||
$data = $this->toJSON();
|
||||
$firstList = array();
|
||||
$restList = array();
|
||||
foreach($data as $name => $value)
|
||||
@@ -259,7 +259,7 @@ class props extends \zin\utils\dataset
|
||||
{
|
||||
if(is_string($pickProps)) $pickProps = explode(',', $pickProps);
|
||||
|
||||
$data = $this->toJsonData();
|
||||
$data = $this->toJSON();
|
||||
foreach($data as $name => $value)
|
||||
{
|
||||
if($value === null || !in_array($name, $pickProps)) unset($data[$name]);
|
||||
@@ -277,8 +277,8 @@ class props extends \zin\utils\dataset
|
||||
public function clone(): props
|
||||
{
|
||||
$props = new props($this->data);
|
||||
$props->style = clone $this->style;
|
||||
$props->class = clone $this->class;
|
||||
$props->style = clone $this->style;
|
||||
$props->class = clone $this->class;
|
||||
return $props;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ class set
|
||||
return directive('prop', array($prop => $value));
|
||||
}
|
||||
|
||||
public static function class(...$args)
|
||||
public static function class(/* ...$args */)
|
||||
{
|
||||
return directive('prop', array('class' => $args));
|
||||
return directive('prop', array('class' => func_get_args()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ class wg
|
||||
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return $this->toJsonData();
|
||||
return $this->toJSON();
|
||||
}
|
||||
|
||||
public function isDomElement(): bool
|
||||
@@ -429,7 +429,7 @@ class wg
|
||||
*/
|
||||
public function setProp(props|array|string $prop, mixed $value = null)
|
||||
{
|
||||
if($prop instanceof props) $prop = $prop->toJsonData();
|
||||
if($prop instanceof props) $prop = $prop->toJSON();
|
||||
|
||||
if(is_array($prop))
|
||||
{
|
||||
@@ -498,11 +498,11 @@ class wg
|
||||
return $this->prop('id');
|
||||
}
|
||||
|
||||
public function toJsonData(): array
|
||||
public function toJSON(): array
|
||||
{
|
||||
$data = array();
|
||||
$data['gid'] = $this->gid;
|
||||
$data['props'] = $this->props->toJsonData();
|
||||
$data['props'] = $this->props->toJSON();
|
||||
|
||||
$data['type'] = $this->type();
|
||||
if(str_starts_with($data['type'], 'zin\\')) $data['type'] = substr($data['type'], 4);
|
||||
@@ -512,9 +512,9 @@ class wg
|
||||
{
|
||||
foreach($value as $index => $child)
|
||||
{
|
||||
if($child instanceof wg || (is_object($child) && method_exists($child, 'toJsonData')))
|
||||
if($child instanceof wg || (is_object($child) && method_exists($child, 'toJSON')))
|
||||
{
|
||||
$value[$index] = $child->toJsonData();
|
||||
$value[$index] = $child->toJSON();
|
||||
}
|
||||
elseif(isDirective($child, 'html'))
|
||||
{
|
||||
|
||||
+76
-8
@@ -658,6 +658,14 @@ function cell(): cell
|
||||
return createWg('cell', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Divider widget.
|
||||
*/
|
||||
function divider(): divider
|
||||
{
|
||||
return createWg('divider', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Action item widget.
|
||||
*
|
||||
@@ -1056,14 +1064,6 @@ function toolbar(): toolbar
|
||||
return createWg('toolbar', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Zentao search form widget.
|
||||
*/
|
||||
function searchForm(): searchForm
|
||||
{
|
||||
return createWg('searchForm', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Zentao search toggle widget.
|
||||
*
|
||||
@@ -1627,6 +1627,15 @@ function overviewBlock(): overviewBlock
|
||||
return createWg('overviewBlock', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistic block widget.
|
||||
*
|
||||
*/
|
||||
function statisticBlock(): statisticBlock
|
||||
{
|
||||
return createWg('statisticBlock', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Picker widget.
|
||||
*/
|
||||
@@ -1689,3 +1698,62 @@ function tableChart(): tableChart
|
||||
{
|
||||
return createWg('tableChart', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Password widget.
|
||||
*/
|
||||
function password(): password
|
||||
{
|
||||
return createWg('password', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Mindmap widget.
|
||||
*/
|
||||
function mindmap(): mindmap
|
||||
{
|
||||
return createWg('mindmap', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Image cutter widget.
|
||||
*/
|
||||
function imgCutter(): imgCutter
|
||||
{
|
||||
return createWg('imgCutter', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Next step modal widget.
|
||||
*
|
||||
* string tip
|
||||
* array items
|
||||
*/
|
||||
function modalNextStep(): modalNextStep
|
||||
{
|
||||
return createWg('modalNextStep', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigator widget.
|
||||
*/
|
||||
function navigator(): navigator
|
||||
{
|
||||
return createWg('navigator', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gantt widget.
|
||||
*/
|
||||
function gantt(): gantt
|
||||
{
|
||||
return createWg('gantt', func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* road map widget.
|
||||
*/
|
||||
function roadMap(): roadMap
|
||||
{
|
||||
return createWg('roadmap', func_get_args());
|
||||
}
|
||||
|
||||
@@ -20,10 +20,32 @@ class classlist
|
||||
/**
|
||||
* Store classname list, key => value
|
||||
*
|
||||
* @access public
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
public $list = array();
|
||||
private array $list = array();
|
||||
|
||||
/**
|
||||
* Create an classlist instance
|
||||
*
|
||||
* @param string|array|null $names - A string or a class name list
|
||||
* @return classlist
|
||||
*/
|
||||
public static function new(string|array|null $names = array()): classlist
|
||||
{
|
||||
return new classlist($names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stringify class list
|
||||
*
|
||||
* @param string|array $names - A string or a class name list
|
||||
* @return string
|
||||
*/
|
||||
public static function str(string|array $names): string
|
||||
{
|
||||
return classlist::new($names)->toStr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create classname instance
|
||||
@@ -43,58 +65,18 @@ class classlist
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->toStr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override __invoke
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $classlist = classlist::create('btn primary');
|
||||
* echo $classlist(); // Output: "btn primary"
|
||||
*
|
||||
* @access public
|
||||
* @param array $list - Class name list
|
||||
* @return string
|
||||
*/
|
||||
public function __invoke()
|
||||
{
|
||||
$list = func_get_args();
|
||||
if(empty($list)) return $this->toStr();
|
||||
return $this->set($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override __call to invoke toggle method conveniently
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $classlist = classlist::new();
|
||||
*
|
||||
* // Add "primary" class
|
||||
* $classlist->primary();
|
||||
*
|
||||
* // Remove "primary" class
|
||||
* $classlist->primary(false);
|
||||
*
|
||||
* @access public
|
||||
* @return classlist
|
||||
*/
|
||||
public function __call($name, $args)
|
||||
{
|
||||
return $this->toggle($name, !count($args) || $args[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create classname instance
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* // Set class names
|
||||
* $classlist = new classlist();
|
||||
* $classlist = classlist::new();
|
||||
* $classlist->set('btn primary rounded');
|
||||
*
|
||||
* // Set multiple classnames by string list
|
||||
@@ -104,11 +86,11 @@ class classlist
|
||||
* $classlist->set(array('btn' => true, 'primary' => true, 'rounded' => $isRounded));
|
||||
*
|
||||
* @access public
|
||||
* @param string|array $list - A string or a class name list
|
||||
* @param bool $reset
|
||||
* @param string|array|null $list - A string or a class name list
|
||||
* @param bool $reset
|
||||
* @return classlist
|
||||
*/
|
||||
public function set($list, $reset = false)
|
||||
public function set(string|array|null $list, bool $reset = false): classlist
|
||||
{
|
||||
if(is_string($list)) $list = explode(' ', $list);
|
||||
|
||||
@@ -151,7 +133,7 @@ class classlist
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $classlist = new classlist();
|
||||
* $classlist = classlist::new();
|
||||
* $classlist->add('btn primary rounded');
|
||||
*
|
||||
* // Add multiple classnames by string list
|
||||
@@ -171,7 +153,7 @@ class classlist
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $classlist = new classlist('btn primary rounded');
|
||||
* $classlist = classlist::new('btn primary rounded');
|
||||
* $classlist->remove('btn primary');
|
||||
*
|
||||
* // Add multiple classnames by string list
|
||||
@@ -181,7 +163,7 @@ class classlist
|
||||
* @param array|string $list - classname string joined by space or string array
|
||||
* @return classlist
|
||||
*/
|
||||
public function remove($list)
|
||||
public function remove(array|string $list): classlist
|
||||
{
|
||||
if(is_string($list)) $list = explode(' ', $list);
|
||||
|
||||
@@ -201,7 +183,7 @@ class classlist
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $classlist = new classlist('btn');
|
||||
* $classlist = classlist::new('btn');
|
||||
* $classlist->toggle('btn'); // class list is ""
|
||||
*
|
||||
* // Toggle class name by flag
|
||||
@@ -211,7 +193,7 @@ class classlist
|
||||
* @param string $name - classname string
|
||||
* @return classlist
|
||||
*/
|
||||
public function toggle($name, $toggle = null)
|
||||
public function toggle(string $name, bool|null $toggle = null): classlist
|
||||
{
|
||||
$name = trim($name);
|
||||
if(strlen($name))
|
||||
@@ -227,13 +209,13 @@ class classlist
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $classlist = new classlist('btn primary rounded');
|
||||
* $classlist = classlist::new('btn primary rounded');
|
||||
* echo $classlist->has('btn'); // Output true
|
||||
*
|
||||
* // Check multiple names
|
||||
* echo $classlist->has('btn primary'); // Output true
|
||||
*/
|
||||
public function has($list)
|
||||
public function has(array|string $list): bool
|
||||
{
|
||||
if(is_string($list)) $list = explode(' ', $list);
|
||||
|
||||
@@ -280,34 +262,12 @@ class classlist
|
||||
* @access public
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an classlist instance
|
||||
*
|
||||
* @param string|array $names - A string or a class name list
|
||||
* @return classlist
|
||||
*/
|
||||
static public function new($names = null)
|
||||
{
|
||||
return (new classlist($names));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stringify class list
|
||||
*
|
||||
* @param string|array $names - A string or a class name list
|
||||
* @return string
|
||||
*/
|
||||
static public function str($names)
|
||||
{
|
||||
return (new classlist($names))->toStr();
|
||||
}
|
||||
|
||||
public function toJSON()
|
||||
public function toJSON(): array
|
||||
{
|
||||
return $this->list;
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* The data class file of zin of ZenTaoPMS.
|
||||
*
|
||||
* @copyright Copyright 2023 青岛易软天创网络科技有限公司(QingDao Nature Easy Soft Network Technology Co,LTD, www.cnezsoft.com)
|
||||
* @author Hao Sun <sunhao@easycorp.ltd>
|
||||
* @package zin
|
||||
* @version $Id
|
||||
* @link https://www.zentao.net
|
||||
*/
|
||||
|
||||
namespace zin\utils;
|
||||
|
||||
require_once __DIR__ . DS . 'dataset.class.php';
|
||||
|
||||
/**
|
||||
* Manage data for html element and widgets
|
||||
*/
|
||||
class data extends dataset
|
||||
{
|
||||
public function __constructor()
|
||||
{
|
||||
$list = func_get_args();
|
||||
|
||||
foreach($list as $data) $this->set($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for sub class to modify value on setting it
|
||||
*
|
||||
* @access public
|
||||
* @param array|string $prop - Property name or properties list
|
||||
* @param mixed $value - Property value
|
||||
* @param bool $removeEmpty - Whether to remove empty value
|
||||
* @return dataset
|
||||
*/
|
||||
protected function setVal($prop, $value, $removeEmpty = false)
|
||||
{
|
||||
if($prop[0] === '$') $prop = substr($prop, 1);
|
||||
|
||||
if($value === null || ($removeEmpty && empty($value))) return $this->remove($prop);
|
||||
|
||||
$names = explode('.', $prop);
|
||||
$lastName = array_pop($names);
|
||||
$data = &$this->data;
|
||||
if(!empty($names))
|
||||
{
|
||||
foreach($names as $name)
|
||||
{
|
||||
if(!is_array($data))
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
if(!isset($data[$name])) $data[$name] = array();
|
||||
$data = &$data[$name];
|
||||
}
|
||||
}
|
||||
|
||||
if($value === null || ($removeEmpty && empty($value)))
|
||||
{
|
||||
if(isset($data[$lastName])) unset($data[$lastName]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
$data[$lastName] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function getVal($prop)
|
||||
{
|
||||
if($prop[0] === '$') $prop = substr($prop, 1);
|
||||
|
||||
$names = explode('.', $prop);
|
||||
$data = &$this->data;
|
||||
foreach($names as $name)
|
||||
{
|
||||
if(!is_array($data)) return null;
|
||||
$data = &$data[$name];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,9 @@ class dataset
|
||||
* Store dataset properties list in an array
|
||||
*
|
||||
* @var array
|
||||
* @access public
|
||||
* @access protected
|
||||
*/
|
||||
public array $data = array();
|
||||
protected array $data = array();
|
||||
|
||||
/**
|
||||
* Create an instance, the initialed data can be passed
|
||||
@@ -31,60 +31,11 @@ class dataset
|
||||
* @access public
|
||||
* @param array $data - Properties list array
|
||||
*/
|
||||
public function __construct(?array $data = null)
|
||||
public function __construct(array $data = array())
|
||||
{
|
||||
if($data !== null) $this->set($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override __set
|
||||
*
|
||||
* @access public
|
||||
* @param string $prop - Property name
|
||||
* @param mixed $value - Property value
|
||||
* @return void
|
||||
*/
|
||||
public function __set(string $name, mixed $value)
|
||||
{
|
||||
$this->set($name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override __get
|
||||
*
|
||||
* @access public
|
||||
* @param string $prop - Property name
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get(string $name)
|
||||
{
|
||||
$this->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override __isset
|
||||
*
|
||||
* @access public
|
||||
* @param string $prop - Property name
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset(string $name): bool
|
||||
{
|
||||
return $this->has($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override __unset
|
||||
*
|
||||
* @access public
|
||||
* @param string $prop - Property name
|
||||
* @return void
|
||||
*/
|
||||
public function __unset(string $name)
|
||||
{
|
||||
$this->remove($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert dataset to json string
|
||||
*
|
||||
@@ -96,43 +47,6 @@ class dataset
|
||||
return $this->toStr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override __invoke
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function __invoke(string|array $name = null, mixed $value = null): string
|
||||
{
|
||||
if($value !== null || is_array($name)) return $this->set($name, $value);
|
||||
if(is_string($name)) return $this->get($name);
|
||||
|
||||
return $this->toStr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override __call for setting property conveniently
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* $dataset = dataset::new();
|
||||
*
|
||||
* // Set color property
|
||||
* $dataset->color('red');
|
||||
*
|
||||
* // Get color property
|
||||
* echo $dataset->color(); // Output "Red"
|
||||
*
|
||||
* @access public
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $name, array $args): mixed
|
||||
{
|
||||
if(count($args)) return $this->set($name, $args[0]);
|
||||
|
||||
return $this->get($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for sub class to modify value on setting it
|
||||
*
|
||||
@@ -179,10 +93,10 @@ class dataset
|
||||
*/
|
||||
public function toStr(): string
|
||||
{
|
||||
return json_encode($this->toJsonData());
|
||||
return json_encode($this->toJSON());
|
||||
}
|
||||
|
||||
public function toJsonData(): array
|
||||
public function toJSON(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
@@ -233,12 +147,6 @@ class dataset
|
||||
return $this->get($prop, array());
|
||||
}
|
||||
|
||||
public function list($prop, $values = null)
|
||||
{
|
||||
if($values === null) return $this->getList($prop);
|
||||
return $this->setList($prop, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete property by name
|
||||
*
|
||||
|
||||
@@ -16,7 +16,7 @@ $logs = array();
|
||||
|
||||
function log($type, $msg = null, $file)
|
||||
{
|
||||
global $config;
|
||||
global $config, $logs;
|
||||
|
||||
if(!$config->debug) return;
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
declare(strict_types=1);
|
||||
namespace zin\utils;
|
||||
|
||||
function flat($array, $prefix = '', $separator = '.')
|
||||
function flat(array $array, string $prefix = '')
|
||||
{
|
||||
$result = array();
|
||||
foreach($array as $key => $value)
|
||||
{
|
||||
if(is_array($value))
|
||||
{
|
||||
$result = array_merge($result, flat($value, $prefix . $key . $separator));
|
||||
$result = array_merge($result, flat($value, "{$prefix}{$key}."));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -43,6 +43,41 @@ require_once __DIR__ . DS . 'dataset.class.php';
|
||||
*/
|
||||
class style extends dataset
|
||||
{
|
||||
/**
|
||||
* Create an instance
|
||||
*
|
||||
* @param array $style - CSS style list
|
||||
* @return style
|
||||
*/
|
||||
public static function new(array $style = array()): style
|
||||
{
|
||||
return new style($style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create css from style list
|
||||
*
|
||||
* @access public
|
||||
* @param array $style - CSS style list
|
||||
* @return string
|
||||
*/
|
||||
public static function str(array $style): string
|
||||
{
|
||||
return (new style($style))->toStr();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format CSS variable name with prefix "--"
|
||||
*
|
||||
* @access public
|
||||
* @param string $name - CSS variable name
|
||||
* @return string
|
||||
*/
|
||||
public static function formatVarName(string $name): string
|
||||
{
|
||||
return \zin\str_starts_with($name, '--') ? $name : "--$name";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or get css variable, an array can be passed to set multiple variables
|
||||
* If only pass variable name, then the variable value will be returned
|
||||
@@ -70,9 +105,9 @@ class style extends dataset
|
||||
* $style->cssVar('text-color', '');
|
||||
*
|
||||
* @access public
|
||||
* @param array|string $name - Variable name or variables list
|
||||
* @param mixed $value - Property value
|
||||
* @return mixed
|
||||
* @param array|string $name - Variable name or variables list
|
||||
* @param string|null $value - Property value
|
||||
* @return style|array|string
|
||||
*/
|
||||
public function cssVar(array|string $name = '', ?string $value = null): style|array|string
|
||||
{
|
||||
@@ -112,17 +147,6 @@ class style extends dataset
|
||||
* @return string
|
||||
*/
|
||||
public function toStr(): string
|
||||
{
|
||||
return $this->toCss();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert style to css string
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function toCss(): string
|
||||
{
|
||||
$pairs = array();
|
||||
|
||||
@@ -136,39 +160,4 @@ class style extends dataset
|
||||
|
||||
return implode(' ', $pairs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance
|
||||
*
|
||||
* @param ?array $style - CSS style list
|
||||
* @return style
|
||||
*/
|
||||
public static function new(?array $style = null): style
|
||||
{
|
||||
return new style($style);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create css from style list
|
||||
*
|
||||
* @access public
|
||||
* @param ?array $style - CSS style list
|
||||
* @return string
|
||||
*/
|
||||
public static function css(?array $style): string
|
||||
{
|
||||
return (new style($style))->toCss();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format CSS variable name with prefix "--"
|
||||
*
|
||||
* @access public
|
||||
* @param string $name - CSS variable name
|
||||
* @return string
|
||||
*/
|
||||
public static function formatVarName(string $name): string
|
||||
{
|
||||
return \zin\str_starts_with($name, '--') ? $name : "--$name";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class avatar extends wg
|
||||
$this->finalStyle->background = $this->prop('background');
|
||||
$this->finalStyle->color = $this->prop('foreColor');
|
||||
|
||||
foreach($this->props->style->data as $attr => $val) $this->finalStyle->{$attr} = $val;
|
||||
foreach($this->props->style->toJSON() as $attr => $val) $this->finalStyle->{$attr} = $val;
|
||||
|
||||
/* Init avatar size. */
|
||||
$this->initSize();
|
||||
@@ -263,6 +263,7 @@ class avatar extends wg
|
||||
{
|
||||
$src = $this->prop('src');
|
||||
$text = $this->prop('text');
|
||||
$code = $this->prop('code');
|
||||
|
||||
/* With avatar. */
|
||||
if($src)
|
||||
@@ -272,8 +273,9 @@ class avatar extends wg
|
||||
return h::img
|
||||
(
|
||||
setClass('avatar-img'),
|
||||
set('src', $src ),
|
||||
set('alt', $text)
|
||||
set('src', $src),
|
||||
set('alt', $text),
|
||||
set('data-code', $code),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ class backBtn extends btn
|
||||
'build' => 'execution-build,build-view',
|
||||
'projectbuild' => 'projectbuild-browse,projectbuild-view',
|
||||
'mr' => 'mr-browse',
|
||||
'repo' => 'repo-log,repo-browse',
|
||||
'compile' => 'compile-browse',
|
||||
'store' => 'store-browse',
|
||||
'space' => 'space-browse',
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
/**
|
||||
* Determines whether its argument represents a JavaScript number.
|
||||
* @param {*} obj
|
||||
* @returns bool
|
||||
*/
|
||||
function isNumeric(obj)
|
||||
{
|
||||
return (!isNaN(obj) && typeof obj === 'number') || $.isNumeric(obj);;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new item.
|
||||
*
|
||||
|
||||
@@ -30,6 +30,7 @@ class blockPanel extends panel
|
||||
'block?: object|array', // 区块对象。
|
||||
'title?: string', // 标题。
|
||||
'headingClass?: string="border-b"', // 标题栏类名。
|
||||
'longBlock?: bool', // 是否为长区块。
|
||||
'moreLink?: string' // 更多链接。
|
||||
);
|
||||
|
||||
@@ -59,19 +60,24 @@ class blockPanel extends panel
|
||||
|
||||
if(empty($this->prop('title'))) $props['title'] = empty($block) ? $lang->block->titleList[$name] : $block->title;
|
||||
|
||||
if($this->prop('longBlock') === null) $props['longBlock'] = data('longBlock');
|
||||
|
||||
$this->setProp($props);
|
||||
}
|
||||
|
||||
protected function buildProps(): array
|
||||
{
|
||||
$props = parent::buildProps();
|
||||
$name = $this->prop('name');
|
||||
$name = $this->prop('name');
|
||||
if(!empty($name))
|
||||
{
|
||||
$props[] = setData('block', $name);
|
||||
$props[] = setClass("block-{$name}");
|
||||
$props[] = setID($this->prop('id'));
|
||||
}
|
||||
|
||||
$props[] = setClass($this->prop('longBlock') ? 'is-long' : 'is-short');
|
||||
|
||||
return $props;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class btn extends wg
|
||||
|
||||
protected function getProps()
|
||||
{
|
||||
$url = $this->prop('url');
|
||||
$url = $this->prop('disabled') ? null : $this->prop('url');
|
||||
$target = $this->prop('target');
|
||||
$props = array_merge($this->getRestProps(), array('title' => $this->prop('hint')));
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ class center extends wg
|
||||
{
|
||||
return div
|
||||
(
|
||||
setClass("flex justify-center items-center"),
|
||||
setClass("center"),
|
||||
set($this->getRestProps()),
|
||||
$this->children()
|
||||
);
|
||||
|
||||
@@ -12,7 +12,8 @@ class checkList extends wg
|
||||
'name?: string',
|
||||
'value?: string|array',
|
||||
'items?: array',
|
||||
'inline?: bool'
|
||||
'inline?: bool',
|
||||
'disabled?: bool'
|
||||
);
|
||||
|
||||
public function getValueList()
|
||||
@@ -26,7 +27,7 @@ class checkList extends wg
|
||||
|
||||
public function onBuildItem($item): checkbox
|
||||
{
|
||||
if($item instanceof item) $item = $item->props->toJsonData();
|
||||
if($item instanceof item) $item = $item->props->toJSON();
|
||||
|
||||
if(!isset($item['checked']))
|
||||
{
|
||||
@@ -34,15 +35,18 @@ class checkList extends wg
|
||||
$valueList = $this->getValueList();
|
||||
|
||||
$item['checked'] = in_array($value, $valueList);
|
||||
$item['disabled'] = $this->prop('disabled');
|
||||
}
|
||||
|
||||
$props = $this->props->pick(['primary', 'type', 'name']);
|
||||
$props = $this->props->pick(['primary', 'type', 'name', 'disabled']);
|
||||
if(!empty($props['name']) && !empty($item['value'])) $props['id'] = $props['name'] . $item['value'];
|
||||
|
||||
return new checkbox(set($props), set($item));
|
||||
}
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
list($items, $inline) = $this->prop(['items', 'inline']);
|
||||
list($items, $inline, $disabled) = $this->prop(['items', 'inline', 'disabled']);
|
||||
|
||||
if(!empty($items))
|
||||
{
|
||||
@@ -59,6 +63,7 @@ class checkList extends wg
|
||||
(
|
||||
setClass($inline ? 'check-list-inline' : 'check-list'),
|
||||
set($this->getRestProps()),
|
||||
$disabled ? set('disabled', 'disabled') : '',
|
||||
$items,
|
||||
$this->children()
|
||||
);
|
||||
|
||||
@@ -22,8 +22,8 @@ class commentDialog extends wg
|
||||
|
||||
return modal
|
||||
(
|
||||
set::id('comment-dialog'),
|
||||
set::title($title),
|
||||
setID('comment-dialog'),
|
||||
set::modalProps(array('title' => $title)),
|
||||
commentForm
|
||||
(
|
||||
set::url($url),
|
||||
|
||||
@@ -21,7 +21,7 @@ class control extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'type?: string', // 表单输入元素类型,值可以为:static, text, password, email, number, date, time, datetime, month, url, search, tel, color, picker, pri, severity, select, checkbox, radio, checkboxList, radioList, checkboxListInline, radioListInline, file, textarea
|
||||
'name?: string', // HTML name 属性
|
||||
'name: string', // HTML name 属性
|
||||
'id?: string', // HTML id 属性
|
||||
'value?: string', // HTML value 属性
|
||||
'placeholder?: string', // HTML placeholder 属性
|
||||
|
||||
@@ -39,7 +39,8 @@ class dashboard extends wg
|
||||
'blockDefaultSize?: array', // 区块默认大小。
|
||||
'blockSizeMap?: array', // 区块大小映射。
|
||||
'blockMenu?: array', // 区块菜单。
|
||||
'onLayoutChange?: function' // 布局变更事件。
|
||||
'onLayoutChange?: function', // 布局变更事件。
|
||||
'onClickMenu?: function' // 布局变更事件。
|
||||
);
|
||||
|
||||
static $dashboardID = 0;
|
||||
@@ -55,6 +56,11 @@ class dashboard extends wg
|
||||
*/
|
||||
protected function build(): wg
|
||||
{
|
||||
return zui::dashboard(set($this->props->skip(array('id'))), set('_id', $this->prop('id')));
|
||||
return zui::dashboard
|
||||
(
|
||||
set($this->props->skip(array('id'))),
|
||||
set('_id', $this->prop('id')),
|
||||
set('_props', $this->getRestProps())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,71 @@ namespace zin;
|
||||
|
||||
require_once dirname(__DIR__) . DS . 'input' . DS . 'v1.php';
|
||||
|
||||
class datetimePicker extends input
|
||||
class datetimePicker extends wg
|
||||
{
|
||||
protected static array $defaultProps = array(
|
||||
'type' => 'datetime-local'
|
||||
/**
|
||||
* Define widget properties.
|
||||
*
|
||||
* @var array
|
||||
* @access protected
|
||||
*/
|
||||
protected static array $defineProps = array
|
||||
(
|
||||
'id?: string="$GID"', // 组件根元素的 ID。
|
||||
'formID?: string', // 组件隐藏的表单元素 ID。
|
||||
'className?: string|array', // 类名。
|
||||
'style?: array', // 样式。
|
||||
'tagName?: string', // 组件根元素的标签名。
|
||||
'attrs?: array', // 附加到组件根元素上的属性。
|
||||
'clickType?: "toggle"|"open"', // 点击类型,`toggle` 表示点击按钮时切换显示隐藏,`open` 表示点击按钮时只打。
|
||||
'afterRender?: function', // 渲染完成后的回调函数。
|
||||
'beforeDestroy?: function', // 销毁前的回调函数。
|
||||
'name?: string', // 作为表单项的名称。
|
||||
'value?: string|string[]', // 默认值。
|
||||
'onChange?: function', // 值变更回调函数。
|
||||
'disabled?: boolean', // 是否禁用。
|
||||
'multiple?: boolean|number=false', // 是否允许选择多个值,如果指定为数字,则限制多选的数目,默认 `false`。
|
||||
'required?: boolean', // 是否必选(不允许空值,不可以被清除)。
|
||||
'placeholder?: string', // 选择框上的占位文本。
|
||||
'icon?: string|array="calendar"', // 在输入框右侧显示的图标。
|
||||
'weekNames?: string[]', // 星期名称,索引为 0 表示周日。
|
||||
'monthNames?: string[]', // 月份名称,索引为 0 表示一月份。
|
||||
'yearText?: string', // 用于显示年份的格式化文本。
|
||||
'todayText?: string', // 用于显示“今天”的文本。
|
||||
'clearText?: string', // 用于显示“清除”的文本。
|
||||
'weekStart?: int', // 一周从星期几开始,默认 1。
|
||||
'minDate?: string|int', // 最小可选的日期。
|
||||
'maxDate?: string|int', // 最大可选的日期。
|
||||
'menu?: array', // 左侧显示的菜单设置。
|
||||
'actions?: array', // 底部工具栏设置。
|
||||
'onInvalid?: function', // 日期值无效时的回调函数。
|
||||
'dateFormat?: string', // 日期格式,默认 yyyy-MM-dd。
|
||||
'timeFormat?: string', // 时间格式,默认 hh:mm
|
||||
'joiner?: string' // 日期与时间的连接符,默认为单个空格
|
||||
);
|
||||
|
||||
/**
|
||||
* Build the widget.
|
||||
*
|
||||
* @access protected
|
||||
* @return wg
|
||||
*/
|
||||
protected function build(): wg
|
||||
{
|
||||
list($props, $restProps) = $this->props->split(array_keys(static::definedPropsList()));
|
||||
if(isset($props['id']))
|
||||
{
|
||||
$props['_id'] = $props['id'];
|
||||
unset($props['id']);
|
||||
}
|
||||
|
||||
return zui::datetimePicker
|
||||
(
|
||||
set::_class('form-group-wrapper'),
|
||||
set::_map(array('value' => 'defaultValue', 'formID' => 'id')),
|
||||
set($props),
|
||||
set::_props($restProps),
|
||||
$this->children(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace zin;
|
||||
|
||||
class divider extends wg
|
||||
{
|
||||
protected function build(): wg
|
||||
{
|
||||
return div
|
||||
(
|
||||
setClass("divider"),
|
||||
set($this->getRestProps()),
|
||||
$this->children()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
.module-menu {max-height: calc(100vh - 105px);}
|
||||
.module-menu {padding: 0 0 8px;}
|
||||
.module-menu {max-height: calc(100vh - 105px); padding: 0 0 8px;}
|
||||
.module-menu .active {color: var(--color-primary-600); font-weight: 500;}
|
||||
.module-menu header a:hover > .icon {color: var(--color-primary-600) !important;}
|
||||
.module-menu .tree-item * {white-space: nowrap;}
|
||||
|
||||
#docDropmenu {padding-bottom: 0.5rem;}
|
||||
#docDropmenu .is-leading {display: none;}
|
||||
#docDropmenu .primary {--tw-ring-color: var(--btn-border-color); background-color: var(--btn-bg); color: inherit; width: 100%;}
|
||||
|
||||
.module-menu .tree .tree-item .tree-link {text-overflow: clip; overflow: hidden; flex: 1 10 auto;}
|
||||
.module-menu .tree .tree-item .tree-actions {margin-left: 0;}
|
||||
.module-menu .tree .tree-item .tree-actions .icon {display: none;}
|
||||
.module-menu .tree .tree-item .tree-actions .icon-ellipsis-v {display: none;}
|
||||
.module-menu .tree .tree-item .tree-item-content:hover .tree-actions .icon {display: block;}
|
||||
.module-menu .tree .tree-item .tree-item-content .tree-actions .with-popover-show .icon {display: block;}
|
||||
|
||||
|
||||
@@ -4,19 +4,23 @@ window.saveModule = function()
|
||||
if(!name) return $(this).closest('.tree-item').remove();
|
||||
|
||||
const {id, type, lib, module} = $(this).data();
|
||||
const parentID = $(this).data('parent');
|
||||
const parentID = $(this).data('parent');
|
||||
|
||||
const $element = $(`div[data-id='${id}']`);
|
||||
$.ajaxSubmit({
|
||||
url: $.createLink('tree', 'ajaxCreateModule'),
|
||||
url: $.createLink('tree', 'ajaxCreateModule'),
|
||||
data: {
|
||||
name : name,
|
||||
libID : lib,
|
||||
parentID : parentID,
|
||||
parentID : type == 'child' ? id : parentID,
|
||||
objectID : id,
|
||||
moduleType : module,
|
||||
isUpdate : false,
|
||||
createType : type,
|
||||
},
|
||||
onSuccess: () =>
|
||||
{
|
||||
$(this).val('');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -26,7 +30,7 @@ window.addModule = function(id, addType)
|
||||
const $element = $(`div[data-id='${id}']`);
|
||||
const {lib, type, module} = $element.data();
|
||||
|
||||
let parentID = ['docLib'].includes(type) ? '0' : $element.data('parent');
|
||||
let parentID = ['docLib', 'apiLib'].includes(type) ? '0' : $element.data('parent');
|
||||
if(addType == 'child') parentID = id;
|
||||
|
||||
const level = addType == 'same' ? $element.data('level') : $element.data('level') + 1;
|
||||
@@ -57,5 +61,9 @@ window.addModule = function(id, addType)
|
||||
{
|
||||
$('#moduleName').trigger('focus');
|
||||
document.getElementById("moduleName").addEventListener('blur', saveModule);
|
||||
document.getElementById("moduleName").addEventListener('keydown', function(e)
|
||||
{
|
||||
if(e.keyCode == 13) saveModule.call(this);
|
||||
});
|
||||
}, 1);
|
||||
}
|
||||
|
||||
+93
-63
@@ -12,7 +12,6 @@ class docMenu extends wg
|
||||
'modules: array',
|
||||
'activeKey?: int',
|
||||
'settingLink?: string',
|
||||
'closeLink: string',
|
||||
'menuLink: string',
|
||||
'title?: string',
|
||||
'linkParams?: string="%s"',
|
||||
@@ -34,7 +33,7 @@ class docMenu extends wg
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
}
|
||||
|
||||
private function buildLink($item): string
|
||||
private function buildLink($item, $releaseID = 0): string
|
||||
{
|
||||
$url = $item->url;
|
||||
if(!empty($url)) return $url;
|
||||
@@ -88,6 +87,19 @@ class docMenu extends wg
|
||||
$linkParams = str_replace(array('browseType=&', 'param=0'), array('browseType=byrelease&', "param={$this->release}"), $linkParams);
|
||||
}
|
||||
}
|
||||
|
||||
if($releaseID)
|
||||
{
|
||||
if($this->currentModule == 'doc')
|
||||
{
|
||||
$linkParams = str_replace(array('browseType=&', 'param=0'), array('browseType=byrelease&', "param={$releaseID}"), $linkParams);
|
||||
if($this->rawMethod == 'view') $linkParams = "libID={$this->libID}&moduleID=0&browseType=byrelease&orderBy=&status,id_desc¶m={$releaseID}";
|
||||
}
|
||||
else
|
||||
{
|
||||
$linkParams = "libID={$this->libID}&moduleID=0&apiID=0&version=0&release={$releaseID}";
|
||||
}
|
||||
}
|
||||
return helper::createLink($moduleName, $methodName, $linkParams);
|
||||
}
|
||||
|
||||
@@ -96,9 +108,12 @@ class docMenu extends wg
|
||||
if(empty($items)) $items = $this->modules;
|
||||
if(empty($items)) return array();
|
||||
|
||||
$activeKey = $this->prop('activeKey');
|
||||
$activeKey = $this->prop('activeKey');
|
||||
$parentItems = array();
|
||||
foreach($items as $setting)
|
||||
{
|
||||
if(!is_object($setting)) continue;
|
||||
|
||||
$setting->parentID = $parentID;
|
||||
|
||||
$itemID = 0;
|
||||
@@ -109,8 +124,9 @@ class docMenu extends wg
|
||||
'text' => $setting->name,
|
||||
'icon' => $this->getIcon($setting),
|
||||
'url' => $this->buildLink($setting),
|
||||
'attrs' => array('data-app' => $this->tab),
|
||||
'data-id' => $itemID,
|
||||
'data-lib' => $setting->type == 'docLib' ? $itemID : $setting->libID,
|
||||
'data-lib' => in_array($setting->type, array('docLib', 'apiLib')) ? $itemID : $setting->libID,
|
||||
'data-type' => $setting->type,
|
||||
'data-parent' => $setting->parentID,
|
||||
'data-module' => $this->currentModule,
|
||||
@@ -134,6 +150,7 @@ class docMenu extends wg
|
||||
{
|
||||
global $app, $lang;
|
||||
$this->lang = $lang;
|
||||
$this->tab = $app->tab;
|
||||
$this->rawModule = $app->rawModule;
|
||||
$this->rawMethod = $app->rawMethod;
|
||||
$this->currentModule = $app->moduleName;
|
||||
@@ -149,7 +166,7 @@ class docMenu extends wg
|
||||
$this->spaceMethod = $this->prop('spaceMethod');
|
||||
|
||||
if($this->rawModule == 'api' && $this->rawMethod == 'view') $this->spaceType = 'api';
|
||||
if($this->spaceType != 'project')
|
||||
if(empty($this->modules['project']))
|
||||
{
|
||||
$this->setProp('items', $this->buildMenuTree(array(), $this->libID));
|
||||
}
|
||||
@@ -189,24 +206,56 @@ class docMenu extends wg
|
||||
|
||||
private function getActions($item): array|null
|
||||
{
|
||||
if(isset($item->hasAction) && !$item->hasAction) return null;
|
||||
if(in_array($item->type, array('mine', 'view', 'collect', 'createdBy', 'editedBy'))) return null;
|
||||
|
||||
$actions = $this->getOperateItems($item);
|
||||
if(empty($actions)) return null;
|
||||
|
||||
return array(
|
||||
array(
|
||||
'key' => 'more',
|
||||
'icon' => 'ellipsis-v',
|
||||
$versionBtn = array();
|
||||
if(isset($item->versions) && $item->versions)
|
||||
{
|
||||
global $lang;
|
||||
$versionTitle = $lang->build->common;
|
||||
$versionBtn = array(
|
||||
'key' => 'version',
|
||||
'text' => $versionTitle,
|
||||
'type' => 'dropdown',
|
||||
'caret' => false,
|
||||
'dropdown' => array(
|
||||
'placement' => 'bottom-end',
|
||||
'items' => $actions,
|
||||
'items' => array(),
|
||||
)
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
foreach($item->versions as $version)
|
||||
{
|
||||
if($version->id == $this->release) $versionBtn['text'] = $version->version;
|
||||
|
||||
$versionBtn['dropdown']['items'][] = array(
|
||||
'text' => $version->version,
|
||||
'href' => $this->buildLink($item, $version->id),
|
||||
'active' => $version->id == $this->release,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$moreBtn = array();
|
||||
if(!isset($item->hasAction) || $item->hasAction || in_array($item->type, array('mine', 'view', 'collect', 'createdBy', 'editedBy')))
|
||||
{
|
||||
$actions = $this->getOperateItems($item);
|
||||
if($actions)
|
||||
{
|
||||
$moreBtn = array(
|
||||
'key' => 'more',
|
||||
'icon' => 'ellipsis-v',
|
||||
'type' => 'dropdown',
|
||||
'caret' => false,
|
||||
'dropdown' => array(
|
||||
'placement' => 'bottom-end',
|
||||
'items' => $actions,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$actions = array();
|
||||
if($versionBtn) $actions[] = $versionBtn;
|
||||
if($moreBtn) $actions[] = $moreBtn;
|
||||
return $actions ? $actions : null;
|
||||
}
|
||||
|
||||
private function getOperateItems($item): array
|
||||
@@ -357,27 +406,6 @@ class docMenu extends wg
|
||||
);
|
||||
}
|
||||
|
||||
private function buildCloseBtn(): ?wg
|
||||
{
|
||||
$activeKey = $this->prop('activeKey');
|
||||
if(empty($activeKey)) return null;
|
||||
|
||||
return a
|
||||
(
|
||||
set('href', $this->prop('closeLink')),
|
||||
icon('close', setStyle('color', 'var(--color-slate-600)'))
|
||||
);
|
||||
}
|
||||
|
||||
private function buildDropDownMenu()
|
||||
{
|
||||
return menu
|
||||
(
|
||||
setID('dropdownMenu'),
|
||||
set::items(array())
|
||||
);
|
||||
}
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
$this->setMenuTreeProps();
|
||||
@@ -385,32 +413,34 @@ class docMenu extends wg
|
||||
$menuLink = $this->prop('menuLink', '');
|
||||
|
||||
return div
|
||||
(
|
||||
setClass('module-menu rounded shadow-sm bg-white col rounded-sm'),
|
||||
$title && empty($menuLink) ? h::header
|
||||
(
|
||||
setClass('h-10 flex items-center pl-4 flex-none gap-3'),
|
||||
span
|
||||
$menuLink ? dropmenu
|
||||
(
|
||||
setClass('module-title text-lg font-semibold'),
|
||||
html($title)
|
||||
set::id('docDropmenu'),
|
||||
set::menuID('docDropmenuMenu'),
|
||||
set::text($title),
|
||||
set::url($menuLink),
|
||||
) : null,
|
||||
div
|
||||
(
|
||||
setClass('module-menu rounded shadow-sm bg-white col rounded-sm'),
|
||||
$title && empty($menuLink) ? h::header
|
||||
(
|
||||
setClass('h-10 flex items-center pl-4 flex-none gap-3'),
|
||||
span
|
||||
(
|
||||
setClass('module-title text-lg font-semibold'),
|
||||
html($title)
|
||||
),
|
||||
) : null,
|
||||
h::main
|
||||
(
|
||||
setClass($menuLink ? 'pt-3' : ''),
|
||||
setClass('col flex-auto overflow-y-auto overflow-x-hidden pl-4 pr-1'),
|
||||
zui::tree(set($this->props->pick(array('items', 'activeClass', 'activeIcon', 'activeKey', 'onClickItem', 'defaultNestedShow', 'changeActiveKey', 'isDropdownMenu', 'hover'))))
|
||||
),
|
||||
$this->buildBtns()
|
||||
),
|
||||
$this->buildCloseBtn(),
|
||||
) : null,
|
||||
$menuLink ? dropmenu
|
||||
(
|
||||
set::id('docDropmenu'),
|
||||
set::text($title),
|
||||
set::url($menuLink),
|
||||
) : null,
|
||||
h::main
|
||||
(
|
||||
setClass($menuLink ? 'pt-3' : ''),
|
||||
setClass('col flex-auto overflow-y-auto overflow-x-hidden pl-4 pr-1'),
|
||||
zui::tree(set($this->props->pick(array('items', 'activeClass', 'activeIcon', 'activeKey', 'onClickItem', 'defaultNestedShow', 'changeActiveKey', 'isDropdownMenu', 'hover'))))
|
||||
),
|
||||
$this->buildBtns(),
|
||||
$this->buildDropDownMenu(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ class dropdown extends wg
|
||||
foreach($itemsList as $item)
|
||||
{
|
||||
if(!($item instanceof item)) continue;
|
||||
$items[] = $item->props->toJsonData();
|
||||
$items[] = $item->props->toJSON();
|
||||
}
|
||||
}
|
||||
foreach($items as $index => $item)
|
||||
|
||||
@@ -38,7 +38,7 @@ class dropmenu extends wg
|
||||
'text?: string', // 选择按钮上显示的文本。
|
||||
'cache?: bool|int=true', // 是否启用缓存。
|
||||
'data?: array', // 手动指定数据。
|
||||
'menuID?: string="$GID"', // 指定下拉菜单的ID。
|
||||
'menuID?: string', // 指定下拉菜单的ID。
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -58,13 +58,14 @@ class dropmenu extends wg
|
||||
* @access protected
|
||||
* @return wg
|
||||
*/
|
||||
protected function build(): zui
|
||||
protected function build(): array
|
||||
{
|
||||
list($url, $text, $objectID, $cache, $tab, $module, $method, $extra, $id, $data, $menuID) = $this->prop(array('url', 'text', 'objectID', 'cache', 'tab', 'module', 'method', 'extra', 'id', 'data', 'menuID'));
|
||||
|
||||
$app = data('app');
|
||||
$lang = data('lang');
|
||||
$app = data('app');
|
||||
$lang = data('lang');
|
||||
|
||||
if(empty($menuID)) $menuID = $id . '-menu';
|
||||
if(empty($tab)) $tab = $app->tab;
|
||||
if(empty($module)) $module = $app->rawModule;
|
||||
if(empty($method)) $method = $app->rawMethod;
|
||||
@@ -76,6 +77,46 @@ class dropmenu extends wg
|
||||
if(isset($object->id)) $objectID = $object->id;
|
||||
}
|
||||
|
||||
$branchMenu = null;
|
||||
if(($tab == 'product' || $tab == 'qa') and in_array($module, $app->config->hasBranchMenuModules))
|
||||
{
|
||||
if($objectID)
|
||||
{
|
||||
$product = $app->control->loadModel('product')->getByID((int)$objectID);
|
||||
if($product->type != 'normal')
|
||||
{
|
||||
$branchID = data('branchID');
|
||||
|
||||
/* Get current branch name. */
|
||||
$branchName = '';
|
||||
if($branchID == 'all' || $branchID === '')
|
||||
{
|
||||
$branchID = 'all';
|
||||
$branchName = $lang->branch->all;
|
||||
}
|
||||
elseif($branchID == 0)
|
||||
{
|
||||
$branchName = $lang->branch->main;
|
||||
}
|
||||
elseif($branchID > 0)
|
||||
{
|
||||
$branchName = $app->control->loadModel('branch')->getById((int)$branchID);
|
||||
}
|
||||
|
||||
$branchURL = createLink('branch', 'ajaxGetDropMenu', "objectID=$objectID&branch=$branchID&module=$module&method=$method&extra=$extra");
|
||||
$branchMenu = zui::dropmenu
|
||||
(
|
||||
setID('branch-dropmenu'),
|
||||
set('_id', 'branch-dropmenu'),
|
||||
set('_props', array('data-fetcher' => $branchURL)),
|
||||
set('data', $data),
|
||||
set(array('fetcher' => $branchURL, 'text' => $branchName, 'defaultValue' => $branchID)),
|
||||
set($this->getRestProps())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($tab == 'admin')
|
||||
{
|
||||
$currentMenuKey = $app->control->loadModel('admin')->getMenuKey();
|
||||
@@ -91,7 +132,7 @@ class dropmenu extends wg
|
||||
$text = $object->name;
|
||||
}
|
||||
|
||||
return zui::dropmenu
|
||||
return array(zui::dropmenu
|
||||
(
|
||||
setID($menuID),
|
||||
set('_id', $id),
|
||||
@@ -99,6 +140,6 @@ class dropmenu extends wg
|
||||
set('data', $data),
|
||||
set(array('fetcher' => $url, 'text' => $text, 'defaultValue' => $objectID, 'cache' => $cache)),
|
||||
set($this->getRestProps())
|
||||
);
|
||||
), $branchMenu);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
.dynamic > li.green > div:after {background-color: rgb(25, 190, 131);}
|
||||
.dynamic > li.yellow > div:after {background-color: rgb(255, 210, 169);}
|
||||
.dynamic > li.trophy > div:after {background-color: unset;}
|
||||
.dynamic > li:after {position: absolute; top: 12px; bottom: -13px; left: -13px; z-index: 1; display: block; content: ' '; border-left: 2px solid #eee;}
|
||||
.dynamic > li + li:after {position: absolute; top: -12px; bottom: 20px; left: -13px; z-index: 1; display: block; content: ' '; border-left: 2px solid #eee;}
|
||||
.dynamic > li > div:after {opacity: 1}
|
||||
.dynamic > li:before {top: 12px; left: -20px; width: 16px; height: 16px; background-color: var(--color-slate-100); border: none;}
|
||||
.dynamic > li.trophy:before {background: url('static/svg/trophy.svg') no-repeat; width: 18px; height: 18px; background-size: 100%; left: -21px; top: 13px; background-color: var(--color-slate-100);}
|
||||
|
||||
@@ -29,6 +29,6 @@ class echarts extends wg
|
||||
global $app;
|
||||
$jsFile = $app->getWebRoot() . 'js/echarts/echarts.common.min.js';
|
||||
|
||||
return zui::echarts(inherit($this), set::_call("~((name,selector,options) => $.getScript('$jsFile', null, () => zui.create(name,selector,options)))"));
|
||||
return zui::echarts(inherit($this), set::_call("~((name,selector,options) => $.getLib('$jsFile', {root: false}, () => zui.create(name,selector,options)))"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ textarea[size="sm"],
|
||||
tiptap-editor[size="sm"] {min-height: 142px;}
|
||||
textarea[size="lg"],
|
||||
tiptap-editor[size="lg"] {min-height: 250px;}
|
||||
textarea[size="full"] {min-height: 100%; height: 100%;}
|
||||
|
||||
[data-tippy-root] tiptap-menu-item > button {font-size: inherit; font-family: Arial; line-height: normal;}
|
||||
|
||||
.editor-container {height: auto; width: 100%;}
|
||||
.editor-container {width: 100%;}
|
||||
.editor-container tiptap-editor {display: none;}
|
||||
.editor-container textarea {height: initial;}
|
||||
[data-loaded-editor] .editor-container tiptap-editor {display: block;}
|
||||
|
||||
@@ -30,7 +30,7 @@ class editor extends wg
|
||||
// global $app;
|
||||
// $jsFile = $app->getWebRoot() . 'js/zeneditor/tiptap-component.esm.js';
|
||||
$jsFile = 'https://zui-dist.oop.cc/zeneditor/tiptap-component.esm.js';
|
||||
return '$.getScript("' . $jsFile . '", {type: "module"}, () => {document.body.dataset.loadedEditor = true;});';
|
||||
return '$.getLib("' . $jsFile . '", {type: "module", root: false}, () => {document.body.dataset.loadedEditor = true;});';
|
||||
}
|
||||
|
||||
protected function build(): wg
|
||||
@@ -38,7 +38,8 @@ class editor extends wg
|
||||
$editor = new h
|
||||
(
|
||||
setTag('tiptap-editor'),
|
||||
setClass('form-control', 'p-0', 'h-auto'),
|
||||
setClass('form-control', 'p-0'),
|
||||
$this->prop('size') === 'full' ? setStyle('height', '100%') : setClass('h-auto'),
|
||||
);
|
||||
$props = $this->props->pick(array('createInput', 'uploadUrl', 'placeholder', 'fullscreenable', 'resizable', 'exposeEditor', 'size', 'hideMenubar', 'bubbleMenu', 'menubarMode', 'collaborative', 'hocuspocus', 'docName', 'username', 'userColor'));
|
||||
foreach($props as $key => $value)
|
||||
@@ -56,7 +57,8 @@ class editor extends wg
|
||||
|
||||
return div
|
||||
(
|
||||
setClass('editor-container'),
|
||||
setClass('editor-container p-px'),
|
||||
$props['size'] === 'full' ? setStyle('height', '100%') : setClass('h-auto'),
|
||||
$editor,
|
||||
textarea
|
||||
(
|
||||
|
||||
@@ -13,6 +13,7 @@ class fileList extends wg
|
||||
'showDelete?:bool=true',
|
||||
'showEdit?:bool=true',
|
||||
'object?:object',
|
||||
'padding?:bool=true',
|
||||
);
|
||||
|
||||
public static function getPageCSS(): string|false
|
||||
@@ -77,7 +78,8 @@ class fileList extends wg
|
||||
|
||||
return $fieldset ? new section
|
||||
(
|
||||
setClass('files', 'pt-4', $px, $pb, 'canvas'),
|
||||
setClass('files', 'pt-4', 'canvas'),
|
||||
$this->prop('padding') ? setClass($px, $pb) : null,
|
||||
set::title($lang->files),
|
||||
to::actions
|
||||
(
|
||||
|
||||
@@ -18,12 +18,12 @@ class floatPreNextBtn extends wg
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
}
|
||||
protected function build(): wg
|
||||
protected function build(): wg|array
|
||||
{
|
||||
$preLink = $this->prop('preLink');
|
||||
$nextLink = $this->prop('nextLink');
|
||||
|
||||
return fragment
|
||||
return array
|
||||
(
|
||||
!empty($preLink) ? btn
|
||||
(
|
||||
|
||||
@@ -22,9 +22,9 @@ class floatToolbar extends wg
|
||||
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
|
||||
}
|
||||
|
||||
private function buildDivider(wg|array|null $wg): wg|null
|
||||
private function buildDivider(wg|array|null $wg1, wg|array|null $wg2): wg|null
|
||||
{
|
||||
if(empty($wg)) return null;
|
||||
if(empty($wg1) || empty($wg2)) return null;
|
||||
|
||||
return div(setClass('divider w-px h-6 mx-2'));
|
||||
}
|
||||
@@ -92,9 +92,9 @@ class floatToolbar extends wg
|
||||
(
|
||||
setClass('float-toolbar inline-flex rounded p-1.5 items-center'),
|
||||
$prefixBtns,
|
||||
$this->buildDivider($prefixBtns),
|
||||
$this->buildDivider($prefixBtns, $mainBtns),
|
||||
$mainBtns,
|
||||
empty($mainBtns) ? null : $this->buildDivider($suffixBtns),
|
||||
$this->buildDivider($mainBtns, $suffixBtns),
|
||||
$suffixBtns,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class formBatchItem extends wg
|
||||
*/
|
||||
protected static array $defineProps = array(
|
||||
'name: string', // 表单项名称,无需包含 `[]`。
|
||||
'label?: string|bool', // 列标题。
|
||||
'label: string|bool', // 列标题。
|
||||
'labelClass?: string', // 列标题类名。
|
||||
'labelProps?: string', // 列标题属性,例如 `array('data-toggle' => 'tooltip', 'data-title' 。=> 'This is a tip')`
|
||||
'required?:bool|string="auto"', // 是否必填,如果设置为 `"auto"`,则自动从当前模块 config 中查询。
|
||||
@@ -80,6 +80,7 @@ class formBatchItem extends wg
|
||||
|
||||
$asIndex = $control['type'] === 'index';
|
||||
if($asIndex) $control['type'] = 'static';
|
||||
if($control['type'] == 'static') $name .= '_static';
|
||||
|
||||
return array(
|
||||
h::th
|
||||
|
||||
@@ -27,7 +27,7 @@ class formLabel extends wg
|
||||
setClass('form-label', $required ? 'required' : null),
|
||||
set('for', $for),
|
||||
set($this->getRestProps()),
|
||||
$text,
|
||||
html($text),
|
||||
$this->children(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,20 +40,18 @@ function hideSingleField(hiddenFields)
|
||||
function hideBatchField(hiddenFields)
|
||||
{
|
||||
if(typeof hiddenFields == 'undefined') return false;
|
||||
if(hiddenFields.length == 0) return false;
|
||||
|
||||
hiddenFields.forEach(function(field)
|
||||
{
|
||||
$('th.form-batch-head[data-name="' + field + '"]').addClass('hidden');
|
||||
$('td.form-batch-control[data-name="' + field + '"]').addClass('hidden');
|
||||
$($('template.form-batch-template')[0].content).find('td.form-batch-control[data-name="' + field + '"]').addClass('hidden');
|
||||
});
|
||||
if(hiddenFields.includes('source')) hiddenFields.push('sourceNote');
|
||||
batchForm = $('th.form-batch-head').closest('.form-batch').zui('batchForm');
|
||||
batchForm.toggleCols(hiddenFields, false);
|
||||
}
|
||||
|
||||
if(typeof hiddenFields != 'undefined')
|
||||
{
|
||||
if(typeof formBatch == 'undefined' || !formBatch)
|
||||
{
|
||||
hideSingleField(hiddenFields);
|
||||
setTimeout(function(){hideSingleField(hiddenFields)}, 300);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+55
-16
@@ -67,6 +67,40 @@ class formPanel extends panel
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
}
|
||||
|
||||
protected function created()
|
||||
{
|
||||
$customFields = $this->prop('customFields');
|
||||
if($customFields === true)
|
||||
{
|
||||
global $app, $config, $lang;
|
||||
$module = $app->rawModule;
|
||||
$method = $app->rawMethod;
|
||||
|
||||
$app->loadLang($module);
|
||||
$app->loadModuleConfig($module);
|
||||
|
||||
$key = $method . 'Fields';
|
||||
$listFields = array();
|
||||
$listFieldsKey = 'custom' . ucfirst($key);
|
||||
if(!empty($config->$module->$listFieldsKey)) $listFields = explode(',', $config->$module->$listFieldsKey);
|
||||
if(!empty($config->$module->list->$listFieldsKey)) $listFields = explode(',', $config->$module->list->$listFieldsKey);
|
||||
|
||||
if(empty($listFields))
|
||||
{
|
||||
$this->setProp('customFields', array());
|
||||
return false;
|
||||
}
|
||||
|
||||
$fields = array();
|
||||
foreach($listFields as $field) $fields[$field] = $lang->$module->$field;
|
||||
|
||||
$showFields = explode(',', $config->$module->custom->$key);
|
||||
$customFields = array('list' => $fields, 'show' => $showFields, 'key' => $key);
|
||||
|
||||
$this->setProp('customFields', $customFields);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build heading actions.
|
||||
*
|
||||
@@ -78,19 +112,28 @@ class formPanel extends panel
|
||||
$headingActions = $this->prop('headingActions');
|
||||
if(!$headingActions) $headingActions = array();
|
||||
|
||||
$customFields = $this->prop('customFields');
|
||||
|
||||
/* Custom fields. */
|
||||
$customFields = $this->prop('customFields', array());
|
||||
if($customFields)
|
||||
{
|
||||
global $app;
|
||||
$urlParams = isset($customFields['urlParams']) ? $customFields['urlParams'] : "module={$app->rawModule}§ion=custom&key=batchCreateFields";
|
||||
$listFields = zget($customFields, 'list', array());
|
||||
$showFields = zget($customFields, 'show', array());
|
||||
$key = zget($customFields, 'key', $app->rawMethod);
|
||||
|
||||
$headingActions[] = formSettingBtn(set::customFields($customFields['items']), set::urlParams($urlParams));
|
||||
if($listFields && $key)
|
||||
{
|
||||
$urlParams = "module={$app->rawModule}§ion=custom&key={$key}";
|
||||
$headingActions[] = formSettingBtn
|
||||
(
|
||||
set::customFields(array('list' => $listFields, 'show' => $showFields)),
|
||||
set::urlParams(zget($customFields, 'urlParams', $urlParams)),
|
||||
);
|
||||
|
||||
$this->setProp('headingActions', $headingActions);
|
||||
}
|
||||
}
|
||||
|
||||
$this->setProp('headingActions', $headingActions);
|
||||
|
||||
return parent::buildHeadingActions();
|
||||
}
|
||||
|
||||
@@ -102,15 +145,10 @@ class formPanel extends panel
|
||||
*/
|
||||
protected function buildForm(): wg
|
||||
{
|
||||
$customFields = $this->prop('customFields');
|
||||
$hiddenFields = array();
|
||||
if(!empty($customFields['items']))
|
||||
{
|
||||
$hiddenFields = array_values(array_filter(array_map(function($item)
|
||||
{
|
||||
return $item['show'] ? false : $item['name'];
|
||||
}, $customFields['items'])));
|
||||
}
|
||||
$customFields = $this->prop('customFields', array());
|
||||
$listFields = zget($customFields, 'list', array());
|
||||
$showFields = zget($customFields, 'show', array());
|
||||
$hiddenFields = $listFields && $showFields ? array_values(array_diff(array_keys($listFields), $showFields)) : array();
|
||||
|
||||
if($this->prop('batch'))
|
||||
{
|
||||
@@ -160,7 +198,8 @@ class formPanel extends panel
|
||||
{
|
||||
return div
|
||||
(
|
||||
setClass('panel-body'),
|
||||
setClass('panel-body ' . $this->prop('bodyClass')),
|
||||
set($this->prop('bodyProps')),
|
||||
$this->buildForm()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ class formRowGroup extends formRow
|
||||
$this->prop('title'),
|
||||
$this->block('suffix'),
|
||||
),
|
||||
set($this->getRestProps()),
|
||||
$this->prop('items'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,35 @@
|
||||
function closeCustomPopupMenu(e)
|
||||
{
|
||||
$(e.target).closest('menu').removeClass('show');
|
||||
$('#formSettingBtn-toggle').trigger('click');
|
||||
}
|
||||
|
||||
function cancelFormSetting(e)
|
||||
{
|
||||
$.get($(e.target).closest('button').data('url'), function(data)
|
||||
{
|
||||
e.target.closest('form').querySelectorAll('input').forEach(function(field)
|
||||
{
|
||||
const checked = `,${data},`.indexOf(`,${$(field).val()},`) >=0;
|
||||
$(field).prop('checked', checked);
|
||||
});
|
||||
});
|
||||
|
||||
closeCustomPopupMenu(e);
|
||||
}
|
||||
|
||||
function revertDefaultFields(e)
|
||||
{
|
||||
$.get($(e.target).closest('button').data('url'), function(data, status)
|
||||
$.getJSON($(e.target).closest('button').data('url'), function(response, status)
|
||||
{
|
||||
const customFields = [];
|
||||
const showFields = [];
|
||||
e.target.closest('form').querySelectorAll('input').forEach(function(field)
|
||||
const customFields = response.customFields.split(',');
|
||||
const showFields = response.showFields.split(',');
|
||||
showFields.forEach(function(field)
|
||||
{
|
||||
/* Gather all custom fields. */
|
||||
customFields.push(field.value);
|
||||
if(field.value === 'source') customFields.push('sourceNote');
|
||||
if(!field) return true;
|
||||
|
||||
$(field).prop('checked', false);
|
||||
if(!$(field).data('default')) return;
|
||||
|
||||
/* Gather checked fields to be visible. */
|
||||
$(field).prop('checked', true);
|
||||
showFields.push(field.value);
|
||||
if(field.value === 'source') showFields.push('sourceNote');
|
||||
var $fieldCheckbox = $(e.target).closest('form').find('input[type=checkbox][value="' + field + '"]');
|
||||
if($fieldCheckbox.length == 0) return false;
|
||||
$fieldCheckbox.prop('checked', true);
|
||||
});
|
||||
|
||||
hideAndShowFormFields(customFields, showFields);
|
||||
@@ -68,38 +76,47 @@ function toggleSingleField(customFields, showFields)
|
||||
|
||||
customFields.forEach(function(field)
|
||||
{
|
||||
var $this = $('form [name^="' + field + '"]');
|
||||
if($this.length == 0) return;
|
||||
let $field = $('form [name^="' + field + '"]');
|
||||
if($field.length == 0) return;
|
||||
|
||||
var hidden = !showFields.includes(field);
|
||||
var $inputGroup = $this.closest('.input-group');
|
||||
var $formGroup = $this.closest('.form-group');
|
||||
let hidden = !showFields.includes(field);
|
||||
let $formRow = $field.closest('.form-row');
|
||||
let $formGroup = $field.closest('.form-group');
|
||||
let $inputGroup = $field.closest('.input-group');
|
||||
let $inputControl = $field.closest('.input-control');
|
||||
if($inputGroup.length == 1)
|
||||
{
|
||||
$prev = $this.prev();
|
||||
$prev = $field.prev();
|
||||
if($prev.hasClass('input-group-addon')) $prev.toggleClass('hidden', hidden);
|
||||
|
||||
$this.toggleClass('hidden', hidden);
|
||||
if($this.hasClass('pick-value'))
|
||||
$field.toggleClass('hidden', hidden);
|
||||
if($field.hasClass('pick-value'))
|
||||
{
|
||||
$pickBox = $this.closest('.pick').parent();
|
||||
$pickBox = $field.closest('.pick').parent();
|
||||
$pickBox.toggleClass('hidden', hidden);
|
||||
|
||||
$prev = $pickBox.prev();
|
||||
if($prev.hasClass('input-group-addon')) $prev.toggleClass('hidden', hidden);
|
||||
}
|
||||
|
||||
if($inputControl.length == 1)
|
||||
{
|
||||
if($inputControl.hasClass('has-suffix'))
|
||||
{
|
||||
if($inputControl.prev().hasClass('input-group-addon')) $inputControl.prev().toggleClass('hidden', hidden);
|
||||
$inputControl.toggleClass('hidden', hidden);
|
||||
}
|
||||
}
|
||||
|
||||
if($inputGroup.prev().hasClass('form-label')) $inputGroup.prev().toggleClass('hidden', hidden);
|
||||
$inputGroup.toggleClass('hidden', $inputGroup.children().length == $inputGroup.children('.hidden').length);
|
||||
$formGroup.toggleClass('hidden', $formGroup.children().length == $formGroup.children('.hidden').length);
|
||||
}
|
||||
else
|
||||
{
|
||||
$formGroup.toggleClass('hidden', hidden);
|
||||
}
|
||||
})
|
||||
|
||||
$('form .form-row').each(function()
|
||||
{
|
||||
var $this = $(this);
|
||||
$this.removeClass('hidden');
|
||||
if($this.find('.form-group.hidden').length > 0 && $this.find('.form-group:not(.hidden)').length == 0) $this.addClass('hidden');
|
||||
$formRow.toggleClass('hidden', $formRow.children().length == $formRow.children('.hidden').length);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,11 +124,28 @@ function toggleBatchField(customFields, showFields)
|
||||
{
|
||||
if(typeof customFields == 'undefined') return false;
|
||||
|
||||
let hiddenFields = [];
|
||||
let shownFields = [];
|
||||
customFields.forEach(function(field)
|
||||
{
|
||||
var hidden = !showFields.includes(field);
|
||||
if($('th.form-batch-head[data-name="' + field + '"]').length == 0) return true;
|
||||
|
||||
let hidden = !showFields.includes(field);
|
||||
$('th.form-batch-head[data-name="' + field + '"]').toggleClass('hidden', hidden);
|
||||
$('td.form-batch-control[data-name="' + field + '"]').toggleClass('hidden', hidden);
|
||||
$($('template.form-batch-template')[0].content).find('td.form-batch-control[data-name="' + field + '"]').toggleClass('hidden', hidden);
|
||||
if(field === 'source')
|
||||
{
|
||||
$('th.form-batch-head[data-name="sourceNote"]').toggleClass('hidden', hidden);
|
||||
$('td.form-batch-control[data-name="sourceNote"]').toggleClass('hidden', hidden);
|
||||
$($('template.form-batch-template')[0].content).find('td.form-batch-control[data-name="sourceNote"]').toggleClass('hidden', hidden);
|
||||
}
|
||||
|
||||
hidden ? hiddenFields.push(field) : shownFields.push(field);
|
||||
if(field === 'source') hidden ? hiddenFields.push('sourceNote') : shownFields.push('sourceNote');
|
||||
});
|
||||
|
||||
batchForm = $('th.form-batch-head').closest('.form-batch').zui('batchForm');
|
||||
if(hiddenFields.length > 0) batchForm.toggleCols(hiddenFields, false);
|
||||
if(shownFields.length > 0) batchForm.toggleCols(shownFields, true);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,26 @@ class formSettingBtn extends wg
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
}
|
||||
|
||||
private function buildCustomFields(array $customFields): array
|
||||
{
|
||||
$listFields = zget($customFields, 'list', array());
|
||||
$showFields = zget($customFields, 'show', array());
|
||||
if(!$listFields) return array();
|
||||
|
||||
$items = array();
|
||||
foreach($listFields as $field => $text)
|
||||
{
|
||||
$items[] = checkbox
|
||||
(
|
||||
set::name('fields[]'),
|
||||
set::value($field),
|
||||
set::text($text),
|
||||
set::checked($showFields ? in_array($field, $showFields) : true)
|
||||
);
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
$customFields = $this->prop('customFields', array());
|
||||
@@ -36,10 +56,12 @@ class formSettingBtn extends wg
|
||||
global $lang;
|
||||
|
||||
$customLink = createLink('custom', 'ajaxSaveCustomFields', $this->prop('urlParams', ''));
|
||||
$cancelLink = createLink('custom', 'ajaxGetCustomFields', $this->prop('urlParams', ''));
|
||||
return dropdown
|
||||
(
|
||||
set::arrow('false'),
|
||||
set::placement('bottom-end'),
|
||||
set::id('formSettingBtn'),
|
||||
to::trigger(btn(set::icon('cog-outline'), setClass('ghost'), set::caret(false))),
|
||||
to::menu(menu
|
||||
(
|
||||
@@ -53,21 +75,11 @@ class formSettingBtn extends wg
|
||||
set::actions(array
|
||||
(
|
||||
btn(set::text($lang->save), setClass('primary'), on::click('onSubmitFormtSetting')),
|
||||
btn(set::text($lang->cancel), set::btnType('reset'), on::click('closeCustomPopupMenu')),
|
||||
btn(set::text($lang->restore), setClass('text-primary ghost font-bold'), set::href('#'), set('data-url', $customLink), on::click('revertDefaultFields')),
|
||||
btn(set::text($lang->cancel), set::btnType('button'), on::click('cancelFormSetting'), set('data-url', $cancelLink)),
|
||||
btn(set::text($lang->restore), setClass('text-primary ghost'), set::href('#'), set('data-url', $customLink), on::click('revertDefaultFields')),
|
||||
)),
|
||||
to::headingActions(array(btn(set::icon('close'), setClass('ghost'), set::size('sm'), on::click('closeCustomPopupMenu')))),
|
||||
array_map(function($field)
|
||||
{
|
||||
return checkbox
|
||||
(
|
||||
set::name('fields[]'),
|
||||
set::value($field['name']),
|
||||
set::text($field['text']),
|
||||
set::checked(isset($field['show']) ? $field['show'] : false),
|
||||
set('data-default', isset($field['default']) ? $field['default'] : false)
|
||||
);
|
||||
}, $customFields),
|
||||
$this->buildCustomFields($customFields)
|
||||
)
|
||||
))
|
||||
);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace zin;
|
||||
|
||||
class gantt extends wg
|
||||
{
|
||||
public static function getPageCSS(): string
|
||||
{
|
||||
return file_get_contents(dirname(__DIR__, 4) . '/www/js/dhtmlxgantt/min.css');
|
||||
}
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
global $app;
|
||||
$jsFile = $app->getWebRoot() . 'js/dhtmlxgantt/min.js';
|
||||
|
||||
return zui::gantt(inherit($this), set::_call("~((name,selector,options) => $.getLib('$jsFile', {root: false}, () => {gantt.plugins({marker: true, critical_path: true, fullscreen: true, tooltip: true, click_drag: true});zui.create(name,selector,options);}))"));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
$(function()
|
||||
{
|
||||
const $searchQuery = $('#globalSearchInput');
|
||||
const setSelected = function()
|
||||
{
|
||||
$searchQuery.data('selectedKey', getSearchType());
|
||||
};
|
||||
|
||||
$searchQuery.on('change keyup paste input propertychange', setSelected).on('focus', function()
|
||||
{
|
||||
setTimeout(setSelected, 300);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the search Type according to the current module and the current method.
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
function getSearchType()
|
||||
{
|
||||
if(vision == 'lite') return 'story';
|
||||
|
||||
const appInfo = $.apps.getLastApp();
|
||||
const appPageModuleName = appInfo.iframe.contentWindow.config.currentModule;
|
||||
const appPageMethodName = appInfo.iframe.contentWindow.config.currentMethod;
|
||||
|
||||
if(appPageModuleName == 'product' && appPageMethodName == 'browse') return 'story';
|
||||
if(appPageModuleName == 'my' || appPageModuleName == 'user') return appPageMethodName;
|
||||
|
||||
const projectMethod = 'task|story|bug|build';
|
||||
if(appPageModuleName == 'project' && projectMethod.indexOf(appPageMethodName) != -1) return appPageMethodName;
|
||||
|
||||
if(searchObjectList.indexOf(appPageModuleName) == -1) return 'bug';
|
||||
|
||||
return appPageModuleName;
|
||||
}
|
||||
|
||||
function getSearchUrl(searchType, searchValue)
|
||||
{
|
||||
if(searchValue)
|
||||
{
|
||||
const reg = /[^0-9]/;
|
||||
let searchUrl = $.createLink('search', 'index');
|
||||
searchUrl += (searchUrl.indexOf('?') >= 0 ? '&' : '?') + 'words=' + searchValue + '&type=all';
|
||||
if(!searchType || searchType == 'all' || reg.test(searchValue)) return searchUrl;
|
||||
|
||||
const types = searchType.split('-');
|
||||
const searchModule = types[0];
|
||||
const searchMethod = typeof(types[1]) == 'undefined' ? 'view' : types[1];
|
||||
searchUrl = $.createLink(searchModule, searchMethod, "id=" + searchValue);
|
||||
const assetType = ',story,issue,risk,opportunity,doc,';
|
||||
if(assetType.indexOf(',' + searchModule + ',') == -1) return searchUrl;
|
||||
|
||||
const link = $.createLink('index', 'ajaxGetViewMethod' , 'objectID=' + searchValue + '&objectType=' + searchModule);
|
||||
$.get(link, function(data)
|
||||
{
|
||||
if(data) return $.createLink('assetlib', data, "id=" + searchValue);
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
window.globalSearch = function(key, searchValue)
|
||||
{
|
||||
let searchType = key;
|
||||
if(key == 'program') searchType = 'program-product';
|
||||
if(key == 'deploystep') searchType = 'deploy-viewstep';
|
||||
|
||||
const searchUrl = getSearchUrl(searchType, searchValue);
|
||||
if(searchUrl) openUrl(searchUrl);
|
||||
};
|
||||
@@ -8,12 +8,35 @@ class globalSearch extends wg
|
||||
'commonSearchText?: string',
|
||||
'commonSearchUrl: string',
|
||||
'searchItems: array',
|
||||
'searchFunc: callable'
|
||||
);
|
||||
|
||||
public static function getPageJS(): string|false
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
}
|
||||
|
||||
protected function build(): array
|
||||
{
|
||||
global $lang;
|
||||
$this->setDefaultProps(array('commonSearchText' => $lang->searchAB));
|
||||
global $config, $lang;
|
||||
|
||||
jsVar('searchObjectList', array_keys($lang->searchObjects));
|
||||
|
||||
if($config->systemMode == 'light') unset($lang->searchObjects['program']);
|
||||
unset($lang->searchObjects['all']);
|
||||
|
||||
$searchItems = array();
|
||||
foreach($lang->searchObjects as $key => $module)
|
||||
{
|
||||
$searchItems[] = array('key' => $key, 'text' => $module);
|
||||
}
|
||||
|
||||
$this->setDefaultProps(array(
|
||||
'commonSearchText' => $lang->searchAB,
|
||||
'commonSearchKey' => 'all',
|
||||
'searchItems' => $searchItems,
|
||||
'searchFunc' => jsRaw('window.globalSearch'),
|
||||
));
|
||||
|
||||
$input = inputGroup
|
||||
(
|
||||
@@ -32,7 +55,7 @@ class globalSearch extends wg
|
||||
$input->setProp('data-zin-id', $input->gid);
|
||||
$props = array_merge
|
||||
(
|
||||
$this->props->pick(array('commonSearchText', 'commonSearchUrl', 'searchItems')),
|
||||
$this->props->pick(array('commonSearchText', 'commonSearchKey', 'searchItems', 'searchFunc')),
|
||||
array('_to' => "[data-zin-id='{$input->gid}']")
|
||||
);
|
||||
return array(
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
window.updateUserAvatar = function(dialog)
|
||||
{
|
||||
const newSrc = $(dialog).find('.avatar-img').prop('src');
|
||||
const code = $('#toolbar').find('.avatar-img').dataset('code');
|
||||
$('.avatar-img[data-code="' + code + '"]').prop('src', newSrc);
|
||||
};
|
||||
+16
-17
@@ -18,6 +18,11 @@ class header extends wg
|
||||
'toolbar' => array('map' => 'btn')
|
||||
);
|
||||
|
||||
public static function getPageJS(): string|false
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
}
|
||||
|
||||
protected function buildHeading()
|
||||
{
|
||||
if($this->hasBlock('heading')) return $this->block('heading');
|
||||
@@ -87,14 +92,7 @@ class header extends wg
|
||||
/* The standalone lite version removes the lite interface button */
|
||||
if(trim($config->visions, ',') == 'lite') return true;
|
||||
|
||||
if(count($userVisions) < 2 || count($configVisions) < 2)
|
||||
{
|
||||
return btn
|
||||
(
|
||||
setClass('secondary ring-0 rounded'),
|
||||
$lang->visionList[$currentVision]
|
||||
);
|
||||
}
|
||||
if(count($userVisions) < 2 || count($configVisions) < 2) return btn($lang->visionList[$currentVision]);
|
||||
|
||||
$items = array();
|
||||
foreach($userVisions as $vision)
|
||||
@@ -116,6 +114,7 @@ class header extends wg
|
||||
set::text($lang->visionList[$currentVision]),
|
||||
set::caret(false)
|
||||
),
|
||||
|
||||
set::id('versionMenu'),
|
||||
set::trigger('hover'),
|
||||
set::placement('bottom'),
|
||||
@@ -145,7 +144,7 @@ class header extends wg
|
||||
'href' => createLink('my', 'profile', '', '', true),
|
||||
'className' => 'items-center gap-2 px-2 py-1 row text-inherit',
|
||||
'style' => array('padding-left' => 0),
|
||||
'data-toggle' => 'iframeModal',
|
||||
'data-toggle' => 'modal',
|
||||
'data-size' => 700,
|
||||
'data-id' => 'profile',
|
||||
'renders' => array(array('__html' => implode('', array
|
||||
@@ -168,7 +167,7 @@ class header extends wg
|
||||
'icon' => 'account',
|
||||
'text' => $lang->profile,
|
||||
'class' => 'iframe',
|
||||
'data-toggle' => 'iframeModal',
|
||||
'data-toggle' => 'modal',
|
||||
'data-size' => 700,
|
||||
'data-id' => 'profile',
|
||||
);
|
||||
@@ -199,7 +198,7 @@ class header extends wg
|
||||
'text' => $lang->preference,
|
||||
'class' => 'iframe',
|
||||
'data-width' => 700,
|
||||
'data-toggle' => 'iframeModal'
|
||||
'data-toggle' => 'modal'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -210,7 +209,7 @@ class header extends wg
|
||||
'url' => createLink('my', 'changepassword', '', '', true),
|
||||
'icon' => 'cog-outline',
|
||||
'text' => $lang->changePassword,
|
||||
'data-toggle' => 'iframeModal',
|
||||
'data-toggle' => 'modal',
|
||||
'data-size' => 'sm'
|
||||
);
|
||||
}
|
||||
@@ -241,12 +240,12 @@ class header extends wg
|
||||
$helpItems = array();
|
||||
$manualUrl = ((!empty($config->isINT)) ? $config->manualUrl['int'] : $config->manualUrl['home']) . '&theme=' . $_COOKIE['theme'];
|
||||
$helpItems[] = array('text' => $lang->manual, 'url' => $manualUrl, 'attrs' => array('data-app' => 'help'));
|
||||
$helpItems[] = array('text' => $lang->changeLog, 'url' => createLink('misc', 'changeLog'), 'data-width' => 800, 'data-toggle' => 'iframeModal', 'data-headerless' => true, 'data-keyboard' => true, 'data-backdrop' => true);
|
||||
$helpItems[] = array('text' => $lang->changeLog, 'url' => createLink('misc', 'changeLog'), 'data-toggle' => 'modal');
|
||||
$items[] = array('text' => $lang->help, 'icon' => 'help', 'items' => $helpItems);
|
||||
|
||||
/* printClientLink */
|
||||
|
||||
$items[] = array('text' => $lang->aboutZenTao, 'icon' => 'about', 'url' => createLink('misc', 'about'), 'data-toggle' => 'iframeModal', 'data-width' => 1050, 'data-headerless' => true, 'data-keyboard' => true, 'data-backdrop' => true);
|
||||
$items[] = array('text' => $lang->aboutZenTao, 'icon' => 'about', 'url' => createLink('misc', 'about'), 'data-toggle' => 'modal');
|
||||
$items[] = array('type' => 'html', 'className' => 'menu-item', 'html' => $lang->designedByAIUX);
|
||||
|
||||
$items[] = array('type' => 'divider');
|
||||
@@ -257,7 +256,7 @@ class header extends wg
|
||||
}
|
||||
else
|
||||
{
|
||||
$items[] = array('text' => $lang->logout, 'url' => createLink('user', 'logout'), 'target' => '_top', 'icon' => 'exit');
|
||||
$items[] = array('text' => $lang->logout, 'url' => "javascript:$.apps.logout()", 'icon' => 'exit');
|
||||
}
|
||||
|
||||
return dropdown
|
||||
@@ -347,7 +346,7 @@ class header extends wg
|
||||
case 'doc':
|
||||
$params = "objectType=&objectID=0&libID=0";
|
||||
$createMethod = 'selectLibType';
|
||||
$item['data-toggle'] = 'iframeModal';
|
||||
$item['data-toggle'] = 'modal';
|
||||
break;
|
||||
case 'project':
|
||||
if($config->vision == 'lite')
|
||||
@@ -358,7 +357,7 @@ class header extends wg
|
||||
{
|
||||
$params = "programID=0&from=global";
|
||||
$createMethod = 'createGuide';
|
||||
$item['data-toggle'] = 'iframeModal';
|
||||
$item['data-toggle'] = 'modal';
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
.history-list > li strong {color: var(--color-slate-800);}
|
||||
.history-list > li {position: relative;}
|
||||
.history-list > li::before {display: block; content: ''; position: absolute; width: 1px; height: 100%; background: var(--color-gray-200); top: 12px; left: 10px;}
|
||||
.history-list > li:last-child::before {display: none;}
|
||||
.history-list.sort-reverse > li::before {display: block; content: ''; position: absolute; width: 1px; height: 100%; background: var(--color-gray-200); top: 12px; left: 10px;}
|
||||
.history-list.sort-reverse > li:first-child::before {display: none;}
|
||||
.history-list.sort-reverse > li:last-child::before {display: block;}
|
||||
.history-list > li:last-child::before {display: none;}
|
||||
.history-list.sort-reverse > li:first-child::before {display: none;}
|
||||
|
||||
@@ -13,6 +13,7 @@ class history extends wg
|
||||
'commentUrl?: string',
|
||||
'commentBtn?: bool',
|
||||
'bodyClass?: string',
|
||||
'hasComment?: bool',
|
||||
);
|
||||
|
||||
public static function getPageCSS(): string|false
|
||||
@@ -25,12 +26,12 @@ class history extends wg
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
}
|
||||
|
||||
private function marker(int $num): wg
|
||||
private function marker(int|string $content): wg
|
||||
{
|
||||
return span
|
||||
(
|
||||
setClass('marker', 'relative', 'z-10', 'text-sm', 'rounded-full', 'aspect-square', 'inline-flex', 'justify-center', 'items-center', 'mr-1', 'border', 'h-5', 'w-5', 'z-10'),
|
||||
$num
|
||||
is_int($content) ? $content : icon('check', setClass('text-success font-semibold'))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -105,8 +106,7 @@ class history extends wg
|
||||
return li
|
||||
(
|
||||
setClass('mb-2 flex'),
|
||||
set::value($i),
|
||||
$this->marker($i),
|
||||
$this->marker($action->action === 'finished' ? 'finished' : $i),
|
||||
$actionItemView
|
||||
);
|
||||
}
|
||||
@@ -231,7 +231,7 @@ class history extends wg
|
||||
$isInModal = isAjaxRequest('modal');
|
||||
$padding = $isInModal ? 'px-3 pd-3' : 'px-6 pb-6';
|
||||
|
||||
list($commentUrl, $bodyClass) = $this->prop(array('commentUrl', 'bodyClass'));
|
||||
list($commentUrl, $bodyClass, $hasComment) = $this->prop(array('commentUrl', 'bodyClass', 'hasComment'));
|
||||
return panel
|
||||
(
|
||||
setClass('history', 'pt-4', 'h-full', $padding),
|
||||
@@ -258,11 +258,11 @@ class history extends wg
|
||||
)
|
||||
),
|
||||
div(setClass('mt-3'), $this->historyList()),
|
||||
commentDialog
|
||||
$hasComment !== false ? commentDialog
|
||||
(
|
||||
set::name('comment'),
|
||||
set::url($commentUrl),
|
||||
)
|
||||
) : null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace zin;
|
||||
|
||||
class imgCutter extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'src: string',
|
||||
'btnText?: string',
|
||||
'tipText?: string',
|
||||
'coverColor?: string',
|
||||
'coverOpacity?: number',
|
||||
'defaultWidth?: number',
|
||||
'defaultHeight?: number',
|
||||
'minWidth?: number',
|
||||
'minHeight?: number',
|
||||
'fixedRatio?: boolean',
|
||||
'onSizeError?: callable',
|
||||
'ready: callable',
|
||||
'handleBtnClick: callable',
|
||||
);
|
||||
|
||||
protected function build(): array
|
||||
{
|
||||
$btnText = $this->prop('btnText');
|
||||
$tipText = $this->prop('tipText');
|
||||
|
||||
$imgCutter = div
|
||||
(
|
||||
set($this->getRestProps()),
|
||||
setClass('img-cutter'),
|
||||
div
|
||||
(
|
||||
setClass('canvas'),
|
||||
img(set::src($this->prop('src')))
|
||||
),
|
||||
div
|
||||
(
|
||||
setClass('text-xl font-bold py-3'),
|
||||
$tipText
|
||||
),
|
||||
btn
|
||||
(
|
||||
set::type('primary'),
|
||||
$btnText
|
||||
)
|
||||
);
|
||||
$imgCutter->setProp('data-zin-id', $imgCutter->gid);
|
||||
|
||||
$props = array_merge($this->props->pick(array('coverColor', 'coverOpacity', 'defaultWidth', 'defaultHeight', 'minWidth', 'minHeight', 'fixedRatio', 'onSizeError', 'ready', 'handleBtnClick')), array('_to' => "[data-zin-id='{$imgCutter->gid}']"));
|
||||
|
||||
return array(
|
||||
$imgCutter,
|
||||
zui::imgCutter(set($props)),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ class input extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'type: string',
|
||||
'name: string',
|
||||
'name?: string',
|
||||
'id?: string',
|
||||
'class?: string',
|
||||
'value?: string',
|
||||
|
||||
@@ -11,6 +11,7 @@ class inputControl extends wg
|
||||
'suffix?: mixed',
|
||||
'prefixWidth?: string|int',
|
||||
'suffixWidth?: string|int',
|
||||
'class?: string',
|
||||
);
|
||||
|
||||
protected static array $defineBlocks = array(
|
||||
@@ -20,27 +21,27 @@ class inputControl extends wg
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
list($prefix, $suffix, $prefixWidth, $suffixWidth) = $this->prop(['prefix', 'suffix', 'prefixWidth', 'suffixWidth']);
|
||||
list($prefix, $suffix, $prefixWidth, $suffixWidth, $class) = $this->prop(['prefix', 'suffix', 'prefixWidth', 'suffixWidth', 'class']);
|
||||
|
||||
if(empty($prefix)) $prefix = $this->block('prefix');
|
||||
if(empty($suffix)) $suffix = $this->block('suffix');
|
||||
|
||||
$class = array('input-control');
|
||||
$class = "input-control {$class}";
|
||||
$vars = array();
|
||||
if(!empty($prefix))
|
||||
{
|
||||
if(is_numeric($prefixWidth))
|
||||
{
|
||||
$vars['input-control-prefix'] = $prefixWidth . 'px';
|
||||
$class[] = 'has-prefix';
|
||||
$class .= ' has-prefix';
|
||||
}
|
||||
elseif(!empty($prefixWidth))
|
||||
{
|
||||
$class[] = "has-prefix-$prefixWidth";
|
||||
$class .= " has-prefix-$prefixWidth";
|
||||
}
|
||||
else
|
||||
{
|
||||
$class[] = 'has-prefix';
|
||||
$class .= ' has-prefix';
|
||||
}
|
||||
}
|
||||
if(!empty($suffix))
|
||||
@@ -48,15 +49,15 @@ class inputControl extends wg
|
||||
if(is_numeric($suffixWidth))
|
||||
{
|
||||
$vars['input-control-suffix'] = $suffixWidth . 'px';
|
||||
$class[] = 'has-suffix';
|
||||
$class .= ' has-suffix';
|
||||
}
|
||||
elseif(!empty($suffixWidth))
|
||||
{
|
||||
$class[] = "has-suffix-$suffixWidth";
|
||||
$class .= " has-suffix-$suffixWidth";
|
||||
}
|
||||
else
|
||||
{
|
||||
$class[] = 'has-suffix';
|
||||
$class .= ' has-suffix';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
.label.status-clarify, .label.status-draft {--tw-ring-color: #8166ee;}
|
||||
.label.status-cancel, .label.status-canceled, .label.status-investigate {--tw-ring-color: #838a9d;}
|
||||
.label.status-canceled, .label.status-closed, .label.status-testtask .label.status-done {--tw-ring-color: #9ea3b0;}
|
||||
.label.status-blocked, .label.status-hangup, .label.status-pause, .label.status-suspended {--tw-ring-color: #b89664;}
|
||||
.label.status-noreview, .label.status-reviewing, .label.status-wait .label.status-testcase {--tw-ring-color: #18a6fd;}
|
||||
.label.status-active .label.status-risk, .label.status-changed, .label.status-changing, .label.status-fail {--tw-ring-color: #fb2b2b;}
|
||||
.label.status-active, .label.status-asked, .label.status-normal, .label.status-unconfirmed, .label.status-wait {--tw-ring-color: #313c52;}
|
||||
.label.status-active .label.status-issue, .label.status-checking, .label.status-commenting, .label.status-confirmed, .label.status-doing {--tw-ring-color: #ff6f42;}
|
||||
.label.status-checked, .label.status-done, .label.status-pass, .label.status-replied, .label.status-resolved, .label.status-success {--tw-ring-color: #0dbb7d;}
|
||||
@@ -8,6 +8,11 @@ class label extends wg
|
||||
'text?:string'
|
||||
);
|
||||
|
||||
public static function getPageCSS(): string|false
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
|
||||
}
|
||||
|
||||
public function onAddChild($child)
|
||||
{
|
||||
if(is_string($child) && !$this->props->has('text'))
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#mainNavbar .main-navbar-left {position: absolute; z-index: 2;}
|
||||
#mainNavbar .main-navbar-left #switcher {position: relative; top: 5px; left: 1rem;}
|
||||
#mainNavbar .main-navbar-left #switcher > .dropmenu-btn {background: #FFF;}
|
||||
#mainNavbar .main-navbar-left #switcher > .dropmenu-btn:before {background: unset;}
|
||||
#mainNavbar .main-navbar-left #switcher:after, #mainNavbar .main-navbar-left #switcher:before {position: absolute; display: block; width: 0; height: 0; content: ' '; border-style: solid; border-width: 17px 0 17px 8px; top: -1px;}
|
||||
#mainNavbar .main-navbar-left #switcher:before {border-color: transparent transparent transparent rgb(var(--color-primary-500-rgb)); right: -8px;}
|
||||
#mainNavbar .main-navbar-left #switcher:after {border-color: transparent transparent transparent #FFF; right: -7px; border-radius: 2px;}
|
||||
#mainNavbar .main-navbar-left #switcher .icon-angle-right {display: none;}
|
||||
#mainNavbar .main-navbar-left #switcher .caret {color: rgb(var(--color-link-hover-rgb));}
|
||||
#mainNavbar .main-navbar-left #switcher .text {color: rgb(var(--color-primary-500-rgb));}
|
||||
|
||||
@media (min-width: 1400px) {#mainNavbar .main-navbar-left #switcher {left: 2.5rem;};}
|
||||
@@ -34,6 +34,17 @@ class mainNavbar extends nav
|
||||
'right' => array('map' => 'toolbar'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Load the css file.
|
||||
*
|
||||
* @access public
|
||||
* @return string|false
|
||||
*/
|
||||
public static function getPageCSS(): string|false
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
|
||||
}
|
||||
|
||||
protected function created()
|
||||
{
|
||||
global $app;
|
||||
@@ -91,6 +102,7 @@ class mainNavbar extends nav
|
||||
|
||||
$leftBlock = $this->block('left');
|
||||
$rightBlock = $this->block('right');
|
||||
if(empty($leftBlock)) $leftBlock = $this->buildSwitcher();
|
||||
|
||||
return div
|
||||
(
|
||||
@@ -104,4 +116,37 @@ class mainNavbar extends nav
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建2.5级下拉菜单。
|
||||
* Build switcher.
|
||||
*
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function buildSwitcher(): array|null
|
||||
{
|
||||
global $app, $config;
|
||||
|
||||
$moduleName = $app->rawModule;
|
||||
$methodName = $app->rawMethod;
|
||||
|
||||
if(in_array("$moduleName-$methodName", is_array($config->excludeSwitcherList) ? $config->excludeSwitcherList : array())) return null;
|
||||
|
||||
if(in_array($moduleName, is_array($config->hasSwitcherModules) ? $config->hasSwitcherModules : array()))
|
||||
{
|
||||
$fetcher = createLink($moduleName, 'ajaxGetDropMenu', data('switcherParams'));
|
||||
return array(zui::dropmenu
|
||||
(
|
||||
setID("{$moduleName}-menu"),
|
||||
set('_id', 'switcher'),
|
||||
set('data', data('data')),
|
||||
set('_props', array('data-fetcher' => $fetcher)),
|
||||
set(array('fetcher' => createLink($moduleName, 'ajaxGetDropMenu', data('switcherParams')), 'text' => data('switcherText'), 'defaultValue' => data('switcherObjectID'))),
|
||||
set($this->getRestProps())
|
||||
));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* The mindmap widget class file of zin module of ZenTaoPMS.
|
||||
*
|
||||
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
|
||||
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
* @author sunhao<sunhao@easycorp.ltd>
|
||||
* @package zin
|
||||
* @link http://www.zentao.net
|
||||
*/
|
||||
namespace zin;
|
||||
|
||||
/**
|
||||
* 脑图(mindmap)部件。
|
||||
* The mindmap widget class.
|
||||
*/
|
||||
class mindmap extends wg
|
||||
{
|
||||
protected static array $defineProps = array
|
||||
(
|
||||
'data?: array',
|
||||
'width?: string|number="100%"',
|
||||
'height?: string|number="300px"',
|
||||
'hotkeyEnable?: bool',
|
||||
'hotkeys?: array',
|
||||
'lang?: string',
|
||||
'langs?: array',
|
||||
'nodeTeamplate?: string',
|
||||
'hSpace?: number',
|
||||
'vSpace?: number',
|
||||
'canvasPadding?: number',
|
||||
'removingNodeTip?: string',
|
||||
'lineCurvature?: number',
|
||||
'subLineWidth?: number',
|
||||
'lineColor?: string',
|
||||
'lineOpacity?: number',
|
||||
'lineSaturation?: number',
|
||||
'lineLightness?: number',
|
||||
'nodeLineWidth?: number',
|
||||
'showToggleButton?: bool',
|
||||
'readonly?: bool',
|
||||
'minimap?: bool',
|
||||
'toolbar?: bool',
|
||||
'zoom?: number',
|
||||
'zoomMax?: number',
|
||||
'zoomMin?: number',
|
||||
'minimapHeight?: number'
|
||||
);
|
||||
|
||||
protected function build(): array
|
||||
{
|
||||
global $app;
|
||||
|
||||
list($width, $height) = $this->prop(array('width', 'height', 'data'));
|
||||
$dataVarName = "_mindmap_$this->gid";
|
||||
$mindmapPath = $app->getWebRoot() . 'js/mindmap/index.html?options=' . $dataVarName;
|
||||
$options = $this->props->pick(array('hotkeyEnable', 'hotkeys', 'lang', 'langs', 'data', 'nodeTeamplate', 'hSpace', 'vSpace', 'canvasPadding', 'removingNodeTip', 'lineCurvature', 'subLineWidth', 'lineColor', 'lineOpacity', 'lineSaturation', 'lineLightness', 'nodeLineWidth', 'showToggleButton', 'readonly', 'minimap', 'toolbar', 'zoom', 'zoomMax', 'zoomMin', 'minimapHeight'));
|
||||
return array
|
||||
(
|
||||
h::iframe
|
||||
(
|
||||
set::class('mindmap-iframe'),
|
||||
set::src($mindmapPath),
|
||||
set::allowfullscreen(true),
|
||||
set::allowtransparency(true),
|
||||
set::frameborder('no'),
|
||||
set::scrolling('auto'),
|
||||
set::style(array('width' => $width, 'height' => $height)),
|
||||
set($this->getRestProps())
|
||||
),
|
||||
h::jsVar("window.$dataVarName", $options)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,7 @@ class modalDialog extends wg
|
||||
return div
|
||||
(
|
||||
setClass('modal-body', $this->prop('bodyClass')),
|
||||
on::scroll('e.stopPropagation();'),
|
||||
set($this->prop('bodyProps')),
|
||||
$this->children(),
|
||||
$rawContent ? rawContent() : null,
|
||||
|
||||
@@ -62,7 +62,8 @@ class modalHeader extends wg
|
||||
|
||||
return h::div
|
||||
(
|
||||
set::class('modal-header panel-form rounded-md canvas mx-auto size-lg'),
|
||||
set::class('modal-header panel-form rounded-md canvas mx-auto'),
|
||||
set::style(array('margin-bottom' => '0px')),
|
||||
$header
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.modal-next-step {width: min-content !important;}
|
||||
.modal-next-step .modal-footer {padding: 0 32px 24px 32px; justify-content: center;}
|
||||
.modal-next-step .modal-footer .toolbar {gap: 8px;}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace zin;
|
||||
|
||||
class modalNextStep extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'tip: string',
|
||||
'items: array',
|
||||
);
|
||||
|
||||
public static function getPageCSS(): string|false
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
|
||||
}
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
$tip = $this->prop('tip');
|
||||
$items = $this->prop('items');
|
||||
|
||||
return modalDialog
|
||||
(
|
||||
setClass('modal-next-step'),
|
||||
row
|
||||
(
|
||||
set::align('center'),
|
||||
setClass('gap-2'),
|
||||
center
|
||||
(
|
||||
setClass('w-8 h-8 rounded-full success'),
|
||||
icon(setClass('text-xl font-bold'), 'check'),
|
||||
),
|
||||
span
|
||||
(
|
||||
setClass('font-medium text-md'),
|
||||
$tip
|
||||
)
|
||||
),
|
||||
set::footerActions($items),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace zin;
|
||||
class moduleMenu extends wg
|
||||
{
|
||||
private array $modules = array();
|
||||
private static array $filterMap = array();
|
||||
|
||||
protected static array $defineProps = array(
|
||||
'modules: array',
|
||||
@@ -12,7 +13,8 @@ class moduleMenu extends wg
|
||||
'settingLink?: string',
|
||||
'closeLink: string',
|
||||
'showDisplay?: bool=true',
|
||||
'allText?: string'
|
||||
'allText?: string',
|
||||
'title?: string'
|
||||
);
|
||||
|
||||
public static function getPageCSS(): string|false
|
||||
@@ -41,12 +43,22 @@ class moduleMenu extends wg
|
||||
else unset($item['items']);
|
||||
$parentItems[] = $item;
|
||||
}
|
||||
|
||||
return $parentItems;
|
||||
}
|
||||
|
||||
private function getChildModule(int|string $id): array
|
||||
{
|
||||
return array_filter($this->modules, fn($module) => $module->parent == $id);
|
||||
return array_filter($this->modules, function($module) use($id)
|
||||
{
|
||||
/* Remove the rendered module. */
|
||||
if(isset(static::$filterMap["$module->parent-$module->id"])) return false;
|
||||
|
||||
if($module->parent != $id) return false;
|
||||
|
||||
static::$filterMap["$module->parent-$module->id"] = true;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private function setMenuTreeProps(): void
|
||||
@@ -57,6 +69,8 @@ class moduleMenu extends wg
|
||||
|
||||
private function getTitle(): string
|
||||
{
|
||||
if($this->prop('title')) return $this->prop('title');
|
||||
|
||||
global $lang;
|
||||
$activeKey = $this->prop('activeKey');
|
||||
|
||||
@@ -117,14 +131,17 @@ class moduleMenu extends wg
|
||||
);
|
||||
}
|
||||
|
||||
private function buildCloseBtn(): ?wg
|
||||
private function buildCloseBtn(): wg|null
|
||||
{
|
||||
$closeLink = $this->prop('closeLink');
|
||||
if(!$closeLink) return null;
|
||||
|
||||
$activeKey = $this->prop('activeKey');
|
||||
if(empty($activeKey)) return null;
|
||||
|
||||
return a
|
||||
(
|
||||
set('href', $this->prop('closeLink')),
|
||||
set('href', $closeLink),
|
||||
icon('close', setStyle('color', 'var(--color-slate-600)'))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#navigator > li {position: relative; display: inline-block;}
|
||||
#navigator > li > a {min-width: 100px;}
|
||||
#navigator > li:first-child > a {border-radius: 4px 0 0 4px;}
|
||||
#navigator > li:last-child > a {border-radius: 0 4px 4px 0;}
|
||||
#navigator > li + li:before,
|
||||
#navigator > li + li:after {content: ' '; width: 0; height: 0; border-style: solid; border-width: 15px 0 15px 15px; border-color: transparent transparent transparent #a6aab8; position: absolute; left: 0; top: 0;}
|
||||
#navigator > li + li:after {border-left-color: rgba(var(--color-canvas-rgb),var(--tw-bg-opacity)); left: -1px;}
|
||||
#navigator > li.active + li:after {border-left-color: rgba(var(--color-secondary-500-rgb),var(--tw-bg-opacity));}
|
||||
#navigator > li.active:hover + li:after {border-left-color: rgba(var(--color-secondary-500-rgb));}
|
||||
#navigator > li + li > a {padding-left: 35px;}
|
||||
#navigator > li:hover {background-color: var(--zt-page-bg);}
|
||||
#navigator > li:hover + li:after {border-left-color: var(--zt-page-bg);}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace zin;
|
||||
|
||||
class navigator extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'items?: array'
|
||||
);
|
||||
|
||||
public static function getPageCSS(): string|false
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
|
||||
}
|
||||
|
||||
protected function buildSteps(): array
|
||||
{
|
||||
$items = $this->prop('items');
|
||||
if(!$items) return array();
|
||||
|
||||
$steps = array();
|
||||
foreach($items as $item)
|
||||
{
|
||||
$steps[] = li
|
||||
(
|
||||
!empty($item->active) ? setClass('active') : null,
|
||||
!empty($item->url) ? set::href($item->url) : null,
|
||||
a
|
||||
(
|
||||
setClass('btn shadow-none' . (!empty($item->active) ? ' secondary' : '')),
|
||||
$item->text
|
||||
)
|
||||
);
|
||||
}
|
||||
return $steps;
|
||||
}
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
return ul
|
||||
(
|
||||
setID('navigator'),
|
||||
setClass('nav nav-primary'),
|
||||
$this->buildSteps()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class overviewBlock extends wg
|
||||
),
|
||||
span
|
||||
(
|
||||
setClass('text-center'),
|
||||
setClass('text-center text-sm'),
|
||||
$card->label
|
||||
)
|
||||
);
|
||||
@@ -78,14 +78,14 @@ class overviewBlock extends wg
|
||||
span
|
||||
(
|
||||
set::title($bar->value),
|
||||
setClass('block primary w-2'),
|
||||
setClass('block primary bg-opacity-70 w-2'),
|
||||
setStyle(array('height' => $bar->rate))
|
||||
)
|
||||
);
|
||||
|
||||
$labels[] = span
|
||||
(
|
||||
setClass('text-center'),
|
||||
setClass('text-center text-gray text-sm'),
|
||||
$bar->label
|
||||
);
|
||||
}
|
||||
@@ -98,7 +98,7 @@ class overviewBlock extends wg
|
||||
setClass('basis-48'),
|
||||
span
|
||||
(
|
||||
setClass('mb-2'),
|
||||
setClass('mb-3 text-sm text-gray'),
|
||||
$group->title
|
||||
),
|
||||
div
|
||||
|
||||
@@ -62,22 +62,24 @@ class pageBase extends wg
|
||||
$imports = context::current()->getImportList();
|
||||
$webRoot = $app->getWebRoot();
|
||||
$themeName = $app->cookie->theme;
|
||||
$zuiPath = $config->zin->zuiPath;
|
||||
|
||||
$jsConfig->zin = true;
|
||||
$jsConfig->zin = true;
|
||||
|
||||
$headImports = array();
|
||||
if($zui)
|
||||
{
|
||||
$headImports[] = h::importCss($config->zin->zuiPath . 'zui.zentao.css', setID('zuiCSS'));
|
||||
$headImports[] = h::importCss($config->zin->zuiPath . 'themes/' . $themeName . '.css', setID('zuiTheme'));
|
||||
$headImports[] = h::importJs($config->zin->zuiPath . 'zui.zentao.umd.cjs', setID('zuiJS'));
|
||||
$headImports[] = h::importCss($zuiPath . 'zui.zentao.css', setID('zuiCSS'));
|
||||
$headImports[] = h::importCss($zuiPath . 'themes/' . $themeName . '.css', setID('zuiTheme'));
|
||||
$headImports[] = h::importJs($zuiPath . 'zui.zentao.umd.cjs', setID('zuiJS'));
|
||||
$headImports[] = h::jsCall('$.setLibRoot', $zuiPath);
|
||||
}
|
||||
$headImports[] = h::jsVar('window.config', $jsConfig, setID('configJS'));
|
||||
if($zui) $headImports[] = h::importJs($webRoot . 'js/zui3/zin.js', setID('zinJS'));
|
||||
|
||||
if($config->debug)
|
||||
{
|
||||
$js[] = h::createJsVarCode('window.zin', array('page' => $this->toJsonData(), 'definedProps' => wg::$definedPropsMap, 'wgBlockMap' => wg::$wgToBlockMap, 'config' => jsRaw('window.config')));
|
||||
$js[] = h::createJsVarCode('window.zin', array('page' => $this->toJSON(), 'definedProps' => wg::$definedPropsMap, 'wgBlockMap' => wg::$wgToBlockMap, 'config' => jsRaw('window.config')));
|
||||
$js[] = 'console.log("[ZIN] ", window.zin);';
|
||||
}
|
||||
else
|
||||
|
||||
@@ -4,8 +4,51 @@ namespace zin;
|
||||
|
||||
class pager extends wg
|
||||
{
|
||||
protected static array $defineProps = array
|
||||
(
|
||||
'type?: string="full"',
|
||||
'page?: int',
|
||||
'recTotal?: int',
|
||||
'recPerPage?: int',
|
||||
'linkCreator?: string',
|
||||
'items?: array'
|
||||
);
|
||||
|
||||
protected function buildProps(string $type = 'full'): void
|
||||
{
|
||||
global $lang;
|
||||
$pager = data('pager');
|
||||
$pager->setParams();
|
||||
$params = $pager->params;
|
||||
foreach($params as $key => $value)
|
||||
{
|
||||
if(strtolower($key) === 'recperpage') $params[$key] = '{recPerPage}';
|
||||
if(strtolower($key) === 'pageid') $params[$key] = '{page}';
|
||||
}
|
||||
|
||||
$props = array();
|
||||
$props['page'] = $pager->pageID;
|
||||
$props['recTotal'] = $pager->recTotal;
|
||||
$props['recPerPage'] = $pager->recPerPage;
|
||||
$props['linkCreator'] = createLink($pager->moduleName, $pager->methodName, $params);
|
||||
$props['items'] = array
|
||||
(
|
||||
$type == 'short' ? null : array('type' => 'info', 'text' => $lang->pager->totalCountAB),
|
||||
$type == 'short' ? null : array('type' => 'size-menu', 'text' => $lang->pager->pageSizeAB),
|
||||
array('type' => 'link', 'hint' => $lang->pager->firstPage, 'page' => 'first', 'icon' => 'icon-first-page'),
|
||||
array('type' => 'link', 'hint' => $lang->pager->previousPage, 'page' => 'prev', 'icon' => 'icon-angle-left'),
|
||||
array('type' => 'info', 'text' => '{page}/{pageTotal}'),
|
||||
array('type' => 'link', 'hint' => $lang->pager->nextPage, 'page' => 'next', 'icon' => 'icon-angle-right'),
|
||||
array('type' => 'link', 'hint' => $lang->pager->lastPage, 'page' => 'last', 'icon' => 'icon-last-page'),
|
||||
);
|
||||
|
||||
$this->setProp($props);
|
||||
}
|
||||
|
||||
protected function build(): zui
|
||||
{
|
||||
$this->buildProps($this->prop('type'));
|
||||
|
||||
return zui::pager(inherit($this));
|
||||
}
|
||||
}
|
||||
|
||||
+11
-4
@@ -5,9 +5,10 @@ namespace zin;
|
||||
class panel extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'class?: string="rounded shadow ring-0 bg-canvas"', // 类名。
|
||||
'class?: string="rounded ring-0 bg-canvas"', // 类名。
|
||||
'size?: "sm"|"lg"', // 额外尺寸。
|
||||
'title?: string', // 标题。
|
||||
'shadow?: bool=true', // 阴影效果。
|
||||
'titleClass?: string', // 标题类名。
|
||||
'titleProps?: array', // 标题属性。
|
||||
'headingClass?: string', // 标题栏类名。
|
||||
@@ -53,7 +54,13 @@ class panel extends wg
|
||||
(
|
||||
setClass('panel-heading', $this->prop('headingClass')),
|
||||
set($this->prop('headingProps')),
|
||||
empty($title) ? null : div(setClass('panel-title', $this->prop('titleClass', empty($size) ? null : "text-$size")), $title, set($this->prop('titleProps'))),
|
||||
!empty($title) ? div
|
||||
(
|
||||
setClass('panel-title', $this->prop('titleClass', empty($size) ? null : "text-$size")),
|
||||
$this->prop('titleIcon') ? icon($this->prop('titleIcon')) : null,
|
||||
set($this->prop('titleProps')),
|
||||
$title
|
||||
) : null,
|
||||
$headingBlock,
|
||||
$actions
|
||||
);
|
||||
@@ -87,8 +94,8 @@ class panel extends wg
|
||||
|
||||
protected function buildProps(): array
|
||||
{
|
||||
list($class, $size) = $this->prop(array('class', 'size'));
|
||||
return array(setClass('panel', $class, empty($size) ? null : "size-$size"));
|
||||
list($class, $size, $shadow) = $this->prop(array('class', 'size', 'shadow'));
|
||||
return array(setClass('panel', $class, empty($size) ? null : "size-$size", $shadow ? 'shadow' : null));
|
||||
}
|
||||
|
||||
protected function build(): wg
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
function checkPassword()
|
||||
{
|
||||
const password = $(event.target).val();
|
||||
const $strength = $(event.target).closest('.input-group').find('.' + strengthClass);
|
||||
if(password == '')
|
||||
{
|
||||
$strength.html('').addClass('hidden');
|
||||
return false;
|
||||
}
|
||||
|
||||
const passwordStrength = passwordStrengthList[computePasswordStrength(password)];
|
||||
$strength.html(passwordStrength).removeClass('hidden');
|
||||
}
|
||||
|
||||
function computePasswordStrength(password)
|
||||
{
|
||||
if(password.length == 0) return 0;
|
||||
|
||||
var strength = 0;
|
||||
var length = password.length;
|
||||
|
||||
var complexity = new Array();
|
||||
for(i = 0; i < length; i++)
|
||||
{
|
||||
letter = password.charAt(i);
|
||||
var asc = letter.charCodeAt();
|
||||
if(asc >= 48 && asc <= 57)
|
||||
{
|
||||
complexity[0] = 1;
|
||||
}
|
||||
else if((asc >= 65 && asc <= 90))
|
||||
{
|
||||
complexity[1] = 2;
|
||||
}
|
||||
else if(asc >= 97 && asc <= 122)
|
||||
{
|
||||
complexity[2] = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
complexity[3] = 8;
|
||||
}
|
||||
}
|
||||
|
||||
var sumComplexity = 0;
|
||||
for(i in complexity) sumComplexity += complexity[i];
|
||||
|
||||
if((sumComplexity == 7 || sumComplexity == 15) && password.length >= 6) strength = 1;
|
||||
if(sumComplexity == 15 && password.length >= 10) strength = 2;
|
||||
|
||||
return strength;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace zin;
|
||||
|
||||
class password extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'id?: string="password1"',
|
||||
'name?: string="password1"',
|
||||
'checkStrength?: bool=false',
|
||||
'strengthID?: string="passwordStrength"',
|
||||
'strengthClass?: string="passwordStrength"'
|
||||
);
|
||||
|
||||
public static function getPageJS(): string|false
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
}
|
||||
|
||||
protected function build(): array|wg
|
||||
{
|
||||
global $app, $config, $lang;
|
||||
$app->loadLang('user');
|
||||
$jsRoot = $app->getWebRoot() . 'js/';
|
||||
|
||||
list($id, $name, $checkStrength, $strengthID, $strengthClass) = $this->prop(array('id', 'name', 'checkStrength', 'strengthID', 'strengthClass'));
|
||||
|
||||
return $checkStrength ? array
|
||||
(
|
||||
h::jsCall('$.getLib', $jsRoot . 'md5.js'),
|
||||
jsVar('window.strengthClass', $strengthClass),
|
||||
jsVar('window.passwordStrengthList', $lang->user->passwordStrengthList),
|
||||
inputGroup
|
||||
(
|
||||
input
|
||||
(
|
||||
setID($id),
|
||||
on::keyup('checkPassword'),
|
||||
set::type('password'),
|
||||
set::name($name),
|
||||
set::placeholder(zget($lang->user->placeholder->passwordStrength, $config->safe->mode, ''))
|
||||
),
|
||||
span
|
||||
(
|
||||
setID($strengthID),
|
||||
setClass("input-group-addon {$strengthClass} hidden")
|
||||
)
|
||||
)
|
||||
) : input
|
||||
(
|
||||
set::type('password'),
|
||||
set::name($name),
|
||||
set($this->getRestProps()),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ class picker extends wg
|
||||
$pickerProps['items'] = $pickerItems;
|
||||
$pickerProps['defaultValue'] = $defaultValue;
|
||||
|
||||
if(!isset($pickerProps['emptyValue'])) $pickerProps['emptyValue'] = ($hasZeroValue || (!is_array($defaultValue) && "$defaultValue" !== '0')) ? '' : '0,';
|
||||
if(!isset($pickerProps['emptyValue'])) $pickerProps['emptyValue'] = ($hasZeroValue || "$defaultValue" !== '0') ? '' : '0,';
|
||||
|
||||
if(isset($pickerProps['id']))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
.release-head {width: 120px; border: 2px solid var(--color-gray-200); border-radius: 4px; margin-left: 30px; margin-bottom: 58px;}
|
||||
.release-line > li {height: 156px;}
|
||||
.release-line > li:nth-child(even) {align-items: start;}
|
||||
.release-line > li:nth-child(odd) {align-items: end;}
|
||||
.release-line > li:nth-child(even) > a {border-bottom: 4px solid var(--color-primary-200);}
|
||||
.release-line > li:nth-child(odd) > a {border-top: 4px solid var(--color-primary-200);}
|
||||
.release-line > li:last-child > a {border-color: transparent !important;}
|
||||
.release-line > li > a {height: 80px; box-sizing: border-box;}
|
||||
.release-line > li > a:before {display: block; content: ' '; width: 12px; height: 12px; background: #fff; border: 2px solid var(--color-success-500); border-radius: 50%; position: absolute;}
|
||||
.release-line > li:nth-child(even) > a:before {bottom: -8px;}
|
||||
.release-line > li:nth-child(odd) > a:before {top: -8px;}
|
||||
.release-line > li:nth-child(odd) > a:after {top: 4px; left: 5px;}
|
||||
.release-line > li:nth-child(even) > a:after {bottom: 4px; left: 5px;}
|
||||
.release-line > li > a:after {position: absolute; display: block; width: 2px; height: 30px; content: ' '; background: var(--color-primary-200);}
|
||||
.release-line > li:nth-child(odd) > a > div {margin-top: 36px;}
|
||||
.release-line > li:nth-child(even) > a > div {margin-bottom: 36px;}
|
||||
.release-link-line {pointer-events: none; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA+YAAADiCAMAAAD0xy2eAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAABOUExURUdwTOXl5eXl5ebm5ufn5+rq6u7u7unp6eXl5f///+Xl5eXl5efn5+Xl5eXl5eXl5ebm5uXl5eXl5ebm5ubm5ubm5uXl5ebm5uXl5eXl5Uh6G7kAAAAZdFJOUwDuuVQtFQwg1gXH+Dpzoq1CmIV7aEuOX+MTWsN0AAAGrUlEQVQYGe3ACbaYOHRF0SuQkETfw5v/RPNdLpMZZKWez5buVQBcu8zmJACO1WOwcAqAZ7kx66MAeNa+ZncnAI51t1kvAK7F/hEAAAAAAACA/xP93gmAZ6tZOKsAODb2ZuUSAM/aYtZEAXCsTsFs7gTAse4eigD4lqMAAAAAAMD/D09/CYBrvVl/CYBjdXrN+lUAHOumYNavAuBYNwWzTQA866ZwCYBvnQAAAAD8V7RHEgDParHhzgLg2NqYDXMUAMfiPJg1qwA4lu/BbBMAz7rjvQTAtyoAAOBG1wmAb1O4swB4NptZcwmAY3EbzN4pCYBfaSpmwywAnl2NNQLgW44CAABuZAHwLQ1lSgLg2BjMhuUSAL9q25jZu2cB8CsfxcweAfBsnYcsAL51AgAAboxL2wmAZ5vZMF9VANxKZ29mYRsFwK98FDN7swA4Fvc3VAHwLQsAALgxlSMKgGeNmZU9CoBb9dqCmb33KABu1XV7zSwJgGfjPQsAALixHlEAXJvN3u3qBMCtdXvNbGjOLABuxaO3H1EAHEvPUgQAANwYlycLgGe7mZX76gTAq3wuwcyGZsoC4FUd997MLgHwLLVbJwB/j7UTANeyDf2+dgLgVuwHMxv6fa0C4FR37b39WATAse66+0kA/h45CYBvjZX5yQLg1xLsR1imsQqATzVOy2s/kgA4lp+tEYC/R9rOsRMAx1r7UebjygLgU57mMtgvswC4VeNzN++hT0oC4FDVZ7ehLPe55ioAPu3BfhseAXCqi+2xNa9d+rTnFTsB8KLqH7Xq09iPoTTzngXApXNrSrBfRn1i7ATAlS5e5570KWbD2y/b8XQC4NJSgv2W9RljrgLgR5fH9tznqk8ws1D6ZTs6AXCp6d/B/lH1Oc52jakKgBcpj+156FPtt1B6AXCp27elKcHMgj5d6Zdtn55rzALgRcpRn2x/vPrUMaYqAP9hVZ8a1/Y87rnpG32y/Qhv38y7ALiU+xIG+6Xok5r5Ps52jbkTABe6PF5Pq89ofxR9upg7AXAiXc+0b0tT3kaf0cyGtzTLdgqAS2N5B/tHo09ctn162jGmKgAedDmu7Xnp09ofjT5dTFUAnEjtedzz0pd30+cxs/D2zXy3AuBSW4L9tumzzvdxtmvMnQC40OXxes5Vn8n+2PTpkgC4EZ9p35amvMOuz2QWSr/M9zQKgEtTsH/t+qzH2a4xVQFwoaY4Xs+0r/rs9lsopwC4tO7b0pcwmJ367KVftn16rjEJgBddTvrM9sejT46pCoATKY7tedxz06/6zGYWSr9sexQAl+4S7LdLn3Z6rpirAHiR4tieR9ansX+E0o8C4NJzz015BzMb9bmX7TjbMXcC4EaXx06fYv8aogC4NLbnvi19CZb06Usz38dzjVkA/Kj6X8H+SPpUAfAjxfWZ9rnp36pPCKWZ7+m5ogC4VAf7o+rTCYAjKa7PdM9Nr0+1UJr5ntoxC4BLebB/BQFwKsX1me6lb/RJ89HGKgB+jfZLae5zFACX0nk3xX5pBMCvGttjnvRJWQB8myw0e5sFwK0p2C+h2aMAOJXbvQlm1gqAZ/k6sgD8PfpmXzsB8KuzX8rWZgFwKrV3P9iPIgB+1fFoQi8AziV9qgA4d4bljALg2GY/3vnJAuBVfubXfryrAPgVzyVYFgDfogD8Per8JAHwbDWzfh+rAHiVpmYws7CcWQC8qtddzOwWAM/yuawCAABu9HPbCYBj2cyG5swC4FacevtR9lEA3ErPEswWAfCsrncrAADgxraPAuBZN5i921oFwK31fs0szG0nAG7FvZhZLwCe5ak/BAAA3FizAPj2WjmyAPjVzcHMyhEFwK16zcHMyiQAftVrCzYLgGt1jQIAAG7MRxYAz7KZ9VMWALfqNQcz66ckAG7VdhnM7BYAx2q7DKcA+NZ1AgAAXtSyjQLg2WVm7x4FwK94v2ZWpiwAfo1bMLNJAByr1zyMAuBbJwAA4EYsUxYAzw4za55OANyq1zKYDctVBcCt7mnMLIwC4Fg+ytAJgG9ZAADAjWuPAuBaY9afSQD8GrdgNixXFQC3atuYWbirAPiVpmJFAHyLqwAAgBtjEgDX6jssVxUAv9IymIU7CoBfaSpm1p9JAPwat2A2C4BntW1WAQAAAPiPyGVKAuDZYTYslwD4VdvGzN49C4Bf6Shm1kQBcGydhyEJgGvdJQAA4EZOAuDbMiyrAHi2mNl7ZAHwKx2vmTVXFQC/1mUw6wXAszSVQwCcqwIAAG4cSQBcW4ckAK7lU/8D/a9fSZZlwxkAAAAASUVORK5CYII="); height: 196px; position: absolute; left: 10px; right: 100px; top: 97px; background-size: 100% 100%; background-repeat: no-repeat;}
|
||||
.release-line .icon-flag {font-size: 24px;}
|
||||
.release-line > li:nth-child(even) .icon-flag {bottom: -3px; left: 4px;}
|
||||
.release-line > li:nth-child(odd) .icon-flag {left: 4px; top: -25px;}
|
||||
.release-line > li:hover .title {color: var(--color-primary-500);}
|
||||
.release-line > li:hover > a:before {border-color: var(--color-success-700);}
|
||||
.release-line > li:hover > a:after {background: var(--color-primary-400);}
|
||||
@@ -0,0 +1,67 @@
|
||||
$(function()
|
||||
{
|
||||
// 修复连接曲线位置
|
||||
const fixReleasePathLine = function()
|
||||
{
|
||||
const $lines = $('.release-paths .release-line');
|
||||
$lines.each(function()
|
||||
{
|
||||
const $line = $(this);
|
||||
const $next = $line.next();
|
||||
let $nextLine, $linkLine;
|
||||
if($next.hasClass('release-line'))
|
||||
{
|
||||
$nextLine = $next;
|
||||
}
|
||||
else if($next.hasClass('release-link-line'))
|
||||
{
|
||||
$linkLine = $next;
|
||||
$nextLine = $next.next();
|
||||
}
|
||||
if ($nextLine && $nextLine.length)
|
||||
{
|
||||
if(!$linkLine) $linkLine = $('<div class="release-link-line" />').insertAfter($line)
|
||||
const $startPos = $line.find(':first-child > a').position();
|
||||
const $endPos = $nextLine.find(':last-child > a').position();
|
||||
$linkLine.css({
|
||||
top: $startPos.top + 6,
|
||||
left: $startPos.left + 12,
|
||||
width: $endPos.left - $startPos.left,
|
||||
height: 172
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const $paths = $('.release-path');
|
||||
$paths.each(function()
|
||||
{
|
||||
const $path = $(this);
|
||||
const $next = $path.next();
|
||||
let $nextPath, $linkLine;
|
||||
if($next.hasClass('release-path'))
|
||||
{
|
||||
$nextPath = $next;
|
||||
}
|
||||
else if($next.hasClass('release-link-line'))
|
||||
{
|
||||
$linkLine = $next;
|
||||
$nextPath = $next.next();
|
||||
}
|
||||
if ($nextPath && $nextPath.length)
|
||||
{
|
||||
if(!$linkLine) $linkLine = $('<div class="release-link-line" />').insertAfter($path);
|
||||
const $startPos = $path.find('.grow > .release-line:last-child > :first-child > a').position();
|
||||
const $endPos = $nextPath.find('.grow > .release-line:first-child > :last-child > a').position();
|
||||
$linkLine.css({
|
||||
top: $startPos.top + 6,
|
||||
left: $startPos.left + 12,
|
||||
width: $endPos.left - $startPos.left,
|
||||
height: 172
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
fixReleasePathLine();
|
||||
window.addEventListener('resize', fixReleasePathLine);
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace zin;
|
||||
|
||||
class roadMap extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'releases: array',
|
||||
);
|
||||
|
||||
public static function getPageJS(): string
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'js' . DS . 'v1.js');
|
||||
}
|
||||
|
||||
public static function getPageCSS(): string
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
|
||||
}
|
||||
|
||||
private function releaseHead(string|int $title, string $subtitle)
|
||||
{
|
||||
return div
|
||||
(
|
||||
setClass('release-head shrink-0 py-1 px-2 text-center'),
|
||||
div
|
||||
(
|
||||
setClass('title text-primary text-xl'),
|
||||
$title,
|
||||
),
|
||||
div
|
||||
(
|
||||
setClass('subtitle text-gray text-base'),
|
||||
$subtitle,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private function releaseLine(array $releases)
|
||||
{
|
||||
$releaseVersions = array();
|
||||
foreach($releases as $release)
|
||||
{
|
||||
$releaseVersions[] = $this->releaseVersion($release);
|
||||
}
|
||||
|
||||
return ul
|
||||
(
|
||||
setClass('release-line flex py-3'),
|
||||
$releaseVersions,
|
||||
);
|
||||
}
|
||||
|
||||
private function releaseVersion(array $release)
|
||||
{
|
||||
return li
|
||||
(
|
||||
setClass('flex grow'),
|
||||
a
|
||||
(
|
||||
setClass('inline-block w-full relative'),
|
||||
set::href($release['href']),
|
||||
$release['marker'] ? icon('flag', setClass('absolute text-primary')) : null,
|
||||
div
|
||||
(
|
||||
div(setClass('title ellipsis text-lg text-dark'), set::title($release['version']), $release['version']),
|
||||
div(setClass('date ellipsis text-sm text-gray'), $release['date'])
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function releasePath(string|int $year, array $yearReleases)
|
||||
{
|
||||
$releaseLines = array();
|
||||
$count = 0;
|
||||
foreach($yearReleases as $releases)
|
||||
{
|
||||
$count += count($releases);
|
||||
$releaseLines[] = $this->releaseLine($releases);
|
||||
}
|
||||
|
||||
global $lang;
|
||||
if(!isset($lang->execution->iterationInfo))
|
||||
{
|
||||
global $app;
|
||||
$app->loadLang('execution');
|
||||
}
|
||||
$iterationInfo = $lang->execution->iterationInfo;
|
||||
|
||||
return div
|
||||
(
|
||||
setClass('release-path flex gap-6 items-end'),
|
||||
$this->releaseHead($year, sprintf($iterationInfo, (string)$count)),
|
||||
div
|
||||
(
|
||||
setClass('grow'),
|
||||
$releaseLines,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
$releases = $this->prop('releases');
|
||||
$releasePaths = array();
|
||||
|
||||
foreach($releases as $year => $yearReleases)
|
||||
{
|
||||
$releasePaths[] = $this->releasePath($year, $yearReleases);
|
||||
}
|
||||
|
||||
return div
|
||||
(
|
||||
setClass('release-paths bg-white relative'),
|
||||
$releasePaths
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace zin;
|
||||
|
||||
class searchForm extends wg
|
||||
{
|
||||
protected function build(): zui
|
||||
{
|
||||
return zui::searchForm(inherit($this));
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ window.toggleSearchForm = function(moduleName, formName, open)
|
||||
if(!$form.length) $form = $('<div id="searchFormPanel" data-module="' + moduleName + '"></div>').insertAfter('#mainMenu');
|
||||
if(!$form.data('loaded'))
|
||||
{
|
||||
const url = $.createLink('search', 'buildZinForm', 'module=' + moduleName + '&fields=¶ms=&actionURL=&queryID=0&formName=' + formName);
|
||||
const url = $.createLink('search', 'buildForm', 'module=' + moduleName + '&fields=¶ms=&actionURL=&queryID=0&formName=' + formName);
|
||||
$.get(url, html =>
|
||||
{
|
||||
$form.html(html).data('loaded', true);
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace zin;
|
||||
class section extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'title?: string', // 标题
|
||||
'title: string', // 标题
|
||||
'content?: string|array', // 内容
|
||||
'useHtml?: bool=false', // 内容是否解析 HTML 标签
|
||||
);
|
||||
|
||||
@@ -64,7 +64,7 @@ class select extends wg
|
||||
*/
|
||||
public function onBuildItem(wg|array $item): wg
|
||||
{
|
||||
if($item instanceof item) $item = $item->props->toJsonData();
|
||||
if($item instanceof item) $item = $item->props->toJSON();
|
||||
|
||||
$text = isset($item['text']) ? $item['text'] : '';
|
||||
unset($item['text']);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.block-statistic-nav {overflow-y: overlay;}
|
||||
.block-statistic-nav-item {width: auto!important; height: 36px!important;}
|
||||
.block-statistic-nav-item:hover {padding-right: 32px;}
|
||||
.block-statistic-nav-item.active {background: var(--color-canvas); box-shadow: inset 2px 0 0 var(--color-primary-500);}
|
||||
.block-statistic-nav-item .text {opacity: .8;}
|
||||
.block-statistic-nav-item.active .text, .block-statistic-nav-item:hover .text {opacity: 1;}
|
||||
.block-statistic-nav-url {position: absolute!important; padding: 0!important; width: 32px!important; justify-content: center!important; height: 36px!important;}
|
||||
.block-statistic-nav-url:hover {background-color: var(--color-canvas);}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* The statistic block widget class file of zin module of ZenTaoPMS.
|
||||
*
|
||||
* @copyright Copyright 2009-2023 禅道软件(青岛)有限公司(ZenTao Software (Qingdao) Co., Ltd. www.zentao.net)
|
||||
* @license ZPL(https://zpl.pub/page/zplv12.html) or AGPL(https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
* @author Gang Liu <liugang@easycorp.ltd>
|
||||
* @package zin
|
||||
* @link https://www.zentao.net
|
||||
*/
|
||||
|
||||
namespace zin;
|
||||
|
||||
require_once dirname(__DIR__) . DS . 'blockpanel' . DS . 'v1.php';
|
||||
|
||||
class statisticBlock extends wg
|
||||
{
|
||||
protected static array $defineProps = array
|
||||
(
|
||||
'id?: string',
|
||||
'title?: string',
|
||||
'block?: object',
|
||||
'longBlock?: bool',
|
||||
'items: array', // {id: string, text: string, url: string}
|
||||
'active?: string'
|
||||
);
|
||||
|
||||
public static function getPageCSS(): string|false
|
||||
{
|
||||
return file_get_contents(__DIR__ . DS . 'css' . DS . 'v1.css');
|
||||
}
|
||||
|
||||
protected function buildNav($items, $active, $longBlock): wg|null
|
||||
{
|
||||
if(empty($items)) return null;
|
||||
|
||||
$navItems = array();
|
||||
$gid = $this->gid;
|
||||
foreach($items as $item)
|
||||
{
|
||||
$navItems[] = li
|
||||
(
|
||||
setClass('nav-item group'),
|
||||
a
|
||||
(
|
||||
toggle::tab(array('target' => "#tab_{$gid}_{$item['id']}")),
|
||||
setClass('block-statistic-nav-item flex-auto min-w-0', $item['id'] === $active ? 'active' : ''),
|
||||
span(setClass('text clip'), $item['text'])
|
||||
),
|
||||
(isset($item['url']) && !empty($item['url'])) ? a
|
||||
(
|
||||
setClass('block-statistic-nav-url top-0 right-0 opacity-0 group-hover:opacity-100 transition-opacity'),
|
||||
set::href($item['url']),
|
||||
icon('import rotate-270 primary-pale rounded-full w-5 h-5 center'),
|
||||
) : null
|
||||
);
|
||||
}
|
||||
|
||||
return div
|
||||
(
|
||||
setClass('flex-none block-statistic-nav scrollbar-hover scrollbar-thin bg-surface overflow-y-auto overflow-x-hidden border-r', $longBlock ? 'w-52' : 'w-full'),
|
||||
nav
|
||||
(
|
||||
set::stacked(true),
|
||||
$navItems
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected function buildPanes($items, $active, $longBlock): wg|null
|
||||
{
|
||||
if(empty($items)) return null;
|
||||
|
||||
$panes = array();
|
||||
$gid = $this->gid;
|
||||
foreach($items as $item)
|
||||
{
|
||||
$isActive = $item['id'] === $active;
|
||||
$panes[] = div
|
||||
(
|
||||
setID("tab_{$gid}_{$item['id']}"),
|
||||
setClass('tab-pane h-full', $isActive ? 'active' : ''),
|
||||
$isActive ? $this->children() : null
|
||||
);
|
||||
}
|
||||
|
||||
return div
|
||||
(
|
||||
setClass('flex-auto block-statistic-panes'),
|
||||
$panes
|
||||
);
|
||||
}
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
list($id, $title, $block, $longBlock, $items, $active) = $this->prop(array('id', 'title', 'block', 'longBlock', 'items', 'active'));
|
||||
if($longBlock === null) $longBlock = data('longBlock');
|
||||
|
||||
return new blockPanel
|
||||
(
|
||||
set::block($block),
|
||||
set::title($title),
|
||||
set::id($id),
|
||||
set::longBlock($longBlock),
|
||||
set::bodyClass('block-statistic flex p-0'),
|
||||
set($this->getRestProps()),
|
||||
$this->buildNav($items, $active, $longBlock),
|
||||
$this->buildPanes($items, $active, $longBlock)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
.steps-editor {width: 100%}
|
||||
.steps-editor-header {padding-right: 40px; color: var(--color-slate-700);}
|
||||
.steps-editor-header {padding-right: 24px; color: var(--color-slate-700);}
|
||||
.steps-editor-row {position: relative; display: flex; align-items: stretch; min-height: 32px; --tw-ring-color: rgba(var(--color-border-strong-rgb), 1); box-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); margin-top: 1px; z-index: 1;}
|
||||
.steps-editor-row.is-focus {z-index: 100;}
|
||||
.steps-editor-header .steps-editor-row {border-radius: 2px 2px 0 0; background-color: var(--color-slate-100);}
|
||||
@@ -9,15 +9,50 @@
|
||||
.steps-editor-col-add {width: 80px; border-left: 1px solid var(--color-border-strong); border-right: 1px solid var(--color-border-strong); align-items: center; padding: 0!important;}
|
||||
.steps-editor-col-add > div {width: 50%; display: flex; align-items: center; justify-content: center; position: relative;}
|
||||
.steps-editor-col-expect {flex: auto;}
|
||||
.steps-editor-body {padding-right: 40px;}
|
||||
.steps-editor-body {padding-right: 24px;}
|
||||
.steps-editor-col-step.form-control {padding: 0; border-radius: 0; height: auto; z-index: 100;}
|
||||
.steps-editor-step-name {padding: 6px 0 6px 8px; flex: none; text-align: right; opacity: .5;}
|
||||
.steps-editor-step-name {padding: 6px 0; flex: none; opacity: .5; margin-left: 8px; position: relative;}
|
||||
.steps-editor-step-expect,
|
||||
.steps-editor-step-text {padding: 6px 12px;; flex: auto; border: none; min-height: 32px; background: none; border-radius: 0; resize: none}
|
||||
.steps-editor-step-text {padding: 6px 12px 6px 6px;; flex: auto; border: none; min-height: 32px; background: none; border-radius: 0; resize: none}
|
||||
.steps-editor-step-text {outline: none;}
|
||||
.steps-editor-body .steps-editor-col-add > div + div::before {content: ' '; position: absolute; display: block; border-left: 1px solid var(--color-border-strong); top: 4px; bottom: 4px; left: 0; opacity: .7;}
|
||||
.steps-editor .btn-action {color: var(--color-slate-600);}
|
||||
.steps-editor .btn-action:hover {color: var(--color-primary-500);}
|
||||
.steps-editor-col-delete {width: 40px; height: 32px; display: flex; align-items: center; justify-content: center; position: absolute; right: -40px; top: 0}
|
||||
.steps-editor-col-delete {width: 24px; height: 100%; display: flex; align-items: center; justify-content: center; position: absolute; right: -24px; top: 0}
|
||||
.steps-editor-row .form-control {background: inherit;}
|
||||
.steps-editor-row {background: var(--color-canvas);}
|
||||
.steps-editor-row[data-level="1"] {margin-top: 7px;}
|
||||
.steps-editor-body > .steps-editor-row:first-child {margin-top: 1px;}
|
||||
.steps-editor-step-move {color: var(--color-slate-600); width: 24px; display: flex; justify-content: center; align-items: center; border-right: 1px solid var(--color-border-strong); cursor: move;}
|
||||
.steps-editor-drag-ghost {position: absolute; left: 0; top: 0; bottom: 0; opacity: 0; right: 0; pointer-events: none;}
|
||||
.steps-editor-step-move:hover {color: var(--color-primary-500)}
|
||||
.steps-editor-col-step::before {display: block; content: ' '; position: absolute; left: -90px; top: 0; bottom: 0; width: 90px;}
|
||||
.has-dragging .steps-editor-step-move > .icon-move,
|
||||
.steps-editor-row.is-dragging .steps-editor-col-delete {display: none;}
|
||||
.steps-editor-row.move-hover {background-color: rgba(var(--color-primary-100-rgb),.2);}
|
||||
.steps-editor-row.is-dragging {background-color: rgba(var(--color-primary-100-rgb),.4);}
|
||||
.steps-editor-row.is-sub-dragging {background-color: rgba(var(--color-primary-100-rgb),.2);}
|
||||
.steps-editor-row.is-dropping {z-index: 5;}
|
||||
.steps-editor-row.is-dropping::before,
|
||||
.steps-editor-row.is-dropping::after {content: ' '; display: block; position: absolute; left: 24px; z-index: 110;}
|
||||
.steps-editor-row.is-dropping::after {transform: translateX(-2px);}
|
||||
.steps-editor-row.is-dropping[data-drop-level="2"]::before,
|
||||
.steps-editor-row.is-dropping[data-drop-level="2"]::after {left: 46px;}
|
||||
.steps-editor-row.is-dropping[data-drop-level="3"]::before,
|
||||
.steps-editor-row.is-dropping[data-drop-level="3"]::after {left: 60px;}
|
||||
.steps-editor-row.is-dropping::before {height: 2px; background: var(--color-primary-500); right: 0;}
|
||||
.steps-editor-row.is-dropping[data-drop-side="top"]::before {top: -1px;}
|
||||
.steps-editor-row.is-dropping[data-drop-side="bottom"]::before {bottom: -2px;}
|
||||
.steps-editor-row.is-dropping[data-drop-side="top"]::after {top: -5px;}
|
||||
.steps-editor-row.is-dropping[data-drop-side="bottom"]::after {bottom: -7px;}
|
||||
.steps-editor-row.is-dropping::after {width: 12px; height: 12px; border-radius: 50%; background: var(--color-primary-500); border: var(--color-primary-100) 3px solid; z-index: 120;}
|
||||
.steps-editor-row.is-dropping[data-level="1"][data-drop-side="top"]::before {top: -4px;}
|
||||
.steps-editor-row.is-dropping.no-child[data-level="1"][data-drop-side="bottom"]::before {bottom: -5px;}
|
||||
.steps-editor-row.is-dropping[data-level="1"][data-drop-side="top"]::after {top: -9px;}
|
||||
.steps-editor-row.is-dropping.no-child[data-level="1"][data-drop-side="bottom"]::after {bottom: -10px;}
|
||||
.steps-editor-row.is-invalid-drop-level.is-dropping::before,
|
||||
.steps-editor-row.is-invalid-drop-level.is-dropping::after {filter: grayscale(1); opacity: .5;}
|
||||
.steps-editor-row.is-invalid-drop-level.is-dropping .steps-editor-step-name {opacity: 1;}
|
||||
.steps-editor-row.is-invalid-drop-level.is-dropping .steps-editor-step-name::before {content: attr(data-invalid-nested); display: block; position: absolute; left: calc(var(--name-indent) + 12px); top: 0; white-space: nowrap; background: var(--color-gray-100); padding: 1px 6px; border: 1px solid var(--color-border); color: var(--color-gray-500)}
|
||||
.steps-editor-row.is-invalid-drop-level.is-dropping[data-drop-side="top"] .steps-editor-step-name::before {top: -30px;}
|
||||
.steps-editor-row.is-invalid-drop-level.is-dropping[data-drop-side="bottom"] .steps-editor-step-name::before {bottom: -30px; top: auto}
|
||||
|
||||
+319
-133
@@ -1,3 +1,26 @@
|
||||
function updateItemName(item, name)
|
||||
{
|
||||
if(name === undefined) name = item.name;
|
||||
if(item.infoName === name) return;
|
||||
item.infoName = name;
|
||||
item.name = name;
|
||||
const namePath = name.split('.');
|
||||
item.order = namePath.reduce((total, level, index) => total + (+level * ([1000000, 1000, 1, 0.001, 0.000001][index])), 0);
|
||||
item.level = namePath.length;
|
||||
item.selfName = namePath.pop();
|
||||
item.parentName = namePath.join('.');
|
||||
}
|
||||
|
||||
function updateChildrenName(item)
|
||||
{
|
||||
if(!item.children) return;
|
||||
item.children.forEach((subItem, subIndex) =>
|
||||
{
|
||||
updateItemName(subItem, `${item.name}.${subIndex + 1}`);
|
||||
updateChildrenName(subItem);
|
||||
});
|
||||
}
|
||||
|
||||
/** Steps editor component. */
|
||||
class StepsEditor extends zui.Component
|
||||
{
|
||||
@@ -13,213 +36,376 @@ class StepsEditor extends zui.Component
|
||||
moveIcon: 'move',
|
||||
deleteIcon: 'trash',
|
||||
expectDisabledTip: '',
|
||||
dragNestedTip: '',
|
||||
changeLevelByDrag: false,
|
||||
};
|
||||
|
||||
init()
|
||||
{
|
||||
this.update(this.options.data, true, true);
|
||||
this.reset(this.options.data, true);
|
||||
}
|
||||
|
||||
afterInit()
|
||||
{
|
||||
this.render();
|
||||
this.$element
|
||||
.on('focus', 'textarea', e =>
|
||||
{
|
||||
const $textarea = $(e.target);
|
||||
$textarea.closest('.steps-editor-col-step').addClass('focus');
|
||||
$textarea.closest('.steps-editor-row').addClass('is-focus');
|
||||
})
|
||||
.on('blur', 'textarea', e =>
|
||||
{
|
||||
const $textarea = $(e.target);
|
||||
$textarea.closest('.steps-editor-col-step').removeClass('focus');
|
||||
$textarea.closest('.steps-editor-row').removeClass('is-focus');
|
||||
})
|
||||
.on('click', '.btn-action', e =>
|
||||
const $element = this.$element;
|
||||
$element.on('focus', 'textarea', e =>
|
||||
{
|
||||
const action = $(e.currentTarget).attr('data-action');
|
||||
const name = $(e.currentTarget).closest('.steps-editor-row').attr('data-name');
|
||||
const $textarea = $(e.target);
|
||||
$textarea.closest('.steps-editor-col-step').addClass('focus');
|
||||
$textarea.closest('.steps-editor-item').addClass('is-focus');
|
||||
})
|
||||
.on('blur', 'textarea', e =>
|
||||
{
|
||||
const $textarea = $(e.target);
|
||||
$textarea.closest('.steps-editor-col-step').removeClass('focus');
|
||||
$textarea.closest('.steps-editor-item').removeClass('is-focus');
|
||||
})
|
||||
.on('click', '.btn-action', e =>
|
||||
{
|
||||
const $btn = $(e.currentTarget);
|
||||
if($btn.is('.disabled')) return;
|
||||
|
||||
const action = $btn.attr('data-action');
|
||||
const name = $btn.closest('.steps-editor-item').attr('data-name');
|
||||
if(action === 'delete') this.deleteStep(name);
|
||||
else if(action === 'sib') this.addSib(name);
|
||||
else if(action === 'sub') this.addSub(name);
|
||||
}).on('mouseenter', '.steps-editor-step-move', e =>
|
||||
{
|
||||
$element.find('.move-hover').removeClass('move-hover');
|
||||
$(e.currentTarget).closest('.steps-editor-item').addClass('move-hover');
|
||||
}).on('mouseleave', '.steps-editor-step-move', e =>
|
||||
{
|
||||
$element.find('.move-hover').removeClass('move-hover');
|
||||
});
|
||||
|
||||
$element.draggable(
|
||||
{
|
||||
selector: '.steps-editor-item',
|
||||
handle: '.steps-editor-step-move',
|
||||
beforeDrag: (_event, dragElement) =>
|
||||
{
|
||||
const dragName = $(dragElement).attr('data-name');
|
||||
this.dragName = dragName;
|
||||
this.dragItem = this.getByName(dragName);
|
||||
this.dragMaxLevel = this.dragItem.level;
|
||||
this.isValidLevel = true;
|
||||
$element.find(`.steps-editor-item[data-name^="${dragName}."]`).addClass('is-sub-dragging').each((_index, ele) =>
|
||||
{
|
||||
const level = +($(ele).attr('data-level'));
|
||||
if(level > this.dragMaxLevel) this.dragMaxLevel = level;
|
||||
});
|
||||
$element.removeClass('cursor-not-allowed');
|
||||
},
|
||||
onDragStart: (event, dragElement) =>
|
||||
{
|
||||
event.dataTransfer.setDragImage($(dragElement).find('.steps-editor-drag-ghost')[0], 0, 0);
|
||||
},
|
||||
onDragEnd: () =>
|
||||
{
|
||||
$element.find('.is-sub-dragging').removeClass('is-sub-dragging');
|
||||
const dropName = this.dropName;
|
||||
if(dropName)
|
||||
{
|
||||
this.dropName = null;
|
||||
const $dropRow = this.getRow(dropName);
|
||||
if($dropRow && $dropRow.length) $dropRow.removeAttr('data-drop-side').removeAttr('data-drop-level');
|
||||
}
|
||||
|
||||
if(!this.isValidLevel) return;
|
||||
const dragName = this.dragName;
|
||||
if(!dropName || dragName === dropName) return;
|
||||
if(this.dropSide === 'bottom') this.moveAfter(dragName, dropName);
|
||||
else this.moveBefore(dragName, dropName);
|
||||
},
|
||||
onDragOver: (event, dragElement, dropElement) =>
|
||||
{
|
||||
const $row = $(dropElement);
|
||||
const dropName = $row.attr('data-name');
|
||||
let idDiff = false;
|
||||
if(dropName !== this.dropName)
|
||||
{
|
||||
this.dropName = dropName;
|
||||
const $oldRow = this.getRow(dropName);
|
||||
$oldRow.removeAttr('data-drop-side')
|
||||
.removeAttr('data-drop-level')
|
||||
.removeAttr('data-invalid-nested');
|
||||
$oldRow.find('.steps-editor-step-name').removeClass('is-invalid-drop-level');
|
||||
this.dropSide = '';
|
||||
this.dropLevel = '';
|
||||
this.isValidLevel = undefined;
|
||||
idDiff = true;
|
||||
}
|
||||
const dropItem = this.getByName(dropName);
|
||||
const dropBounding = dropElement.getBoundingClientRect();
|
||||
const dropSide = event.clientY > (dropBounding.top + dropBounding.height / 2) ? 'bottom' : 'top';
|
||||
const dropLevel = Math.max(1, Math.min(3, (!this.options.changeLevelByDrag || event.clientX <= (dropBounding.left + 24)) ? dropItem.level : (Math.round((event.clientX - dropBounding.left - 24 - 8) / 14))));
|
||||
const isValidLevel = (this.dragMaxLevel + (dropLevel - this.dragItem.level)) <= 3;
|
||||
if(this.isValidLevel !== isValidLevel)
|
||||
{
|
||||
this.isValidLevel = isValidLevel;
|
||||
$row.toggleClass('is-invalid-drop-level', !isValidLevel);
|
||||
const $name = $row.find('.steps-editor-step-name');
|
||||
if(isValidLevel) $name.removeAttr('data-invalid-nested');
|
||||
else $name.attr('data-invalid-nested', this.options.dragNestedTip);
|
||||
}
|
||||
$element.toggleClass('cursor-not-allowed', !isValidLevel);
|
||||
if(this.dropSide !== dropSide)
|
||||
{
|
||||
this.dropSide = dropSide;
|
||||
$row.attr('data-drop-side', dropSide);
|
||||
idDiff = true;
|
||||
}
|
||||
if(this.dropLevel !== dropLevel)
|
||||
{
|
||||
this.dropLevel = dropLevel;
|
||||
$row.attr('data-drop-level', dropLevel);
|
||||
idDiff = true;
|
||||
}
|
||||
},
|
||||
target: (dragElement) =>
|
||||
{
|
||||
return $element.find('.steps-editor-item').not('.is-sub-dragging').not(dragElement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getByID(id)
|
||||
{
|
||||
const map = this._map;
|
||||
const keys = Object.keys(map);
|
||||
for(let i = 0; i < keys.length; i++)
|
||||
{
|
||||
const item = map[keys[i]];
|
||||
if(item.id === id) return item;
|
||||
}
|
||||
return null;
|
||||
return this._items.find(x => x.id === id);
|
||||
}
|
||||
|
||||
update(data, reset, skipRender)
|
||||
getByName(name)
|
||||
{
|
||||
return this._items.find(x => x.name === name);
|
||||
}
|
||||
|
||||
getParent(item)
|
||||
{
|
||||
if(typeof item === 'string') item = this.getByName(item);
|
||||
return (item && item.parentName) ? this.getByName(item.parentName) : null;
|
||||
}
|
||||
|
||||
getRow(name)
|
||||
{
|
||||
return this.$element.find(`.steps-editor-item[data-name="${name}"]`);
|
||||
}
|
||||
|
||||
reset(data, skipRender)
|
||||
{
|
||||
this._items = [];
|
||||
this.update(data, skipRender);
|
||||
}
|
||||
|
||||
update(data, skipRender)
|
||||
{
|
||||
this._map = (reset ? {} : this._map) || {};
|
||||
data.forEach(item =>{this.updateItem(item, true);});
|
||||
if(!skipRender) this.render();
|
||||
}
|
||||
|
||||
updateItem(item, skipRender)
|
||||
{
|
||||
const map = this._map;
|
||||
if(typeof item === 'string') item = {name: item, id: $.guid++};
|
||||
item = $.extend({id: $.guid++, step: '', expect: ''}, item);
|
||||
const namePath = item.name.split('.');
|
||||
item.order = namePath.reduce((total, level, index) => total + (+level * ([1000000, 1000, 1, 0.001, 0.000001][index])), 0);
|
||||
item.level = namePath.length;
|
||||
item.selfName = namePath.pop();
|
||||
item.parentName = namePath.join('.');
|
||||
item = typeof item === 'string' ? {name: item, id: $.guid++, step: '', expect: ''} : $.extend({id: $.guid++, step: '', expect: ''}, item);
|
||||
updateItemName(item);
|
||||
|
||||
const oldItem = this.getByID(item.id);
|
||||
|
||||
if(oldItem && oldItem.name !== item.name) delete map[oldItem.name];
|
||||
map[item.name] = oldItem ? $.extend({}, oldItem, item) : item;
|
||||
const index = this._items.findIndex(x => x.id === item.id);
|
||||
if(index >= 0)
|
||||
{
|
||||
const oldItem = this._items[index];
|
||||
const parent = this.getParent(oldItem);
|
||||
if(parent) parent.children.splice(parent.children.indexOf(oldItem), 1);
|
||||
item = $.extend(oldItem, item);
|
||||
this._items.splice(index, 1);
|
||||
updateChildrenName(item);
|
||||
}
|
||||
if(item.level > 1)
|
||||
{
|
||||
this._items.push(item);
|
||||
const parent = this._ensureItem(item.parentName);
|
||||
const siblings = parent.children || [];
|
||||
const oldIndex = siblings.findIndex(x => x.id === item.id);
|
||||
if(oldIndex >= 0) siblings.splice(oldIndex, 1);
|
||||
const index = siblings.findIndex(x => x.order > item.order);
|
||||
if(index === 0) siblings.unshift(item);
|
||||
else if(index < 0) siblings.push(item);
|
||||
else if(index > 0) siblings.splice(index, 0, item);
|
||||
parent.children = siblings;
|
||||
}
|
||||
else
|
||||
{
|
||||
const siblings = this._items;
|
||||
const index = siblings.findIndex(x => x.level === 1 && x.order > item.order);
|
||||
if(index === 0) siblings.unshift(item);
|
||||
else if(index < 0) siblings.push(item);
|
||||
else if(index > 0) siblings.splice(index, 0, item);
|
||||
}
|
||||
|
||||
if(!skipRender) this.render();
|
||||
return item;
|
||||
}
|
||||
|
||||
_ensureItem(name)
|
||||
{
|
||||
return this.getByName(name) || this.updateItem(name);
|
||||
}
|
||||
|
||||
_createRow(item, options)
|
||||
{
|
||||
const $row = $
|
||||
([
|
||||
`<div class="steps-editor-row steps-editor-item" data-id="${item.id}">`,
|
||||
'<div class="steps-editor-drag-ghost"></div>',
|
||||
'<div class="steps-editor-col steps-editor-col-step form-control">',
|
||||
'<div class="steps-editor-step-move"><i class="icon icon-move"></i></div>',
|
||||
'<div class="steps-editor-step-name"></div>',
|
||||
`<textarea class="steps-editor-step-text" rows="1">${item.step}</textarea>`,
|
||||
'</div>',
|
||||
'<div class="steps-editor-col steps-editor-col-add">',
|
||||
`<div><button type="button" class="btn ghost rounded size-sm square btn-action" data-action="sib"><i class="icon icon-${options.sameLevelIcon}"></i></button></div>`,
|
||||
`<div><button type="button" class="btn ghost rounded size-sm square btn-action" data-action="sub"><i class="icon icon-${options.subLevelIcon}"></i></button></div>`,
|
||||
'</div>',
|
||||
'<div class="steps-editor-col steps-editor-col-expect">',
|
||||
`<textarea class="steps-editor-step-expect form-control" rows="1">${item.expect}</textarea>`,
|
||||
'</div>',
|
||||
'<div class="steps-editor-col steps-editor-col-delete">',
|
||||
`<div><button type="button" class="btn ghost rounded size-sm square btn-action" data-action="delete"><i class="icon icon-${options.deleteIcon}"></i></button></div>`,
|
||||
'<input class="steps-editor-step-type" type="hidden" />',
|
||||
'</div>',
|
||||
'</div>'
|
||||
].join(''));
|
||||
$row.find('textarea').autoHeight();
|
||||
return $row;
|
||||
}
|
||||
|
||||
_renderRow(item, $preRow, $list, $rows)
|
||||
{
|
||||
const options = this.options;
|
||||
let $row = $rows.filter(`[data-id="${item.id}"]`);
|
||||
if(!$row.length) $row = this._createRow(item, options);
|
||||
|
||||
if($preRow) $row.insertAfter($preRow);
|
||||
else $list.prepend($row);
|
||||
|
||||
const hasSub = !!(item.children && item.children.length);
|
||||
$row.attr('data-level', item.level)
|
||||
.attr('data-name', item.name)
|
||||
.attr('data-index', item.index)
|
||||
.toggleClass('has-children', hasSub)
|
||||
.toggleClass('no-child', !hasSub)
|
||||
.removeClass('is-expired');
|
||||
$row.css('--name-indent', `${(item.level - 1) * 14}px`).find('.steps-editor-step-name').css('width', item.name.length * 12).text(item.name).css('paddingLeft', (item.level - 1) * 14);
|
||||
$row.find('.steps-editor-step-text').attr('name', `${options.name}[${item.name}]`);
|
||||
$row.find('.steps-editor-step-type').attr('name', `stepType[${item.name}]`).val(hasSub ? 'group' : (!!item.parent ? 'item' : 'step'));
|
||||
const $expect = $row.find('.steps-editor-step-expect').attr(
|
||||
{
|
||||
name: `${options.expectsName}[${item.name}]`,
|
||||
placeholder: hasSub ? options.expectDisabledTip : null,
|
||||
}).toggleClass('disabled', hasSub);
|
||||
if(hasSub) $expect.val('');
|
||||
$row.find('.steps-editor-col-delete .btn').toggleClass('disabled', hasSub);
|
||||
$row.find('.steps-editor-col-add .btn-action[data-action="sub"]').toggleClass('disabled', item.level >= 3);
|
||||
return $row;
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
const map = this._map;
|
||||
const list = Object.keys(map).map(key =>
|
||||
const items = this._items;
|
||||
let rootIndex = 0;
|
||||
items.forEach(item =>
|
||||
{
|
||||
const item = map[key];
|
||||
return $.extend(item, {children: [], parent: item.parentName ? map[item.parentName] : null})
|
||||
})
|
||||
.sort((a, b) => a.order - b.order);
|
||||
list.forEach(item =>
|
||||
{
|
||||
if(item.parent) item.parent.children.push(item);
|
||||
updateItemName(item);
|
||||
if(item.level === 1)
|
||||
{
|
||||
item.index = rootIndex;
|
||||
rootIndex++;
|
||||
updateItemName(item, `${rootIndex}`);
|
||||
}
|
||||
if(item.children)
|
||||
{
|
||||
item.children.forEach((subItem, subIndex) =>
|
||||
{
|
||||
subItem.index = subIndex;
|
||||
updateItemName(subItem, `${item.name}.${subIndex + 1}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
items.sort((a, b) => a.order - b.order);
|
||||
|
||||
const $list = this.$element.find('.steps-editor-body');
|
||||
const $rows = $list.find('.steps-editor-row').addClass('is-expired');
|
||||
const options = this.options;
|
||||
const $rows = $list.find('.steps-editor-item').addClass('is-expired');
|
||||
let $preRow = null;
|
||||
let $row = null;
|
||||
list.forEach(item =>
|
||||
items.forEach(item =>
|
||||
{
|
||||
$row = $rows.filter(`[data-id="${item.id}"]`);
|
||||
if(!$row.length)
|
||||
{
|
||||
$row = $
|
||||
([
|
||||
`<div class="steps-editor-row row ring items-streach" data-id="${item.id}">`,
|
||||
'<div class="steps-editor-col steps-editor-col-step form-control">',
|
||||
'<div class="steps-editor-step-name"></div>',
|
||||
`<textarea class="steps-editor-step-text" rows="1">${item.step}</textarea>`,
|
||||
'</div>',
|
||||
'<div class="steps-editor-col steps-editor-col-add">',
|
||||
`<div><button type="button" class="btn ghost rounded size-sm square btn-action" data-action="sib"><i class="icon icon-${options.sameLevelIcon}"></i></button></div>`,
|
||||
`<div><button type="button" class="btn ghost rounded size-sm square btn-action" data-action="sub"><i class="icon icon-${options.subLevelIcon}"></i></button></div>`,
|
||||
'</div>',
|
||||
'<div class="steps-editor-col steps-editor-col-expect">',
|
||||
`<textarea class="steps-editor-step-expect form-control" rows="1">${item.expect}</textarea>`,
|
||||
'</div>',
|
||||
'<div class="steps-editor-col steps-editor-col-delete">',
|
||||
`<div><button type="button" class="btn ghost rounded size-sm square btn-action" data-action="delete"><i class="icon icon-${options.deleteIcon}"></i></button></div>`,
|
||||
'<input class="steps-editor-step-type" type="hidden" />',
|
||||
'</div>',
|
||||
'</div>'
|
||||
].join(''));
|
||||
}
|
||||
if($preRow) $row.insertAfter($preRow);
|
||||
else $list.prepend($row);
|
||||
$preRow = $row;
|
||||
|
||||
const hasSub = !!(item.children && item.children.length);
|
||||
$row.attr('data-level', item.level)
|
||||
.attr('data-name', item.name)
|
||||
.toggleClass('has-children', hasSub)
|
||||
.removeClass('is-expired');
|
||||
$row.find('.steps-editor-step-name').css('width', 6 + (item.level * 18)).text(item.name);
|
||||
$row.find('.steps-editor-step-text').attr('name', `${options.name}[${item.name}]`);
|
||||
$row.find('.steps-editor-step-type').attr('name', `stepType[${item.name}]`).val(hasSub ? 'item' : 'step');
|
||||
const $expect = $row.find('.steps-editor-step-expect').attr(
|
||||
{
|
||||
name: `${options.expectsName}[${item.name}]`,
|
||||
placeholder: hasSub ? options.expectDisabledTip : null,
|
||||
disabled: hasSub ? 'disabled' : null,
|
||||
}).toggleClass('disabled', hasSub);
|
||||
if(hasSub) $expect.val('');
|
||||
$row.find('.steps-editor-col-delete .btn').toggleClass('disabled', hasSub).attr('disabled', hasSub ? 'disabled' : null);
|
||||
$row.find('.steps-editor-col-add .btn-action[data-action="sub"]').attr('disabled', item.level >= 3 ? 'disabled' : null);
|
||||
$preRow = this._renderRow(item, $preRow, $list, $rows);
|
||||
});
|
||||
$rows.filter('.is-expired').remove();
|
||||
}
|
||||
|
||||
deleteStep(name)
|
||||
{
|
||||
const item = this._map[name];
|
||||
if (!item) return;
|
||||
delete this._map[name];
|
||||
const siblings = item.parent ? item.parent.children.filter(x => x.name !== name) : Object.keys(this._map).map(key => this._map[key]).filter(x => !x.parent);
|
||||
const updateItems = [];
|
||||
siblings.forEach((sibling, idx) =>
|
||||
const index = this._items.findIndex(x => x.name === name);
|
||||
if (index < 0) return;
|
||||
const item = this._items[index];
|
||||
this._items.splice(index, 1);
|
||||
const parent = this.getParent(item);
|
||||
if(parent)
|
||||
{
|
||||
const newName = item.parent ? `${item.parent.name}.${idx + 1}` : `${idx + 1}`;
|
||||
if(sibling.name !== newName)
|
||||
{
|
||||
updateItems.push($.extend({}, sibling, {name: newName}));
|
||||
}
|
||||
});
|
||||
if(updateItems.length) this.update(updateItems, false, true);
|
||||
if(!Object.keys(this._map).length) this.update(['1'], false, true);
|
||||
const siblings = parent.children;
|
||||
const index = siblings.indexOf(item);
|
||||
if(index > -1) siblings.splice(index, 1);
|
||||
}
|
||||
if (!this._items.length) this.update(['1'], false);
|
||||
this.render();
|
||||
}
|
||||
|
||||
focus(name)
|
||||
{
|
||||
const $step = this.$element.find('.steps-editor-row[data-name="' + name + '"] .steps-editor-step-text');
|
||||
const $step = this.$element.find('.steps-editor-item[data-name="' + name + '"] .steps-editor-step-text');
|
||||
if($step.length) $step[0].focus();
|
||||
}
|
||||
|
||||
addSub(fromName)
|
||||
{
|
||||
const item = this._map[fromName];
|
||||
const item = this.getByName(fromName);
|
||||
if(!item) return;
|
||||
|
||||
const newStepName = item.children ? `${item.name}.${item.children.length + 1}` : `${item.name}.1`;
|
||||
|
||||
this.updateItem({name: newStepName});
|
||||
this.updateItem(newStepName);
|
||||
this.focus(newStepName);
|
||||
}
|
||||
|
||||
addSib(fromName)
|
||||
{
|
||||
const item = this._map[fromName];
|
||||
const item = this.getByName(fromName);
|
||||
if(!item) return;
|
||||
|
||||
const siblings = item.parent ? item.parent.children : Object.keys(this._map).map(key => this._map[key]).filter(item => !item.parent);
|
||||
const index = siblings.indexOf(item);
|
||||
const newStepName = item.parent ? `${item.parent.name}.${index + 2}` : `${index + 2}`;
|
||||
const updateItems = [newStepName];
|
||||
if(index < siblings.length - 1)
|
||||
{
|
||||
siblings.slice(index + 1).forEach((nextItem, idx) =>
|
||||
{
|
||||
const newIndex = index + idx + 3;
|
||||
updateItems.push($.extend({}, nextItem, {name: nextItem.parent ? `${nextItem.parent.name}.${newIndex}` : `${newIndex}`}))
|
||||
});
|
||||
}
|
||||
this.update(updateItems);
|
||||
this.focus(newStepName);
|
||||
const newItem = this.updateItem(item.name);
|
||||
this.focus(newItem.name);
|
||||
}
|
||||
|
||||
addStep(fromName, asSib)
|
||||
moveAfter(fromName, toName)
|
||||
{
|
||||
if(asSib) this.addSib(fromName);
|
||||
else this.addSub(fromName);
|
||||
if(fromName === toName) return;
|
||||
const item = this.getByName(fromName);
|
||||
if(!item) return;
|
||||
item.name = toName;
|
||||
this.updateItem(item);
|
||||
}
|
||||
|
||||
moveBefore(fromName, toName)
|
||||
{
|
||||
if(fromName === toName) return;
|
||||
const item = this.getByName(fromName);
|
||||
const toItem = this.getByName(toName);
|
||||
if(!item || !toItem) return;
|
||||
item.name = toItem.level > 1 ? `${toItem.parentName}.${+toItem.selfName - 1}` : `${+toItem.selfName - 1}`;
|
||||
this.updateItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
/* Define $.fn.stepsEditor() helper. */
|
||||
StepsEditor.defineFn();
|
||||
|
||||
/* Extend StepsEditor to zui object. */
|
||||
$.extend(zui, {StepsEditor});
|
||||
|
||||
@@ -35,7 +35,8 @@ class stepsEditor extends wg
|
||||
'expectText?: string', // 预期文本。
|
||||
'sameLevelText?: string', // 同级文本。
|
||||
'subLevelText?: string', // 子级文本。
|
||||
'expectDisabledTip?: string' // 预期输入框禁用提示。
|
||||
'expectDisabledTip?: string', // 预期输入框禁用提示。
|
||||
'dragNestedTip?: string' // 拖拽超出提示。
|
||||
);
|
||||
|
||||
public static function getPageJS(): string|false
|
||||
@@ -62,6 +63,7 @@ class stepsEditor extends wg
|
||||
$subLevelText = $this->prop('subLevelText', data('lang.testcase.stepSubLevel'));
|
||||
$id = $this->prop('id');
|
||||
$expectDisabledTip = $this->prop('expectDisabledTip', data('lang.testcase.expectDisabledTip'));
|
||||
$dragNestedTip = $this->prop('dragNestedTip', data('lang.testcase.dragNestedTip'));
|
||||
|
||||
return div
|
||||
(
|
||||
@@ -99,6 +101,7 @@ class stepsEditor extends wg
|
||||
(
|
||||
set::_to("#$id"),
|
||||
set::expectDisabledTip($expectDisabledTip),
|
||||
set::dragNestedTip($dragNestedTip),
|
||||
set($this->props->pick(array('name', 'expectsName', 'data')))
|
||||
)
|
||||
);
|
||||
|
||||
@@ -7,19 +7,48 @@ class tableChart extends wg
|
||||
protected static array $defineProps = array(
|
||||
'type:string',
|
||||
'title:string',
|
||||
'datas?:array'
|
||||
'tableHeaders?:array',
|
||||
'datas?:array',
|
||||
'tableWidth?:string',
|
||||
'chartHeight?:int',
|
||||
'overflow?:bool'
|
||||
);
|
||||
|
||||
protected function build(): wg
|
||||
private function genTableHeaders(): wg
|
||||
{
|
||||
global $lang;
|
||||
|
||||
$tableHeaders = $this->prop('tableHeaders');
|
||||
if(empty($tableHeaders))
|
||||
{
|
||||
$tableHeaders = array
|
||||
(
|
||||
'item' => $lang->report->item,
|
||||
'value' => $lang->report->value,
|
||||
'percent' => $lang->report->percent
|
||||
);
|
||||
}
|
||||
|
||||
return h::tr
|
||||
(
|
||||
setClass('border-t'),
|
||||
h::th($tableHeaders['item']),
|
||||
h::th(set::width('100px'), $tableHeaders['value']),
|
||||
h::th(set::width('120px'), $tableHeaders['percent'])
|
||||
);
|
||||
}
|
||||
|
||||
protected function build(): wg
|
||||
{
|
||||
$type = $this->prop('type');
|
||||
$title = $this->prop('title');
|
||||
$datas = $this->prop('datas');
|
||||
$colorList = array('#5470C6', '#91CC75', '#FAC858', '#EE6666', '#73C0DE', '#3BA272', '#FC8452', '#9A60B4', '#EA7CCC');
|
||||
$chartOption = array();
|
||||
foreach($datas as $key => $data)
|
||||
|
||||
shuffle($colorList);
|
||||
|
||||
foreach($datas as $data)
|
||||
{
|
||||
$color = current($colorList);
|
||||
$chartOption[] = array('name' => $data->name, 'value' => $type == 'pie' ? $data->value : array('value' => $data->value, 'itemStyle' => array('color' => $color)));
|
||||
@@ -32,13 +61,15 @@ class tableChart extends wg
|
||||
if(!next($colorList)) reset($colorList);
|
||||
}
|
||||
|
||||
$tableWdith = $this->prop('tableWidth', '50%');
|
||||
$chartHeight = $this->prop('chartHeight', 300);
|
||||
$overflow = $this->prop('overflow', true);
|
||||
return div
|
||||
(
|
||||
set::class('flex border'),
|
||||
set::class('flex border py-2'),
|
||||
cell
|
||||
(
|
||||
set::width('50%'),
|
||||
set::class('border-r chart'),
|
||||
setClass('border-r chart flex-auto'),
|
||||
div(set::class('center text-base font-bold py-2'), $title),
|
||||
echarts
|
||||
(
|
||||
@@ -63,21 +94,21 @@ class tableChart extends wg
|
||||
)
|
||||
)
|
||||
)
|
||||
)->size('100%', 300),
|
||||
)->size('100%', $chartHeight),
|
||||
),
|
||||
cell
|
||||
(
|
||||
set::width('50%'),
|
||||
h::table
|
||||
set::width($tableWdith),
|
||||
div
|
||||
(
|
||||
set::class('table'),
|
||||
h::tr
|
||||
setClass('overflow-y-auto'),
|
||||
$overflow ? setStyle('max-height', ($chartHeight + 50) .'px') : null,
|
||||
h::table
|
||||
(
|
||||
h::th($lang->report->item),
|
||||
h::th(set::width('100px'), $lang->report->value),
|
||||
h::th(set::width('120px'), $lang->report->percent)
|
||||
),
|
||||
$tableTR
|
||||
set::class('table'),
|
||||
$this->genTableHeaders(),
|
||||
$tableTR
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ class tabPane extends wg
|
||||
{
|
||||
protected static array $defineProps = array(
|
||||
'key: string',
|
||||
'title?: string',
|
||||
'title: string',
|
||||
'active?: bool=false',
|
||||
'param?: string',
|
||||
);
|
||||
|
||||
@@ -3,4 +3,4 @@ window.editItem = function(item)
|
||||
const modal = zui.Modal.open({
|
||||
url: $.createLink('tree', 'edit', 'moduleID=' + item.id + '&type=' + item.editType),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
+22
-17
@@ -38,27 +38,32 @@ class tree extends wg
|
||||
foreach($items as $key => $item)
|
||||
{
|
||||
$item = (array)$item;
|
||||
$item['url'] = isset($item['url']) ? $item['url'] : '';
|
||||
$item['type'] = isset($item['type']) ? $item['type'] : '';
|
||||
|
||||
$treeItem = array('text' => $item['name'], 'url' => $item['url'], 'id' => $item['id'], 'key' => $item['key']);
|
||||
if($item['type'] == 'product')
|
||||
if(!isset($item['content']))
|
||||
{
|
||||
$treeItem['icon'] = 'product';
|
||||
}
|
||||
else
|
||||
{
|
||||
$treeItem['actions'] = array();
|
||||
$treeItem['actions']['items'] = array();
|
||||
if(!isset($item['text'])) $item['text'] = $item['name'];
|
||||
if(!isset($item['url'])) $item['url'] = '';
|
||||
|
||||
if($canEdit) $treeItem['actions']['items'][] = array('key' => 'edit', 'icon' => 'edit', 'id' => $item['id'], 'editType' => $editType, 'onClick' => jsRaw('(event, item) => window.editItem(item)'));
|
||||
if($canDelete) $treeItem['actions']['items'][] = array('key' => 'delete', 'icon' => 'trash', 'id' => $item['id'], 'class' => 'btn ghost toolbar-item square size-sm rounded ajax-submit','url' => helper::createLink('tree', 'delete', 'module=' . $item['id']));
|
||||
if($canSplit) $treeItem['actions']['items'][] = array('key' => 'view', 'icon' => 'split', 'url' => $item['url']);
|
||||
if(isset($item['type']) && $item['type'] == 'product')
|
||||
{
|
||||
$item['icon'] = 'product';
|
||||
}
|
||||
else
|
||||
{
|
||||
$item['actions'] = array();
|
||||
$item['actions']['items'] = array();
|
||||
|
||||
if($canEdit) $item['actions']['items'][] = array('key' => 'edit', 'icon' => 'edit', 'id' => $item['id'], 'editType' => $editType, 'onClick' => jsRaw('(event, item) => window.editItem(item)'));
|
||||
if($canDelete) $item['actions']['items'][] = array('key' => 'delete', 'icon' => 'trash', 'id' => $item['id'], 'className' => 'btn ghost toolbar-item square size-sm rounded ajax-submit','url' => helper::createLink('tree', 'delete', 'module=' . $item['id']));
|
||||
if($canSplit) $item['actions']['items'][] = array('key' => 'view', 'icon' => 'split', 'url' => $item['url']);
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($item['children'])) $treeItem['items'] = $this->buildTree($item['children']);
|
||||
|
||||
$items[$key] = $treeItem;
|
||||
if(!empty($item['children']))
|
||||
{
|
||||
$item['items'] = $this->buildTree($item['children']);
|
||||
unset($item['children']);
|
||||
}
|
||||
$items[$key] = $item;
|
||||
}
|
||||
|
||||
return $items;
|
||||
|
||||
@@ -22,12 +22,15 @@ class html extends \html
|
||||
{
|
||||
}
|
||||
|
||||
class commonModel extends \commonModel
|
||||
if(empty($_SESSION['installing']))
|
||||
{
|
||||
}
|
||||
class commonModel extends \commonModel
|
||||
{
|
||||
}
|
||||
|
||||
class common extends \commonModel
|
||||
{
|
||||
class common extends \commonModel
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +53,7 @@ function inLink(string $methodName = 'index', string|array $vars = '', string $v
|
||||
return \inlink($methodName, $vars, $viewType, $onlybody);
|
||||
}
|
||||
|
||||
function zget(array|object $var, string|int $key, mixed $valueWhenNone = false, mixed $valueWhenExists = false): mixed
|
||||
function zget(array|object $var, string|int|bool $key, mixed $valueWhenNone = false, mixed $valueWhenExists = false): mixed
|
||||
{
|
||||
return \zget($var, $key, $valueWhenNone, $valueWhenExists);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ require_once __DIR__ . DS . 'toggle.func.php';
|
||||
|
||||
class toggle
|
||||
{
|
||||
public static function __callStatic($name, $args)
|
||||
public static function __callStatic(string $name, array $args): directive
|
||||
{
|
||||
return toggle($name, empty($args) ? null : $args[0]);
|
||||
return toggle($name, empty($args) ? array() : $args[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,8 @@ namespace zin;
|
||||
* @param string $name
|
||||
* @param array $dataset
|
||||
*/
|
||||
function toggle($name, $dataset = null): directive
|
||||
function toggle(string $name, array $dataset = array()): directive
|
||||
{
|
||||
if(empty($dataset)) $dataset = array();
|
||||
$dataset['toggle'] = $name;
|
||||
return setData($dataset);
|
||||
}
|
||||
|
||||
+32
-18
@@ -29,22 +29,18 @@ class zui extends wg
|
||||
'_size?: array',
|
||||
'_id?: string',
|
||||
'_class?: string',
|
||||
'_call?: string'
|
||||
'_call?: string',
|
||||
'_initWithShareData?: bool',
|
||||
);
|
||||
|
||||
protected function build(): wg
|
||||
protected function build(): wg|array
|
||||
{
|
||||
list($name, $target, $tagName, $targetProps, $size, $id, $class, $map, $call) = $this->prop(array('_name', '_to', '_tag', '_props', '_size', '_id', '_class', '_map', '_call'));
|
||||
list($name, $target, $tagName, $targetProps, $size, $id, $class, $map, $call, $initWithShareData) = $this->prop(array('_name', '_to', '_tag', '_props', '_size', '_id', '_class', '_map', '_call', '_initWithShareData'));
|
||||
list($width, $height) = $size;
|
||||
|
||||
$options = $this->getRestProps();
|
||||
$children = $this->children();
|
||||
$selector = $target;
|
||||
if(empty($selector))
|
||||
{
|
||||
if(empty($id)) $id = $this->gid;
|
||||
$selector = "#$id";
|
||||
}
|
||||
|
||||
if(is_array($map))
|
||||
{
|
||||
foreach($options as $key => $value)
|
||||
@@ -55,7 +51,24 @@ class zui extends wg
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($call)) $call = '~zui.create';
|
||||
if($initWithShareData && empty($call) && empty($target))
|
||||
{
|
||||
if(empty($id)) $id = $this->gid;
|
||||
$optionsName = "_options_$id";
|
||||
$children[] = setData(array('zui' => "$name:$optionsName"));
|
||||
$children[] = h::jsShare($optionsName, $options);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(empty($call)) $call = '~zui.create';
|
||||
$selector = $target;
|
||||
if(empty($selector))
|
||||
{
|
||||
if(empty($id)) $id = $this->gid;
|
||||
$selector = "#$id";
|
||||
}
|
||||
$children[] = h::jsCall($call, $name, $selector, $options);
|
||||
}
|
||||
|
||||
if(empty($target))
|
||||
{
|
||||
@@ -68,11 +81,10 @@ class zui extends wg
|
||||
setStyle('width', $width),
|
||||
setStyle('height', $height),
|
||||
$children,
|
||||
h::jsCall($call, $name, $selector, $options)
|
||||
);
|
||||
}
|
||||
|
||||
return h::jsCall($call, $name, $selector, $options);
|
||||
return $children;
|
||||
}
|
||||
|
||||
public static function __callStatic($name, $args)
|
||||
@@ -80,13 +92,15 @@ class zui extends wg
|
||||
return new zui(set('_name', $name), $args);
|
||||
}
|
||||
|
||||
public static function toggle($name, $options = null)
|
||||
public static function toggle($name, $options = array())
|
||||
{
|
||||
return toggle($name, $options);
|
||||
}
|
||||
|
||||
public static function setClass($name, ...$args)
|
||||
public static function setClass(/* $name, ...$args */)
|
||||
{
|
||||
$args = func_get_args();
|
||||
$name = array_shift($args);
|
||||
$class = array($name => true);
|
||||
foreach($args as $arg)
|
||||
{
|
||||
@@ -199,13 +213,13 @@ class zui extends wg
|
||||
return zui::skin('h', $value, '0', 'width');
|
||||
}
|
||||
|
||||
public static function ring(...$args)
|
||||
public static function ring(/* ...$args */)
|
||||
{
|
||||
return zui::skin('ring', $args, '0');
|
||||
return zui::skin('ring', func_get_args(), '0');
|
||||
}
|
||||
|
||||
public static function border(...$args)
|
||||
public static function border(/* ...$args */)
|
||||
{
|
||||
return zui::skin('border', $args, 'none', 'border');
|
||||
return zui::skin('border', func_get_args(), 'none', 'border');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,4 +19,5 @@ function opacity($value) {return zui::opacity($value);}
|
||||
function disabled($value = true) {return zui::disabled($value);}
|
||||
function width($value) {return zui::width($value);}
|
||||
function height($value) {return zui::height($value);}
|
||||
function ring(...$args) {return zui::ring(...$args);}
|
||||
function ring(/* ...$args */) {return call_user_func_array('\zin\zui::ring', func_get_args());}
|
||||
function border(/* ...$args */) {return call_user_func_array('\zin\zui::border', func_get_args());}
|
||||
|
||||
Reference in New Issue
Block a user