* zin: update zin lib.

This commit is contained in:
sunhao
2023-08-29 16:42:22 +08:00
parent f9a5edc520
commit dfb442f8c1
97 changed files with 2158 additions and 841 deletions
+1 -1
View File
@@ -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'];
}
+3 -1
View File
@@ -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]);
}
+1 -1
View File
@@ -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
View File
@@ -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)
-27
View File
@@ -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);
}
}
+10 -10
View File
@@ -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;
}
}
+2 -2
View File
@@ -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()));
}
}
+6 -6
View File
@@ -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
View File
@@ -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());
}
+38 -78
View File
@@ -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;
}
-84
View File
@@ -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;
}
}
+5 -97
View File
@@ -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
*
+1 -1
View File
@@ -16,7 +16,7 @@ $logs = array();
function log($type, $msg = null, $file)
{
global $config;
global $config, $logs;
if(!$config->debug) return;
+2 -2
View File
@@ -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
{
+38 -49
View File
@@ -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";
}
}
+5 -3
View File
@@ -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),
);
}
+1
View File
@@ -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',
+10
View File
@@ -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.
*
+7 -1
View File
@@ -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;
}
}
+1 -1
View File
@@ -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')));
+1 -1
View File
@@ -8,7 +8,7 @@ class center extends wg
{
return div
(
setClass("flex justify-center items-center"),
setClass("center"),
set($this->getRestProps()),
$this->children()
);
+9 -4
View File
@@ -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()
);
+2 -2
View File
@@ -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),
+1 -1
View File
@@ -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 属性
+8 -2
View File
@@ -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())
);
}
}
+65 -3
View File
@@ -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(),
);
}
}
+16
View File
@@ -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()
);
}
}
+3 -3
View File
@@ -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;}
+12 -4
View File
@@ -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
View File
@@ -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&param={$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(),
);
}
}
+1 -1
View File
@@ -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)
+47 -6
View File
@@ -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);
}
}
+1 -1
View File
@@ -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);}
+1 -1
View File
@@ -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 -1
View File
@@ -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;}
+5 -3
View File
@@ -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
(
+3 -1
View File
@@ -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
(
+2 -2
View File
@@ -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
(
+4 -4
View File
@@ -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,
);
}
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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(),
);
}
+5 -7
View File
@@ -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
View File
@@ -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}&section=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}&section=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()
);
}
+1
View File
@@ -34,6 +34,7 @@ class formRowGroup extends formRow
$this->prop('title'),
$this->block('suffix'),
),
set($this->getRestProps()),
$this->prop('items'),
);
}
+66 -32
View File
@@ -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);
}
+25 -13
View File
@@ -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)
)
))
);
+20
View File
@@ -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);}))"));
}
}
+74
View File
@@ -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);
};
+26 -3
View File
@@ -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(
+6
View File
@@ -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
View File
@@ -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
{
+2 -2
View File
@@ -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;}
+7 -7
View File
@@ -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
);
}
}
+57
View File
@@ -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)),
);
}
}
+1 -1
View File
@@ -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',
+9 -8
View File
@@ -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';
}
}
+9
View File
@@ -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;}
+5
View File
@@ -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'))
+12
View File
@@ -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;};}
+45
View File
@@ -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;
}
}
+75
View File
@@ -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)
);
}
}
View File
+1
View File
@@ -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,
+2 -1
View File
@@ -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
);
}
+3
View File
@@ -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;}
+43
View File
@@ -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),
);
}
}
+21 -4
View File
@@ -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)'))
);
}
+12
View File
@@ -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);}
+47
View File
@@ -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()
);
}
}
+4 -4
View File
@@ -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
+7 -5
View File
@@ -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
+43
View File
@@ -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
View File
@@ -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
+52
View File
@@ -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;
}
+57
View File
@@ -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()),
);
}
}
+1 -1
View File
@@ -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']))
{
+23
View File
@@ -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);}
+67
View File
@@ -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);
});
+119
View File
@@ -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
);
}
}
-11
View File
@@ -1,11 +0,0 @@
<?php
declare(strict_types=1);
namespace zin;
class searchForm extends wg
{
protected function build(): zui
{
return zui::searchForm(inherit($this));
}
}
+1 -1
View File
@@ -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=&params=&actionURL=&queryID=0&formName=' + formName);
const url = $.createLink('search', 'buildForm', 'module=' + moduleName + '&fields=&params=&actionURL=&queryID=0&formName=' + formName);
$.get(url, html =>
{
$form.html(html).data('loaded', true);
+1 -1
View File
@@ -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 标签
);
+1 -1
View File
@@ -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']);
+8
View File
@@ -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);}
+112
View File
@@ -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)
);
}
}
+40 -5
View File
@@ -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
View File
@@ -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});
+4 -1
View File
@@ -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')))
)
);
+47 -16
View File
@@ -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
)
)
)
);
+1 -1
View File
@@ -6,7 +6,7 @@ class tabPane extends wg
{
protected static array $defineProps = array(
'key: string',
'title?: string',
'title: string',
'active?: bool=false',
'param?: string',
);
+1 -1
View File
@@ -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
View File
@@ -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;
+8 -5
View File
@@ -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);
}
+2 -2
View File
@@ -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]);
}
}
+1 -2
View File
@@ -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
View File
@@ -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');
}
}
+2 -1
View File
@@ -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());}