代码格式化

This commit is contained in:
liaoxuezhi 2019-05-08 13:52:04 +08:00
parent 39afde9f97
commit 43a2b42c7c
53 changed files with 4271 additions and 4701 deletions

View File

@ -6,5 +6,6 @@
"semi": true, "semi": true,
"trailingComma": "es5", "trailingComma": "es5",
"bracketSpacing": false, "bracketSpacing": false,
"arrowParens": "avoid" "arrowParens": "avoid",
"jsxBracketSameLine": false
} }

View File

@ -9,7 +9,8 @@
"start": "fis3 server start --www ./public --port 8888 --no-daemon --no-browse", "start": "fis3 server start --www ./public --port 8888 --no-daemon --no-browse",
"stop": "fis3 server stop", "stop": "fis3 server stop",
"dev": "fis3 release -cwd ./public", "dev": "fis3 release -cwd ./public",
"publish2npm": "sh publish.sh && npm publish" "publish2npm": "sh publish.sh && npm publish",
"prettier": "prettier --write src/**/*.tsx"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -120,6 +121,7 @@
"lint-staged": "^8.1.6", "lint-staged": "^8.1.6",
"marked": "^0.3.7", "marked": "^0.3.7",
"mobx-wiretap": "^0.12.0", "mobx-wiretap": "^0.12.0",
"prettier": "1.17.0",
"react-frame-component": "^2.0.0", "react-frame-component": "^2.0.0",
"react-router": "3.2.0", "react-router": "3.2.0",
"react-test-renderer": "^16.8.6", "react-test-renderer": "^16.8.6",

View File

@ -1,23 +1,46 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {filter} from '../utils/tpl';
RendererProps
} from '../factory';
import {
filter
} from '../utils/tpl';
import Button from '../components/Button'; import Button from '../components/Button';
import pick = require('lodash/pick'); import pick = require('lodash/pick');
const ActionProps = [ const ActionProps = [
'dialog', 'drawer', 'url', 'link', 'confirmText', 'tooltip', 'disabledTip', 'className', 'asyncApi', 'redirect', 'dialog',
'size', 'level', 'primary', 'feedback', 'api', 'blank', 'tooltipPlacement', 'to', 'content', 'required', 'drawer',
'type', 'actionType', 'label', 'icon', 'reload', 'target', 'close', 'messages', 'mergeData', 'index', 'url',
'copy', 'content' 'link',
'confirmText',
'tooltip',
'disabledTip',
'className',
'asyncApi',
'redirect',
'size',
'level',
'primary',
'feedback',
'api',
'blank',
'tooltipPlacement',
'to',
'content',
'required',
'type',
'actionType',
'label',
'icon',
'reload',
'target',
'close',
'messages',
'mergeData',
'index',
'copy',
'content',
]; ];
import { filterContents } from './Remark'; import {filterContents} from './Remark';
import { ClassNamesFn, themeable } from '../theme'; import {ClassNamesFn, themeable} from '../theme';
import { Omit } from '../types'; import {Omit} from '../types';
import { autobind } from '../utils/helper'; import {autobind} from '../utils/helper';
export interface ActionProps { export interface ActionProps {
className?: string; className?: string;
@ -28,13 +51,13 @@ export interface ActionProps {
iconClassName?: string; iconClassName?: string;
size?: 'xs' | 'sm' | 'md' | 'lg'; size?: 'xs' | 'sm' | 'md' | 'lg';
level?: 'info' | 'success' | 'warning' | 'danger' | 'link'; level?: 'info' | 'success' | 'warning' | 'danger' | 'link';
onAction?: (e: React.MouseEvent<any> | void | null, action:object) => void; onAction?: (e: React.MouseEvent<any> | void | null, action: object) => void;
isCurrentUrl?: (link: string) => boolean; isCurrentUrl?: (link: string) => boolean;
onClick?: (e:React.MouseEvent<any>) => void; onClick?: (e: React.MouseEvent<any>) => void;
primary?: boolean; primary?: boolean;
activeClassName: string; activeClassName: string;
componentClass: React.ReactType; componentClass: React.ReactType;
tooltipPlacement: "bottom" | "top" | "right" | "left" | undefined; tooltipPlacement: 'bottom' | 'top' | 'right' | 'left' | undefined;
disabled?: boolean; disabled?: boolean;
block?: boolean; block?: boolean;
data?: any; data?: any;
@ -52,22 +75,18 @@ export interface ActionProps {
const allowedType = ['button', 'submit', 'reset']; const allowedType = ['button', 'submit', 'reset'];
export class Action extends React.Component<ActionProps> { export class Action extends React.Component<ActionProps> {
static defaultProps:Pick<ActionProps, "type" | "componentClass" | "tooltipPlacement" | "activeClassName"> = { static defaultProps: Pick<ActionProps, 'type' | 'componentClass' | 'tooltipPlacement' | 'activeClassName'> = {
type: 'button', type: 'button',
componentClass: 'button', componentClass: 'button',
tooltipPlacement: 'bottom', tooltipPlacement: 'bottom',
activeClassName: 'is-active' activeClassName: 'is-active',
}; };
dom:any; dom: any;
@autobind @autobind
handleAction(e:React.MouseEvent<any>) { handleAction(e: React.MouseEvent<any>) {
const { const {onAction, onClick, disabled} = this.props;
onAction,
onClick,
disabled
} = this.props;
onClick && onClick(e); onClick && onClick(e);
@ -105,7 +124,7 @@ export class Action extends React.Component<ActionProps> {
active, active,
activeLevel, activeLevel,
tooltipContainer, tooltipContainer,
classnames: cx classnames: cx,
} = this.props; } = this.props;
let isActive = !!active; let isActive = !!active;
@ -118,20 +137,20 @@ export class Action extends React.Component<ActionProps> {
<a <a
className={cx(className, { className={cx(className, {
[activeClassName || 'is-active']: isActive, [activeClassName || 'is-active']: isActive,
'is-disabled': disabled 'is-disabled': disabled,
})} })}
onClick={this.handleAction} onClick={this.handleAction}
> >
{label} {label}
{icon ? <i className={cx('Button-icon', icon)} /> : null} {icon ? <i className={cx('Button-icon', icon)} /> : null}
</a> </a>
) : ( ) : (
<Button <Button
className={cx(className, { className={cx(className, {
[activeClassName || 'is-active']: isActive [activeClassName || 'is-active']: isActive,
})} })}
size={size} size={size}
level={activeLevel && isActive ? activeLevel : (level || (primary ? 'primary': undefined))} level={activeLevel && isActive ? activeLevel : level || (primary ? 'primary' : undefined)}
onClick={this.handleAction} onClick={this.handleAction}
type={type && ~allowedType.indexOf(type) ? type : 'button'} type={type && ~allowedType.indexOf(type) ? type : 'button'}
disabled={disabled} disabled={disabled}
@ -141,9 +160,9 @@ export class Action extends React.Component<ActionProps> {
placement={tooltipPlacement} placement={tooltipPlacement}
tooltipContainer={tooltipContainer} tooltipContainer={tooltipContainer}
block={block} block={block}
iconOnly={!!(icon && !label && level !== "link")} iconOnly={!!(icon && !label && level !== 'link')}
> >
{label ? (<span>{filter(label, data)}</span>) : null} {label ? <span>{filter(label, data)}</span> : null}
{icon ? <i className={cx('Button-icon', icon, iconClassName)} /> : null} {icon ? <i className={cx('Button-icon', icon, iconClassName)} /> : null}
</Button> </Button>
); );
@ -154,53 +173,43 @@ export default themeable(Action);
@Renderer({ @Renderer({
test: /(^|\/)action$/, test: /(^|\/)action$/,
name: 'action' name: 'action',
}) })
export class ActionRenderer extends React.Component<RendererProps & Omit<ActionProps, 'onAction' | 'isCurrentUrl' | 'tooltipContainer'> & { export class ActionRenderer extends React.Component<
onAction: (e: React.MouseEvent<any> | void | null, action:object, data:any) => void; RendererProps &
btnDisabled?: boolean; Omit<ActionProps, 'onAction' | 'isCurrentUrl' | 'tooltipContainer'> & {
}> { onAction: (e: React.MouseEvent<any> | void | null, action: object, data: any) => void;
btnDisabled?: boolean;
}
> {
@autobind @autobind
handleAction(e: React.MouseEvent<any> | void | null, action:any) { handleAction(e: React.MouseEvent<any> | void | null, action: any) {
const { const {env, onAction, data} = this.props;
env,
onAction,
data
} = this.props;
if (action.confirmText && env.confirm) { if (action.confirmText && env.confirm) {
env env.confirm(filter(action.confirmText, data)).then(
.confirm(filter(action.confirmText, data)) (confirmed: boolean) => confirmed && onAction(e, action, data)
.then((confirmed:boolean) => confirmed && onAction(e, action, data)); );
} else { } else {
onAction(e, action, data); onAction(e, action, data);
} }
} }
@autobind @autobind
isCurrentAction(link:string) { isCurrentAction(link: string) {
const { const {env, data} = this.props;
env,
data
} = this.props;
return env.isCurrentUrl(filter(link, data)); return env.isCurrentUrl(filter(link, data));
} }
render() { render() {
const { const {env, disabled, btnDisabled, ...rest} = this.props;
env,
disabled,
btnDisabled,
...rest
} = this.props;
return ( return (
<Action <Action
{...rest} {...rest}
disabled={disabled || btnDisabled} disabled={disabled || btnDisabled}
onAction={this.handleAction} onAction={this.handleAction}
isCurrentUrl={this.isCurrentAction} isCurrentUrl={this.isCurrentAction}
tooltipContainer={env.getModalContainer ? env.getModalContainer() : undefined} tooltipContainer={env.getModalContainer ? env.getModalContainer() : undefined}
/> />
); );
@ -209,18 +218,18 @@ export class ActionRenderer extends React.Component<RendererProps & Omit<ActionP
@Renderer({ @Renderer({
test: /(^|\/)button$/, test: /(^|\/)button$/,
name: 'button' name: 'button',
}) })
export class ButtonRenderer extends ActionRenderer {} export class ButtonRenderer extends ActionRenderer {}
@Renderer({ @Renderer({
test: /(^|\/)submit$/, test: /(^|\/)submit$/,
name: 'submit' name: 'submit',
}) })
export class SubmitRenderer extends ActionRenderer {} export class SubmitRenderer extends ActionRenderer {}
@Renderer({ @Renderer({
test: /(^|\/)reset$/, test: /(^|\/)reset$/,
name: 'reset' name: 'reset',
}) })
export class ResetRenderer extends ActionRenderer {} export class ResetRenderer extends ActionRenderer {}

View File

@ -1,23 +1,14 @@
import { Renderer, RendererProps } from "../factory"; import {Renderer, RendererProps} from '../factory';
import React = require("react"); import React = require('react');
import Alert, { AlertProps } from "../components/Alert2"; import Alert, {AlertProps} from '../components/Alert2';
@Renderer({ @Renderer({
test: /(^|\/)alert$/, test: /(^|\/)alert$/,
name: 'alert' name: 'alert',
}) })
export class TplRenderer extends React.Component<AlertProps & RendererProps> { export class TplRenderer extends React.Component<AlertProps & RendererProps> {
render() { render() {
const { const {render, body, ...rest} = this.props;
render, return <Alert {...rest}>{render('body', body)}</Alert>;
body,
...rest
} = this.props;
return (
<Alert {...rest}>
{render('body', body)}
</Alert>
);
} }
}; }

View File

@ -1,32 +1,29 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {autobind} from '../utils/helper';
RendererProps import {volumeIcon, muteIcon, playIcon, pauseIcon} from '../components/icons';
} from '../factory';
import { autobind } from '../utils/helper';
import { volumeIcon, muteIcon, playIcon, pauseIcon} from '../components/icons';
export interface AudioProps extends RendererProps { export interface AudioProps extends RendererProps {
className?: string; className?: string;
inline?: boolean, inline?: boolean;
src?: string, src?: string;
autoPlay?: boolean, autoPlay?: boolean;
loop?: boolean, loop?: boolean;
rates?: number[] rates?: number[];
} }
export interface AudioState { export interface AudioState {
isReady?: boolean, isReady?: boolean;
muted?: boolean, muted?: boolean;
playing?: boolean, playing?: boolean;
played: number, played: number;
seeking?: boolean, seeking?: boolean;
volume: number, volume: number;
prevVolume: number, prevVolume: number;
loaded?: number, loaded?: number;
playbackRate: number, playbackRate: number;
showHandlePlaybackRate: boolean, showHandlePlaybackRate: boolean;
showHandleVolume: boolean showHandleVolume: boolean;
} }
export class Audio extends React.Component<AudioProps, AudioState> { export class Audio extends React.Component<AudioProps, AudioState> {
@ -34,16 +31,19 @@ export class Audio extends React.Component<AudioProps, AudioState> {
progressTimeout: any; progressTimeout: any;
durationTimeout: any; durationTimeout: any;
static defaultProps:Pick<AudioProps, 'inline' | 'autoPlay' | 'playbackRate' | 'loop' | 'rates' | 'progressInterval'> = { static defaultProps: Pick<
AudioProps,
'inline' | 'autoPlay' | 'playbackRate' | 'loop' | 'rates' | 'progressInterval'
> = {
inline: true, inline: true,
autoPlay: false, autoPlay: false,
playbackRate: 1, playbackRate: 1,
loop: false, loop: false,
rates: [1.0, 2.0, 4.0], rates: [1.0, 2.0, 4.0],
progressInterval: 1000 progressInterval: 1000,
}; };
state:AudioState = { state: AudioState = {
isReady: false, isReady: false,
muted: false, muted: false,
playing: false, playing: false,
@ -54,19 +54,22 @@ export class Audio extends React.Component<AudioProps, AudioState> {
loaded: 0, loaded: 0,
playbackRate: 1.0, playbackRate: 1.0,
showHandlePlaybackRate: false, showHandlePlaybackRate: false,
showHandleVolume: false showHandleVolume: false,
} };
componentWillUnmount () { componentWillUnmount() {
clearTimeout(this.progressTimeout); clearTimeout(this.progressTimeout);
} }
componentDidMount() { componentDidMount() {
const autoPlay = this.props.autoPlay; const autoPlay = this.props.autoPlay;
const playing = autoPlay ? true : false; const playing = autoPlay ? true : false;
this.setState({ this.setState(
playing: playing {
}, this.progress); playing: playing,
},
this.progress
);
} }
@autobind @autobind
@ -77,33 +80,33 @@ export class Audio extends React.Component<AudioProps, AudioState> {
const duration = this.audio.duration; const duration = this.audio.duration;
const played = currentTime / duration; const played = currentTime / duration;
let playing = this.state.playing; let playing = this.state.playing;
playing = (played != 1 && playing) ? true : false; playing = played != 1 && playing ? true : false;
this.setState({ this.setState({
played, played,
playing playing,
}); });
this.progressTimeout = setTimeout(this.progress, (this.props.progressInterval / this.state.playbackRate)) this.progressTimeout = setTimeout(this.progress, this.props.progressInterval / this.state.playbackRate);
} }
} }
@autobind @autobind
audioRef(audio:any) { audioRef(audio: any) {
this.audio = audio; this.audio = audio;
} }
@autobind @autobind
load() { load() {
this.setState({ this.setState({
isReady: true isReady: true,
}); });
} }
@autobind @autobind
handlePlaybackRate(rate:number) { handlePlaybackRate(rate: number) {
this.audio.playbackRate = rate; this.audio.playbackRate = rate;
this.setState({ this.setState({
playbackRate: rate, playbackRate: rate,
showHandlePlaybackRate: false showHandlePlaybackRate: false,
}); });
} }
@ -117,7 +120,7 @@ export class Audio extends React.Component<AudioProps, AudioState> {
this.audio.muted = !muted; this.audio.muted = !muted;
this.setState({ this.setState({
muted: !muted, muted: !muted,
volume: curVolume volume: curVolume,
}); });
} }
@ -129,7 +132,7 @@ export class Audio extends React.Component<AudioProps, AudioState> {
let playing = this.state.playing; let playing = this.state.playing;
playing ? this.audio.pause() : this.audio.play(); playing ? this.audio.pause() : this.audio.play();
this.setState({ this.setState({
playing: !playing playing: !playing,
}); });
} }
@ -144,7 +147,7 @@ export class Audio extends React.Component<AudioProps, AudioState> {
} }
@autobind @autobind
getDuration () { getDuration() {
if (!this.audio || !this.props.src) { if (!this.audio || !this.props.src) {
return '0:00'; return '0:00';
} }
@ -152,11 +155,11 @@ export class Audio extends React.Component<AudioProps, AudioState> {
this.onDurationCheck(); this.onDurationCheck();
return '0:00'; return '0:00';
} }
const { duration, seekable } = this.audio; const {duration, seekable} = this.audio;
// on iOS, live streams return Infinity for the duration // on iOS, live streams return Infinity for the duration
// so instead we use the end of the seekable timerange // so instead we use the end of the seekable timerange
if (duration === Infinity && seekable.length > 0) { if (duration === Infinity && seekable.length > 0) {
return seekable.end(seekable.length - 1) return seekable.end(seekable.length - 1);
} }
return this.formatTime(duration); return this.formatTime(duration);
} }
@ -172,21 +175,21 @@ export class Audio extends React.Component<AudioProps, AudioState> {
} }
@autobind @autobind
onSeekChange(e:any) { onSeekChange(e: any) {
if (!this.props.src) { if (!this.props.src) {
return; return;
} }
const played = e.target.value; const played = e.target.value;
this.setState({ played: played }); this.setState({played: played});
} }
@autobind @autobind
onSeekMouseDown() { onSeekMouseDown() {
this.setState({ seeking: true }); this.setState({seeking: true});
} }
@autobind @autobind
onSeekMouseUp(e:any) { onSeekMouseUp(e: any) {
if (!this.state.seeking) { if (!this.state.seeking) {
return; return;
} }
@ -196,15 +199,15 @@ export class Audio extends React.Component<AudioProps, AudioState> {
const loop = this.props.loop; const loop = this.props.loop;
let playing = this.state.playing; let playing = this.state.playing;
playing = (played < 1 || loop) ? playing : false; playing = played < 1 || loop ? playing : false;
this.setState({ this.setState({
playing: playing, playing: playing,
seeking: false seeking: false,
}); });
} }
@autobind @autobind
setVolume(e:any) { setVolume(e: any) {
if (!this.props.src) { if (!this.props.src) {
return; return;
} }
@ -212,12 +215,12 @@ export class Audio extends React.Component<AudioProps, AudioState> {
this.audio.volume = volume; this.audio.volume = volume;
this.setState({ this.setState({
volume: volume, volume: volume,
prevVolume: volume prevVolume: volume,
}); });
} }
@autobind @autobind
formatTime(seconds:number) { formatTime(seconds: number) {
const date = new Date(seconds * 1000); const date = new Date(seconds * 1000);
const hh = date.getUTCHours(); const hh = date.getUTCHours();
const mm = date.getUTCMinutes(); const mm = date.getUTCMinutes();
@ -229,8 +232,8 @@ export class Audio extends React.Component<AudioProps, AudioState> {
} }
@autobind @autobind
pad(string:number) { pad(string: number) {
return ('0' + string).slice(-2) return ('0' + string).slice(-2);
} }
@autobind @autobind
@ -239,39 +242,23 @@ export class Audio extends React.Component<AudioProps, AudioState> {
return; return;
} }
this.setState({ this.setState({
showHandlePlaybackRate: !this.state.showHandlePlaybackRate showHandlePlaybackRate: !this.state.showHandlePlaybackRate,
}); });
} }
@autobind @autobind
toggleHandleVolume(type:boolean) { toggleHandleVolume(type: boolean) {
if (!this.props.src) { if (!this.props.src) {
return; return;
} }
this.setState({ this.setState({
showHandleVolume: type showHandleVolume: type,
}); });
} }
render() { render() {
const { const {className, inline, src, autoPlay, loop, rates, classnames: cx} = this.props;
className, const {playing, played, volume, muted, playbackRate, showHandlePlaybackRate, showHandleVolume} = this.state;
inline,
src,
autoPlay,
loop,
rates,
classnames: cx
} = this.props;
const {
playing,
played,
volume,
muted,
playbackRate,
showHandlePlaybackRate,
showHandleVolume
} = this.state;
return ( return (
<div className={cx(inline ? 'Audio--inline' : '')}> <div className={cx(inline ? 'Audio--inline' : '')}>
@ -282,55 +269,75 @@ export class Audio extends React.Component<AudioProps, AudioState> {
autoPlay={autoPlay} autoPlay={autoPlay}
controls controls
muted={muted} muted={muted}
loop={loop}> loop={loop}
<source src={src}/> >
<source src={src} />
</audio> </audio>
<div className={cx('Audio', className)}> <div className={cx('Audio', className)}>
{rates && rates.length ? {rates && rates.length ? (
(<div className={cx('Audio-rates')}> <div className={cx('Audio-rates')}>
<div className={cx('Audio-rate')} <div className={cx('Audio-rate')} onClick={this.toggleHandlePlaybackRate}>
onClick={this.toggleHandlePlaybackRate}>
x{playbackRate.toFixed(1)} x{playbackRate.toFixed(1)}
</div> </div>
{showHandlePlaybackRate ? {showHandlePlaybackRate ? (
(<div className={cx('Audio-rateControl')}> <div className={cx('Audio-rateControl')}>
{rates.map((rate, index) => {rates.map((rate, index) => (
<span className={cx('Audio-rateControlItem')} <span
key={index} className={cx('Audio-rateControlItem')}
onClick={() => this.handlePlaybackRate(rate)}> key={index}
onClick={() => this.handlePlaybackRate(rate)}
>
x{rate.toFixed(1)} x{rate.toFixed(1)}
</span> </span>
)} </div>) ))}{' '}
: null} </div>
</div>) ) : null}
: (<div className={cx('Audio-rates-holder')}></div>) } </div>
) : (
<div className={cx('Audio-rates-holder')} />
)}
<div className={cx('Audio-play')} onClick={this.handlePlaying}> <div className={cx('Audio-play')} onClick={this.handlePlaying}>
{playing ? pauseIcon : playIcon} {playing ? pauseIcon : playIcon}
</div> </div>
<div className={cx('Audio-times')}>{this.getCurrentTime()} / {this.getDuration()}</div> <div className={cx('Audio-times')}>
{this.getCurrentTime()} / {this.getDuration()}
</div>
<div className={cx('Audio-process')}> <div className={cx('Audio-process')}>
<input <input
type="range" type="range"
min={0} max={1} step="any" min={0}
max={1}
step="any"
value={played || 0} value={played || 0}
onMouseDown={this.onSeekMouseDown} onMouseDown={this.onSeekMouseDown}
onChange={this.onSeekChange} onChange={this.onSeekChange}
onMouseUp={this.onSeekMouseUp}/> onMouseUp={this.onSeekMouseUp}
/>
</div> </div>
<div className={cx('Audio-volume')} <div
onMouseEnter={() => this.toggleHandleVolume(true)} className={cx('Audio-volume')}
onMouseLeave={() => this.toggleHandleVolume(false)}> onMouseEnter={() => this.toggleHandleVolume(true)}
{showHandleVolume ? onMouseLeave={() => this.toggleHandleVolume(false)}
(<div className={cx('Audio-volumeControl')}> >
{showHandleVolume ? (
<div className={cx('Audio-volumeControl')}>
<input <input
type='range' min={0} max={1} step='any' type="range"
min={0}
max={1}
step="any"
value={volume} value={volume}
onChange={this.setVolume} /> onChange={this.setVolume}
<div className={cx('Audio-volumeControlIcon')} />
onClick={this.handleMute}> <div className={cx('Audio-volumeControlIcon')} onClick={this.handleMute}>
{volume > 0 ? volumeIcon : muteIcon} {volume > 0 ? volumeIcon : muteIcon}
</div></div>) </div>
: volume > 0 ? volumeIcon : muteIcon} </div>
) : volume > 0 ? (
volumeIcon
) : (
muteIcon
)}
</div> </div>
</div> </div>
</div> </div>
@ -340,6 +347,6 @@ export class Audio extends React.Component<AudioProps, AudioState> {
@Renderer({ @Renderer({
test: /(^|\/)audio/, test: /(^|\/)audio/,
name: 'audio' name: 'audio',
}) })
export class AudioRenderer extends Audio {}; export class AudioRenderer extends Audio {}

View File

@ -1,16 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import ButtonGroup from './Form/ButtonGroup'; import ButtonGroup from './Form/ButtonGroup';
import { import {Renderer} from '../factory';
Renderer
} from '../factory';
export default ButtonGroup; export default ButtonGroup;
@Renderer({ @Renderer({
test: /(^|\/)(?:button|action)\-group$/, test: /(^|\/)(?:button|action)\-group$/,
name: 'button-group' name: 'button-group',
}) })
export class ButtonGroupRenderer extends ButtonGroup { export class ButtonGroupRenderer extends ButtonGroup {}
}

View File

@ -1,36 +1,26 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {Action} from '../types';
RendererProps
} from '../factory';
import {
Action
} from '../types';
export interface ButtonToolbarProps extends RendererProps { export interface ButtonToolbarProps extends RendererProps {
buttons: Array<Action>; buttons: Array<Action>;
} }
export default class ButtonToolbar extends React.Component<ButtonToolbarProps, object> { export default class ButtonToolbar extends React.Component<ButtonToolbarProps, object> {
static propsList: Array<string> = [ static propsList: Array<string> = ['buttons'];
"buttons",
];
render() { render() {
const { const {buttons, className, classnames: cx, render} = this.props;
buttons,
className,
classnames: cx,
render
} = this.props;
return ( return (
<div <div className={cx('ButtonToolbar', className)}>
className={cx("ButtonToolbar", className)} {Array.isArray(buttons)
> ? buttons.map((button, key) =>
{Array.isArray(buttons) ? buttons.map((button, key) => render(`${key}`, button, { render(`${key}`, button, {
key key,
})) : null} })
)
: null}
</div> </div>
); );
} }
@ -38,6 +28,6 @@ export default class ButtonToolbar extends React.Component<ButtonToolbarProps, o
@Renderer({ @Renderer({
test: /(^|\/)button-toolbar$/, test: /(^|\/)button-toolbar$/,
name: 'button-toolbar' name: 'button-toolbar',
}) })
export class ButtonToolbarRenderer extends ButtonToolbar {}; export class ButtonToolbarRenderer extends ButtonToolbar {}

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,20 @@
import * as React from 'react'; import * as React from 'react';
import { import {findDOMNode} from 'react-dom';
findDOMNode import {Renderer, RendererProps} from '../factory';
} from 'react-dom'; import {SchemaNode, Schema, Action} from '../types';
import { import {filter, evalExpression} from '../utils/tpl';
Renderer,
RendererProps
} from '../factory';
import {
SchemaNode,
Schema,
Action
} from '../types';
import {
filter, evalExpression
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
import Checkbox from '../components/Checkbox'; import Checkbox from '../components/Checkbox';
import { IItem} from '../store/list'; import {IItem} from '../store/list';
import { padArr, isVisible, isDisabled, noop } from '../utils/helper'; import {padArr, isVisible, isDisabled, noop} from '../utils/helper';
import { resolveVariable } from '../utils/tpl-builtin'; import {resolveVariable} from '../utils/tpl-builtin';
import QuickEdit from './QuickEdit'; import QuickEdit from './QuickEdit';
import PopOver from './PopOver'; import PopOver from './PopOver';
import { TableCell } from './Table'; import {TableCell} from './Table';
import Copyable from './Copyable'; import Copyable from './Copyable';
export interface CardProps extends RendererProps { export interface CardProps extends RendererProps {
onCheck: (item:IItem) => void; onCheck: (item: IItem) => void;
multiple?: boolean; multiple?: boolean;
highlightClassName?: string; highlightClassName?: string;
hideCheckToggler?: boolean; hideCheckToggler?: boolean;
@ -33,8 +22,7 @@ export interface CardProps extends RendererProps {
checkOnItemClick?: boolean; checkOnItemClick?: boolean;
} }
export class Card extends React.Component<CardProps> { export class Card extends React.Component<CardProps> {
static defaultProps: Partial<CardProps> = {
static defaultProps:Partial<CardProps> = {
className: '', className: '',
avatarClassName: '', avatarClassName: '',
bodyClassName: '', bodyClassName: '',
@ -42,10 +30,10 @@ export class Card extends React.Component<CardProps> {
titleClassName: '', titleClassName: '',
highlightClassName: '', highlightClassName: '',
subTitleClassName: '', subTitleClassName: '',
descClassName: '' descClassName: '',
}; };
static propsList:Array<string> = [ static propsList: Array<string> = [
'multiple', 'multiple',
'avatarClassName', 'avatarClassName',
'bodyClassName', 'bodyClassName',
@ -54,10 +42,10 @@ export class Card extends React.Component<CardProps> {
'highlightClassName', 'highlightClassName',
'subTitleClassName', 'subTitleClassName',
'descClassName', 'descClassName',
'hideCheckToggler' 'hideCheckToggler',
]; ];
constructor(props:CardProps) { constructor(props: CardProps) {
super(props); super(props);
this.getPopOverContainer = this.getPopOverContainer.bind(this); this.getPopOverContainer = this.getPopOverContainer.bind(this);
@ -68,14 +56,14 @@ export class Card extends React.Component<CardProps> {
this.handleCheck = this.handleCheck.bind(this); this.handleCheck = this.handleCheck.bind(this);
} }
handleClick(e:React.MouseEvent<HTMLDivElement>) { handleClick(e: React.MouseEvent<HTMLDivElement>) {
const target:HTMLElement = e.target as HTMLElement; const target: HTMLElement = e.target as HTMLElement;
const ns = this.props.classPrefix; const ns = this.props.classPrefix;
if ( if (
!e.currentTarget.contains(target) !e.currentTarget.contains(target) ||
|| ~['INPUT', 'TEXTAREA'].indexOf(target.tagName) ~['INPUT', 'TEXTAREA'].indexOf(target.tagName) ||
|| target.closest(`button, a, .${ns}Form-item`) target.closest(`button, a, .${ns}Form-item`)
) { ) {
return; return;
} }
@ -89,17 +77,16 @@ export class Card extends React.Component<CardProps> {
this.props.onCheck && this.props.onCheck(item); this.props.onCheck && this.props.onCheck(item);
} }
handleAction(e:React.UIEvent<any>, action: Action, ctx: object) { handleAction(e: React.UIEvent<any>, action: Action, ctx: object) {
const {onAction, item} = this.props; const {onAction, item} = this.props;
onAction && onAction(e, action, ctx || item.data); onAction && onAction(e, action, ctx || item.data);
} }
handleQuickChange(values:object, saveImmediately?: boolean, saveSlient?: boolean) { handleQuickChange(values: object, saveImmediately?: boolean, saveSlient?: boolean) {
const {onQuickChange, item} = this.props; const {onQuickChange, item} = this.props;
onQuickChange && onQuickChange(item, values, saveImmediately, saveSlient); onQuickChange && onQuickChange(item, values, saveImmediately, saveSlient);
} }
getPopOverContainer() { getPopOverContainer() {
return findDOMNode(this); return findDOMNode(this);
} }
@ -115,24 +102,24 @@ export class Card extends React.Component<CardProps> {
multiple, multiple,
hideCheckToggler, hideCheckToggler,
classnames: cx, classnames: cx,
classPrefix: ns classPrefix: ns,
} = this.props; } = this.props;
if (dragging) { if (dragging) {
return ( return (
<div className={cx("Card-dragBtn")}> <div className={cx('Card-dragBtn')}>
<i className="fa fa-exchange" /> <i className="fa fa-exchange" />
</div> </div>
); );
} else if (selectable && !hideCheckToggler) { } else if (selectable && !hideCheckToggler) {
return ( return (
<div className={cx("Card-checkBtn")}> <div className={cx('Card-checkBtn')}>
<Checkbox <Checkbox
classPrefix={ns} classPrefix={ns}
type={multiple ? 'checkbox' : 'radio'} type={multiple ? 'checkbox' : 'radio'}
disabled={!checkable} disabled={!checkable}
checked={selected} checked={selected}
onChange={checkOnItemClick ? noop : this.handleCheck} onChange={checkOnItemClick ? noop : this.handleCheck}
/> />
</div> </div>
); );
@ -142,36 +129,36 @@ export class Card extends React.Component<CardProps> {
} }
renderActions() { renderActions() {
const { const {actions, render, dragging, actionsCount, data, classnames: cx} = this.props;
actions,
render,
dragging,
actionsCount,
data,
classnames: cx
} = this.props;
if (Array.isArray(actions)) { if (Array.isArray(actions)) {
const group = padArr(actions.filter(item => isVisible(item, data)), actionsCount) const group = padArr(actions.filter(item => isVisible(item, data)), actionsCount);
return group.map((actions, groupIndex) => ( return group.map((actions, groupIndex) => (
<div key={groupIndex} className={cx("Card-actions")}> <div key={groupIndex} className={cx('Card-actions')}>
{actions.map((action, index) => { {actions.map((action, index) => {
const size = action.size || 'sm'; const size = action.size || 'sm';
return render(`action/${index}`, { return render(
level: 'link', `action/${index}`,
type: 'button', {
...action, level: 'link',
size type: 'button',
}, { ...action,
isMenuItem: true, size,
key: index, },
index, {
disabled: dragging || isDisabled(action, data), isMenuItem: true,
className: cx('Card-action', action.className || `${size ? `Card-action--${size}` : ''}`), key: index,
componentClass: 'a', index,
onAction: this.handleAction disabled: dragging || isDisabled(action, data),
}) className: cx(
'Card-action',
action.className || `${size ? `Card-action--${size}` : ''}`
),
componentClass: 'a',
onAction: this.handleAction,
}
);
})} })}
</div> </div>
)); ));
@ -180,36 +167,31 @@ export class Card extends React.Component<CardProps> {
return null; return null;
} }
renderChild(node:SchemaNode, region: string = 'body', key:any = 0): JSX.Element { renderChild(node: SchemaNode, region: string = 'body', key: any = 0): JSX.Element {
const { const {render} = this.props;
render
} = this.props;
if (typeof node === 'string' || typeof node === 'number') { if (typeof node === 'string' || typeof node === 'number') {
return render(region, node, {key}) as JSX.Element; return render(region, node, {key}) as JSX.Element;
} }
const childNode:Schema = node as Schema; const childNode: Schema = node as Schema;
if (childNode.type === 'hbox' || childNode.type === 'grid') { if (childNode.type === 'hbox' || childNode.type === 'grid') {
return render(region, node, { return render(region, node, {
key, key,
itemRender: this.itemRender itemRender: this.itemRender,
}) as JSX.Element; }) as JSX.Element;
} }
return this.renderFeild(region,childNode, key, this.props); return this.renderFeild(region, childNode, key, this.props);
} }
itemRender(field:any, index:number, props:any) { itemRender(field: any, index: number, props: any) {
return this.renderFeild(`column/${index}`, field, index, props); return this.renderFeild(`column/${index}`, field, index, props);
} }
renderFeild(region:string, field:any, key:any, props:any) { renderFeild(region: string, field: any, key: any, props: any) {
const { const {render, classnames: cx} = props;
render,
classnames: cx
} = props;
const data = this.props.data; const data = this.props.data;
const $$id = field.$$id ? `${field.$$id}-field` : ''; const $$id = field.$$id ? `${field.$$id}-field` : '';
@ -220,33 +202,37 @@ export class Card extends React.Component<CardProps> {
<label className={cx('Card-fieldLabel', field.labelClassName)}>{field.label}</label> <label className={cx('Card-fieldLabel', field.labelClassName)}>{field.label}</label>
) : null} ) : null}
{render(region, { {
...field, render(
field: field, region,
$$id, {
type: 'card-item-field', ...field,
}, { field: field,
className: cx("Card-fieldValue", field.className), $$id,
value: field.name ? resolveVariable(field.name, data) : undefined, type: 'card-item-field',
popOverContainer: this.getPopOverContainer, },
onAction: this.handleAction, {
onQuickChange: this.handleQuickChange className: cx('Card-fieldValue', field.className),
}) as JSX.Element} value: field.name ? resolveVariable(field.name, data) : undefined,
popOverContainer: this.getPopOverContainer,
onAction: this.handleAction,
onQuickChange: this.handleQuickChange,
}
) as JSX.Element
}
</div> </div>
); );
} }
renderBody() { renderBody() {
const { const {body} = this.props;
body
} = this.props;
if (!body) { if (!body) {
return null; return null;
} }
if (Array.isArray(body)) { if (Array.isArray(body)) {
return body.map((child, index) => this.renderChild(child, `body/${index}`, index)) return body.map((child, index) => this.renderChild(child, `body/${index}`, index));
} }
return this.renderChild(body, 'body'); return this.renderChild(body, 'body');
@ -266,7 +252,7 @@ export class Card extends React.Component<CardProps> {
checkOnItemClick, checkOnItemClick,
checkable, checkable,
classnames: cx, classnames: cx,
classPrefix: ns classPrefix: ns,
} = this.props; } = this.props;
let heading = null; let heading = null;
@ -281,7 +267,7 @@ export class Card extends React.Component<CardProps> {
subTitle: subTitleTpl, subTitle: subTitleTpl,
subTitlePlaceholder, subTitlePlaceholder,
desc: descTpl, desc: descTpl,
descPlaceholder descPlaceholder,
} = header; } = header;
const highlight = !!evalExpression(highlightTpl, data as object); const highlight = !!evalExpression(highlightTpl, data as object);
@ -297,7 +283,7 @@ export class Card extends React.Component<CardProps> {
<img className={cx('Card-img', header.imageClassName || imageClassName)} src={avatar} /> <img className={cx('Card-img', header.imageClassName || imageClassName)} src={avatar} />
</span> </span>
) : null} ) : null}
<div className={cx("Card-meta")}> <div className={cx('Card-meta')}>
{highlight ? ( {highlight ? (
<i className={cx('Card-highlight', header.highlightClassName || highlightClassName)} /> <i className={cx('Card-highlight', header.highlightClassName || highlightClassName)} />
) : null} ) : null}
@ -309,15 +295,19 @@ export class Card extends React.Component<CardProps> {
) : null} ) : null}
{subTitle || subTitlePlaceholder ? ( {subTitle || subTitlePlaceholder ? (
<div className={cx('Card-subTitle', header.subTitleClassName || subTitleClassName)}>{render('sub-title', subTitle || subTitlePlaceholder, { <div className={cx('Card-subTitle', header.subTitleClassName || subTitleClassName)}>
className: cx(!subTitle ? 'Card-placeholder' : undefined) {render('sub-title', subTitle || subTitlePlaceholder, {
})}</div> className: cx(!subTitle ? 'Card-placeholder' : undefined),
})}
</div>
) : null} ) : null}
{desc || descPlaceholder ? ( {desc || descPlaceholder ? (
<div className={cx('Card-desc', header.descClassName || descClassName)}>{render('desc', desc || descPlaceholder, { <div className={cx('Card-desc', header.descClassName || descClassName)}>
className: !desc ? 'text-muted' : undefined {render('desc', desc || descPlaceholder, {
})}</div> className: !desc ? 'text-muted' : undefined,
})}
</div>
) : null} ) : null}
</div> </div>
</div> </div>
@ -326,14 +316,14 @@ export class Card extends React.Component<CardProps> {
const body = this.renderBody(); const body = this.renderBody();
return ( return (
<div onClick={checkOnItemClick && checkable ? this.handleClick : undefined} className={cx('Card', className)}> <div
onClick={checkOnItemClick && checkable ? this.handleClick : undefined}
className={cx('Card', className)}
>
{this.renderToolbar()} {this.renderToolbar()}
{heading} {heading}
{body ? ( {body ? <div className={cx('Card-body', bodyClassName)}>{body}</div> : null}
<div className={cx('Card-body', bodyClassName)}>{body}</div>
) : null}
{this.renderActions()} {this.renderActions()}
</div> </div>
); );
@ -342,15 +332,13 @@ export class Card extends React.Component<CardProps> {
@Renderer({ @Renderer({
test: /(^|\/)card$/, test: /(^|\/)card$/,
name: 'card' name: 'card',
}) })
export class CardRenderer extends Card { export class CardRenderer extends Card {}
};
@Renderer({ @Renderer({
test: /(^|\/)card-item-field$/, test: /(^|\/)card-item-field$/,
name: 'card-item' name: 'card-item',
}) })
@QuickEdit() @QuickEdit()
@PopOver() @PopOver()
@ -358,15 +346,10 @@ export class CardRenderer extends Card {
export class CardItemFieldRenderer extends TableCell { export class CardItemFieldRenderer extends TableCell {
static defaultProps = { static defaultProps = {
...TableCell.defaultProps, ...TableCell.defaultProps,
wrapperComponent: 'div' wrapperComponent: 'div',
}; };
static propsList = [ static propsList = ['quickEdit', 'popOver', 'copyable', ...TableCell.propsList];
'quickEdit',
'popOver',
'copyable',
...TableCell.propsList
];
render() { render() {
let { let {
@ -390,23 +373,21 @@ export class CardItemFieldRenderer extends TableCell {
const schema = { const schema = {
...field, ...field,
className: innerClassName, className: innerClassName,
type: field && field.type || 'plain', type: (field && field.type) || 'plain',
}; };
let body = children ? children : render('field', schema, { let body = children
...rest, ? children
value, : render('field', schema, {
data ...rest,
}); value,
data,
});
if (width) { if (width) {
style = style || {}; style = style || {};
style.width = style.width || width; style.width = style.width || width;
body = ( body = <div style={{width: !/%/.test(String(width)) ? width : ''}}>{body}</div>;
<div style={{width: !/%/.test(String(width)) ? width : ''}}>
{body}
</div>
);
} }
if (!Component) { if (!Component) {
@ -414,14 +395,9 @@ export class CardItemFieldRenderer extends TableCell {
} }
return ( return (
<Component <Component style={style} className={className} tabIndex={tabIndex} onKeyUp={onKeyUp}>
style={style}
className={className}
tabIndex={tabIndex}
onKeyUp={onKeyUp}
>
{body} {body}
</Component> </Component>
) );
} }
}; }

View File

@ -1,30 +1,22 @@
import * as React from 'react'; import * as React from 'react';
import { import {findDOMNode} from 'react-dom';
findDOMNode import {Renderer, RendererProps} from '../factory';
} from 'react-dom'; import {SchemaNode, Action} from '../types';
import {
Renderer,
RendererProps
} from '../factory';
import {
SchemaNode,
Action
} from '../types';
import * as cx from 'classnames'; import * as cx from 'classnames';
import Button from '../components/Button'; import Button from '../components/Button';
import { ListStore, IListStore, IItem} from '../store/list'; import {ListStore, IListStore, IItem} from '../store/list';
import { observer } from 'mobx-react'; import {observer} from 'mobx-react';
import { anyChanged, getScrollParent, difference, ucFirst } from '../utils/helper'; import {anyChanged, getScrollParent, difference, ucFirst} from '../utils/helper';
import { resolveVariable } from '../utils/tpl-builtin'; import {resolveVariable} from '../utils/tpl-builtin';
import Sortable = require('sortablejs'); import Sortable = require('sortablejs');
import { filter } from '../utils/tpl'; import {filter} from '../utils/tpl';
import debounce = require('lodash/debounce'); import debounce = require('lodash/debounce');
import { resizeSensor } from '../utils/resize-sensor'; import {resizeSensor} from '../utils/resize-sensor';
export interface Column { export interface Column {
type: string; type: string;
[propName:string]: any; [propName: string]: any;
}; }
export interface GridProps extends RendererProps { export interface GridProps extends RendererProps {
title?: string; // 标题 title?: string; // 标题
@ -37,16 +29,21 @@ export interface GridProps extends RendererProps {
footerClassName?: string; footerClassName?: string;
itemClassName?: string; itemClassName?: string;
card?: any; card?: any;
source?:string; source?: string;
selectable?: boolean; selectable?: boolean;
selected?: Array<any>; selected?: Array<any>;
multiple?: boolean; multiple?: boolean;
valueField?: string; valueField?: string;
draggable?:boolean; draggable?: boolean;
onSelect: (selectedItems:Array<object>, unSelectedItems:Array<object>) => void; onSelect: (selectedItems: Array<object>, unSelectedItems: Array<object>) => void;
onSave?: (items:Array<object> | object, diff: Array<object> | object, rowIndexes: Array<number> | number, unModifiedItems?:Array<object>) => void; onSave?: (
onSaveOrder?: (moved: Array<object>, items:Array<object>) => void; items: Array<object> | object,
onQuery: (values:object) => void; diff: Array<object> | object,
rowIndexes: Array<number> | number,
unModifiedItems?: Array<object>
) => void;
onSaveOrder?: (moved: Array<object>, items: Array<object>) => void;
onQuery: (values: object) => void;
hideCheckToggler?: boolean; hideCheckToggler?: boolean;
itemCheckableOn?: string; itemCheckableOn?: string;
itemDraggableOn?: string; itemDraggableOn?: string;
@ -71,10 +68,10 @@ export default class Cards extends React.Component<GridProps, object> {
'itemCheckableOn', 'itemCheckableOn',
'itemDraggableOn', 'itemDraggableOn',
'masonryLayout', 'masonryLayout',
"items", 'items',
"valueField" 'valueField',
]; ];
static defaultProps:Partial<GridProps>= { static defaultProps: Partial<GridProps> = {
className: '', className: '',
placeholder: '没有数据', placeholder: '没有数据',
source: '$items', source: '$items',
@ -86,7 +83,7 @@ export default class Cards extends React.Component<GridProps, object> {
hideCheckToggler: false, hideCheckToggler: false,
masonryLayout: false, masonryLayout: false,
affixHeader: true, affixHeader: true,
itemsClassName: '' itemsClassName: '',
}; };
dragTip?: HTMLElement; dragTip?: HTMLElement;
@ -95,7 +92,7 @@ export default class Cards extends React.Component<GridProps, object> {
body?: any; body?: any;
// fixAlignmentLazy: Function; // fixAlignmentLazy: Function;
unSensor: Function; unSensor: Function;
constructor(props:GridProps) { constructor(props: GridProps) {
super(props); super(props);
this.handleAction = this.handleAction.bind(this); this.handleAction = this.handleAction.bind(this);
@ -115,10 +112,10 @@ export default class Cards extends React.Component<GridProps, object> {
// }) // })
} }
static syncItems(store:IListStore, props:GridProps, prevProps?:GridProps) { static syncItems(store: IListStore, props: GridProps, prevProps?: GridProps) {
const source = props.source; const source = props.source;
const value = props.value || props.items; const value = props.value || props.items;
let items:Array<object> = []; let items: Array<object> = [];
let updateItems = true; let updateItems = true;
if (Array.isArray(value)) { if (Array.isArray(value)) {
@ -148,7 +145,7 @@ export default class Cards extends React.Component<GridProps, object> {
multiple, multiple,
hideCheckToggler, hideCheckToggler,
itemCheckableOn, itemCheckableOn,
itemDraggableOn itemDraggableOn,
} = this.props; } = this.props;
store.update({ store.update({
@ -159,7 +156,7 @@ export default class Cards extends React.Component<GridProps, object> {
multiple, multiple,
hideCheckToggler, hideCheckToggler,
itemCheckableOn, itemCheckableOn,
itemDraggableOn itemDraggableOn,
}); });
Cards.syncItems(store, this.props); Cards.syncItems(store, this.props);
@ -167,7 +164,7 @@ export default class Cards extends React.Component<GridProps, object> {
} }
componentDidMount() { componentDidMount() {
let parent:HTMLElement | Window | null = getScrollParent(findDOMNode(this) as HTMLElement); let parent: HTMLElement | Window | null = getScrollParent(findDOMNode(this) as HTMLElement);
if (!parent || parent === document.body) { if (!parent || parent === document.body) {
parent = window; parent = window;
} }
@ -178,20 +175,26 @@ export default class Cards extends React.Component<GridProps, object> {
window.addEventListener('resize', this.affixDetect); window.addEventListener('resize', this.affixDetect);
} }
componentWillReceiveProps(nextProps:GridProps) { componentWillReceiveProps(nextProps: GridProps) {
const props = this.props; const props = this.props;
const store = nextProps.store; const store = nextProps.store;
if (anyChanged([ if (
'selectable', anyChanged(
'draggable', [
'orderBy', 'selectable',
'orderDir', 'draggable',
'multiple', 'orderBy',
'hideCheckToggler', 'orderDir',
'itemCheckableOn', 'multiple',
'itemDraggableOn' 'hideCheckToggler',
], props, nextProps)) { 'itemCheckableOn',
'itemDraggableOn',
],
props,
nextProps
)
) {
store.update({ store.update({
selectable: nextProps.selectable, selectable: nextProps.selectable,
draggable: nextProps.draggable, draggable: nextProps.draggable,
@ -200,15 +203,14 @@ export default class Cards extends React.Component<GridProps, object> {
multiple: nextProps.multiple, multiple: nextProps.multiple,
hideCheckToggler: nextProps.hideCheckToggler, hideCheckToggler: nextProps.hideCheckToggler,
itemCheckableOn: nextProps.itemCheckableOn, itemCheckableOn: nextProps.itemCheckableOn,
itemDraggableOn: nextProps.itemDraggableOn itemDraggableOn: nextProps.itemDraggableOn,
}) });
} }
if (anyChanged([ if (
'source', anyChanged(['source', 'value', 'items'], props, nextProps) ||
'value', (!nextProps.value && !nextProps.items && nextProps.data !== props.data)
'items' ) {
], props, nextProps) || !nextProps.value && !nextProps.items && nextProps.data !== props.data) {
Cards.syncItems(store, nextProps, props); Cards.syncItems(store, nextProps, props);
this.syncSelected(); this.syncSelected();
} else if (props.selected !== nextProps.selected) { } else if (props.selected !== nextProps.selected) {
@ -239,11 +241,11 @@ export default class Cards extends React.Component<GridProps, object> {
// cards.forEach((item: HTMLElement) => item.style.cssText += `min-height: ${maxHeight}px;`); // cards.forEach((item: HTMLElement) => item.style.cssText += `min-height: ${maxHeight}px;`);
// } // }
bodyRef(ref:HTMLDivElement) { bodyRef(ref: HTMLDivElement) {
this.body = ref; this.body = ref;
} }
itemsRef(ref:HTMLDivElement) { itemsRef(ref: HTMLDivElement) {
if (ref) { if (ref) {
// this.unSensor = resizeSensor(ref.parentNode as HTMLElement, this.fixAlignmentLazy); // this.unSensor = resizeSensor(ref.parentNode as HTMLElement, this.fixAlignmentLazy);
} else { } else {
@ -261,7 +263,7 @@ export default class Cards extends React.Component<GridProps, object> {
const dom = findDOMNode(this) as HTMLElement; const dom = findDOMNode(this) as HTMLElement;
const clip = (this.body as HTMLElement).getBoundingClientRect(); const clip = (this.body as HTMLElement).getBoundingClientRect();
const offsetY = this.props.env.affixOffsetTop || 0; const offsetY = this.props.env.affixOffsetTop || 0;
const affixed = clip.top < offsetY && (clip.top + clip.height - 40) > offsetY; const affixed = clip.top < offsetY && clip.top + clip.height - 40 > offsetY;
const afixedDom = dom.querySelector(`.${ns}Cards-fixedTop`) as HTMLElement; const afixedDom = dom.querySelector(`.${ns}Cards-fixedTop`) as HTMLElement;
this.body.offsetWidth && (afixedDom.style.cssText = `top: ${offsetY}px;width: ${this.body.offsetWidth}px;`); this.body.offsetWidth && (afixedDom.style.cssText = `top: ${offsetY}px;width: ${this.body.offsetWidth}px;`);
@ -269,10 +271,8 @@ export default class Cards extends React.Component<GridProps, object> {
// store.markHeaderAffix(clip.top < offsetY && (clip.top + clip.height - 40) > offsetY); // store.markHeaderAffix(clip.top < offsetY && (clip.top + clip.height - 40) > offsetY);
} }
handleAction(e:React.UIEvent<any>, action: Action, ctx: object) { handleAction(e: React.UIEvent<any>, action: Action, ctx: object) {
const { const {onAction} = this.props;
onAction
} = this.props;
// 需要支持特殊事件吗? // 需要支持特殊事件吗?
onAction(e, action, ctx); onAction(e, action, ctx);
@ -284,24 +284,19 @@ export default class Cards extends React.Component<GridProps, object> {
} }
handleCheckAll() { handleCheckAll() {
const { const {store} = this.props;
store,
} = this.props;
store.toggleAll(); store.toggleAll();
this.syncSelected(); this.syncSelected();
} }
syncSelected() { syncSelected() {
const { const {store, onSelect} = this.props;
store,
onSelect
} = this.props;
onSelect && onSelect(store.selectedItems.map(item => item.data), store.unSelectedItems.map(item => item.data)); onSelect && onSelect(store.selectedItems.map(item => item.data), store.unSelectedItems.map(item => item.data));
} }
handleQuickChange(item: IItem, values:object, saveImmediately?: boolean | any, saveSilent?: boolean) { handleQuickChange(item: IItem, values: object, saveImmediately?: boolean | any, saveSilent?: boolean) {
item.change(values, saveSilent); item.change(values, saveSilent);
if (!saveImmediately || saveSilent) { if (!saveImmediately || saveSilent) {
@ -309,16 +304,18 @@ export default class Cards extends React.Component<GridProps, object> {
} }
if (saveImmediately && saveImmediately.api) { if (saveImmediately && saveImmediately.api) {
this.props.onAction(null, { this.props.onAction(
actionType: 'ajax', null,
api: saveImmediately.api {
}, values); actionType: 'ajax',
api: saveImmediately.api,
},
values
);
return; return;
} }
const { const {onSave} = this.props;
onSave
} = this.props;
if (!onSave) { if (!onSave) {
return; return;
@ -328,10 +325,7 @@ export default class Cards extends React.Component<GridProps, object> {
} }
handleSave() { handleSave() {
const { const {store, onSave} = this.props;
store,
onSave
} = this.props;
if (!onSave || !store.modifiedItems.length) { if (!onSave || !store.modifiedItems.length) {
return; return;
@ -345,10 +339,7 @@ export default class Cards extends React.Component<GridProps, object> {
} }
handleSaveOrder() { handleSaveOrder() {
const { const {store, onSaveOrder} = this.props;
store,
onSaveOrder
} = this.props;
if (!onSaveOrder || !store.movedItems.length) { if (!onSaveOrder || !store.movedItems.length) {
return; return;
@ -358,41 +349,34 @@ export default class Cards extends React.Component<GridProps, object> {
} }
reset() { reset() {
const { const {store} = this.props;
store
} = this.props;
store.reset(); store.reset();
} }
bulkUpdate(value:object, items:Array<object>) { bulkUpdate(value: object, items: Array<object>) {
const { const {store} = this.props;
store
} = this.props;
const items2 = store.items.filter(item => ~items.indexOf(item.pristine)); const items2 = store.items.filter(item => ~items.indexOf(item.pristine));
items2.forEach(item => item.change(value)); items2.forEach(item => item.change(value));
} }
getSelected() { getSelected() {
const { const {store} = this.props;
store
} = this.props;
return store.selectedItems.map(item => item.data); return store.selectedItems.map(item => item.data);
} }
dragTipRef(ref:any) { dragTipRef(ref: any) {
if (!this.dragTip && ref) { if (!this.dragTip && ref) {
this.initDragging(); this.initDragging();
} else if (this.dragTip && !ref) { } else if (this.dragTip && !ref) {
this.destroyDragging() this.destroyDragging();
} }
this.dragTip = ref; this.dragTip = ref;
} }
initDragging() { initDragging() {
const store = this.props.store; const store = this.props.store;
const dom = findDOMNode(this) as HTMLElement; const dom = findDOMNode(this) as HTMLElement;
@ -401,21 +385,21 @@ export default class Cards extends React.Component<GridProps, object> {
group: 'table', group: 'table',
handle: `.${ns}Card-dragBtn`, handle: `.${ns}Card-dragBtn`,
ghostClass: `is-dragging`, ghostClass: `is-dragging`,
onEnd: (e:any) => { onEnd: (e: any) => {
// 没有移动 // 没有移动
if (e.newIndex === e.oldIndex) { if (e.newIndex === e.oldIndex) {
return; return;
} }
const parent = e.to as HTMLElement; const parent = e.to as HTMLElement;
if (e.oldIndex < parent.childNodes.length -1) { if (e.oldIndex < parent.childNodes.length - 1) {
parent.insertBefore(e.item, parent.childNodes[e.oldIndex]); parent.insertBefore(e.item, parent.childNodes[e.oldIndex]);
} else { } else {
parent.appendChild(e.item); parent.appendChild(e.item);
} }
store.exchange(e.oldIndex, e.newIndex); store.exchange(e.oldIndex, e.newIndex);
} },
}); });
} }
@ -423,19 +407,8 @@ export default class Cards extends React.Component<GridProps, object> {
this.sortable && this.sortable.destroy(); this.sortable && this.sortable.destroy();
} }
renderActions(region: string) {
let {actions, render, store, multiple, selectable, classnames: cx, classPrefix: ns, env} = this.props;
renderActions(region:string) {
let {
actions,
render,
store,
multiple,
selectable,
classnames: cx,
classPrefix: ns,
env
} = this.props;
actions = Array.isArray(actions) ? actions.concat() : []; actions = Array.isArray(actions) ? actions.concat() : [];
@ -443,15 +416,17 @@ export default class Cards extends React.Component<GridProps, object> {
actions.unshift({ actions.unshift({
type: 'button', type: 'button',
children: ( children: (
<Button <Button
key="checkall" key="checkall"
classPrefix={ns} classPrefix={ns}
tooltip="切换全选" tooltip="切换全选"
onClick={this.handleCheckAll} onClick={this.handleCheckAll}
size="sm" size="sm"
level={store.allChecked ? 'info' : 'default'} level={store.allChecked ? 'info' : 'default'}
></Button> >
)
</Button>
),
}); });
} }
@ -467,7 +442,7 @@ export default class Cards extends React.Component<GridProps, object> {
tooltipContainer={env && env.getModalContainer ? env.getModalContainer() : undefined} tooltipContainer={env && env.getModalContainer ? env.getModalContainer() : undefined}
size="sm" size="sm"
active={store.dragging} active={store.dragging}
onClick={(e:React.MouseEvent<any>) => { onClick={(e: React.MouseEvent<any>) => {
e.preventDefault(); e.preventDefault();
store.toggleDragging(); store.toggleDragging();
store.dragging && store.clear(); store.dragging && store.clear();
@ -475,61 +450,81 @@ export default class Cards extends React.Component<GridProps, object> {
> >
<i className="fa fa-exchange" /> <i className="fa fa-exchange" />
</Button> </Button>
) ),
}); });
} }
return Array.isArray(actions) && actions.length ? ( return Array.isArray(actions) && actions.length ? (
<div className={cx("Cards-actions")}> <div className={cx('Cards-actions')}>
{actions.map((action, key) => render(`action/${key}`, { {actions.map((action, key) =>
type: 'button', render(
...action `action/${key}`,
}, { {
onAction: this.handleAction, type: 'button',
key, ...action,
btnDisabled: store.dragging },
}))} {
onAction: this.handleAction,
key,
btnDisabled: store.dragging,
}
)
)}
</div> </div>
) : null; ) : null;
} }
renderHeading() { renderHeading() {
let { let {title, store, hideQuickSaveBtn, classnames: cx, data} = this.props;
title,
store,
hideQuickSaveBtn,
classnames: cx,
data
} = this.props;
if (title || store.modified && !hideQuickSaveBtn || store.moved) { if (title || (store.modified && !hideQuickSaveBtn) || store.moved) {
return ( return (
<div className={cx("Cards-heading")}> <div className={cx('Cards-heading')}>
{store.modified && !hideQuickSaveBtn ? ( {store.modified && !hideQuickSaveBtn ? (
<span> <span>
{`当前有 ${store.modified} 条记录修改了内容, 但并没有提交。请选择:`} {`当前有 ${store.modified} 条记录修改了内容, 但并没有提交。请选择:`}
<button type="button" className={cx("Button Button--xs Button--success m-l-sm")} onClick={this.handleSave}> <button
type="button"
className={cx('Button Button--xs Button--success m-l-sm')}
onClick={this.handleSave}
>
<i className="fa fa-check m-r-xs" /> <i className="fa fa-check m-r-xs" />
</button> </button>
<button type="button" className={cx("Button Button--xs Button--danger m-l-sm")} onClick={this.reset}> <button
type="button"
className={cx('Button Button--xs Button--danger m-l-sm')}
onClick={this.reset}
>
<i className="fa fa-times m-r-xs" /> <i className="fa fa-times m-r-xs" />
</button> </button>
</span> </span>
) : store.moved ? ( ) : store.moved ? (
<span> <span>
{`当前有 ${store.moved} 条记录修改了顺序, 但并没有提交。请选择:`} {`当前有 ${store.moved} 条记录修改了顺序, 但并没有提交。请选择:`}
<button type="button" className={cx("Button Button--xs Button--success m-l-sm")} onClick={this.handleSaveOrder}> <button
type="button"
className={cx('Button Button--xs Button--success m-l-sm')}
onClick={this.handleSaveOrder}
>
<i className="fa fa-check m-r-xs" /> <i className="fa fa-check m-r-xs" />
</button> </button>
<button type="button" className={cx("Button Button--xs Button--danger m-l-sm")} onClick={this.reset}> <button
type="button"
className={cx('Button Button--xs Button--danger m-l-sm')}
onClick={this.reset}
>
<i className="fa fa-times m-r-xs" /> <i className="fa fa-times m-r-xs" />
</button> </button>
</span> </span>
) : title ? filter(title, data) : ''} ) : title ? (
filter(title, data)
) : (
''
)}
</div> </div>
); );
} }
@ -546,33 +541,40 @@ export default class Cards extends React.Component<GridProps, object> {
showHeader, showHeader,
render, render,
store, store,
classnames: cx classnames: cx,
} = this.props; } = this.props;
if (showHeader === false) { if (showHeader === false) {
return null; return null;
} }
const actions = this.renderActions('header'); const actions = this.renderActions('header');
const child = headerToolbarRender ? headerToolbarRender({ const child = headerToolbarRender
...this.props, ? headerToolbarRender({
selectedItems: store.selectedItems.map(item => item.data), ...this.props,
items: store.items.map(item => item.data), selectedItems: store.selectedItems.map(item => item.data),
unSelectedItems: store.unSelectedItems.map(item => item.data), items: store.items.map(item => item.data),
}) : null; unSelectedItems: store.unSelectedItems.map(item => item.data),
const toolbarNode = actions || child || store.dragging ? ( })
<div className={cx('Cards-toolbar')} key="header-toolbar"> : null;
{actions} const toolbarNode =
{child} actions || child || store.dragging ? (
{store.dragging ? <div className={cx("Cards-dragTip")} ref={this.dragTipRef}></div> : null} <div className={cx('Cards-toolbar')} key="header-toolbar">
</div> {actions}
) : null; {child}
{store.dragging ? (
<div className={cx('Cards-dragTip')} ref={this.dragTipRef}>
</div>
) : null}
</div>
) : null;
const headerNode = header ? ( const headerNode = header ? (
<div className={cx('Cards-header', headerClassName)} key="header"> <div className={cx('Cards-header', headerClassName)} key="header">
{render('header', header)} {render('header', header)}
</div> </div>
) : null; ) : null;
return headerNode && toolbarNode ? [headerNode, toolbarNode] : (headerNode || toolbarNode || null); return headerNode && toolbarNode ? [headerNode, toolbarNode] : headerNode || toolbarNode || null;
} }
renderFooter() { renderFooter() {
@ -584,33 +586,36 @@ export default class Cards extends React.Component<GridProps, object> {
render, render,
showFooter, showFooter,
store, store,
classnames: cx classnames: cx,
} = this.props; } = this.props;
if (showFooter === false) { if (showFooter === false) {
return null; return null;
} }
const actions = this.renderActions('footer'); const actions = this.renderActions('footer');
const child = footerToolbarRender ? footerToolbarRender({ const child = footerToolbarRender
...this.props, ? footerToolbarRender({
selectedItems: store.selectedItems.map(item => item.data), ...this.props,
items: store.items.map(item => item.data), selectedItems: store.selectedItems.map(item => item.data),
unSelectedItems: store.unSelectedItems.map(item => item.data), items: store.items.map(item => item.data),
}) : null; unSelectedItems: store.unSelectedItems.map(item => item.data),
})
: null;
const toolbarNode = actions || child ? ( const toolbarNode =
<div className={cx('Cards-toolbar')} key="footer-toolbar"> actions || child ? (
{actions} <div className={cx('Cards-toolbar')} key="footer-toolbar">
{child} {actions}
</div> {child}
) : null; </div>
) : null;
const footerNode = footer ? ( const footerNode = footer ? (
<div className={cx('Cards-footer', footerClassName)} key="footer"> <div className={cx('Cards-footer', footerClassName)} key="footer">
{render('footer', footer)} {render('footer', footer)}
</div> </div>
) : null; ) : null;
return footerNode && toolbarNode ? [toolbarNode, footerNode] : (footerNode || toolbarNode || null); return footerNode && toolbarNode ? [toolbarNode, footerNode] : footerNode || toolbarNode || null;
} }
render() { render() {
@ -629,36 +634,41 @@ export default class Cards extends React.Component<GridProps, object> {
checkOnItemClick, checkOnItemClick,
masonryLayout, masonryLayout,
itemsClassName, itemsClassName,
classnames: cx classnames: cx,
} = this.props; } = this.props;
let itemFinalClassName:string = columnsCount ? `Grid-col--sm${Math.round(12/columnsCount)}` : itemClassName || ''; let itemFinalClassName: string = columnsCount
? `Grid-col--sm${Math.round(12 / columnsCount)}`
: itemClassName || '';
const header = this.renderHeader(); const header = this.renderHeader();
const heading = this.renderHeading(); const heading = this.renderHeading();
const footer = this.renderFooter(); const footer = this.renderFooter();
let masonryClassName = ''; let masonryClassName = '';
if (masonryLayout) { if (masonryLayout) {
masonryClassName = 'Cards--masonry ' + itemFinalClassName.split(/\s/).map(item => { masonryClassName =
if (/^Grid-col--(xs|sm|md|lg)(\d+)/.test(item)) { 'Cards--masonry ' +
return `Cards--masonry${ucFirst(RegExp.$1)}${RegExp.$2}`; itemFinalClassName
} .split(/\s/)
.map(item => {
if (/^Grid-col--(xs|sm|md|lg)(\d+)/.test(item)) {
return `Cards--masonry${ucFirst(RegExp.$1)}${RegExp.$2}`;
}
return item; return item;
}).join(' '); })
.join(' ');
} }
return ( return (
<div <div
ref={this.bodyRef} ref={this.bodyRef}
className={cx('Cards', className, { className={cx('Cards', className, {
'Cards--unsaved': !!store.modified || !!store.moved 'Cards--unsaved': !!store.modified || !!store.moved,
})} })}
> >
{affixHeader ? ( {affixHeader ? (
<div <div className={cx('Cards-fixedTop')}>
className={cx("Cards-fixedTop")}
>
{heading} {heading}
{header} {header}
</div> </div>
@ -669,13 +679,15 @@ export default class Cards extends React.Component<GridProps, object> {
<div ref={this.itemsRef} className={cx('Cards-body Grid', itemsClassName, masonryClassName)}> <div ref={this.itemsRef} className={cx('Cards-body Grid', itemsClassName, masonryClassName)}>
{store.items.map((item, index) => { {store.items.map((item, index) => {
return ( return (
( <div key={item.index} className={cx(itemFinalClassName)}>
<div key={item.index} className={cx(itemFinalClassName)}> {render(
{render(`${index}`, { `${index}`,
{
type: 'card', type: 'card',
...card ...card,
}, { },
className: cx(card && card.className || '', { {
className: cx((card && card.className) || '', {
'is-checked': item.checked, 'is-checked': item.checked,
'is-modified': item.modified, 'is-modified': item.modified,
'is-moved': item.moved, 'is-moved': item.moved,
@ -693,17 +705,15 @@ export default class Cards extends React.Component<GridProps, object> {
checkOnItemClick, checkOnItemClick,
onAction, onAction,
onCheck: this.handleCheck, onCheck: this.handleCheck,
onQuickChange: store.dragging ? null : this.handleQuickChange onQuickChange: store.dragging ? null : this.handleQuickChange,
})} }
</div> )}
) </div>
); );
})} })}
</div> </div>
) : ( ) : (
<div className={cx("Cards-placeholder")}> <div className={cx('Cards-placeholder')}>{placeholder}</div>
{placeholder}
</div>
)} )}
{footer} {footer}
@ -716,7 +726,7 @@ export default class Cards extends React.Component<GridProps, object> {
test: /(^|\/)(?:crud\/body\/grid|cards)$/, test: /(^|\/)(?:crud\/body\/grid|cards)$/,
name: 'cards', name: 'cards',
storeType: ListStore.name, storeType: ListStore.name,
weight: -100 // 默认的 grid 不是这样,这个只识别 crud 下面的 grid weight: -100, // 默认的 grid 不是这样,这个只识别 crud 下面的 grid
}) })
export class CardsRenderer extends Cards { export class CardsRenderer extends Cards {
dragging: boolean; dragging: boolean;
@ -730,5 +740,4 @@ export class CardsRenderer extends Cards {
avatarClassName?: string; avatarClassName?: string;
body?: SchemaNode; body?: SchemaNode;
actions?: Array<Action>; actions?: Array<Action>;
}; }

View File

@ -1,28 +1,19 @@
import * as React from 'react'; import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { import {Api, ApiObject, Action} from '../types';
Api, import {filter, evalExpression} from '../utils/tpl';
ApiObject,
Action
} from '../types';
import {
filter, evalExpression} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
import LazyComponent from '../components/LazyComponent'; import LazyComponent from '../components/LazyComponent';
import {resizeSensor} from '../utils/resize-sensor'; import {resizeSensor} from '../utils/resize-sensor';
import { resolveVariableAndFilter } from '../utils/tpl-builtin'; import {resolveVariableAndFilter} from '../utils/tpl-builtin';
import { isApiOutdated } from '../utils/api'; import {isApiOutdated} from '../utils/api';
import { ScopedContext, IScopedContext } from '../Scoped'; import {ScopedContext, IScopedContext} from '../Scoped';
export interface ChartProps extends RendererProps { export interface ChartProps extends RendererProps {
chartRef?: (echart:any) => void; chartRef?: (echart: any) => void;
onDataFilter?: (config:any) => any; onDataFilter?: (config: any) => any;
api?: Api; api?: Api;
source?: string; source?: string;
config?: object; config?: object;
@ -32,24 +23,22 @@ export interface ChartProps extends RendererProps {
replaceChartOption: boolean; replaceChartOption: boolean;
} }
export class Chart extends React.Component<ChartProps> { export class Chart extends React.Component<ChartProps> {
static defaultProps: Partial<ChartProps> = {
static defaultProps:Partial<ChartProps> = {
offsetY: 50, offsetY: 50,
replaceChartOption: false replaceChartOption: false,
}; };
static propsList:Array<string> = [ static propsList: Array<string> = [];
];
ref:any; ref: any;
echarts:any; echarts: any;
unSensor:Function; unSensor: Function;
pending?:object; pending?: object;
timer: number; timer: number;
mounted: boolean; mounted: boolean;
reloadCancel: Function; reloadCancel: Function;
constructor(props:ChartProps) { constructor(props: ChartProps) {
super(props); super(props);
this.refFn = this.refFn.bind(this); this.refFn = this.refFn.bind(this);
@ -58,13 +47,7 @@ export class Chart extends React.Component<ChartProps> {
} }
componentWillMount() { componentWillMount() {
const { const {config, api, data, initFetch, source} = this.props;
config,
api,
data,
initFetch,
source
} = this.props;
this.mounted = true; this.mounted = true;
@ -74,18 +57,20 @@ export class Chart extends React.Component<ChartProps> {
} else if (api && initFetch !== false) { } else if (api && initFetch !== false) {
this.reload(); this.reload();
} }
config && this.renderChart(config); config && this.renderChart(config);
} }
componentDidUpdate(prevProps:ChartProps) { componentDidUpdate(prevProps: ChartProps) {
const props = this.props; const props = this.props;
const api:string = props.api && (props.api as ApiObject).url || (props.api as string); const api: string = (props.api && (props.api as ApiObject).url) || (props.api as string);
if (isApiOutdated(prevProps.api, props.api, prevProps.data, props.data)) { if (isApiOutdated(prevProps.api, props.api, prevProps.data, props.data)) {
this.reload(); this.reload();
} else if (props.source && /^\$(?:([a-z0-9_.]+)|{.+})$/.test(props.source)) { } else if (props.source && /^\$(?:([a-z0-9_.]+)|{.+})$/.test(props.source)) {
const prevRet = prevProps.source ? resolveVariableAndFilter(prevProps.source, prevProps.data, '| raw') : null; const prevRet = prevProps.source
? resolveVariableAndFilter(prevProps.source, prevProps.data, '| raw')
: null;
const ret = resolveVariableAndFilter(props.source, props.data, '| raw'); const ret = resolveVariableAndFilter(props.source, props.data, '| raw');
if (prevRet !== ret) { if (prevRet !== ret) {
@ -101,28 +86,25 @@ export class Chart extends React.Component<ChartProps> {
clearTimeout(this.timer); clearTimeout(this.timer);
} }
handleClick(ctx:object) { handleClick(ctx: object) {
const { const {onAction, clickAction} = this.props;
onAction,
clickAction,
} = this.props;
clickAction && onAction && onAction(null, clickAction, ctx); clickAction && onAction && onAction(null, clickAction, ctx);
} }
refFn(ref:any) { refFn(ref: any) {
const chartRef = this.props.chartRef; const chartRef = this.props.chartRef;
if (ref) { if (ref) {
(require as any)(['echarts', 'echarts/map/js/china', 'echarts/map/js/world'], (echarts:any) => { (require as any)(['echarts', 'echarts/map/js/china', 'echarts/map/js/world'], (echarts: any) => {
(window as any).echarts = echarts; (window as any).echarts = echarts;
this.echarts = echarts.init(ref); this.echarts = echarts.init(ref);
this.echarts.on('click', this.handleClick) this.echarts.on('click', this.handleClick);
this.unSensor = resizeSensor(ref, () => { this.unSensor = resizeSensor(ref, () => {
const width = ref.offsetWidth; const width = ref.offsetWidth;
const height = ref.offsetHeight; const height = ref.offsetHeight;
this.echarts.resize({ this.echarts.resize({
width, width,
height height,
}); });
}); });
@ -137,13 +119,8 @@ export class Chart extends React.Component<ChartProps> {
this.ref = ref; this.ref = ref;
} }
reload(query?:any) { reload(query?: any) {
const { const {api, env, store, interval} = this.props;
api,
env,
store,
interval
} = this.props;
if (query) { if (query) {
return this.receive(query); return this.receive(query);
@ -162,12 +139,10 @@ export class Chart extends React.Component<ChartProps> {
this.echarts && this.echarts.hideLoading(); this.echarts && this.echarts.hideLoading();
} }
this.echarts && this.echarts.showLoading(); this.echarts && this.echarts.showLoading();
env env.fetcher(api, store.data, {
.fetcher(api, store.data, { cancelExecutor: (executor: Function) => (this.reloadCancel = executor),
cancelExecutor: (executor:Function) => this.reloadCancel = executor })
})
.then(result => { .then(result => {
delete this.reloadCancel; delete this.reloadCancel;
this.renderChart(result.data || {}); this.renderChart(result.data || {});
@ -185,7 +160,7 @@ export class Chart extends React.Component<ChartProps> {
}); });
} }
receive(data:object) { receive(data: object) {
const store = this.props.store; const store = this.props.store;
store.updateData(data); store.updateData(data);
@ -201,7 +176,7 @@ export class Chart extends React.Component<ChartProps> {
config = config || this.pending; config = config || this.pending;
if (typeof config === 'string') { if (typeof config === 'string') {
config = (new Function("return " + config))(); config = new Function('return ' + config)();
} }
onDataFilter && (config = onDataFilter(config) || config); onDataFilter && (config = onDataFilter(config) || config);
@ -215,41 +190,24 @@ export class Chart extends React.Component<ChartProps> {
} }
render() { render() {
const { const {className, width, height, classPrefix: ns} = this.props;
className,
width,
height,
classPrefix: ns
} = this.props;
let style = this.props.style || {}; let style = this.props.style || {};
width && (style.width = width); width && (style.width = width);
height && (style.height = height); height && (style.height = height);
return ( return (
<LazyComponent <LazyComponent
unMountOnHidden unMountOnHidden
placeholder={( placeholder={
<div <div className={cx(`${ns}Chart`, className)} style={style}>
className={cx(`${ns}Chart`, className)}
style={style}
>
<div className={`${ns}Chart-placeholder`}> <div className={`${ns}Chart-placeholder`}>
<i key="loading" className="fa fa-spinner fa-spin fa-2x fa-fw" /> <i key="loading" className="fa fa-spinner fa-spin fa-2x fa-fw" />
</div> </div>
</div> </div>
)} }
component={() => ( component={() => <div className={cx(`${ns}Chart`, className)} style={style} ref={this.refFn} />}
<div
className={cx(`${ns}Chart`, className)}
style={style}
ref={this.refFn}
/>
)}
/> />
); );
} }
} }
@ -273,4 +231,4 @@ export class ChartRenderer extends Chart {
const scoped = this.context as IScopedContext; const scoped = this.context as IScopedContext;
scoped.unRegisterComponent(this); scoped.unRegisterComponent(this);
} }
}; }

View File

@ -1,12 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import * as cx from 'classnames'; import * as cx from 'classnames';
import { import {Collapse as BasicCollapse} from 'react-bootstrap';
Collapse as BasicCollapse
} from 'react-bootstrap';
export interface CollapseProps extends RendererProps { export interface CollapseProps extends RendererProps {
title?: string; // 标题 title?: string; // 标题
@ -17,7 +12,7 @@ export interface CollapseProps extends RendererProps {
bodyClassName?: string; bodyClassName?: string;
headingClassName?: string; headingClassName?: string;
// 内容口子 // 内容口子
children?: JSX.Element | ((props?:any) => JSX.Element); children?: JSX.Element | ((props?: any) => JSX.Element);
} }
export interface CollapseState { export interface CollapseState {
@ -26,24 +21,24 @@ export interface CollapseState {
export default class Collapse extends React.Component<CollapseProps, CollapseState> { export default class Collapse extends React.Component<CollapseProps, CollapseState> {
static propsList: Array<string> = [ static propsList: Array<string> = [
"wrapperComponent", 'wrapperComponent',
"headingComponent", 'headingComponent',
"bodyClassName", 'bodyClassName',
"collapsed", 'collapsed',
"headingClassName" 'headingClassName',
]; ];
static defaultProps:Partial<CollapseProps>= { static defaultProps: Partial<CollapseProps> = {
wrapperComponent: 'div', wrapperComponent: 'div',
headingComponent: 'h4', headingComponent: 'h4',
className: '', className: '',
headingClassName: '', headingClassName: '',
bodyClassName: '', bodyClassName: '',
collapsable: true collapsable: true,
}; };
state = { state = {
collapsed: false collapsed: false,
}; };
constructor(props: CollapseProps) { constructor(props: CollapseProps) {
@ -53,19 +48,19 @@ export default class Collapse extends React.Component<CollapseProps, CollapseSta
this.state.collapsed = !!props.collapsed; this.state.collapsed = !!props.collapsed;
} }
componentWillReceiveProps(nextProps:CollapseProps) { componentWillReceiveProps(nextProps: CollapseProps) {
const props = this.props; const props = this.props;
if (props.collapsed !== nextProps.collapsed) { if (props.collapsed !== nextProps.collapsed) {
this.setState({ this.setState({
collapsed: !!nextProps.collapsed collapsed: !!nextProps.collapsed,
}); });
} }
} }
toggleCollapsed() { toggleCollapsed() {
this.setState({ this.setState({
collapsed: !this.state.collapsed collapsed: !this.state.collapsed,
}); });
} }
@ -83,36 +78,39 @@ export default class Collapse extends React.Component<CollapseProps, CollapseSta
body, body,
bodyClassName, bodyClassName,
render, render,
collapsable collapsable,
} = this.props; } = this.props;
// todo 换掉 bootstrap 的 collapse // todo 换掉 bootstrap 的 collapse
return ( return (
<WrapperComponent className={cx(`Collapse`, { <WrapperComponent
'is-collapsed': this.state.collapsed, className={cx(
[`Collapse--${size}`]: size, `Collapse`,
'Collapse--collapsable': collapsable {
}, className)}> 'is-collapsed': this.state.collapsed,
[`Collapse--${size}`]: size,
'Collapse--collapsable': collapsable,
},
className
)}
>
{title ? ( {title ? (
<HeadingComponent className={cx(`Collapse-header`, headingClassName)}> <HeadingComponent className={cx(`Collapse-header`, headingClassName)}>
{render('heading', title)} {render('heading', title)}
{collapsable && ( {collapsable && <span onClick={this.toggleCollapsed} className={cx('Collapse-arrow')} />}
<span
onClick={this.toggleCollapsed}
className={cx('Collapse-arrow')}
></span>
)}
</HeadingComponent> </HeadingComponent>
) : null} ) : null}
<BasicCollapse in={collapsable ? !this.state.collapsed : true}> <BasicCollapse in={collapsable ? !this.state.collapsed : true}>
<div className={cx(`Collapse-body`, bodyClassName)}> <div className={cx(`Collapse-body`, bodyClassName)}>
{children ? ( {children
typeof children === 'function' ? children(this.props) : children ? typeof children === 'function'
) : body ? ( ? children(this.props)
render('body', body) : children
) : null} : body
? render('body', body)
: null}
</div> </div>
</BasicCollapse> </BasicCollapse>
</WrapperComponent> </WrapperComponent>
@ -122,6 +120,6 @@ export default class Collapse extends React.Component<CollapseProps, CollapseSta
@Renderer({ @Renderer({
test: /(^|\/)collapse$/, test: /(^|\/)collapse$/,
name: 'collapse' name: 'collapse',
}) })
export class CollapseRenderer extends Collapse {}; export class CollapseRenderer extends Collapse {}

View File

@ -1,63 +1,44 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {SchemaNode} from '../types'; import {SchemaNode} from '../types';
export interface ContainerProps extends RendererProps { export interface ContainerProps extends RendererProps {
body?: SchemaNode; body?: SchemaNode;
children?: (props:any) => React.ReactNode; children?: (props: any) => React.ReactNode;
className?: string; className?: string;
} }
export default class Container<T> extends React.Component<ContainerProps & T, object> { export default class Container<T> extends React.Component<ContainerProps & T, object> {
static propsList: Array<string> = [ static propsList: Array<string> = ['body', 'className'];
"body", static defaultProps = {
"className",
];
static defaultProps= {
className: '', className: '',
}; };
renderBody():JSX.Element | null { renderBody(): JSX.Element | null {
const { const {children, body, render, classnames: cx} = this.props;
children,
body,
render,
classnames: cx
} = this.props;
return ( return (
<div className={cx("Container-body")}> <div className={cx('Container-body')}>
{children ? ( {children
typeof children === 'function' ? children(this.props) as JSX.Element : children as JSX.Element ? typeof children === 'function'
) : body ? ( ? (children(this.props) as JSX.Element)
render('body', body) as JSX.Element : (children as JSX.Element)
) : null} : body
? (render('body', body) as JSX.Element)
: null}
</div> </div>
); );
} }
render() { render() {
const { const {className, size, classnames: cx} = this.props;
className,
size,
classnames: cx
} = this.props;
return ( return <div className={cx('Container', className)}>{this.renderBody()}</div>;
<div
className={cx('Container', className)}
>
{this.renderBody()}
</div>
);
} }
} }
@Renderer({ @Renderer({
test: /(^|\/)container$/, test: /(^|\/)container$/,
name: 'container' name: 'container',
}) })
export class ContainerRenderer extends Container<{}> {}; export class ContainerRenderer extends Container<{}> {}

View File

@ -1,57 +1,56 @@
/** /**
* @file scoped.jsx. * @file scoped.jsx.
* @author fex * @author fex
*/ */
import * as React from 'react'; import * as React from 'react';
import { RendererProps } from '../factory'; import {RendererProps} from '../factory';
import * as cx from 'classnames'; import * as cx from 'classnames';
import hoistNonReactStatic = require('hoist-non-react-statics'); import hoistNonReactStatic = require('hoist-non-react-statics');
import Button from '../components/Button'; import Button from '../components/Button';
import { filter } from '../utils/tpl'; import {filter} from '../utils/tpl';
export interface CopyableConfig { export interface CopyableConfig {}
}
export interface CopyableConfig { export interface CopyableConfig {
icon?: string; icon?: string;
content?: string; content?: string;
[propName:string]: any; [propName: string]: any;
}; }
export interface CopyableProps extends RendererProps { export interface CopyableProps extends RendererProps {
name?: string; name?: string;
label?: string; label?: string;
copyable: boolean | CopyableConfig; copyable: boolean | CopyableConfig;
}; }
export const HocCopyable = () => (Component: React.ComponentType<any>): any => { export const HocCopyable = () => (Component: React.ComponentType<any>): any => {
class QuickEditComponent extends React.PureComponent<CopyableProps, any> { class QuickEditComponent extends React.PureComponent<CopyableProps, any> {
static ComposedComponent = Component; static ComposedComponent = Component;
handleClick(content: string) { handleClick(content: string) {
const { env } = this.props; const {env} = this.props;
env.copy && env.copy(content); env.copy && env.copy(content);
} }
render() { render() {
const { const {copyable, name, className, data, noHoc, classnames: cx} = this.props;
copyable,
name,
className,
data,
noHoc,
classnames: cx
} = this.props;
if (copyable && !noHoc) { if (copyable && !noHoc) {
const content = filter((copyable as CopyableConfig).content || '${' + name + ' | raw }', data); const content = filter((copyable as CopyableConfig).content || '${' + name + ' | raw }', data);
if (content) { if (content) {
return (<Component {...this.props} className={cx(`Field--copyable`, className)}> return (
<Component {...this.props} wrapperComponent={''} noHoc /> <Component {...this.props} className={cx(`Field--copyable`, className)}>
<i key="edit-btn" data-tooltip="点击复制" className={cx("Field-copyBtn fa fa-clipboard")} onClick={this.handleClick.bind(this, content)} /> <Component {...this.props} wrapperComponent={''} noHoc />
</Component>); <i
key="edit-btn"
data-tooltip="点击复制"
className={cx('Field-copyBtn fa fa-clipboard')}
onClick={this.handleClick.bind(this, content)}
/>
</Component>
);
} }
} }
return (<Component {...this.props} />); return <Component {...this.props} />;
} }
} }
hoistNonReactStatic(QuickEditComponent, Component); hoistNonReactStatic(QuickEditComponent, Component);

View File

@ -1,16 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { import {Api, SchemaNode} from '../types';
Api, import {filter} from '../utils/tpl';
SchemaNode
} from '../types';
import {
filter
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
import * as moment from 'moment'; import * as moment from 'moment';
@ -22,66 +14,55 @@ export interface DateProps extends RendererProps {
} }
export class DateField extends React.Component<DateProps, object> { export class DateField extends React.Component<DateProps, object> {
static defaultProps:Partial<DateProps> = { static defaultProps: Partial<DateProps> = {
placeholder: '-', placeholder: '-',
format: 'YYYY-MM-DD', format: 'YYYY-MM-DD',
valueFormat: 'X' valueFormat: 'X',
}; };
render() { render() {
const { const {className, value, valueFormat, format, placeholder, classnames: cx} = this.props;
className,
value,
valueFormat,
format,
placeholder,
classnames: cx
} = this.props;
let viewValue:React.ReactNode = <span className="text-muted">{placeholder}</span>; let viewValue: React.ReactNode = <span className="text-muted">{placeholder}</span>;
if (value) { if (value) {
let date = moment(value, valueFormat); let date = moment(value, valueFormat);
viewValue = date.isValid() ? date.format(format) : <span className="text-danger"></span>; viewValue = date.isValid() ? date.format(format) : <span className="text-danger"></span>;
} }
return ( return <span className={cx('DateField', className)}>{viewValue}</span>;
<span className={cx('DateField', className)}>
{viewValue}
</span>
);
} }
} }
@Renderer({ @Renderer({
test: /(^|\/)date$/, test: /(^|\/)date$/,
name: 'date-field' name: 'date-field',
}) })
export class DateFieldRenderer extends DateField { export class DateFieldRenderer extends DateField {
static defaultProps:Partial<DateProps> = { static defaultProps: Partial<DateProps> = {
...DateField.defaultProps, ...DateField.defaultProps,
format: 'YYYY-MM-DD' format: 'YYYY-MM-DD',
}; };
}; }
@Renderer({ @Renderer({
test: /(^|\/)datetime$/, test: /(^|\/)datetime$/,
name: 'datetime-field' name: 'datetime-field',
}) })
export class DateTimeFieldRenderer extends DateField { export class DateTimeFieldRenderer extends DateField {
static defaultProps:Partial<DateProps> = { static defaultProps: Partial<DateProps> = {
...DateField.defaultProps, ...DateField.defaultProps,
format: 'YYYY-MM-DD HH:mm:ss' format: 'YYYY-MM-DD HH:mm:ss',
}; };
}; }
@Renderer({ @Renderer({
test: /(^|\/)time$/, test: /(^|\/)time$/,
name: 'time-field' name: 'time-field',
}) })
export class TimeFieldRenderer extends DateField { export class TimeFieldRenderer extends DateField {
static defaultProps:Partial<DateProps> = { static defaultProps: Partial<DateProps> = {
...DateField.defaultProps, ...DateField.defaultProps,
format: 'HH:mm' format: 'HH:mm',
}; };
}; }

View File

@ -1,34 +1,25 @@
import * as React from 'react'; import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import Scoped, { ScopedContext, IScopedContext } from '../Scoped'; import Scoped, {ScopedContext, IScopedContext} from '../Scoped';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { observer } from "mobx-react"; import {observer} from 'mobx-react';
import { import {SchemaNode, Schema, Action} from '../types';
SchemaNode, import {filter} from '../utils/tpl';
Schema,
Action
} from '../types';
import {
filter
} from '../utils/tpl';
import Modal from '../components/Modal'; import Modal from '../components/Modal';
import findLast = require('lodash/findLast'); import findLast = require('lodash/findLast');
import {guid} from '../utils/helper' import {guid} from '../utils/helper';
import {reaction} from 'mobx'; import {reaction} from 'mobx';
import { closeIcon } from '../components/icons'; import {closeIcon} from '../components/icons';
import { ModalStore, IModalStore } from '../store/modal'; import {ModalStore, IModalStore} from '../store/modal';
export interface DialogProps extends RendererProps { export interface DialogProps extends RendererProps {
title?: string; // 标题 title?: string; // 标题
size?: 'md' | 'lg' | 'sm' | 'xl'; size?: 'md' | 'lg' | 'sm' | 'xl';
closeOnEsc?: boolean; closeOnEsc?: boolean;
onClose: () => void; onClose: () => void;
onConfirm: (values:Array<object>, action: Action, ctx: object, targets: Array<any>) => void; onConfirm: (values: Array<object>, action: Action, ctx: object, targets: Array<any>) => void;
children?: React.ReactNode | ((props?:any) => React.ReactNode); children?: React.ReactNode | ((props?: any) => React.ReactNode);
store: IModalStore; store: IModalStore;
className?: string; className?: string;
header?: SchemaNode; header?: SchemaNode;
@ -41,7 +32,6 @@ export interface DialogProps extends RendererProps {
lazyRender?: boolean; lazyRender?: boolean;
wrapperComponent: React.ReactType; wrapperComponent: React.ReactType;
showCloseButton?: boolean; showCloseButton?: boolean;
} }
export interface DialogState { export interface DialogState {
@ -50,20 +40,20 @@ export interface DialogState {
export default class Dialog extends React.Component<DialogProps, DialogState> { export default class Dialog extends React.Component<DialogProps, DialogState> {
static propsList: Array<string> = [ static propsList: Array<string> = [
"title", 'title',
"size", 'size',
"closeOnEsc", 'closeOnEsc',
"children", 'children',
"bodyClassName", 'bodyClassName',
"headerClassName", 'headerClassName',
"confirm", 'confirm',
"onClose", 'onClose',
"onConfirm", 'onConfirm',
"show", 'show',
"showCloseButton", 'showCloseButton',
"actions" 'actions',
]; ];
static defaultProps:Partial<DialogProps>= { static defaultProps: Partial<DialogProps> = {
title: '弹框', title: '弹框',
bodyClassName: '', bodyClassName: '',
confirm: true, confirm: true,
@ -71,17 +61,17 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
lazyRender: false, lazyRender: false,
showCloseButton: true, showCloseButton: true,
wrapperComponent: Modal, wrapperComponent: Modal,
closeOnEsc: false closeOnEsc: false,
}; };
reaction:any; reaction: any;
$$id: string = guid(); $$id: string = guid();
constructor(props:DialogProps) { constructor(props: DialogProps) {
super(props); super(props);
this.state = { this.state = {
entered: !!this.props.show entered: !!this.props.show,
} };
this.handleSelfClose = this.handleSelfClose.bind(this); this.handleSelfClose = this.handleSelfClose.bind(this);
this.handleAction = this.handleAction.bind(this); this.handleAction = this.handleAction.bind(this);
this.handleDialogConfirm = this.handleDialogConfirm.bind(this); this.handleDialogConfirm = this.handleDialogConfirm.bind(this);
@ -117,21 +107,18 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
this.reaction && this.reaction(); this.reaction && this.reaction();
} }
buildActions():Array<Action> { buildActions(): Array<Action> {
const { const {actions, confirm} = this.props;
actions,
confirm
} = this.props;
if (typeof actions !== 'undefined') { if (typeof actions !== 'undefined') {
return actions; return actions;
} }
let ret:Array<Action> = []; let ret: Array<Action> = [];
ret.push({ ret.push({
type: 'button', type: 'button',
actionType: 'cancel', actionType: 'cancel',
label: '取消' label: '取消',
}); });
if (confirm) { if (confirm) {
@ -139,7 +126,7 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
type: 'button', type: 'button',
actionType: 'confirm', actionType: 'confirm',
label: '确认', label: '确认',
primary: true primary: true,
}); });
} }
@ -147,10 +134,7 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
} }
handleSelfClose() { handleSelfClose() {
const { const {onClose, store} = this.props;
onClose,
store
} = this.props;
// clear error // clear error
store.updateMessage(); store.updateMessage();
@ -158,10 +142,7 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
} }
handleAction(e: React.UIEvent<any>, action: Action, data: object) { handleAction(e: React.UIEvent<any>, action: Action, data: object) {
const { const {store, onAction} = this.props;
store,
onAction
} = this.props;
if (action.type === 'reset') { if (action.type === 'reset') {
store.reset(); store.reset();
@ -172,10 +153,8 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
} }
} }
handleDialogConfirm(values: object[], action:Action, ...args:Array<any>) { handleDialogConfirm(values: object[], action: Action, ...args: Array<any>) {
const { const {store} = this.props;
store
} = this.props;
if (action.mergeData && values.length === 1 && values[0]) { if (action.mergeData && values.length === 1 && values[0]) {
store.updateData(values[0]); store.updateData(values[0]);
@ -190,10 +169,8 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
store.closeDialog(); store.closeDialog();
} }
handleDialogClose(...args:Array<any>) { handleDialogClose(...args: Array<any>) {
const { const {store} = this.props;
store
} = this.props;
const action = store.action as Action; const action = store.action as Action;
const dialog = action.dialog as any; const dialog = action.dialog as any;
@ -204,11 +181,9 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
store.closeDialog(); store.closeDialog();
} }
handleDrawerConfirm(values: object[], action:Action, ...args:Array<any>) { handleDrawerConfirm(values: object[], action: Action, ...args: Array<any>) {
const { const {store} = this.props;
store
} = this.props;
if (action.mergeData && values.length === 1 && values[0]) { if (action.mergeData && values.length === 1 && values[0]) {
store.updateData(values[0]); store.updateData(values[0]);
@ -223,10 +198,8 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
store.closeDrawer(); store.closeDrawer();
} }
handleDrawerClose(...args:Array<any>) { handleDrawerClose(...args: Array<any>) {
const { const {store} = this.props;
store
} = this.props;
const action = store.action as Action; const action = store.action as Action;
const drawer = action.drawer as any; const drawer = action.drawer as any;
@ -239,79 +212,72 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
} }
handleEntered() { handleEntered() {
this.state.entered || this.setState({ this.state.entered ||
entered: true this.setState({
}) entered: true,
});
} }
handleExited() { handleExited() {
const {store} = this.props; const {store} = this.props;
store.reset(); store.reset();
this.state.entered && this.setState({ this.state.entered &&
entered: false this.setState({
}) entered: false,
});
} }
handleFormInit(data:any) { handleFormInit(data: any) {
const { const {store} = this.props;
store
} = this.props;
store.setFormData(data); store.setFormData(data);
} }
handleFormChange(data:any) { handleFormChange(data: any) {
const { const {store} = this.props;
store
} = this.props;
store.setFormData(data); store.setFormData(data);
} }
handleFormSaved(data:any, response:any) { handleFormSaved(data: any, response: any) {
const { const {store} = this.props;
store
} = this.props;
store.setFormData({ store.setFormData({
...data, ...data,
...response ...response,
}); });
} }
handleChildFinished(value:any, action:Action) { handleChildFinished(value: any, action: Action) {
// 下面会覆盖 // 下面会覆盖
} }
renderBody(body:SchemaNode, key?:any):React.ReactNode { renderBody(body: SchemaNode, key?: any): React.ReactNode {
let { let {render, store} = this.props;
render,
store,
} = this.props;
if (Array.isArray(body)) { if (Array.isArray(body)) {
return body.map((body, key) => this.renderBody(body, key)); return body.map((body, key) => this.renderBody(body, key));
} }
let subProps:any = { let subProps: any = {
key, key,
disabled: store.loading, disabled: store.loading,
onAction: this.handleAction, onAction: this.handleAction,
onFinished: this.handleChildFinished onFinished: this.handleChildFinished,
}; };
if (!(body as Schema).type) { if (!(body as Schema).type) {
return render(`body${key ? `/${key}` : ''}`, body, subProps); return render(`body${key ? `/${key}` : ''}`, body, subProps);
} }
let schema:Schema = body as Schema; let schema: Schema = body as Schema;
if (schema.type === 'form') { if (schema.type === 'form') {
schema = { schema = {
mode: 'horizontal', mode: 'horizontal',
wrapWithPanel: false, wrapWithPanel: false,
submitText: null, submitText: null,
...schema ...schema,
}; };
// 同步数据到 Dialog 层,方便 actions 根据表单数据联动。 // 同步数据到 Dialog 层,方便 actions 根据表单数据联动。
@ -330,31 +296,35 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
return null; return null;
} }
const { const {store, render, classnames: cx} = this.props;
store,
render,
classnames: cx
} = this.props;
return ( return (
<div className={cx('Modal-footer')}> <div className={cx('Modal-footer')}>
{store.loading || store.error ? ( {store.loading || store.error ? (
<div className={cx("Dialog-info")} key="info"> <div className={cx('Dialog-info')} key="info">
{store.loading ? render('info', { {store.loading
type: 'spinner' ? render(
}, { 'info',
key: 'info', {
size: 'sm' type: 'spinner',
}) : null} },
{store.error ? (<span className={cx("Dialog-error")}>{store.msg}</span>) : null} {
key: 'info',
size: 'sm',
}
)
: null}
{store.error ? <span className={cx('Dialog-error')}>{store.msg}</span> : null}
</div> </div>
) : null} ) : null}
{actions.map((action, key) => render(`action/${key}`, action, { {actions.map((action, key) =>
data: store.formData, render(`action/${key}`, action, {
onAction: this.handleAction, data: store.formData,
key, onAction: this.handleAction,
disabled: action.disabled || store.loading key,
}))} disabled: action.disabled || store.loading,
})
)}
</div> </div>
); );
} }
@ -377,7 +347,7 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
showCloseButton, showCloseButton,
env, env,
classnames: cx, classnames: cx,
classPrefix classPrefix,
} = this.props; } = this.props;
// console.log('Render Dialog'); // console.log('Render Dialog');
@ -401,60 +371,82 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
disabled={store.loading} disabled={store.loading}
> >
{title && typeof title === 'string' ? ( {title && typeof title === 'string' ? (
<div className={cx("Modal-header", headerClassName)}> <div className={cx('Modal-header', headerClassName)}>
{showCloseButton !== false && !store.loading ? (<a data-tooltip="关闭弹窗" onClick={this.handleSelfClose} className={cx("Modal-close")}>{closeIcon}</a>) : null} {showCloseButton !== false && !store.loading ? (
<div className={cx("Modal-title")}>{filter(title, store.formData)}</div> <a data-tooltip="关闭弹窗" onClick={this.handleSelfClose} className={cx('Modal-close')}>
{closeIcon}
</a>
) : null}
<div className={cx('Modal-title')}>{filter(title, store.formData)}</div>
</div> </div>
) : title ? ( ) : title ? (
<div className={cx("Modal-header", headerClassName)}> <div className={cx('Modal-header', headerClassName)}>
{showCloseButton !== false && !store.loading ? (<a data-tooltip="关闭弹窗" onClick={this.handleSelfClose} className={cx("Modal-close")}>{closeIcon}</a>) : null} {showCloseButton !== false && !store.loading ? (
<a data-tooltip="关闭弹窗" onClick={this.handleSelfClose} className={cx('Modal-close')}>
{closeIcon}
</a>
) : null}
{render('title', title, { {render('title', title, {
data: store.formData data: store.formData,
})} })}
</div> </div>
) : showCloseButton !== false && !store.loading ? ( ) : showCloseButton !== false && !store.loading ? (
<a data-tooltip="关闭弹窗" onClick={this.handleSelfClose} className={cx("Modal-close")}>{closeIcon}</a> <a data-tooltip="关闭弹窗" onClick={this.handleSelfClose} className={cx('Modal-close')}>
{closeIcon}
</a>
) : null} ) : null}
{header ? render('header', header, { {header
data: store.formData ? render('header', header, {
}) : null} data: store.formData,
})
: null}
{!this.state.entered && lazyRender ? ( {!this.state.entered && lazyRender ? (
<div className={cx("Modal-body", bodyClassName)}> <div className={cx('Modal-body', bodyClassName)} />
) : body ? (
</div> <div className={cx('Modal-body', bodyClassName)}>{this.renderBody(body, 'body')}</div>
) : body ? (
<div className={cx("Modal-body", bodyClassName)}>
{this.renderBody(body, 'body')}
</div>
) : null} ) : null}
{this.renderFooter()} {this.renderFooter()}
{body ? render('drawer', { // 支持嵌套 {body
...(store.action as Action) && (store.action as Action).drawer as object, ? render(
type: 'drawer' 'drawer',
}, { {
key: 'drawer', // 支持嵌套
data: store.drawerData, ...((store.action as Action) && ((store.action as Action).drawer as object)),
onConfirm: this.handleDrawerConfirm, type: 'drawer',
onClose: this.handleDrawerClose, },
show: store.drawerOpen, {
onAction: this.handleAction key: 'drawer',
}) : null} data: store.drawerData,
onConfirm: this.handleDrawerConfirm,
onClose: this.handleDrawerClose,
show: store.drawerOpen,
onAction: this.handleAction,
}
)
: null}
{body ? render('dialog', { // 支持嵌套 {body
...(store.action as Action) && (store.action as Action).dialog as object, ? render(
type: 'dialog' 'dialog',
}, { {
key: 'dialog', // 支持嵌套
data: store.dialogData, ...((store.action as Action) && ((store.action as Action).dialog as object)),
onConfirm: this.handleDialogConfirm, type: 'dialog',
onClose: this.handleDialogClose, },
show: store.dialogOpen, {
onAction: this.handleAction key: 'dialog',
}) : null} data: store.dialogData,
onConfirm: this.handleDialogConfirm,
onClose: this.handleDialogClose,
show: store.dialogOpen,
onAction: this.handleAction,
}
)
: null}
</Wrapper> </Wrapper>
); );
} }
@ -465,7 +457,7 @@ export default class Dialog extends React.Component<DialogProps, DialogState> {
storeType: ModalStore.name, storeType: ModalStore.name,
storeExtendsData: false, storeExtendsData: false,
name: 'dialog', name: 'dialog',
isolateScope: true isolateScope: true,
}) })
export class DialogRenderer extends Dialog { export class DialogRenderer extends Dialog {
static contextType = ScopedContext; static contextType = ScopedContext;
@ -490,14 +482,16 @@ export class DialogRenderer extends Dialog {
} }
const components = scoped.getComponents(); const components = scoped.getComponents();
const targets:Array<any> = []; const targets: Array<any> = [];
const { const {onConfirm, store} = this.props;
onConfirm,
store
} = this.props;
if (action.target) { if (action.target) {
targets.push(...action.target.split(',').map(name => scoped.getComponentByName(name)).filter(item => item && item.doAction)); targets.push(
...action.target
.split(',')
.map(name => scoped.getComponentByName(name))
.filter(item => item && item.doAction)
);
} }
if (!targets.length) { if (!targets.length) {
@ -511,21 +505,27 @@ export class DialogRenderer extends Dialog {
if (targets.length) { if (targets.length) {
store.markBusying(true); store.markBusying(true);
store.updateMessage(); store.updateMessage();
Promise Promise.all(
.all(targets.map(target => target.doAction({ targets.map(target =>
...action, target.doAction(
from: this.$$id {
}, ctx, true))) ...action,
from: this.$$id,
},
ctx,
true
)
)
)
.then(values => { .then(values => {
if ( if (
(action.type === 'submit' (action.type === 'submit' ||
|| action.actionType === 'submit' action.actionType === 'submit' ||
|| action.actionType === 'confirm') action.actionType === 'confirm') &&
&& action.close !== false action.close !== false
) { ) {
onConfirm onConfirm && onConfirm(values, rawAction || action, ctx, targets);
&& onConfirm(values, rawAction || action, ctx, targets)
} else if (action.close) { } else if (action.close) {
this.handleSelfClose(); this.handleSelfClose();
} }
@ -539,17 +539,17 @@ export class DialogRenderer extends Dialog {
return true; return true;
} }
return false; return false;
} }
handleAction(e: React.UIEvent<any>, action: Action, data: object, throwErrors: boolean = false, delegate?:boolean) { handleAction(
const { e: React.UIEvent<any>,
onAction, action: Action,
store, data: object,
onConfirm, throwErrors: boolean = false,
delegate?: boolean
} = this.props; ) {
const {onAction, store, onConfirm} = this.props;
if (action.from === this.$$id) { if (action.from === this.$$id) {
return onAction ? onAction(e, action, data, throwErrors, true) : false; return onAction ? onAction(e, action, data, throwErrors, true) : false;
@ -563,16 +563,24 @@ export class DialogRenderer extends Dialog {
} else if (action.actionType === 'close' || action.actionType === 'cancel') { } else if (action.actionType === 'close' || action.actionType === 'cancel') {
this.handleSelfClose(); this.handleSelfClose();
} else if (action.actionType === 'confirm') { } else if (action.actionType === 'confirm') {
this.tryChildrenToHandle({ this.tryChildrenToHandle(
...action, {
actionType: 'submit' ...action,
}, data, action) || this.handleSelfClose(); actionType: 'submit',
},
data,
action
) || this.handleSelfClose();
} else if (action.actionType === 'next' || action.actionType === 'prev') { } else if (action.actionType === 'next' || action.actionType === 'prev') {
if (action.type === 'submit') { if (action.type === 'submit') {
this.tryChildrenToHandle({ this.tryChildrenToHandle(
...action, {
actionType: 'submit' ...action,
}, data, action) || this.handleSelfClose(); actionType: 'submit',
},
data,
action
) || this.handleSelfClose();
} else { } else {
onConfirm([data], action, data, []); onConfirm([data], action, data, []);
} }
@ -588,20 +596,22 @@ export class DialogRenderer extends Dialog {
} }
} }
handleChildFinished(value:any, action:Action) { handleChildFinished(value: any, action: Action) {
if (action && action.from === this.$$id || action.close === false) { if ((action && action.from === this.$$id) || action.close === false) {
return; return;
} }
const scoped = this.context as IScopedContext; const scoped = this.context as IScopedContext;
const components = scoped.getComponents().filter((item:any) => !~['drawer', 'dialog'].indexOf(item.props.type)); const components = scoped
.getComponents()
.filter((item: any) => !~['drawer', 'dialog'].indexOf(item.props.type));
const onConfirm = this.props.onConfirm; const onConfirm = this.props.onConfirm;
const onClose = this.props.onClose; const onClose = this.props.onClose;
if ( if (
components.length === 1 components.length === 1 &&
&& (components[0].props.type === 'form' || components[0].props.type === 'wizard') (components[0].props.type === 'form' || components[0].props.type === 'wizard') &&
&& (action.close === true || components[0].props.closeDialogOnSubmit !== false) (action.close === true || components[0].props.closeDialogOnSubmit !== false)
) { ) {
onConfirm && onConfirm([value], action, {}, components); onConfirm && onConfirm([value], action, {}, components);
} else if (action.close === true) { } else if (action.close === true) {
@ -621,11 +631,11 @@ export class DialogRenderer extends Dialog {
scoped.reload(action.reload, store.data); scoped.reload(action.reload, store.data);
} else { } else {
// 没有设置,则自动让页面中 crud 刷新。 // 没有设置,则自动让页面中 crud 刷新。
scoped.getComponents() scoped
.getComponents()
.filter((item: any) => item.props.type === 'crud') .filter((item: any) => item.props.type === 'crud')
.forEach((item: any) => item.reload && item.reload()); .forEach((item: any) => item.reload && item.reload());
} }
} }
handleDrawerConfirm(values: object[], action: Action, ...rest: Array<any>) { handleDrawerConfirm(values: object[], action: Action, ...rest: Array<any>) {
@ -642,10 +652,11 @@ export class DialogRenderer extends Dialog {
scoped.reload(action.reload, store.data); scoped.reload(action.reload, store.data);
} else { } else {
// 没有设置,则自动让页面中 crud 刷新。 // 没有设置,则自动让页面中 crud 刷新。
scoped.getComponents() scoped
.getComponents()
.filter((item: any) => item.props.type === 'crud') .filter((item: any) => item.props.type === 'crud')
.forEach((item: any) => item.reload && item.reload()); .forEach((item: any) => item.reload && item.reload());
} }
}, 300); }, 300);
} }
}; }

View File

@ -1,33 +1,25 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { import {filter} from '../utils/tpl';
filter
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
export interface DividerProps extends RendererProps { export interface DividerProps extends RendererProps {}
}
export default class Divider extends React.Component<DividerProps, object> { export default class Divider extends React.Component<DividerProps, object> {
static defaultProps:Partial<DividerProps>= { static defaultProps: Partial<DividerProps> = {
className: '' className: '',
}; };
render() { render() {
const cx = this.props.classnames; const cx = this.props.classnames;
const className = this.props.className; const className = this.props.className;
return ( return <div className={cx('Divider', className)} />;
<div className={cx('Divider', className)}></div>
);
} }
} }
@Renderer({ @Renderer({
test: /(^|\/)(?:divider|hr)$/, test: /(^|\/)(?:divider|hr)$/,
name: 'divider' name: 'divider',
}) })
export class DividerRenderer extends Divider {} export class DividerRenderer extends Divider {}

View File

@ -1,24 +1,17 @@
import * as React from 'react'; import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import Scoped, { ScopedContext, IScopedContext } from '../Scoped'; import Scoped, {ScopedContext, IScopedContext} from '../Scoped';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {ServiceStore, IServiceStore} from '../store/service';
RendererProps import {observer} from 'mobx-react';
} from '../factory'; import {SchemaNode, Schema, Action} from '../types';
import { ServiceStore, IServiceStore } from '../store/service';
import { observer } from "mobx-react";
import {
SchemaNode,
Schema,
Action
} from '../types';
import * as cx from 'classnames'; import * as cx from 'classnames';
import {default as DrawerContainer} from '../components/Drawer'; import {default as DrawerContainer} from '../components/Drawer';
import findLast = require('lodash/findLast'); import findLast = require('lodash/findLast');
import { guid } from '../utils/helper' import {guid} from '../utils/helper';
import { reaction } from 'mobx'; import {reaction} from 'mobx';
import { findDOMNode } from 'react-dom'; import {findDOMNode} from 'react-dom';
import { IModalStore, ModalStore } from '../store/modal'; import {IModalStore, ModalStore} from '../store/modal';
export interface DrawerProps extends RendererProps { export interface DrawerProps extends RendererProps {
title?: string; // 标题 title?: string; // 标题
@ -43,18 +36,18 @@ export interface DrawerProps extends RendererProps {
export default class Drawer extends React.Component<DrawerProps, object> { export default class Drawer extends React.Component<DrawerProps, object> {
static propsList: Array<string> = [ static propsList: Array<string> = [
"title", 'title',
"size", 'size',
"closeOnEsc", 'closeOnEsc',
"children", 'children',
"bodyClassName", 'bodyClassName',
"confirm", 'confirm',
"position", 'position',
"onClose", 'onClose',
"onConfirm", 'onConfirm',
"show", 'show',
"resizable", 'resizable',
"overlay" 'overlay',
]; ];
static defaultProps: Partial<DrawerProps> = { static defaultProps: Partial<DrawerProps> = {
title: '', title: '',
@ -63,15 +56,15 @@ export default class Drawer extends React.Component<DrawerProps, object> {
position: 'right', position: 'right',
resizable: false, resizable: false,
overlay: true, overlay: true,
closeOnEsc: false closeOnEsc: false,
}; };
reaction: any; reaction: any;
$$id: string = guid(); $$id: string = guid();
drawer: any; drawer: any;
state = { state = {
resizeCoord: 0 resizeCoord: 0,
} };
constructor(props: DrawerProps) { constructor(props: DrawerProps) {
super(props); super(props);
@ -111,10 +104,7 @@ export default class Drawer extends React.Component<DrawerProps, object> {
} }
buildActions(): Array<Action> { buildActions(): Array<Action> {
const { const {actions, confirm} = this.props;
actions,
confirm
} = this.props;
if (typeof actions !== 'undefined') { if (typeof actions !== 'undefined') {
return actions; return actions;
@ -124,7 +114,7 @@ export default class Drawer extends React.Component<DrawerProps, object> {
ret.push({ ret.push({
type: 'button', type: 'button',
actionType: 'close', actionType: 'close',
label: '取消' label: '取消',
}); });
if (confirm) { if (confirm) {
@ -132,7 +122,7 @@ export default class Drawer extends React.Component<DrawerProps, object> {
type: 'button', type: 'button',
actionType: 'confirm', actionType: 'confirm',
label: '确认', label: '确认',
primary: true primary: true,
}); });
} }
@ -140,10 +130,7 @@ export default class Drawer extends React.Component<DrawerProps, object> {
} }
handleSelfClose() { handleSelfClose() {
const { const {onClose, store} = this.props;
onClose,
store
} = this.props;
// clear error // clear error
store.updateMessage(); store.updateMessage();
@ -151,10 +138,7 @@ export default class Drawer extends React.Component<DrawerProps, object> {
} }
handleAction(e: React.UIEvent<any>, action: Action, data: object) { handleAction(e: React.UIEvent<any>, action: Action, data: object) {
const { const {onClose, onAction} = this.props;
onClose,
onAction
} = this.props;
if (action.actionType === 'close') { if (action.actionType === 'close') {
onClose(); onClose();
@ -163,10 +147,8 @@ export default class Drawer extends React.Component<DrawerProps, object> {
} }
} }
handleDrawerConfirm(values: object[], action:Action, ...args: Array<any>) { handleDrawerConfirm(values: object[], action: Action, ...args: Array<any>) {
const { const {store} = this.props;
store
} = this.props;
if (action.mergeData && values.length === 1 && values[0]) { if (action.mergeData && values.length === 1 && values[0]) {
store.updateData(values[0]); store.updateData(values[0]);
@ -183,9 +165,7 @@ export default class Drawer extends React.Component<DrawerProps, object> {
} }
handleDrawerClose(...args: Array<any>) { handleDrawerClose(...args: Array<any>) {
const { const {store} = this.props;
store
} = this.props;
const action = store.action as Action; const action = store.action as Action;
const drawer = action.drawer as any; const drawer = action.drawer as any;
@ -197,10 +177,8 @@ export default class Drawer extends React.Component<DrawerProps, object> {
store.closeDrawer(); store.closeDrawer();
} }
handleDialogConfirm(values: object[], action:Action, ...args: Array<any>) { handleDialogConfirm(values: object[], action: Action, ...args: Array<any>) {
const { const {store} = this.props;
store
} = this.props;
if (action.mergeData && values.length === 1 && values[0]) { if (action.mergeData && values.length === 1 && values[0]) {
store.updateData(values[0]); store.updateData(values[0]);
@ -217,9 +195,7 @@ export default class Drawer extends React.Component<DrawerProps, object> {
} }
handleDialogClose(...args: Array<any>) { handleDialogClose(...args: Array<any>) {
const { const {store} = this.props;
store
} = this.props;
const action = store.action as Action; const action = store.action as Action;
const dialog = action.dialog as any; const dialog = action.dialog as any;
@ -235,30 +211,24 @@ export default class Drawer extends React.Component<DrawerProps, object> {
// 下面会覆盖 // 下面会覆盖
} }
handleFormInit(data:any) { handleFormInit(data: any) {
const { const {store} = this.props;
store
} = this.props;
store.setFormData(data); store.setFormData(data);
} }
handleFormChange(data:any) { handleFormChange(data: any) {
const { const {store} = this.props;
store
} = this.props;
store.setFormData(data); store.setFormData(data);
} }
handleFormSaved(data:any, response:any) { handleFormSaved(data: any, response: any) {
const { const {store} = this.props;
store
} = this.props;
store.setFormData({ store.setFormData({
...data, ...data,
...response ...response,
}); });
} }
@ -268,17 +238,14 @@ export default class Drawer extends React.Component<DrawerProps, object> {
} }
renderBody(body: SchemaNode, key?: any): React.ReactNode { renderBody(body: SchemaNode, key?: any): React.ReactNode {
let { let {render, store} = this.props;
render,
store,
} = this.props;
let schema: Schema = body as Schema; let schema: Schema = body as Schema;
let subProps: any = { let subProps: any = {
key, key,
disabled: store.loading, disabled: store.loading,
onAction: this.handleAction, onAction: this.handleAction,
onFinished: this.handleChildFinished onFinished: this.handleChildFinished,
}; };
if (schema.type === 'form') { if (schema.type === 'form') {
@ -286,7 +253,7 @@ export default class Drawer extends React.Component<DrawerProps, object> {
mode: 'horizontal', mode: 'horizontal',
wrapWithPanel: false, wrapWithPanel: false,
submitText: null, submitText: null,
...schema ...schema,
}; };
// 同步数据到 Dialog 层,方便 actions 根据表单数据联动。 // 同步数据到 Dialog 层,方便 actions 根据表单数据联动。
@ -305,83 +272,93 @@ export default class Drawer extends React.Component<DrawerProps, object> {
return null; return null;
} }
const { const {store, render, classnames: cx} = this.props;
store,
render,
classnames: cx
} = this.props;
return ( return (
<div className={cx("Drawer-footer")}> <div className={cx('Drawer-footer')}>
{store.loading || store.error ? ( {store.loading || store.error ? (
<div className={cx("Drawer-info")}> <div className={cx('Drawer-info')}>
{store.loading ? render('info', { {store.loading
type: 'spinner' ? render(
}, { 'info',
key: 'info' {
}) : null} type: 'spinner',
{store.error ? (<span className={cx("Drawer-msg")}>{store.msg}</span>) : null} },
{
key: 'info',
}
)
: null}
{store.error ? <span className={cx('Drawer-msg')}>{store.msg}</span> : null}
</div> </div>
) : null} ) : null}
{actions.map((action, key) => render(`action/${key}`, action, { {actions.map((action, key) =>
onAction: this.handleAction, render(`action/${key}`, action, {
data: store.formData, onAction: this.handleAction,
key, data: store.formData,
disabled: action.disabled || store.loading key,
}))} disabled: action.disabled || store.loading,
})
)}
</div> </div>
); );
} }
renderResizeCtrl() { renderResizeCtrl() {
const { const {classnames: cx} = this.props;
classnames: cx
} = this.props;
return ( return (
<div <div className={cx('Drawer-resizeCtrl')} onMouseDown={this.resizeMouseDown}>
className={cx('Drawer-resizeCtrl')} <div className={cx('Drawer-resizeIcon')}>···</div>
onMouseDown={this.resizeMouseDown}
>
<div className={cx("Drawer-resizeIcon")}>···</div>
</div> </div>
); );
} }
resizeMouseDown(e: React.MouseEvent<any>) { resizeMouseDown(e: React.MouseEvent<any>) {
const { const {position, classPrefix: ns} = this.props;
position,
classPrefix: ns
} = this.props;
this.drawer = (findDOMNode(this) as HTMLElement).querySelector(`.${ns}Drawer-content`) as HTMLElement; this.drawer = (findDOMNode(this) as HTMLElement).querySelector(`.${ns}Drawer-content`) as HTMLElement;
const resizeCtrl = (findDOMNode(this) as HTMLElement).querySelector(`.${ns}Drawer-content .${ns}Drawer-resizeCtrl`) as HTMLElement; const resizeCtrl = (findDOMNode(this) as HTMLElement).querySelector(
`.${ns}Drawer-content .${ns}Drawer-resizeCtrl`
) as HTMLElement;
const drawerWidth = getComputedStyle(this.drawer).width as string; const drawerWidth = getComputedStyle(this.drawer).width as string;
const drawerHeight = getComputedStyle(this.drawer).height as string; const drawerHeight = getComputedStyle(this.drawer).height as string;
this.setState({ this.setState({
resizeCoord: resizeCoord:
(position === 'left' && e.clientX - resizeCtrl.offsetWidth - parseInt(drawerWidth.substring(0, drawerWidth.length - 2))) || (position === 'left' &&
(position === 'right' && document.body.offsetWidth - e.clientX - resizeCtrl.offsetWidth - parseInt(drawerWidth.substring(0, drawerWidth.length - 2))) || e.clientX - resizeCtrl.offsetWidth - parseInt(drawerWidth.substring(0, drawerWidth.length - 2))) ||
(position === 'top' && e.clientY - resizeCtrl.offsetHeight - parseInt(drawerHeight.substring(0, drawerHeight.length - 2))) || (position === 'right' &&
(position === 'bottom' && document.body.offsetHeight - e.clientY - resizeCtrl.offsetHeight - parseInt(drawerHeight.substring(0, drawerHeight.length - 2))) || 0 document.body.offsetWidth -
}) e.clientX -
resizeCtrl.offsetWidth -
parseInt(drawerWidth.substring(0, drawerWidth.length - 2))) ||
(position === 'top' &&
e.clientY -
resizeCtrl.offsetHeight -
parseInt(drawerHeight.substring(0, drawerHeight.length - 2))) ||
(position === 'bottom' &&
document.body.offsetHeight -
e.clientY -
resizeCtrl.offsetHeight -
parseInt(drawerHeight.substring(0, drawerHeight.length - 2))) ||
0,
});
document.body.addEventListener('mousemove', this.bindResize); document.body.addEventListener('mousemove', this.bindResize);
document.body.addEventListener('mouseup', this.removeResize); document.body.addEventListener('mouseup', this.removeResize);
} }
bindResize(e: any) { bindResize(e: any) {
const { const {position} = this.props;
position
} = this.props;
const maxWH = 'calc(100% - 50px)'; const maxWH = 'calc(100% - 50px)';
const drawerStyle = this.drawer.style; const drawerStyle = this.drawer.style;
let wh = let wh =
(position === 'left' && e.clientX) || (position === 'left' && e.clientX) ||
(position === 'right' && document.body.offsetWidth - e.clientX) || (position === 'right' && document.body.offsetWidth - e.clientX) ||
(position === 'top' && e.clientY) || (position === 'top' && e.clientY) ||
(position === 'bottom' && document.body.offsetHeight - e.clientY) || 0; (position === 'bottom' && document.body.offsetHeight - e.clientY) ||
0;
wh = wh - this.state.resizeCoord + 'px'; wh = wh - this.state.resizeCoord + 'px';
if (position === 'left' || position === 'right') { if (position === 'left' || position === 'right') {
@ -418,7 +395,7 @@ export default class Drawer extends React.Component<DrawerProps, object> {
overlay, overlay,
closeOnOutside, closeOnOutside,
classPrefix: ns, classPrefix: ns,
classnames: cx classnames: cx,
} = this.props; } = this.props;
const Container = wrapperComponent || DrawerContainer; const Container = wrapperComponent || DrawerContainer;
@ -438,46 +415,60 @@ export default class Drawer extends React.Component<DrawerProps, object> {
closeOnOutside={closeOnOutside} closeOnOutside={closeOnOutside}
container={env && env.getModalContainer ? env.getModalContainer() : undefined} container={env && env.getModalContainer ? env.getModalContainer() : undefined}
> >
<div className={cx("Drawer-header")}> <div className={cx('Drawer-header')}>
{title ? ( {title ? (
<div className={cx("Drawer-title")}>{render('title', title, { <div className={cx('Drawer-title')}>
data: store.formData {render('title', title, {
})}</div> data: store.formData,
})}
</div>
) : null} ) : null}
{header ? render('header', header, { {header
data: store.formData ? render('header', header, {
}) : null} data: store.formData,
})
: null}
</div> </div>
<div className={cx("Drawer-body", bodyClassName)}> <div className={cx('Drawer-body', bodyClassName)}>{body ? this.renderBody(body, 'body') : null}</div>
{body ? this.renderBody(body, 'body') : null}
</div>
{this.renderFooter()} {this.renderFooter()}
{body ? render('dialog', { {body
...(store.action as Action) && (store.action as Action).dialog as object, ? render(
type: 'dialog' 'dialog',
}, { {
key: 'dialog', ...((store.action as Action) && ((store.action as Action).dialog as object)),
data: store.dialogData, type: 'dialog',
onConfirm: this.handleDialogConfirm, },
onClose: this.handleDialogClose, {
onAction: this.handleAction, key: 'dialog',
show: store.dialogOpen, data: store.dialogData,
}) : null} onConfirm: this.handleDialogConfirm,
onClose: this.handleDialogClose,
onAction: this.handleAction,
show: store.dialogOpen,
}
)
: null}
{body ? render('drawer', { {body
...(store.action as Action) && (store.action as Action).drawer as object, ? render(
type: 'drawer' 'drawer',
}, { {
key: 'drawer', ...((store.action as Action) && ((store.action as Action).drawer as object)),
data: store.drawerData, type: 'drawer',
onConfirm: this.handleDrawerConfirm, },
onClose: this.handleDrawerClose, {
onAction: this.handleAction, key: 'drawer',
show: store.drawerOpen, data: store.drawerData,
}) : null} onConfirm: this.handleDrawerConfirm,
onClose: this.handleDrawerClose,
onAction: this.handleAction,
show: store.drawerOpen,
}
)
: null}
{resizable ? this.renderResizeCtrl() : null} {resizable ? this.renderResizeCtrl() : null}
</Container> </Container>
@ -490,7 +481,7 @@ export default class Drawer extends React.Component<DrawerProps, object> {
storeType: ModalStore.name, storeType: ModalStore.name,
storeExtendsData: false, storeExtendsData: false,
name: 'drawer', name: 'drawer',
isolateScope: true isolateScope: true,
}) })
export class DrawerRenderer extends Drawer { export class DrawerRenderer extends Drawer {
static contextType = ScopedContext; static contextType = ScopedContext;
@ -516,13 +507,15 @@ export class DrawerRenderer extends Drawer {
const components = scoped.getComponents(); const components = scoped.getComponents();
const targets: Array<any> = []; const targets: Array<any> = [];
const { const {onConfirm, store} = this.props;
onConfirm,
store
} = this.props;
if (action.target) { if (action.target) {
targets.push(...action.target.split(',').map(name => scoped.getComponentByName(name)).filter(item => item && item.doAction)); targets.push(
...action.target
.split(',')
.map(name => scoped.getComponentByName(name))
.filter(item => item && item.doAction)
);
} }
if (!targets.length) { if (!targets.length) {
@ -537,20 +530,26 @@ export class DrawerRenderer extends Drawer {
store.markBusying(true); store.markBusying(true);
store.updateMessage(); store.updateMessage();
Promise Promise.all(
.all(targets.map(target => target.doAction({ targets.map(target =>
...action, target.doAction(
from: this.$$id {
}, ctx, true))) ...action,
from: this.$$id,
},
ctx,
true
)
)
)
.then(values => { .then(values => {
if ( if (
(action.type === 'submit' (action.type === 'submit' ||
|| action.actionType === 'submit' action.actionType === 'submit' ||
|| action.actionType === 'confirm') action.actionType === 'confirm') &&
&& action.close !== false action.close !== false
) { ) {
onConfirm onConfirm && onConfirm(values, rawAction || action, ctx, targets);
&& onConfirm(values, rawAction || action, ctx, targets)
} else if (action.close) { } else if (action.close) {
this.handleSelfClose(); this.handleSelfClose();
} }
@ -564,16 +563,17 @@ export class DrawerRenderer extends Drawer {
return true; return true;
} }
return false; return false;
} }
handleAction(e: React.UIEvent<any>, action: Action, data: object, throwErrors: boolean = false, delegate?: boolean) { handleAction(
const { e: React.UIEvent<any>,
onClose, action: Action,
onAction, data: object,
store throwErrors: boolean = false,
} = this.props; delegate?: boolean
) {
const {onClose, onAction, store} = this.props;
if (action.from === this.$$id) { if (action.from === this.$$id) {
return onAction ? onAction(e, action, data, throwErrors, true) : false; return onAction ? onAction(e, action, data, throwErrors, true) : false;
@ -599,12 +599,14 @@ export class DrawerRenderer extends Drawer {
} }
handleChildFinished(value: any, action: Action) { handleChildFinished(value: any, action: Action) {
if (action && action.from === this.$$id || action.close === false) { if ((action && action.from === this.$$id) || action.close === false) {
return; return;
} }
const scoped = this.context as IScopedContext; const scoped = this.context as IScopedContext;
const components = scoped.getComponents().filter((item: any) => !~['drawer', 'dialog'].indexOf(item.props.type)); const components = scoped
.getComponents()
.filter((item: any) => !~['drawer', 'dialog'].indexOf(item.props.type));
const onConfirm = this.props.onConfirm; const onConfirm = this.props.onConfirm;
if (components.length === 1 && (components[0].props.type === 'form' || components[0].props.type === 'wizard')) { if (components.length === 1 && (components[0].props.type === 'form' || components[0].props.type === 'wizard')) {
@ -624,11 +626,11 @@ export class DrawerRenderer extends Drawer {
scoped.reload(action.reload, store.data); scoped.reload(action.reload, store.data);
} else { } else {
// 没有设置,则自动让页面中 crud 刷新。 // 没有设置,则自动让页面中 crud 刷新。
scoped.getComponents() scoped
.getComponents()
.filter((item: any) => item.props.type === 'crud') .filter((item: any) => item.props.type === 'crud')
.forEach((item: any) => item.reload && item.reload()); .forEach((item: any) => item.reload && item.reload());
} }
} }
handleDrawerConfirm(values: object[], action: Action, ...rest: Array<any>) { handleDrawerConfirm(values: object[], action: Action, ...rest: Array<any>) {
@ -645,10 +647,11 @@ export class DrawerRenderer extends Drawer {
scoped.reload(action.reload, store.data); scoped.reload(action.reload, store.data);
} else { } else {
// 没有设置,则自动让页面中 crud 刷新。 // 没有设置,则自动让页面中 crud 刷新。
scoped.getComponents() scoped
.getComponents()
.filter((item: any) => item.props.type === 'crud') .filter((item: any) => item.props.type === 'crud')
.forEach((item: any) => item.reload && item.reload()); .forEach((item: any) => item.reload && item.reload());
} }
}, 300); }, 300);
} }
}; }

View File

@ -1,15 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {RootCloseWrapper} from 'react-overlays'; import {RootCloseWrapper} from 'react-overlays';
import Overlay from '../components/Overlay'; import Overlay from '../components/Overlay';
import PopOver from '../components/PopOver'; import PopOver from '../components/PopOver';
import * as cx from 'classnames'; import * as cx from 'classnames';
import { isVisible } from '../utils/helper'; import {isVisible} from '../utils/helper';
import { filter } from '../utils/tpl'; import {filter} from '../utils/tpl';
export interface DropDownButtonProps extends RendererProps { export interface DropDownButtonProps extends RendererProps {
block?: boolean; block?: boolean;
@ -18,24 +14,23 @@ export interface DropDownButtonProps extends RendererProps {
buttons?: Array<any>; buttons?: Array<any>;
caretIcon?: string; caretIcon?: string;
iconOnly?: boolean; iconOnly?: boolean;
}; }
export interface DropDownButtonState { export interface DropDownButtonState {
isOpened: boolean; isOpened: boolean;
}; }
export default class DropDownButton extends React.Component<DropDownButtonProps, DropDownButtonState> { export default class DropDownButton extends React.Component<DropDownButtonProps, DropDownButtonState> {
state: DropDownButtonState = {
state:DropDownButtonState = { isOpened: false,
isOpened: false
}; };
static defaultProps = { static defaultProps = {
caretIcon: 'fa fa-angle-down' caretIcon: 'fa fa-angle-down',
}; };
target:any; target: any;
constructor(props:DropDownButtonProps) { constructor(props: DropDownButtonProps) {
super(props); super(props);
this.open = this.open.bind(this); this.open = this.open.bind(this);
@ -44,69 +39,57 @@ export default class DropDownButton extends React.Component<DropDownButtonProps,
this.domRef = this.domRef.bind(this); this.domRef = this.domRef.bind(this);
} }
domRef(ref:any) { domRef(ref: any) {
this.target = ref; this.target = ref;
} }
toogle(e:React.MouseEvent<any>) { toogle(e: React.MouseEvent<any>) {
e.preventDefault(); e.preventDefault();
this.setState({ this.setState({
isOpened: !this.state.isOpened isOpened: !this.state.isOpened,
}); });
} }
open() { open() {
this.setState({ this.setState({
isOpened: true isOpened: true,
}); });
} }
close() { close() {
this.setState({ this.setState({
isOpened: false isOpened: false,
}); });
} }
renderOuter() { renderOuter() {
const { const {render, buttons, data, popOverContainer, classnames: cx, classPrefix: ns, children, align} = this.props;
render,
buttons,
data,
popOverContainer,
classnames: cx,
classPrefix: ns,
children,
align
} = this.props;
let body = ( let body = (
<RootCloseWrapper <RootCloseWrapper disabled={!this.state.isOpened} onRootClose={this.close}>
disabled={!this.state.isOpened} <ul className={cx('DropDown-menu')}>
onRootClose={this.close} {children
> ? children
<ul : Array.isArray(buttons)
className={cx("DropDown-menu")} ? buttons.map((button, index) => {
> if (!isVisible(button, data)) {
{children ? children : Array.isArray(buttons) ? buttons.map((button, index) => { return null;
if (!isVisible(button, data)) { } else if (button === 'divider' || button.type === 'divider') {
return null; return <li key={index} className={cx('DropDown-divider')} />;
} else if (button === 'divider' || button.type === 'divider') { }
return (
<li key={index} className={cx("DropDown-divider")} />
);
}
return ( return (
<li key={index}> <li key={index}>
{render(`button/${index}`, { {render(`button/${index}`, {
type: 'button', type: 'button',
...button, ...button,
isMenuItem: true isMenuItem: true,
})} })}
</li> </li>
); );
}) : null} })
: null}
</ul> </ul>
</RootCloseWrapper> </RootCloseWrapper>
); );
@ -119,11 +102,11 @@ export default class DropDownButton extends React.Component<DropDownButtonProps,
target={() => this.target} target={() => this.target}
show show
> >
<PopOver <PopOver
overlay overlay
onHide={this.close} onHide={this.close}
classPrefix={ns} classPrefix={ns}
className={cx("DropDown-popover")} className={cx('DropDown-popover')}
style={{minWidth: this.target.offsetWidth}} style={{minWidth: this.target.offsetWidth}}
> >
{body} {body}
@ -149,7 +132,7 @@ export default class DropDownButton extends React.Component<DropDownButtonProps,
caretIcon, caretIcon,
align, align,
iconOnly, iconOnly,
data data,
} = this.props; } = this.props;
return ( return (
@ -157,18 +140,24 @@ export default class DropDownButton extends React.Component<DropDownButtonProps,
className={cx('DropDown ', { className={cx('DropDown ', {
'DropDown--block': block, 'DropDown--block': block,
'DropDown--alignRight': align === 'right', 'DropDown--alignRight': align === 'right',
'is-opened': this.state.isOpened 'is-opened': this.state.isOpened,
})} })}
ref={this.domRef} ref={this.domRef}
> >
<button <button
onClick={this.toogle} onClick={this.toogle}
disabled={disabled || btnDisabled} disabled={disabled || btnDisabled}
className={cx('Button', className, (typeof level === 'undefined' ? 'Button--default' : level ? `Button--${level}` : ''), { className={cx(
'Button--block': block, 'Button',
'Button--primary': primary, className,
'Button--iconOnly': iconOnly typeof level === 'undefined' ? 'Button--default' : level ? `Button--${level}` : '',
}, size ? `Button--${size}` : '')} {
'Button--block': block,
'Button--primary': primary,
'Button--iconOnly': iconOnly,
},
size ? `Button--${size}` : ''
)}
> >
{typeof label === 'string' ? filter(label, data) : label} {typeof label === 'string' ? filter(label, data) : label}
<i className={cx('DropDown-caret', caretIcon)} /> <i className={cx('DropDown-caret', caretIcon)} />
@ -182,8 +171,6 @@ export default class DropDownButton extends React.Component<DropDownButtonProps,
@Renderer({ @Renderer({
test: /(^|\/)dropdown-button$/, test: /(^|\/)dropdown-button$/,
name: 'dropdown-button' name: 'dropdown-button',
}) })
export class DropDownButtonRenderer extends DropDownButton { export class DropDownButtonRenderer extends DropDownButton {}
}

View File

@ -1,13 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {Schema} from '../types';
RendererProps import {resolveVariable} from '../utils/tpl-builtin';
} from '../factory'; import {createObject, isObject} from '../utils/helper';
import {
Schema,
} from '../types';
import { resolveVariable } from '../utils/tpl-builtin';
import { createObject, isObject } from '../utils/helper';
export interface EachProps extends RendererProps { export interface EachProps extends RendererProps {
name: string; name: string;
@ -15,34 +10,35 @@ export interface EachProps extends RendererProps {
} }
export default class Each extends React.Component<EachProps> { export default class Each extends React.Component<EachProps> {
static defaultProps: Partial<EachProps> = { static defaultProps: Partial<EachProps> = {
className: '', className: '',
}; };
render() { render() {
const { const {data, name, className, render, value, items} = this.props;
data,
name,
className,
render,
value,
items
} = this.props;
const arr = typeof value !== 'undefined' const arr =
? (isObject(value) ? Object.keys(value).map(key =>({ typeof value !== 'undefined'
key: key, ? isObject(value)
value: value[key] ? Object.keys(value).map(key => ({
})) key: key,
: Array.isArray(value) ? value : []) : resolveVariable(name, data); value: value[key],
}))
: Array.isArray(value)
? value
: []
: resolveVariable(name, data);
return ( return (
<div className={className}> <div className={className}>
{Array.isArray(arr) && items ? arr.map((item:any, index:number) => render(`item/${index}`, items, { {Array.isArray(arr) && items
data: createObject(data, isObject(item) ? item : {[name]: item, item: item}), ? arr.map((item: any, index: number) =>
key: index render(`item/${index}`, items, {
})) : null} data: createObject(data, isObject(item) ? item : {[name]: item, item: item}),
key: index,
})
)
: null}
</div> </div>
); );
} }
@ -50,6 +46,6 @@ export default class Each extends React.Component<EachProps> {
@Renderer({ @Renderer({
test: /(^|\/)(?:repeat|each)$/, test: /(^|\/)(?:repeat|each)$/,
name: "each" name: 'each',
}) })
export class EachRenderer extends Each { } export class EachRenderer extends Each {}

View File

@ -1,15 +1,10 @@
import * as React from "react"; import * as React from 'react';
import { Renderer, RendererProps } from "../factory"; import {Renderer, RendererProps} from '../factory';
import { Schema } from "../types"; import {Schema} from '../types';
import * as cx from "classnames"; import * as cx from 'classnames';
import pick = require("lodash/pick"); import pick = require('lodash/pick');
export const ColProps = [ export const ColProps = ['lg', 'md', 'sm', 'xs'];
"lg",
"md",
"sm",
"xs"
];
export type Column = Schema & { export type Column = Schema & {
xs?: number; xs?: number;
@ -44,19 +39,19 @@ export interface ColumnArray extends Array<ColumnNode> {}
export interface GridProps extends RendererProps { export interface GridProps extends RendererProps {
columns: Array<Column>; columns: Array<Column>;
itemRender?: (item: any, key:number, length:number, props:any) => JSX.Element; itemRender?: (item: any, key: number, length: number, props: any) => JSX.Element;
} }
function fromBsClass(cn:string) { function fromBsClass(cn: string) {
if (typeof cn === "string" && cn) { if (typeof cn === 'string' && cn) {
return cn.replace(/\bcol-(xs|sm|md|lg)-(\d+)\b/g, (_, bp, size) => `Grid-col--${bp}${size}`); return cn.replace(/\bcol-(xs|sm|md|lg)-(\d+)\b/g, (_, bp, size) => `Grid-col--${bp}${size}`);
} }
return cn; return cn;
} }
function copProps2Class(props:any):string { function copProps2Class(props: any): string {
const cns:Array<string> = []; const cns: Array<string> = [];
const modifiers = ColProps; const modifiers = ColProps;
modifiers.forEach(modifier => props && props[modifier] && cns.push(`Grid-col--${modifier}${props[modifier]}`)); modifiers.forEach(modifier => props && props[modifier] && cns.push(`Grid-col--${modifier}${props[modifier]}`));
@ -65,16 +60,13 @@ function copProps2Class(props:any):string {
} }
export default class Grid<T> extends React.Component<GridProps & T, object> { export default class Grid<T> extends React.Component<GridProps & T, object> {
static propsList: Array<string> = ["columns"]; static propsList: Array<string> = ['columns'];
static defaultProps = {}; static defaultProps = {};
renderChild(region:string, node:Schema, key: number, length: number) { renderChild(region: string, node: Schema, key: number, length: number) {
const { const {render, itemRender} = this.props;
render,
itemRender
} = this.props;
return itemRender ? itemRender(node, key, length, this.props) : render(region, node); return itemRender ? itemRender(node, key, length, this.props) : render(region, node);
} }
renderColumn(column: ColumnNode, key: number, length: number) { renderColumn(column: ColumnNode, key: number, length: number) {
@ -83,34 +75,26 @@ export default class Grid<T> extends React.Component<GridProps & T, object> {
} = pick(column, ColProps); } = pick(column, ColProps);
colProps = { colProps = {
...colProps ...colProps,
}; };
const cx = this.props.classnames; const cx = this.props.classnames;
return ( return (
<div <div key={key} className={cx(copProps2Class(colProps), fromBsClass((column as Column).columnClassName))}>
key={key} {Array.isArray(column)
className={cx(copProps2Class(colProps), fromBsClass((column as Column).columnClassName))} ? this.renderColumns(column)
> : this.renderChild(`column/${key}`, column, key, length)}
{Array.isArray(column)
? this.renderColumns(column)
: this.renderChild(`column/${key}`, column, key, length)}
</div> </div>
); );
} }
renderColumns(columns: ColumnArray): React.ReactElement<any> | null { renderColumns(columns: ColumnArray): React.ReactElement<any> | null {
const { const {className, classnames: cx} = this.props;
className,
classnames: cx
} = this.props;
return ( return (
<div className={cx('Grid', className)}> <div className={cx('Grid', className)}>
{columns.map((column, key) => {columns.map((column, key) => this.renderColumn(column, key, columns.length))}
this.renderColumn(column, key, columns.length)
)}
</div> </div>
); );
} }
@ -122,6 +106,6 @@ export default class Grid<T> extends React.Component<GridProps & T, object> {
@Renderer({ @Renderer({
test: /(^|\/)grid$/, test: /(^|\/)grid$/,
name: 'grid' name: 'grid',
}) })
export class GridRenderer extends Grid<{}> {} export class GridRenderer extends Grid<{}> {}

View File

@ -1,9 +1,8 @@
import * as React from "react"; import * as React from 'react';
import { Renderer, RendererProps } from "../factory"; import {Renderer, RendererProps} from '../factory';
import { Api, SchemaNode, Schema, Action } from "../types"; import {Api, SchemaNode, Schema, Action} from '../types';
import * as cx from "classnames"; import * as cx from 'classnames';
import { isVisible } from "../utils/helper"; import {isVisible} from '../utils/helper';
export type Column = Schema & { export type Column = Schema & {
columnClassName?: string; columnClassName?: string;
@ -12,28 +11,22 @@ export type Column = Schema & {
export interface HBoxProps extends RendererProps { export interface HBoxProps extends RendererProps {
columns: Array<Column>; columns: Array<Column>;
className: string; className: string;
itemRender?: (item: any, key:number, length:number, props:any) => JSX.Element; itemRender?: (item: any, key: number, length: number, props: any) => JSX.Element;
} }
export default class HBox extends React.Component<HBoxProps, object> { export default class HBox extends React.Component<HBoxProps, object> {
static propsList: Array<string> = ["columns"]; static propsList: Array<string> = ['columns'];
static defaultProps: Partial<HBoxProps> = {}; static defaultProps: Partial<HBoxProps> = {};
renderChild(region:string, node:Schema) { renderChild(region: string, node: Schema) {
const { const {render} = this.props;
render
} = this.props;
return render(region, node); return render(region, node);
} }
renderColumn(column: Column, key: number, length: number) { renderColumn(column: Column, key: number, length: number) {
const { const {itemRender, data, classPrefix: ns} = this.props;
itemRender,
data,
classPrefix: ns
} = this.props;
if (!isVisible(column, data)) { if (!isVisible(column, data)) {
return null; return null;
@ -42,32 +35,22 @@ export default class HBox extends React.Component<HBoxProps, object> {
let style = { let style = {
width: column.width, width: column.width,
height: column.height, height: column.height,
...column.style ...column.style,
}; };
return ( return (
<div <div key={key} className={cx(`${ns}Hbox-col`, (column as Column).columnClassName)} style={style}>
key={key}
className={cx(`${ns}Hbox-col`, (column as Column).columnClassName)}
style={style}
>
{itemRender ? itemRender(column, key, length, this.props) : this.renderChild(`column/${key}`, column)} {itemRender ? itemRender(column, key, length, this.props) : this.renderChild(`column/${key}`, column)}
</div> </div>
); );
} }
render() { render() {
const { const {className, columns, classPrefix: ns} = this.props;
className,
columns,
classPrefix: ns
} = this.props;
return ( return (
<div className={cx(`${ns}Hbox`, className)}> <div className={cx(`${ns}Hbox`, className)}>
{columns.map((column, key) => {columns.map((column, key) => this.renderColumn(column, key, columns.length))}
this.renderColumn(column, key, columns.length)
)}
</div> </div>
); );
} }
@ -75,6 +58,6 @@ export default class HBox extends React.Component<HBoxProps, object> {
@Renderer({ @Renderer({
test: /(^|\/)hbox$/, test: /(^|\/)hbox$/,
name: 'hbox' name: 'hbox',
}) })
export class HBoxRenderer extends HBox {} export class HBoxRenderer extends HBox {}

View File

@ -1,11 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {filter} from '../utils/tpl';
RendererProps
} from '../factory';
import {
filter
} from '../utils/tpl';
export interface IFrameProps extends RendererProps { export interface IFrameProps extends RendererProps {
className?: string; className?: string;
@ -13,30 +8,19 @@ export interface IFrameProps extends RendererProps {
} }
export default class IFrame extends React.Component<IFrameProps, object> { export default class IFrame extends React.Component<IFrameProps, object> {
static propsList: Array<string> = [ static propsList: Array<string> = ['src', 'className'];
"src", static defaultProps: Partial<IFrameProps> = {
"className"
];
static defaultProps:Partial<IFrameProps>= {
className: '', className: '',
width: '100%', width: '100%',
height: '100%', height: '100%',
frameBorder: 0 frameBorder: 0,
}; };
render() { render() {
let { let {className, src, width, height, frameBorder, data, style} = this.props;
className,
src,
width,
height,
frameBorder,
data,
style
} = this.props;
style = { style = {
...style ...style,
}; };
width !== void 0 && (style.width = width); width !== void 0 && (style.width = width);
@ -55,6 +39,6 @@ export default class IFrame extends React.Component<IFrameProps, object> {
@Renderer({ @Renderer({
test: /(^|\/)iframe$/, test: /(^|\/)iframe$/,
name: 'iframe' name: 'iframe',
}) })
export class IFrameRenderer extends IFrame {}; export class IFrameRenderer extends IFrame {}

View File

@ -1,33 +1,32 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
export interface IconProps extends RendererProps { export interface IconProps extends RendererProps {
icon: string; icon: string;
} }
export class Icon extends React.Component<IconProps, object> { export class Icon extends React.Component<IconProps, object> {
static defaultProps:Partial<IconProps> = { static defaultProps: Partial<IconProps> = {
icon: '', icon: '',
vendor: 'fa' vendor: 'fa',
}; };
render() { render() {
const { const {icon, vendor, classnames: cx, className} = this.props;
icon,
vendor,
classnames: cx,
className
} = this.props;
return (<i className={cx(vendor === 'iconfont' ? `iconfont icon-${icon}` : `${vendor} ${vendor}-${icon}`, className)} />); return (
<i
className={cx(
vendor === 'iconfont' ? `iconfont icon-${icon}` : `${vendor} ${vendor}-${icon}`,
className
)}
/>
);
} }
} }
@Renderer({ @Renderer({
test: /(^|\/)icon$/, test: /(^|\/)icon$/,
name: 'icon' name: 'icon',
}) })
export class TplRenderer extends Icon {}; export class TplRenderer extends Icon {}

View File

@ -1,16 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { import {Api, SchemaNode} from '../types';
Api, import {filter} from '../utils/tpl';
SchemaNode
} from '../types';
import {
filter
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
import * as moment from 'moment'; import * as moment from 'moment';
@ -22,10 +14,10 @@ export interface ImageProps extends RendererProps {
} }
export class ImageField extends React.Component<ImageProps, object> { export class ImageField extends React.Component<ImageProps, object> {
static defaultProps:Partial<ImageProps> = { static defaultProps: Partial<ImageProps> = {
className: 'thumb-lg', className: 'thumb-lg',
imageClassName: 'r', imageClassName: 'r',
defaultImage: 'https://fex.bdstatic.com/n/static/amis/renderers/crud/field/placeholder_cfad9b1.png' defaultImage: 'https://fex.bdstatic.com/n/static/amis/renderers/crud/field/placeholder_cfad9b1.png',
}; };
render() { render() {
@ -38,7 +30,7 @@ export class ImageField extends React.Component<ImageProps, object> {
data, data,
imageClassName, imageClassName,
classnames: cx, classnames: cx,
src src,
} = this.props; } = this.props;
const finnalSrc = src ? filter(src) : ''; const finnalSrc = src ? filter(src) : '';
@ -48,8 +40,8 @@ export class ImageField extends React.Component<ImageProps, object> {
<div className={cx('ImageField', className)}> <div className={cx('ImageField', className)}>
<img className={imageClassName} src={finnalSrc || value || defaultImage} /> <img className={imageClassName} src={finnalSrc || value || defaultImage} />
{title || description ? ( {title || description ? (
<div key="caption" className={cx("ImageField-caption")}> <div key="caption" className={cx('ImageField-caption')}>
{title ? (<div className="text-md">{filter(title, data)}</div>) : null} {title ? <div className="text-md">{filter(title, data)}</div> : null}
{render('description', description as string)} {render('description', description as string)}
</div> </div>
) : null} ) : null}
@ -60,21 +52,21 @@ export class ImageField extends React.Component<ImageProps, object> {
@Renderer({ @Renderer({
test: /(^|\/)image$/, test: /(^|\/)image$/,
name: 'image' name: 'image',
}) })
export class ImageFieldRenderer extends ImageField {}; export class ImageFieldRenderer extends ImageField {}
@Renderer({ @Renderer({
test: /(^|\/)images$/ test: /(^|\/)images$/,
}) })
export class ImagesFieldRenderer extends ImageField { export class ImagesFieldRenderer extends ImageField {
static defaultProps:Partial<ImageProps> = { static defaultProps: Partial<ImageProps> = {
...ImageField.defaultProps, ...ImageField.defaultProps,
multiple: true, multiple: true,
delimiter: ',' delimiter: ',',
}; };
render() { render() {
return <p>Todo</p> return <p>Todo</p>;
} }
}; }

View File

@ -1,16 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { import {Api, SchemaNode} from '../types';
Api, import {filter} from '../utils/tpl';
SchemaNode
} from '../types';
import {
filter
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
import JSONTree from 'react-json-tree'; import JSONTree from 'react-json-tree';
@ -53,36 +45,34 @@ const twilight = {
WebkitUserSelect: 'none', WebkitUserSelect: 'none',
backgroundColor: 'rgba(255, 255, 255, 0.4)', backgroundColor: 'rgba(255, 255, 255, 0.4)',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
display: 'inline-block' display: 'inline-block',
} },
} };
export class JSONField extends React.Component<JSONProps, object> { export class JSONField extends React.Component<JSONProps, object> {
static defaultProps:Partial<JSONProps> = { static defaultProps: Partial<JSONProps> = {
placeholder: '-', placeholder: '-',
levelExpand: 1 levelExpand: 1,
}; };
valueRenderer(raw:any) { valueRenderer(raw: any) {
if (typeof raw === 'string' && /^\"?https?:\/\//.test(raw)) { if (typeof raw === 'string' && /^\"?https?:\/\//.test(raw)) {
return (<a href={raw.replace(/^\"(.*)\"$/, '$1')} target="_blank">{raw}</a>); return (
<a href={raw.replace(/^\"(.*)\"$/, '$1')} target="_blank">
{raw}
</a>
);
} }
return raw; return raw;
} }
shouldExpandNode = (keyName:any, data:any, level:any) => { shouldExpandNode = (keyName: any, data: any, level: any) => {
const { const {levelExpand} = this.props;
levelExpand
} = this.props;
return level < levelExpand; return level < levelExpand;
}; };
render() { render() {
const { const {className, value, classnames: cx} = this.props;
className,
value,
classnames: cx
} = this.props;
let data = value; let data = value;
@ -91,7 +81,7 @@ export class JSONField extends React.Component<JSONProps, object> {
data = JSON.parse(value); data = JSON.parse(value);
} catch (e) { } catch (e) {
data = { data = {
error: e.message error: e.message,
}; };
} }
} }
@ -111,6 +101,6 @@ export class JSONField extends React.Component<JSONProps, object> {
@Renderer({ @Renderer({
test: /(^|\/)json$/, test: /(^|\/)json$/,
name: 'json' name: 'json',
}) })
export class JSONFieldRenderer extends JSONField {} export class JSONFieldRenderer extends JSONField {}

View File

@ -1,11 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {filter} from '../utils/tpl';
RendererProps
} from '../factory';
import {
filter
} from '../utils/tpl';
export interface LinkProps extends RendererProps { export interface LinkProps extends RendererProps {
className?: string; className?: string;
@ -17,26 +12,18 @@ export interface LinkProps extends RendererProps {
export class LinkField extends React.Component<LinkProps, object> { export class LinkField extends React.Component<LinkProps, object> {
static defaultProps = { static defaultProps = {
className: '', className: '',
blank: false blank: false,
}; };
render() { render() {
const { const {className, body, href, classnames: cx, blank, data, render} = this.props;
className,
body,
href,
classnames: cx,
blank,
data,
render
} = this.props;
let value = this.props.value; let value = this.props.value;
const finnalHref = href ? filter(href, data) : ''; const finnalHref = href ? filter(href, data) : '';
return ( return (
<a href={finnalHref || value} target={blank ? '_blank' : '_self'} className={cx('Link', className)}> <a href={finnalHref || value} target={blank ? '_blank' : '_self'} className={cx('Link', className)}>
{body ? render('body', body) : (finnalHref || value || '链接')} {body ? render('body', body) : finnalHref || value || '链接'}
</a> </a>
); );
} }
@ -44,6 +31,6 @@ export class LinkField extends React.Component<LinkProps, object> {
@Renderer({ @Renderer({
test: /(^|\/)link$/, test: /(^|\/)link$/,
name: 'link' name: 'link',
}) })
export class LinkFieldRenderer extends LinkField {}; export class LinkFieldRenderer extends LinkField {}

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { import {Api, SchemaNode, PlainObject} from '../types';
Api, import {filter} from '../utils/tpl';
SchemaNode,
PlainObject
} from '../types';
import {
filter
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
export interface MappingProps extends RendererProps { export interface MappingProps extends RendererProps {
@ -21,39 +12,28 @@ export interface MappingProps extends RendererProps {
} }
export class MappingField extends React.Component<MappingProps, object> { export class MappingField extends React.Component<MappingProps, object> {
static defaultProps:Partial<MappingProps> = { static defaultProps: Partial<MappingProps> = {
placeholder: '-', placeholder: '-',
map: { map: {
'*': '通配值' '*': '通配值',
} },
}; };
render() { render() {
const { const {className, value, placeholder, map, render, classnames: cx} = this.props;
className,
value,
placeholder,
map,
render,
classnames: cx
} = this.props;
let viewValue:React.ReactNode = <span className="text-muted">{placeholder}</span>; let viewValue: React.ReactNode = <span className="text-muted">{placeholder}</span>;
if (typeof value !== "undefined" && map && (map[value] || map['*'])) { if (typeof value !== 'undefined' && map && (map[value] || map['*'])) {
viewValue = render('tpl', map[value] || map['*']); viewValue = render('tpl', map[value] || map['*']);
} }
return ( return <span className={cx('MappingField', className)}>{viewValue}</span>;
<span className={cx('MappingField', className)}>
{viewValue}
</span>
);
} }
} }
@Renderer({ @Renderer({
test: /(^|\/)(?:map|mapping)$/, test: /(^|\/)(?:map|mapping)$/,
name: 'mapping' name: 'mapping',
}) })
export class MappingFieldRenderer extends MappingField {}; export class MappingFieldRenderer extends MappingField {}

View File

@ -1,17 +1,14 @@
import * as React from 'react'; import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {ServiceStore, IServiceStore} from '../store/service';
RendererProps
} from '../factory';
import { ServiceStore, IServiceStore } from '../store/service';
import * as cx from 'classnames'; import * as cx from 'classnames';
import getExprProperties from '../utils/filter-schema'; import getExprProperties from '../utils/filter-schema';
import { filter, evalExpression } from '../utils/tpl'; import {filter, evalExpression} from '../utils/tpl';
import { createObject, mapTree, someTree } from '../utils/helper'; import {createObject, mapTree, someTree} from '../utils/helper';
import { resolveVariable } from '../utils/tpl-builtin'; import {resolveVariable} from '../utils/tpl-builtin';
import { isApiOutdated } from '../utils/api'; import {isApiOutdated} from '../utils/api';
import { ScopedContext, IScopedContext } from '../Scoped'; import {ScopedContext, IScopedContext} from '../Scoped';
export interface Link { export interface Link {
className?: string; className?: string;
@ -21,14 +18,14 @@ export interface Link {
active?: boolean; active?: boolean;
unfolded?: boolean; unfolded?: boolean;
children?: Links; children?: Links;
[propName:string]: any; [propName: string]: any;
}; }
export interface Links extends Array<Link> {}; export interface Links extends Array<Link> {}
export interface NavigationState { export interface NavigationState {
links: Links; links: Links;
error?: string; error?: string;
}; }
export interface NavigationProps extends RendererProps { export interface NavigationProps extends RendererProps {
className?: string; className?: string;
@ -39,37 +36,41 @@ export interface NavigationProps extends RendererProps {
export default class Navigation extends React.Component<NavigationProps, NavigationState> { export default class Navigation extends React.Component<NavigationProps, NavigationState> {
static defaultProps: Partial<NavigationProps> = { static defaultProps: Partial<NavigationProps> = {
togglerClassName: 'fa fa-angle-down' togglerClassName: 'fa fa-angle-down',
}; };
mounted:boolean = true; mounted: boolean = true;
constructor(props:NavigationProps) { constructor(props: NavigationProps) {
super(props); super(props);
this.renderItem = this.renderItem.bind(this); this.renderItem = this.renderItem.bind(this);
this.state = { this.state = {
links: this.syncLinks(props, props.source && /^\$(?:([a-z0-9_.]+)|{.+})$/.test(props.source) && resolveVariable(props.source, props.data) || props.links) links: this.syncLinks(
props,
(props.source &&
/^\$(?:([a-z0-9_.]+)|{.+})$/.test(props.source) &&
resolveVariable(props.source, props.data)) ||
props.links
),
}; };
} }
componentDidMount() { componentDidMount() {
const { const {source} = this.props;
source
} = this.props;
if (source && !/^\$(?:([a-z0-9_.]+)|{.+})$/.test(source)) { if (source && !/^\$(?:([a-z0-9_.]+)|{.+})$/.test(source)) {
this.reload(); this.reload();
} }
} }
componentWillReceiveProps(nextProps:NavigationProps) { componentWillReceiveProps(nextProps: NavigationProps) {
const props = this.props; const props = this.props;
if (nextProps.source && /^\$(?:([a-z0-9_.]+)|{.+})$/.test(nextProps.source)) { if (nextProps.source && /^\$(?:([a-z0-9_.]+)|{.+})$/.test(nextProps.source)) {
if (nextProps.source !== props.source) { if (nextProps.source !== props.source) {
this.setState({ this.setState({
links: this.syncLinks(nextProps) links: this.syncLinks(nextProps),
}); });
} else { } else {
const links = resolveVariable(nextProps.source, nextProps.data); const links = resolveVariable(nextProps.source, nextProps.data);
@ -77,22 +78,22 @@ export default class Navigation extends React.Component<NavigationProps, Navigat
if (links !== prevLinks) { if (links !== prevLinks) {
this.setState({ this.setState({
links: this.syncLinks(nextProps, links) links: this.syncLinks(nextProps, links),
}); });
} }
} }
} else if (props.links !== nextProps.links) { } else if (props.links !== nextProps.links) {
this.setState({ this.setState({
links: this.syncLinks(nextProps) links: this.syncLinks(nextProps),
}); });
} else if (nextProps.location && props.location !== nextProps.location) { } else if (nextProps.location && props.location !== nextProps.location) {
this.setState({ this.setState({
links: this.syncLinks(nextProps, this.state.links, true) links: this.syncLinks(nextProps, this.state.links, true),
}); });
} }
} }
componentDidUpdate(prevProps:NavigationProps) { componentDidUpdate(prevProps: NavigationProps) {
const props = this.props; const props = this.props;
if (props.source && !/^\$(?:([a-z0-9_.]+)|{.+})$/.test(props.source)) { if (props.source && !/^\$(?:([a-z0-9_.]+)|{.+})$/.test(props.source)) {
@ -104,20 +105,15 @@ export default class Navigation extends React.Component<NavigationProps, Navigat
this.mounted = false; this.mounted = false;
} }
reload(target?:string, query?:any, values?:object) { reload(target?: string, query?: any, values?: object) {
if (query) { if (query) {
return this.receive(query); return this.receive(query);
} }
const { const {data, env, source} = this.props;
data,
env,
source
} = this.props;
const finalData = values ? createObject(data, values) : data; const finalData = values ? createObject(data, values) : data;
if (!source || source.sendOn && !evalExpression(source.sendOn, data)) { if (!source || (source.sendOn && !evalExpression(source.sendOn, data))) {
return; return;
} }
@ -126,118 +122,126 @@ export default class Navigation extends React.Component<NavigationProps, Navigat
if (!this.mounted) { if (!this.mounted) {
return; return;
} }
if (!payload.ok) { if (!payload.ok) {
this.setState({ this.setState({
error: payload.msg || '获取链接错误' error: payload.msg || '获取链接错误',
}); });
} else { } else {
const links = Array.isArray(payload.data) ? payload.data : (payload.data.links || payload.data.options || payload.data.items || payload.data.rows); const links = Array.isArray(payload.data)
? payload.data
: payload.data.links || payload.data.options || payload.data.items || payload.data.rows;
if (!Array.isArray(links)) { if (!Array.isArray(links)) {
throw new Error('payload.data.options is not array.') throw new Error('payload.data.options is not array.');
} }
this.setState({ this.setState(
links: this.syncLinks(this.props, links, true) {
}, () => { links: this.syncLinks(this.props, links, true),
if (payload.data && payload.data.value && !someTree(this.state.links, (item:any) => item.active)) { },
env.jumpTo(filter(payload.data.value as string, data)) () => {
if (
payload.data &&
payload.data.value &&
!someTree(this.state.links, (item: any) => item.active)
) {
env.jumpTo(filter(payload.data.value as string, data));
}
} }
}); );
} }
}) })
.catch(e => this.mounted && this.setState({ .catch(
error: e.message e =>
})); this.mounted &&
this.setState({
error: e.message,
})
);
} }
receive(values: object) { receive(values: object) {
const { const {store, initApi} = this.props;
store,
initApi
} = this.props;
this.reload(undefined, undefined, values); this.reload(undefined, undefined, values);
} }
syncLinks(props:NavigationProps, links = props.links, clearActive?: boolean):Links { syncLinks(props: NavigationProps, links = props.links, clearActive?: boolean): Links {
const { const {data, env} = props;
data,
env
} = props;
if (!Array.isArray(links) || !links.length) { if (!Array.isArray(links) || !links.length) {
return []; return [];
} }
return mapTree(links, (link:Link) => { return mapTree(
return { links,
...link, (link: Link) => {
...getExprProperties(link, data as object), return {
active: !clearActive && link.active || !!(link.hasOwnProperty('to') && env && env.isCurrentUrl(filter(link.to as string, data))), ...link,
unfolded: link.unfolded || link.children && link.children.some(link => !!link.active) ...getExprProperties(link, data as object),
} active:
}, 1, true); (!clearActive && link.active) ||
!!(link.hasOwnProperty('to') && env && env.isCurrentUrl(filter(link.to as string, data))),
unfolded: link.unfolded || (link.children && link.children.some(link => !!link.active)),
};
},
1,
true
);
} }
handleClick(link:{ handleClick(link: {label?: string; to?: string; icon?: string; children?: Links}) {
label?: string;
to?: string;
icon?: string;
children?: Links;
}) {
if (!link.to) { if (!link.to) {
link.children && link.children.length && this.toggleLink(link); link.children && link.children.length && this.toggleLink(link);
return; return;
} }
const { const {env, data} = this.props;
env,
data
} = this.props;
env && env.jumpTo(filter(link.to as string, data), link as any); env && env.jumpTo(filter(link.to as string, data), link as any);
} }
toggleLink(target:Link) { toggleLink(target: Link) {
this.setState({ this.setState({
links: mapTree(this.state.links, (link:Link) => target === link ? { links: mapTree(this.state.links, (link: Link) =>
...link, target === link
unfolded: !link.unfolded ? {
} : link) ...link,
unfolded: !link.unfolded,
}
: link
),
}); });
} }
renderItem(link:Link, index:number) { renderItem(link: Link, index: number) {
if (link.hidden === true || link.visible === false) { if (link.hidden === true || link.visible === false) {
return null; return null;
} }
const isActive:boolean = !!link.active; const isActive: boolean = !!link.active;
const { const {disabled, togglerClassName, classnames: cx} = this.props;
disabled,
togglerClassName,
classnames: cx
} = this.props;
return ( return (
<li <li
key={index} key={index}
className={cx('Nav-item', link.className, { className={cx('Nav-item', link.className, {
'is-disabled': disabled || link.disabled, 'is-disabled': disabled || link.disabled,
'is-active': isActive, 'is-active': isActive,
'is-unfolded': link.unfolded 'is-unfolded': link.unfolded,
})} })}
> >
<a onClick={this.handleClick.bind(this, link)}> <a onClick={this.handleClick.bind(this, link)}>
{link.icon ? (<i className={cx('Nav-itemIcon', link.icon)} />) : null} {link.icon ? <i className={cx('Nav-itemIcon', link.icon)} /> : null}
{link.label} {link.label}
</a> </a>
{link.children && link.children.length ? (<i onClick={() => this.toggleLink(link)} className={cx('Nav-itemToggler', togglerClassName)} />) : null} {link.children && link.children.length ? (
<i onClick={() => this.toggleLink(link)} className={cx('Nav-itemToggler', togglerClassName)} />
) : null}
{link.children && link.children.length ? ( {link.children && link.children.length ? (
<ul className={cx("Nav-subItems")}> <ul className={cx('Nav-subItems')}>
{link.children.map((link, index) => this.renderItem(link, index))} {link.children.map((link, index) => this.renderItem(link, index))}
</ul> </ul>
) : null} ) : null}
@ -245,19 +249,13 @@ export default class Navigation extends React.Component<NavigationProps, Navigat
); );
} }
render():JSX.Element { render(): JSX.Element {
const { const {className, stacked, classnames: cx} = this.props;
className,
stacked,
classnames: cx
} = this.props;
const links = this.state.links; const links = this.state.links;
return ( return (
<ul <ul className={cx('Nav', className, stacked ? 'Nav--stacked' : 'Nav--tabs')}>
className={cx('Nav', className, stacked ? 'Nav--stacked' : 'Nav--tabs')}
>
{links.map(this.renderItem)} {links.map(this.renderItem)}
</ul> </ul>
); );
@ -266,7 +264,7 @@ export default class Navigation extends React.Component<NavigationProps, Navigat
@Renderer({ @Renderer({
test: /(^|\/)(?:nav|navigation)$/, test: /(^|\/)(?:nav|navigation)$/,
name: 'nav' name: 'nav',
}) })
export class NavigationRenderer extends Navigation { export class NavigationRenderer extends Navigation {
static contextType = ScopedContext; static contextType = ScopedContext;
@ -281,4 +279,4 @@ export class NavigationRenderer extends Navigation {
scoped.unRegisterComponent(this); scoped.unRegisterComponent(this);
super.componentWillUnmount(); super.componentWillUnmount();
} }
} }

View File

@ -1,17 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { import {Api, SchemaNode, Action} from '../types';
Api, import {filter} from '../utils/tpl';
SchemaNode,
Action
} from '../types';
import {
filter
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
import * as moment from 'moment'; import * as moment from 'moment';
@ -21,43 +12,39 @@ export interface OperationProps extends RendererProps {
} }
export class OperationField extends React.Component<OperationProps, object> { export class OperationField extends React.Component<OperationProps, object> {
static propsList: Array<string> = [ static propsList: Array<string> = ['buttons', 'label'];
"buttons",
"label",
];
static defaultProps:Partial<OperationProps> = { static defaultProps: Partial<OperationProps> = {};
};
render() { render() {
const { const {className, buttons, render, classnames: cx} = this.props;
className,
buttons,
render,
classnames: cx
} = this.props;
return ( return (
<div className={cx('OperationField', className)}> <div className={cx('OperationField', className)}>
{Array.isArray(buttons) ? buttons.map((button, index) => render(`${index}`, { {Array.isArray(buttons)
type: 'button', ? buttons.map((button, index) =>
size: button.size || 'sm', render(
level: button.level || (button.icon && !button.label ? 'link' : ''), `${index}`,
...button {
}, { type: 'button',
key: index size: button.size || 'sm',
})) : null} level: button.level || (button.icon && !button.label ? 'link' : ''),
...button,
},
{
key: index,
}
)
)
: null}
</div> </div>
); );
} }
} }
@Renderer({ @Renderer({
// test: /(^|\/)table\/(.*\/)operation$/, // test: /(^|\/)table\/(.*\/)operation$/,
test: (path: string) => /(^|\/)table\/(.*\/)operation$/.test(path), test: (path: string) => /(^|\/)table\/(.*\/)operation$/.test(path),
name: 'operation' name: 'operation',
}) })
export class OperationFieldRenderer extends OperationField {}; export class OperationFieldRenderer extends OperationField {}

View File

@ -1,28 +1,16 @@
import * as React from 'react'; import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {observer} from 'mobx-react';
RendererProps import {ServiceStore, IServiceStore} from '../store/service';
} from '../factory'; import {Api, SchemaNode, Action, Location, ApiObject, FunctionPropertyNames} from '../types';
import { observer } from "mobx-react"; import {filter, evalExpression} from '../utils/tpl';
import { ServiceStore, IServiceStore } from '../store/service';
import {
Api,
SchemaNode,
Action,
Location,
ApiObject,
FunctionPropertyNames
} from '../types';
import {
filter, evalExpression
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
import * as qs from 'qs'; import * as qs from 'qs';
import { isVisible, autobind, bulkBindFunctions } from '../utils/helper'; import {isVisible, autobind, bulkBindFunctions} from '../utils/helper';
import { ScopedContext, IScopedContext } from '../Scoped'; import {ScopedContext, IScopedContext} from '../Scoped';
import Alert from '../components/Alert2'; import Alert from '../components/Alert2';
import { isApiOutdated } from '../utils/api'; import {isApiOutdated} from '../utils/api';
export interface PageProps extends RendererProps { export interface PageProps extends RendererProps {
title?: string; // 标题 title?: string; // 标题
@ -57,7 +45,7 @@ export interface PageProps extends RendererProps {
export default class Page extends React.Component<PageProps> { export default class Page extends React.Component<PageProps> {
timer: NodeJS.Timer; timer: NodeJS.Timer;
mounted: boolean; mounted: boolean;
static defaultProps = { static defaultProps = {
asideClassName: '', asideClassName: '',
bodyClassName: '', bodyClassName: '',
@ -65,8 +53,7 @@ export default class Page extends React.Component<PageProps> {
initFetch: true, initFetch: true,
// primaryField: 'id', // primaryField: 'id',
toolbarClassName: '', toolbarClassName: '',
messages: { messages: {},
},
}; };
static propsList: Array<string> = [ static propsList: Array<string> = [
@ -83,27 +70,23 @@ export default class Page extends React.Component<PageProps> {
'body', 'body',
'aside', 'aside',
'messages', 'messages',
'style' 'style',
]; ];
componentWillMount() { componentWillMount() {
const { const {store, location} = this.props;
store,
location
} = this.props;
// autobind 会让继承里面的 super 指向有问题,所以先这样! // autobind 会让继承里面的 super 指向有问题,所以先这样!
bulkBindFunctions<Page/*为毛 this 的类型自动识别不出来*/>(this, [ bulkBindFunctions<Page /*为毛 this 的类型自动识别不出来*/>(this, [
"handleAction", 'handleAction',
"handleDialogConfirm", 'handleDialogConfirm',
"handleDialogClose", 'handleDialogClose',
"handleDrawerConfirm", 'handleDrawerConfirm',
"handleDrawerClose", 'handleDrawerClose',
"handleClick", 'handleClick',
"reload", 'reload',
"silentReload", 'silentReload',
"initInterval" 'initInterval',
]); ]);
if (location && location.search) { if (location && location.search) {
@ -124,20 +107,21 @@ export default class Page extends React.Component<PageProps> {
} }
componentDidMount() { componentDidMount() {
const { const {initApi, initFetch, store, messages} = this.props;
initApi,
initFetch,
store,
messages
} = this.props;
this.mounted = true; this.mounted = true;
if (initApi && initFetch && (!(initApi as ApiObject).sendOn || evalExpression((initApi as ApiObject).sendOn as string, store.data))) { if (
store.fetchInitData(initApi, store.data, { initApi &&
successMessage: messages && messages.fetchSuccess, initFetch &&
errorMessage: messages && messages.fetchFailed (!(initApi as ApiObject).sendOn || evalExpression((initApi as ApiObject).sendOn as string, store.data))
}).then(this.initInterval); ) {
store
.fetchInitData(initApi, store.data, {
successMessage: messages && messages.fetchSuccess,
errorMessage: messages && messages.fetchFailed,
})
.then(this.initInterval);
} }
} }
@ -145,11 +129,11 @@ export default class Page extends React.Component<PageProps> {
const props = this.props; const props = this.props;
const store = props.store; const store = props.store;
if ( if (nextProps.location && (!props.location || props.location.search !== nextProps.location.search)) {
nextProps.location const query =
&& (!props.location || props.location.search !== nextProps.location.search) nextProps.location.query ||
) { (nextProps.location.search && qs.parse(nextProps.location.search.substring(1))) ||
const query = nextProps.location.query || nextProps.location.search && qs.parse(nextProps.location.search.substring(1)) || {}; {};
store.updateData({ store.updateData({
...query, ...query,
query: query, query: query,
@ -164,17 +148,18 @@ export default class Page extends React.Component<PageProps> {
if ( if (
// 前一次不构成条件,这次更新构成了条件,则需要重新拉取 // 前一次不构成条件,这次更新构成了条件,则需要重新拉取
props.initFetchOn && props.initFetch && !prevProps.initFetch (props.initFetchOn && props.initFetch && !prevProps.initFetch) ||
// 构成了条件,同时 url 里面有变量,且上次和这次还不一样,则需要重新拉取。 // 构成了条件,同时 url 里面有变量,且上次和这次还不一样,则需要重新拉取。
|| props.initFetch !== false && isApiOutdated(prevProps.initApi, initApi, prevProps.data, props.data) (props.initFetch !== false && isApiOutdated(prevProps.initApi, initApi, prevProps.data, props.data))
) { ) {
const messages = props.messages; const messages = props.messages;
(!(initApi as ApiObject).sendOn || evalExpression((initApi as ApiObject).sendOn as string, store.data)) (!(initApi as ApiObject).sendOn || evalExpression((initApi as ApiObject).sendOn as string, store.data)) &&
&& store.fetchData(initApi as Api, store.data, { store
successMessage: messages && messages.fetchSuccess, .fetchData(initApi as Api, store.data, {
errorMessage: messages && messages.fetchFailed successMessage: messages && messages.fetchSuccess,
}).then(this.initInterval); errorMessage: messages && messages.fetchFailed,
})
.then(this.initInterval);
} }
} }
@ -183,16 +168,12 @@ export default class Page extends React.Component<PageProps> {
clearTimeout(this.timer); clearTimeout(this.timer);
} }
reloadTarget(target:string, data?:any) { reloadTarget(target: string, data?: any) {
// 会被覆写 // 会被覆写
} }
handleAction(e:React.UIEvent<any> | void, action:Action, ctx: object, delegate?: boolean) { handleAction(e: React.UIEvent<any> | void, action: Action, ctx: object, delegate?: boolean) {
const { const {env, store, messages} = this.props;
env,
store,
messages
} = this.props;
// delegate 表示不是当前层的事件,而是孩子节点的。 // delegate 表示不是当前层的事件,而是孩子节点的。
delegate || store.setCurrentAction(action); delegate || store.setCurrentAction(action);
@ -208,28 +189,27 @@ export default class Page extends React.Component<PageProps> {
} else if (action.actionType === 'drawer') { } else if (action.actionType === 'drawer') {
store.openDrawer(ctx); store.openDrawer(ctx);
} else if (action.actionType === 'ajax') { } else if (action.actionType === 'ajax') {
store.saveRemote(action.api as string, ctx, { store
successMessage: action.messages && action.messages.success || messages && messages.saveSuccess, .saveRemote(action.api as string, ctx, {
errorMessage: action.messages && action.messages.failed || messages && messages.saveSuccess successMessage: (action.messages && action.messages.success) || (messages && messages.saveSuccess),
}) errorMessage: (action.messages && action.messages.failed) || (messages && messages.saveSuccess),
.then(async () => { })
if (action.feedback && isVisible(action.feedback, store.data)) { .then(async () => {
await this.openFeedback(action.feedback, store.data); if (action.feedback && isVisible(action.feedback, store.data)) {
} await this.openFeedback(action.feedback, store.data);
}
action.redirect && env.jumpTo(filter(action.redirect, store.data), action); action.redirect && env.jumpTo(filter(action.redirect, store.data), action);
action.reload && this.reloadTarget(action.reload, store.data); action.reload && this.reloadTarget(action.reload, store.data);
}) })
.catch(() => { });; .catch(() => {});
} else if (action.actionType === 'copy' && (action.content || action.copy)) { } else if (action.actionType === 'copy' && (action.content || action.copy)) {
env.copy && env.copy(filter(action.content || action.copy, ctx)); env.copy && env.copy(filter(action.content || action.copy, ctx));
} }
} }
handleDialogConfirm(values: object[], action: Action, ...args: Array<any>) { handleDialogConfirm(values: object[], action: Action, ...args: Array<any>) {
const { const {store} = this.props;
store
} = this.props;
if (action.mergeData && values.length === 1 && values[0]) { if (action.mergeData && values.length === 1 && values[0]) {
store.updateData(values[0]); store.updateData(values[0]);
@ -243,18 +223,13 @@ export default class Page extends React.Component<PageProps> {
store.closeDialog(); store.closeDialog();
} }
handleDialogClose() { handleDialogClose() {
const { const {store} = this.props;
store
} = this.props;
store.closeDialog(); store.closeDialog();
} }
handleDrawerConfirm(values: object[], action: Action, ...args:Array<any>) { handleDrawerConfirm(values: object[], action: Action, ...args: Array<any>) {
const { const {store} = this.props;
store
} = this.props;
if (action.mergeData && values.length === 1 && values[0]) { if (action.mergeData && values.length === 1 && values[0]) {
store.updateData(values[0]); store.updateData(values[0]);
@ -269,80 +244,68 @@ export default class Page extends React.Component<PageProps> {
} }
handleDrawerClose() { handleDrawerClose() {
const { const {store} = this.props;
store
} = this.props;
store.closeDrawer(); store.closeDrawer();
} }
handleClick(e: any) { handleClick(e: any) {
const target: HTMLElement = e.target as HTMLElement; const target: HTMLElement = e.target as HTMLElement;
const { env } = this.props; const {env} = this.props;
if (env && target.tagName === 'A' && target.hasAttribute('data-link')) { if (env && target.tagName === 'A' && target.hasAttribute('data-link')) {
env.jumpTo(target.getAttribute('data-link') as string); env.jumpTo(target.getAttribute('data-link') as string);
e.preventDefault(); e.preventDefault();
} }
}; }
openFeedback(dialog:any, ctx:any) { openFeedback(dialog: any, ctx: any) {
return new Promise((resolve) => { return new Promise(resolve => {
const { const {store} = this.props;
store
} = this.props;
store.setCurrentAction({ store.setCurrentAction({
type: 'button', type: 'button',
actionType: 'dialog', actionType: 'dialog',
dialog: dialog dialog: dialog,
}); });
store.openDialog(ctx, undefined, (confirmed) => { store.openDialog(ctx, undefined, confirmed => {
resolve(confirmed) resolve(confirmed);
}); });
}) });
} }
reload(subpath?: any, query?:any, ctx?: any, silent?: boolean) { reload(subpath?: any, query?: any, ctx?: any, silent?: boolean) {
if (query) { if (query) {
return this.receive(query); return this.receive(query);
} }
const { const {store, initApi} = this.props;
store,
initApi
} = this.props;
clearTimeout(this.timer); clearTimeout(this.timer);
initApi && store.fetchData(initApi, store.data, { initApi &&
silent store
}).then(this.initInterval); .fetchData(initApi, store.data, {
silent,
})
.then(this.initInterval);
} }
receive(values: object) { receive(values: object) {
const { const {store} = this.props;
store
} = this.props;
store.updateData(values); store.updateData(values);
this.reload(); this.reload();
} }
silentReload(target?:string, query?:any) { silentReload(target?: string, query?: any) {
this.reload(query, undefined, undefined, true); this.reload(query, undefined, undefined, true);
} }
initInterval(value: any) { initInterval(value: any) {
const { const {interval, silentPolling, stopAutoRefreshWhen, data} = this.props;
interval,
silentPolling,
stopAutoRefreshWhen,
data
} = this.props;
interval interval &&
&& this.mounted this.mounted &&
&& (!stopAutoRefreshWhen || !evalExpression(stopAutoRefreshWhen, data)) (!stopAutoRefreshWhen || !evalExpression(stopAutoRefreshWhen, data)) &&
&& (this.timer = setTimeout(silentPolling ? this.silentReload : this.reload, Math.max(interval, 3000))); (this.timer = setTimeout(silentPolling ? this.silentReload : this.reload, Math.max(interval, 3000)));
return value; return value;
} }
@ -357,11 +320,11 @@ export default class Page extends React.Component<PageProps> {
render, render,
store, store,
env, env,
classnames: cx classnames: cx,
} = this.props; } = this.props;
const subProps = { const subProps = {
onAction: this.handleAction onAction: this.handleAction,
}; };
let header, right; let header, right;
@ -371,24 +334,24 @@ export default class Page extends React.Component<PageProps> {
{title ? ( {title ? (
<h2 className={cx('Page-title')}> <h2 className={cx('Page-title')}>
{render('title', title, subProps)} {render('title', title, subProps)}
{remark ? render('remark', { {remark
type: 'remark', ? render('remark', {
tooltip: remark, type: 'remark',
container: env && env.getModalContainer ? env.getModalContainer() : undefined tooltip: remark,
}) : null} container: env && env.getModalContainer ? env.getModalContainer() : undefined,
})
: null}
</h2> </h2>
) : null} ) : null}
{subTitle && (<small className={cx('Page-subTitle')}>{render('subTitle', subTitle, subProps)}</small>)} {subTitle && (
<small className={cx('Page-subTitle')}>{render('subTitle', subTitle, subProps)}</small>
)}
</div> </div>
); );
} }
if (toolbar) { if (toolbar) {
right = ( right = <div className={cx(`Page-toolbar`, toolbarClassName)}>{render('toolbar', toolbar, subProps)}</div>;
<div className={cx(`Page-toolbar`, toolbarClassName)}>
{render('toolbar', toolbar, subProps)}
</div>
);
} }
if (header && right) { if (header && right) {
@ -417,42 +380,42 @@ export default class Page extends React.Component<PageProps> {
} = this.props; } = this.props;
const subProps = { const subProps = {
onAction: this.handleAction onAction: this.handleAction,
}; };
const hasAside = aside && (!Array.isArray(aside) || aside.length); const hasAside = aside && (!Array.isArray(aside) || aside.length);
return ( return (
<div <div className={cx(`Page`, hasAside ? `Page--withSidebar` : '', className)} onClick={this.handleClick}>
className={cx(`Page`, hasAside ? `Page--withSidebar` : '', className)} {hasAside ? (
onClick={this.handleClick} <div className={cx(`Page-aside`, asideClassName)}>
> {render('aside', aside as any, {
{hasAside ? <div className={cx(`Page-aside`, asideClassName)}> ...subProps,
{render('aside', aside as any, { ...(typeof aside === 'string'
...subProps, ? {
...(typeof aside === 'string' ? { inline: false,
inline: false, className: `Page-asideTplWrapper`,
className: `Page-asideTplWrapper` }
} : null) : null),
})} })}
</div> : null} </div>
) : null}
<div className={cx('Page-content')}> <div className={cx('Page-content')}>
{header ? render('header', header, subProps) : null} {header ? render('header', header, subProps) : null}
<div className={cx('Page-main')}> <div className={cx('Page-main')}>
{this.renderHeader()} {this.renderHeader()}
<div className={cx(`Page-body`, bodyClassName)}> <div className={cx(`Page-body`, bodyClassName)}>
{store.loading ? render('spinner', { {store.loading
type: 'spinner', ? render('spinner', {
overlay: true, type: 'spinner',
size: 'lg' overlay: true,
}) : null} size: 'lg',
})
: null}
{store.error ? ( {store.error ? (
<Alert <Alert level="danger" showCloseButton onClose={store.clearMessage}>
level="danger"
showCloseButton
onClose={store.clearMessage}>
{store.msg} {store.msg}
</Alert> </Alert>
) : null} ) : null}
@ -462,39 +425,47 @@ export default class Page extends React.Component<PageProps> {
</div> </div>
</div> </div>
{render('dialog', { {render(
...(store.action as Action) && (store.action as Action).dialog as object, 'dialog',
type: 'dialog' {
}, { ...((store.action as Action) && ((store.action as Action).dialog as object)),
key: 'dialog', type: 'dialog',
data: store.dialogData, },
onConfirm: this.handleDialogConfirm, {
onClose: this.handleDialogClose, key: 'dialog',
show: store.dialogOpen, data: store.dialogData,
onAction: this.handleAction onConfirm: this.handleDialogConfirm,
})} onClose: this.handleDialogClose,
show: store.dialogOpen,
onAction: this.handleAction,
}
)}
{render('drawer', { {render(
...(store.action as Action) && (store.action as Action).drawer as object, 'drawer',
type: 'drawer' {
}, { ...((store.action as Action) && ((store.action as Action).drawer as object)),
key: 'drawer', type: 'drawer',
data: store.drawerData, },
onConfirm: this.handleDrawerConfirm, {
onClose: this.handleDrawerClose, key: 'drawer',
show: store.drawerOpen, data: store.drawerData,
onAction: this.handleAction onConfirm: this.handleDrawerConfirm,
})} onClose: this.handleDrawerClose,
show: store.drawerOpen,
onAction: this.handleAction,
}
)}
</div> </div>
); );
} }
}; }
@Renderer({ @Renderer({
test: /(?:^|\/)page$/, test: /(?:^|\/)page$/,
name: 'page', name: 'page',
storeType: ServiceStore.name, storeType: ServiceStore.name,
isolateScope: true isolateScope: true,
}) })
export class PageRenderer extends Page { export class PageRenderer extends Page {
static contextType = ScopedContext; static contextType = ScopedContext;
@ -512,12 +483,12 @@ export class PageRenderer extends Page {
super.componentWillUnmount(); super.componentWillUnmount();
} }
reloadTarget(target:string, data?:any) { reloadTarget(target: string, data?: any) {
const scoped = this.context as IScopedContext; const scoped = this.context as IScopedContext;
scoped.reload(target, data); scoped.reload(target, data);
} }
handleAction(e:React.UIEvent<any>, action:Action, ctx:object, throwErrors: boolean = false, delegate?: boolean) { handleAction(e: React.UIEvent<any>, action: Action, ctx: object, throwErrors: boolean = false, delegate?: boolean) {
const scoped = this.context as IScopedContext; const scoped = this.context as IScopedContext;
if (action.actionType === 'reload') { if (action.actionType === 'reload') {
@ -525,10 +496,15 @@ export class PageRenderer extends Page {
} else if (action.target) { } else if (action.target) {
action.target.split(',').forEach(name => { action.target.split(',').forEach(name => {
let target = scoped.getComponentByName(name); let target = scoped.getComponentByName(name);
target && target.doAction && target.doAction({ target &&
...action, target.doAction &&
target: undefined target.doAction(
}, ctx); {
...action,
target: undefined,
},
ctx
);
}); });
} else { } else {
super.handleAction(e, action, ctx, delegate); super.handleAction(e, action, ctx, delegate);
@ -552,7 +528,6 @@ export class PageRenderer extends Page {
.filter((item: any) => item.props.type === 'crud') .filter((item: any) => item.props.type === 'crud')
.forEach((item: any) => item.reload && item.reload()); .forEach((item: any) => item.reload && item.reload());
} }
} }
handleDrawerConfirm(values: object[], action: Action, ...rest: Array<any>) { handleDrawerConfirm(values: object[], action: Action, ...rest: Array<any>) {
@ -576,4 +551,4 @@ export class PageRenderer extends Page {
} }
}, 300); }, 300);
} }
}; }

View File

@ -1,8 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
export interface PaginationProps extends RendererProps { export interface PaginationProps extends RendererProps {
activePage?: number; activePage?: number;
@ -14,7 +11,7 @@ export interface PaginationProps extends RendererProps {
pageNum?: number; pageNum?: number;
changePageNum: (value: number) => void; changePageNum: (value: number) => void;
showPageInput: boolean; showPageInput: boolean;
}; }
export interface DefaultProps { export interface DefaultProps {
activePage: number; activePage: number;
@ -34,8 +31,8 @@ export default class Pagination extends React.PureComponent<PaginationProps, any
maxButtons: 5, maxButtons: 5,
mode: 'normal', mode: 'normal',
hasNext: false, hasNext: false,
showPageInput: true showPageInput: true,
} };
constructor(props: PaginationProps) { constructor(props: PaginationProps) {
super(props); super(props);
@ -43,43 +40,39 @@ export default class Pagination extends React.PureComponent<PaginationProps, any
} }
renderSimple() { renderSimple() {
const { const {activePage, hasNext, onPageChange, classnames: cx} = this.props as PropsWithDefault;
activePage,
hasNext,
onPageChange,
classnames: cx
} = this.props as PropsWithDefault;
return ( return (
<ul className={cx("Pagination", "Pagination--sm")}> <ul className={cx('Pagination', 'Pagination--sm')}>
<li <li
className={cx({ className={cx({
disabled: activePage < 2 disabled: activePage < 2,
})} })}
onClick={activePage < 2 ? e => e.preventDefault() : () => onPageChange(activePage - 1)} onClick={activePage < 2 ? e => e.preventDefault() : () => onPageChange(activePage - 1)}
> >
<a><i className="fa fa-chevron-left" /></a> <a>
<i className="fa fa-chevron-left" />
</a>
</li> </li>
<li <li
className={cx({ className={cx({
disabled: !hasNext disabled: !hasNext,
})} })}
onClick={!hasNext ? e => e.preventDefault() : () => onPageChange(activePage + 1)} onClick={!hasNext ? e => e.preventDefault() : () => onPageChange(activePage + 1)}
> >
<a><i className="fa fa-chevron-right" /></a> <a>
<i className="fa fa-chevron-right" />
</a>
</li> </li>
</ul> </ul>
); );
} }
handlePageChange(e: React.ChangeEvent<any>) { handlePageChange(e: React.ChangeEvent<any>) {
const { const {changePageNum, items} = this.props;
changePageNum,
items
} = this.props;
let value = e.currentTarget.value; let value = e.currentTarget.value;
if ((typeof value === 'number' || /^\d+$/.test(value)) && value > 0 || value === '') { if (((typeof value === 'number' || /^\d+$/.test(value)) && value > 0) || value === '') {
if (value !== '') { if (value !== '') {
value = parseInt(value, 10); value = parseInt(value, 10);
value = (value > (items as number) ? items : value) as number; value = (value > (items as number) ? items : value) as number;
@ -89,15 +82,8 @@ export default class Pagination extends React.PureComponent<PaginationProps, any
} }
renderNormal() { renderNormal() {
let { let {activePage, items, maxButtons, onPageChange, pageNum, classnames: cx, showPageInput} = this
activePage, .props as PropsWithDefault;
items,
maxButtons,
onPageChange,
pageNum,
classnames: cx,
showPageInput
} = this.props as PropsWithDefault;
let pageButtons: any = []; let pageButtons: any = [];
let startPage: number; let startPage: number;
@ -112,13 +98,7 @@ export default class Pagination extends React.PureComponent<PaginationProps, any
} }
if (maxButtons && maxButtons < items) { if (maxButtons && maxButtons < items) {
startPage = Math.max( startPage = Math.max(Math.min(activePage - Math.floor(maxButtons / 2), items - maxButtons + 1), 1);
Math.min(
activePage - Math.floor(maxButtons / 2),
items - maxButtons + 1
),
1
);
endPage = startPage + maxButtons - 1; endPage = startPage + maxButtons - 1;
} else { } else {
startPage = 1; startPage = 1;
@ -127,9 +107,13 @@ export default class Pagination extends React.PureComponent<PaginationProps, any
for (let page = startPage; page <= endPage; ++page) { for (let page = startPage; page <= endPage; ++page) {
pageButtons.push( pageButtons.push(
<li onClick={() => onPageChange(page)} key={page} className={cx({ <li
active: page === activePage onClick={() => onPageChange(page)}
})}> key={page}
className={cx({
active: page === activePage,
})}
>
<a role="button">{page}</a> <a role="button">{page}</a>
</li> </li>
); );
@ -138,16 +122,20 @@ export default class Pagination extends React.PureComponent<PaginationProps, any
if (startPage > 1) { if (startPage > 1) {
if (startPage > 2) { if (startPage > 2) {
pageButtons.unshift( pageButtons.unshift(
<li onClick={() => onPageChange(startPage - 1)} key="prev-ellipsis" > <li onClick={() => onPageChange(startPage - 1)} key="prev-ellipsis">
<a role="button">...</a> <a role="button">...</a>
</li> </li>
); );
} }
pageButtons.unshift( pageButtons.unshift(
<li onClick={() => onPageChange(1)} key={1} className={cx({ <li
active: 1 === activePage onClick={() => onPageChange(1)}
})}> key={1}
className={cx({
active: 1 === activePage,
})}
>
<a role="button">{1}</a> <a role="button">{1}</a>
</li> </li>
); );
@ -156,16 +144,26 @@ export default class Pagination extends React.PureComponent<PaginationProps, any
if (endPage < items) { if (endPage < items) {
if (items - endPage > 1) { if (items - endPage > 1) {
pageButtons.push( pageButtons.push(
<li className={cx("Pagination-ellipsis")} onClick={() => onPageChange(endPage + 1)} key="next-ellipsis" > <li
<a role="button"><span>...</span></a> className={cx('Pagination-ellipsis')}
onClick={() => onPageChange(endPage + 1)}
key="next-ellipsis"
>
<a role="button">
<span>...</span>
</a>
</li> </li>
); );
} }
pageButtons.push( pageButtons.push(
<li onClick={() => onPageChange(items)} key={items} className={cx({ <li
active: items === activePage onClick={() => onPageChange(items)}
})}> key={items}
className={cx({
active: items === activePage,
})}
>
<a role="button">{items}</a> <a role="button">{items}</a>
</li> </li>
); );
@ -173,59 +171,65 @@ export default class Pagination extends React.PureComponent<PaginationProps, any
pageButtons.unshift( pageButtons.unshift(
<li <li
className={cx("Pagination-prev", { className={cx('Pagination-prev', {
disabled: activePage === 1 disabled: activePage === 1,
})} })}
onClick={activePage === 1 ? (e: any) => e.preventDefault() : () => onPageChange(activePage - 1)} onClick={activePage === 1 ? (e: any) => e.preventDefault() : () => onPageChange(activePage - 1)}
key="prev"> key="prev"
<span></span> >
<span />
</li> </li>
); );
pageButtons.push( pageButtons.push(
<li <li
className={cx("Pagination-next", { className={cx('Pagination-next', {
disabled: activePage === items disabled: activePage === items,
})} })}
onClick={activePage === items ? (e: any) => e.preventDefault() : () => onPageChange(activePage + 1)} onClick={activePage === items ? (e: any) => e.preventDefault() : () => onPageChange(activePage + 1)}
key="next"> key="next"
<span></span> >
<span />
</li> </li>
); );
return ( return (
<div> <div>
<ul <ul className={cx('Pagination', 'Pagination--sm')} onSelect={(value: number) => onPageChange(value)}>
className={cx("Pagination", "Pagination--sm")}
onSelect={(value: number) => onPageChange(value)}
>
{pageButtons} {pageButtons}
</ul> </ul>
{items > 9 && showPageInput ? ( {items > 9 && showPageInput ? (
<div className="inline m-l-xs w-xs" key="toPage"> <div className="inline m-l-xs w-xs" key="toPage">
<span className={cx("Pagination-inputGroup")}> <span className={cx('Pagination-inputGroup')}>
<input type="text" className={cx("Pagination-input")} <input
type="text"
className={cx('Pagination-input')}
onChange={this.handlePageChange} onChange={this.handlePageChange}
onFocus={(e: any) => e.currentTarget.select()} onFocus={(e: any) => e.currentTarget.select()}
onKeyUp={(e: any) => e.keyCode == 13 && onPageChange(parseInt(e.currentTarget.value, 10))} onKeyUp={(e: any) =>
e.keyCode == 13 && onPageChange(parseInt(e.currentTarget.value, 10))
}
value={pageNum} value={pageNum}
/> />
<span> <span>
<button onClick={() => onPageChange(pageNum as number)} type="submit" className={cx('Button', 'Button--default')}>Go</button> <button
onClick={() => onPageChange(pageNum as number)}
type="submit"
className={cx('Button', 'Button--default')}
>
Go
</button>
</span> </span>
</span> </span>
</div> </div>
) : null} ) : null}
</div> </div>
); );
} }
render() { render() {
const { const {mode} = this.props;
mode
} = this.props;
return mode === 'simple' ? this.renderSimple() : this.renderNormal(); return mode === 'simple' ? this.renderSimple() : this.renderNormal();
} }
@ -233,6 +237,6 @@ export default class Pagination extends React.PureComponent<PaginationProps, any
@Renderer({ @Renderer({
test: /(^|\/)pagination$/, test: /(^|\/)pagination$/,
name: 'pagination' name: 'pagination',
}) })
export class PaginationRenderer extends Pagination { } export class PaginationRenderer extends Pagination {}

View File

@ -1,14 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {SchemaNode, Action} from '../types';
RendererProps import {getScrollParent} from '../utils/helper';
} from '../factory'; import {findDOMNode} from 'react-dom';
import {
SchemaNode,
Action
} from '../types';
import { getScrollParent } from '../utils/helper';
import { findDOMNode } from 'react-dom';
export interface PanelProps extends RendererProps { export interface PanelProps extends RendererProps {
title?: string; // 标题 title?: string; // 标题
@ -26,12 +20,7 @@ export interface PanelProps extends RendererProps {
} }
export default class Panel extends React.Component<PanelProps> { export default class Panel extends React.Component<PanelProps> {
static propsList: Array<string> = [ static propsList: Array<string> = ['headerClassName', 'footerClassName', 'actionsClassName', 'bodyClassName'];
"headerClassName",
"footerClassName",
"actionsClassName",
"bodyClassName"
];
static defaultProps = { static defaultProps = {
// className: 'Panel--default', // className: 'Panel--default',
// headerClassName: 'Panel-heading', // headerClassName: 'Panel-heading',
@ -46,7 +35,7 @@ export default class Panel extends React.Component<PanelProps> {
componentDidMount() { componentDidMount() {
const dom = findDOMNode(this) as HTMLElement; const dom = findDOMNode(this) as HTMLElement;
let parent:HTMLElement | Window | null = dom ? getScrollParent(dom) : null; let parent: HTMLElement | Window | null = dom ? getScrollParent(dom) : null;
if (!parent || parent === document.body) { if (!parent || parent === document.body) {
parent = window; parent = window;
} }
@ -73,11 +62,11 @@ export default class Panel extends React.Component<PanelProps> {
const clip = footerDom.getBoundingClientRect(); const clip = footerDom.getBoundingClientRect();
const clientHeight = window.innerHeight; const clientHeight = window.innerHeight;
const affixed = clip.top > clientHeight; const affixed = clip.top > clientHeight;
footerDom.offsetWidth && (affixDom.style.cssText = `width: ${footerDom.offsetWidth}px;`); footerDom.offsetWidth && (affixDom.style.cssText = `width: ${footerDom.offsetWidth}px;`);
affixed ? affixDom.classList.add('in') : affixDom.classList.remove('in'); affixed ? affixDom.classList.add('in') : affixDom.classList.remove('in');
} }
renderBody(): JSX.Element | null { renderBody(): JSX.Element | null {
const { const {
type, type,
@ -100,27 +89,28 @@ export default class Panel extends React.Component<PanelProps> {
const subProps = { const subProps = {
data, data,
...rest ...rest,
}; };
return children ? ( return children ? (
<div className={bodyClassName || `${ns}Panel-body`}>{typeof children === 'function' ? children(this.props) : children}</div> <div className={bodyClassName || `${ns}Panel-body`}>
{typeof children === 'function' ? children(this.props) : children}
</div>
) : body ? ( ) : body ? (
<div className={bodyClassName || `${ns}Panel-body`}>{render('body', body, subProps)}</div> <div className={bodyClassName || `${ns}Panel-body`}>{render('body', body, subProps)}</div>
) : null; ) : null;
} }
renderActions() { renderActions() {
const { const {actions, render} = this.props;
actions,
render,
} = this.props;
if (Array.isArray(actions) && actions.length) { if (Array.isArray(actions) && actions.length) {
return actions.map((action, key) => render('action', action, { return actions.map((action, key) =>
type: action.type || 'button', render('action', action, {
key: key type: action.type || 'button',
})); key: key,
})
);
} }
return null; return null;
@ -149,38 +139,35 @@ export default class Panel extends React.Component<PanelProps> {
const subProps = { const subProps = {
data, data,
...rest ...rest,
}; };
const footerDoms = []; const footerDoms = [];
const actions = this.renderActions(); const actions = this.renderActions();
actions && footerDoms.push( actions &&
<div key="actions" className={cx(`Panel-btnToolbar`, actionsClassName || `Panel-footer`)}> footerDoms.push(
{actions} <div key="actions" className={cx(`Panel-btnToolbar`, actionsClassName || `Panel-footer`)}>
</div> {actions}
); </div>
);
footer && footerDoms.push( footer &&
<div key="footer" className={cx(footerClassName || `Panel-footer`)}> footerDoms.push(
{render('footer', footer, subProps)} <div key="footer" className={cx(footerClassName || `Panel-footer`)}>
</div> {render('footer', footer, subProps)}
); </div>
);
let footerDom = footerDoms.length ? ( let footerDom = footerDoms.length ? <div ref={this.footerDom}>{footerDoms}</div> : null;
<div ref={this.footerDom}>
{footerDoms}
</div>
) : null;
return ( return (
<div <div className={cx(`Panel`, className || `Panel--default`)}>
className={cx(`Panel`, className || `Panel--default`)}
>
{header ? ( {header ? (
<div className={cx(headerClassName || `Panel-heading`)}>{render('header', header, subProps)}</div> <div className={cx(headerClassName || `Panel-heading`)}>{render('header', header, subProps)}</div>
) : title ? ( ) : title ? (
<div className={cx(headerClassName || `Panel-heading`)}><h3 className={cx(`Panel-title`)}>{render('title', title, subProps)}</h3></div> <div className={cx(headerClassName || `Panel-heading`)}>
<h3 className={cx(`Panel-title`)}>{render('title', title, subProps)}</h3>
</div>
) : null} ) : null}
{this.renderBody()} {this.renderBody()}
@ -188,7 +175,7 @@ export default class Panel extends React.Component<PanelProps> {
{footerDom} {footerDom}
{affixFooter && footerDoms.length ? ( {affixFooter && footerDoms.length ? (
<div ref={this.affixDom} className={cx("Panel-fixedBottom")}> <div ref={this.affixDom} className={cx('Panel-fixedBottom')}>
{footerDoms} {footerDoms}
</div> </div>
) : null} ) : null}
@ -199,6 +186,6 @@ export default class Panel extends React.Component<PanelProps> {
@Renderer({ @Renderer({
test: /(^|\/)panel$/, test: /(^|\/)panel$/,
name: 'panel' name: 'panel',
}) })
export class PanelRenderer extends Panel {} export class PanelRenderer extends Panel {}

View File

@ -1,11 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {filter} from '../utils/tpl';
RendererProps
} from '../factory';
import {
filter
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
export interface PlainProps extends RendererProps { export interface PlainProps extends RendererProps {
@ -19,30 +14,26 @@ export interface PlainProps extends RendererProps {
} }
export class Plain extends React.Component<PlainProps, object> { export class Plain extends React.Component<PlainProps, object> {
static defaultProps:Partial<PlainProps> = { static defaultProps: Partial<PlainProps> = {
wrapperComponent: '', wrapperComponent: '',
inline: true, inline: true,
placeholder: '-' placeholder: '-',
}; };
render() { render() {
const { const {className, wrapperComponent, value, text, data, tpl, inline, placeholder, classnames: cx} = this.props;
className,
wrapperComponent,
value,
text,
data,
tpl,
inline,
placeholder,
classnames: cx
} = this.props;
const Component = wrapperComponent || (inline ? 'span' : 'div'); const Component = wrapperComponent || (inline ? 'span' : 'div');
return ( return (
<Component className={cx('PlainField', className)}> <Component className={cx('PlainField', className)}>
{tpl || text ? filter((tpl || text as string), data) : (typeof value === 'undefined' || value === '' || value === null ? <span className="text-muted">{placeholder}</span> : String(value))} {tpl || text ? (
filter(tpl || (text as string), data)
) : typeof value === 'undefined' || value === '' || value === null ? (
<span className="text-muted">{placeholder}</span>
) : (
String(value)
)}
</Component> </Component>
); );
} }
@ -50,6 +41,6 @@ export class Plain extends React.Component<PlainProps, object> {
@Renderer({ @Renderer({
test: /(^|\/)(?:plain|text)$/, test: /(^|\/)(?:plain|text)$/,
name: 'plain' name: 'plain',
}) })
export class PlainRenderer extends Plain {}; export class PlainRenderer extends Plain {}

View File

@ -1,53 +1,57 @@
/** /**
* @file scoped.jsx. * @file scoped.jsx.
* @author fex * @author fex
*/ */
import * as React from 'react'; import * as React from 'react';
import {findDOMNode} from 'react-dom'; import {findDOMNode} from 'react-dom';
import { RendererProps } from '../factory'; import {RendererProps} from '../factory';
import * as cx from 'classnames'; import * as cx from 'classnames';
import hoistNonReactStatic = require('hoist-non-react-statics'); import hoistNonReactStatic = require('hoist-non-react-statics');
import { RootCloseWrapper} from 'react-overlays'; import {RootCloseWrapper} from 'react-overlays';
import PopOver from '../components/PopOver'; import PopOver from '../components/PopOver';
import Overlay from '../components/Overlay'; import Overlay from '../components/Overlay';
export interface PopOverConfig { export interface PopOverConfig {}
}
const allowedPositions = [ const allowedPositions = ['center', 'top'];
'center',
'top'
];
export interface PopOverConfig { export interface PopOverConfig {
saveImmediately?: boolean; saveImmediately?: boolean;
mode?: 'dialog' | 'drawer' | 'popOver'; mode?: 'dialog' | 'drawer' | 'popOver';
title?: string; title?: string;
size?: 'sm' | 'md' | 'lg' | 'xl'; size?: 'sm' | 'md' | 'lg' | 'xl';
position: 'center' | 'left-top' | 'right-top' | 'left-bottom' | 'right-bottom' position:
| 'fixed-center' | 'fixed-left-top' | 'fixed-right-top' | 'fixed-left-bottom' | 'fixed-right-bottom'; | 'center'
[propName:string]: any; | 'left-top'
}; | 'right-top'
| 'left-bottom'
| 'right-bottom'
| 'fixed-center'
| 'fixed-left-top'
| 'fixed-right-top'
| 'fixed-left-bottom'
| 'fixed-right-bottom';
[propName: string]: any;
}
export interface PopOverProps extends RendererProps { export interface PopOverProps extends RendererProps {
name?: string; name?: string;
label?: string; label?: string;
popOver: boolean | PopOverConfig; popOver: boolean | PopOverConfig;
onPopOverOpen: (popover:any) => void; onPopOverOpen: (popover: any) => void;
onPopOverClose: (popover:any) => void; onPopOverClose: (popover: any) => void;
}; }
export interface PopOverState { export interface PopOverState {
isOpened: boolean; isOpened: boolean;
}; }
export const HocPopOver = (config:Partial<PopOverConfig> = {}) => (Component: React.ComponentType<any>):any => {
export const HocPopOver = (config: Partial<PopOverConfig> = {}) => (Component: React.ComponentType<any>): any => {
class PopOverComponent extends React.Component<PopOverProps, PopOverState> { class PopOverComponent extends React.Component<PopOverProps, PopOverState> {
target:HTMLElement; target: HTMLElement;
static ComposedComponent = Component; static ComposedComponent = Component;
constructor(props:PopOverProps) { constructor(props: PopOverProps) {
super(props); super(props);
this.openPopOver = this.openPopOver.bind(this); this.openPopOver = this.openPopOver.bind(this);
@ -55,19 +59,22 @@ export const HocPopOver = (config:Partial<PopOverConfig> = {}) => (Component: Re
this.targetRef = this.targetRef.bind(this); this.targetRef = this.targetRef.bind(this);
// this.handleClickOutside = this.handleClickOutside.bind(this); // this.handleClickOutside = this.handleClickOutside.bind(this);
this.state = { this.state = {
isOpened: false isOpened: false,
}; };
} }
targetRef(ref:any) { targetRef(ref: any) {
this.target = ref; this.target = ref;
} }
openPopOver() { openPopOver() {
const onPopOverOpen = this.props.onPopOverOpen; const onPopOverOpen = this.props.onPopOverOpen;
this.setState({ this.setState(
isOpened: true {
}, () => onPopOverOpen && onPopOverOpen(this.props.popOver)); isOpened: true,
},
() => onPopOverOpen && onPopOverOpen(this.props.popOver)
);
} }
closePopOver() { closePopOver() {
@ -76,24 +83,23 @@ export const HocPopOver = (config:Partial<PopOverConfig> = {}) => (Component: Re
} }
const onPopOverClose = this.props.onPopOverClose; const onPopOverClose = this.props.onPopOverClose;
this.setState({ this.setState(
isOpened: false {
}, () => onPopOverClose && onPopOverClose(this.props.popOver)); isOpened: false,
},
() => onPopOverClose && onPopOverClose(this.props.popOver)
);
} }
buildSchema() { buildSchema() {
const { const {popOver, name, label} = this.props;
popOver,
name,
label
} = this.props;
let schema; let schema;
if (popOver === true) { if (popOver === true) {
schema = { schema = {
type: 'panel', type: 'panel',
body: '${name}' body: '${name}',
}; };
} else if (popOver && (popOver.mode === 'dialog' || popOver.mode === 'drawer')) { } else if (popOver && (popOver.mode === 'dialog' || popOver.mode === 'drawer')) {
schema = { schema = {
@ -102,15 +108,15 @@ export const HocPopOver = (config:Partial<PopOverConfig> = {}) => (Component: Re
{ {
label: '关闭', label: '关闭',
type: 'button', type: 'button',
actionType: 'cancel' actionType: 'cancel',
} },
], ],
...popOver ...popOver,
}; };
} else if (popOver) { } else if (popOver) {
schema = { schema = {
type: 'panel', type: 'panel',
...popOver ...popOver,
}; };
} }
@ -118,41 +124,33 @@ export const HocPopOver = (config:Partial<PopOverConfig> = {}) => (Component: Re
} }
renderPopOver() { renderPopOver() {
let { let {popOver, render, popOverContainer, classnames: cx, classPrefix: ns} = this.props;
popOver,
render,
popOverContainer,
classnames: cx,
classPrefix: ns
} = this.props;
if (popOver && ((popOver as PopOverConfig).mode === 'dialog' if (
|| (popOver as PopOverConfig).mode === 'drawer')) { popOver &&
((popOver as PopOverConfig).mode === 'dialog' || (popOver as PopOverConfig).mode === 'drawer')
) {
return render('popover-detail', this.buildSchema(), { return render('popover-detail', this.buildSchema(), {
show: true, show: true,
onClose: this.closePopOver, onClose: this.closePopOver,
onConfirm: this.closePopOver onConfirm: this.closePopOver,
}); });
} }
const content = render('popover-detail', this.buildSchema(), { const content = render('popover-detail', this.buildSchema(), {
className: cx((popOver as PopOverConfig).className) className: cx((popOver as PopOverConfig).className),
}) as JSX.Element; }) as JSX.Element;
if (!popOverContainer) { if (!popOverContainer) {
popOverContainer = () => findDOMNode(this); popOverContainer = () => findDOMNode(this);
} }
const position = popOver && (popOver as PopOverConfig).position || ''; const position = (popOver && (popOver as PopOverConfig).position) || '';
const isFixed = /^fixed\-/.test(position); const isFixed = /^fixed\-/.test(position);
return isFixed ? ( return isFixed ? (
<RootCloseWrapper <RootCloseWrapper disabled={!this.state.isOpened} onRootClose={this.closePopOver}>
disabled={!this.state.isOpened} <div className={cx(`PopOverAble--fixed PopOverAble--${position}`)}>{content}</div>
onRootClose={this.closePopOver}
><div className={cx(`PopOverAble--fixed PopOverAble--${position}`)}>
{content}
</div>
</RootCloseWrapper> </RootCloseWrapper>
) : ( ) : (
<Overlay <Overlay
@ -163,10 +161,7 @@ export const HocPopOver = (config:Partial<PopOverConfig> = {}) => (Component: Re
rootClose rootClose
show show
> >
<PopOver <PopOver classPrefix={ns} className={cx('PopOverAble-popover')}>
classPrefix={ns}
className={cx("PopOverAble-popover")}
>
{content} {content}
</PopOver> </PopOver>
</Overlay> </Overlay>
@ -174,31 +169,25 @@ export const HocPopOver = (config:Partial<PopOverConfig> = {}) => (Component: Re
} }
render() { render() {
const { const {onQuickChange, popOver, popOverEnabled, className, noHoc, classnames: cx, render} = this.props;
onQuickChange,
popOver,
popOverEnabled,
className,
noHoc,
classnames: cx,
render
} = this.props;
if (!popOver || popOverEnabled === false || noHoc) { if (!popOver || popOverEnabled === false || noHoc) {
return ( return <Component {...this.props} />;
<Component {...this.props} />
);
} }
return ( return (
<Component <Component
{...this.props} {...this.props}
className={cx(`Field--popOverAble`, className, { className={cx(`Field--popOverAble`, className, {
'in': this.state.isOpened in: this.state.isOpened,
})} })}
> >
<Component {...this.props} wrapperComponent={''} noHoc ref={this.targetRef} /> <Component {...this.props} wrapperComponent={''} noHoc ref={this.targetRef} />
<i key="popover-btn" className={cx("Field-popOverBtn fa fa-search-plus")} onClick={this.openPopOver} /> <i
key="popover-btn"
className={cx('Field-popOverBtn fa fa-search-plus')}
onClick={this.openPopOver}
/>
{this.state.isOpened ? this.renderPopOver() : null} {this.state.isOpened ? this.renderPopOver() : null}
</Component> </Component>
); );

View File

@ -1,17 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { import {Api, SchemaNode, PlainObject} from '../types';
Api, import {filter} from '../utils/tpl';
SchemaNode,
PlainObject
} from '../types';
import {
filter
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
export interface ProgressProps extends RendererProps { export interface ProgressProps extends RendererProps {
@ -23,23 +14,17 @@ export interface ProgressProps extends RendererProps {
} }
export class ProgressField extends React.Component<ProgressProps, object> { export class ProgressField extends React.Component<ProgressProps, object> {
static defaultProps:Partial<ProgressProps> = { static defaultProps: Partial<ProgressProps> = {
placeholder: '-', placeholder: '-',
progressClassName: 'progress-xs progress-striped active m-b-none', progressClassName: 'progress-xs progress-striped active m-b-none',
progressBarClassName: '', progressBarClassName: '',
map: [ map: ['bg-danger', 'bg-warning', 'bg-info', 'bg-success', 'bg-success'],
'bg-danger', showLabel: true,
'bg-warning',
'bg-info',
'bg-success',
'bg-success'
],
showLabel: true
}; };
autoClassName(value:number) { autoClassName(value: number) {
const map = this.props.map; const map = this.props.map;
let index = Math.floor(value * map.length / 100); let index = Math.floor((value * map.length) / 100);
index = Math.max(0, Math.min(map.length - 1, index)); index = Math.max(0, Math.min(map.length - 1, index));
return map[index]; return map[index];
} }
@ -52,11 +37,11 @@ export class ProgressField extends React.Component<ProgressProps, object> {
progressBarClassName, progressBarClassName,
map, map,
showLabel, showLabel,
classnames: cx classnames: cx,
} = this.props; } = this.props;
let value = this.props.value; let value = this.props.value;
let viewValue:React.ReactNode = <span className="text-muted">{placeholder}</span>; let viewValue: React.ReactNode = <span className="text-muted">{placeholder}</span>;
if (/^\d*\.?\d+$/.test(value)) { if (/^\d*\.?\d+$/.test(value)) {
value = parseFloat(value); value = parseFloat(value);
@ -64,30 +49,25 @@ export class ProgressField extends React.Component<ProgressProps, object> {
if (typeof value === 'number') { if (typeof value === 'number') {
viewValue = [ viewValue = [
<div key="progress" className={cx("progress", progressClassName)}> <div key="progress" className={cx('progress', progressClassName)}>
<div <div
className={cx("progress-bar", progressBarClassName || this.autoClassName(value))} className={cx('progress-bar', progressBarClassName || this.autoClassName(value))}
title={`${value}%`} title={`${value}%`}
style={{ style={{
width: `${value}%` width: `${value}%`,
}} }}
> />
</div>
</div>, </div>,
showLabel ? <div key="value">{value}%</div> : null showLabel ? <div key="value">{value}%</div> : null,
]; ];
} }
return ( return <span className={cx('ProgressField', className)}>{viewValue}</span>;
<span className={cx('ProgressField', className)}>
{viewValue}
</span>
);
} }
} }
@Renderer({ @Renderer({
test: /(^|\/)progress$/, test: /(^|\/)progress$/,
name: 'progress' name: 'progress',
}) })
export class ProgressFieldRenderer extends ProgressField {}; export class ProgressFieldRenderer extends ProgressField {}

View File

@ -1,33 +1,25 @@
import * as React from 'react'; import * as React from 'react';
import * as cx from 'classnames'; import * as cx from 'classnames';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {FormItem, FormControlProps} from './Form/Item';
RendererProps, import {filter} from '../utils/tpl';
} from '../factory';
import {
FormItem,
FormControlProps
} from './Form/Item';
import {
filter
} from '../utils/tpl';
import QrCode = require('qrcode.react'); import QrCode = require('qrcode.react');
export interface QRCodeProps extends FormControlProps { export interface QRCodeProps extends FormControlProps {
codeSize?: number; codeSize?: number;
backgroundColor?: string; backgroundColor?: string;
foregroundColor?: string; foregroundColor?: string;
level?: string level?: string;
placeholder: string; placeholder: string;
} }
export default class QRCode extends React.Component<QRCodeProps, any>{ export default class QRCode extends React.Component<QRCodeProps, any> {
static defaultProps: Partial<QRCodeProps> = { static defaultProps: Partial<QRCodeProps> = {
codeSize: 128, codeSize: 128,
backgroundColor: '#fff', backgroundColor: '#fff',
foregroundColor: '#000', foregroundColor: '#000',
level: 'L', level: 'L',
placeholder: '-' placeholder: '-',
}; };
render() { render() {
@ -40,7 +32,7 @@ export default class QRCode extends React.Component<QRCodeProps, any>{
level, level,
value, value,
data, data,
classPrefix: ns classPrefix: ns,
} = this.props; } = this.props;
return ( return (
@ -52,20 +44,23 @@ export default class QRCode extends React.Component<QRCodeProps, any>{
bgColor={backgroundColor} bgColor={backgroundColor}
fgColor={foregroundColor} fgColor={foregroundColor}
level={level || 'L'} level={level || 'L'}
/>) : <span className={`${ns}QrCode--placeholder`}>{placeholder}</span>} />
) : (
<span className={`${ns}QrCode--placeholder`}>{placeholder}</span>
)}
</div> </div>
) );
} }
} }
@Renderer({ @Renderer({
test: /(^|\/)qr\-?code$/, test: /(^|\/)qr\-?code$/,
name: 'qrcode' name: 'qrcode',
}) })
export class QRCodeRenderer extends QRCode { } export class QRCodeRenderer extends QRCode {}
@FormItem({ @FormItem({
type: 'qr-code', type: 'qr-code',
sizeMutable: false sizeMutable: false,
}) })
export class QRCodeControlRenderer extends QRCode {} export class QRCodeControlRenderer extends QRCode {}

View File

@ -1,25 +1,24 @@
/** /**
* @file scoped.jsx. * @file scoped.jsx.
* @author fex * @author fex
*/ */
import * as React from 'react'; import * as React from 'react';
import {findDOMNode} from 'react-dom'; import {findDOMNode} from 'react-dom';
import find = require('lodash/find'); import find = require('lodash/find');
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import isPlainObject = require('lodash/isPlainObject'); import isPlainObject = require('lodash/isPlainObject');
import { RendererProps } from '../factory'; import {RendererProps} from '../factory';
import * as cx from 'classnames'; import * as cx from 'classnames';
import hoistNonReactStatic = require('hoist-non-react-statics'); import hoistNonReactStatic = require('hoist-non-react-statics');
import onClickOutside from "react-onclickoutside"; import onClickOutside from 'react-onclickoutside';
import { Action } from '../types'; import {Action} from '../types';
import * as keycode from 'keycode'; import * as keycode from 'keycode';
import matches = require('dom-helpers/query/matches'); import matches = require('dom-helpers/query/matches');
import Overlay from '../components/Overlay'; import Overlay from '../components/Overlay';
import PopOver from '../components/PopOver'; import PopOver from '../components/PopOver';
export interface QuickEditConfig { export interface QuickEditConfig {}
}
export interface QuickEditConfig { export interface QuickEditConfig {
saveImmediately?: boolean; saveImmediately?: boolean;
@ -30,31 +29,29 @@ export interface QuickEditConfig {
fieldSet?: any; fieldSet?: any;
focusable?: boolean; focusable?: boolean;
popOverClassName?: string; popOverClassName?: string;
[propName:string]: any; [propName: string]: any;
}; }
export interface QuickEditProps extends RendererProps { export interface QuickEditProps extends RendererProps {
name?: string; name?: string;
label?: string; label?: string;
quickEdit: boolean | QuickEditConfig; quickEdit: boolean | QuickEditConfig;
quickEditEnabled?: boolean; quickEditEnabled?: boolean;
}; }
export interface QuickEditState { export interface QuickEditState {
isOpened: boolean; isOpened: boolean;
}; }
let inited: boolean = false;
let currentOpened: any;
let inited:boolean = false; export const HocQuickEdit = (config: Partial<QuickEditConfig> = {}) => (Component: React.ComponentType<any>): any => {
let currentOpened:any;
export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component: React.ComponentType<any>):any => {
class QuickEditComponent extends React.PureComponent<QuickEditProps, QuickEditState> { class QuickEditComponent extends React.PureComponent<QuickEditProps, QuickEditState> {
target:HTMLElement; target: HTMLElement;
overlay: HTMLElement; overlay: HTMLElement;
static ComposedComponent = Component; static ComposedComponent = Component;
constructor(props:QuickEditProps) { constructor(props: QuickEditProps) {
super(props); super(props);
this.openQuickEdit = this.openQuickEdit.bind(this); this.openQuickEdit = this.openQuickEdit.bind(this);
@ -65,9 +62,9 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
this.overlayRef = this.overlayRef.bind(this); this.overlayRef = this.overlayRef.bind(this);
this.handleWindowKeyPress = this.handleWindowKeyPress.bind(this); this.handleWindowKeyPress = this.handleWindowKeyPress.bind(this);
this.handleWindowKeyDown = this.handleWindowKeyDown.bind(this); this.handleWindowKeyDown = this.handleWindowKeyDown.bind(this);
this.state = { this.state = {
isOpened: false isOpened: false,
}; };
} }
@ -83,9 +80,9 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
document.body.addEventListener('keydown', this.handleWindowKeyDown); document.body.addEventListener('keydown', this.handleWindowKeyDown);
} }
handleWindowKeyPress(e:Event) { handleWindowKeyPress(e: Event) {
const ns = this.props.classPrefix; const ns = this.props.classPrefix;
let el:HTMLElement = (e.target as HTMLElement).closest(`.${ns}Field--quickEditable`) as HTMLElement; let el: HTMLElement = (e.target as HTMLElement).closest(`.${ns}Field--quickEditable`) as HTMLElement;
if (!el) { if (!el) {
return; return;
} }
@ -93,104 +90,111 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
if (!table) { if (!table) {
return; return;
} }
if (keycode(e) === 'space' && !~['INPUT', 'TEXTAREA'].indexOf(el.tagName)) { if (keycode(e) === 'space' && !~['INPUT', 'TEXTAREA'].indexOf(el.tagName)) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
} }
} }
handleWindowKeyDown(e:Event) { handleWindowKeyDown(e: Event) {
const code = keycode(e); const code = keycode(e);
if (code === 'esc' && currentOpened) { if (code === 'esc' && currentOpened) {
currentOpened.closeQuickEdit(); currentOpened.closeQuickEdit();
} else if ( } else if (
~['INPUT', 'TEXTAREA'].indexOf((e.target as HTMLElement).tagName) ~['INPUT', 'TEXTAREA'].indexOf((e.target as HTMLElement).tagName) ||
|| (e.target as HTMLElement).contentEditable === 'true' (e.target as HTMLElement).contentEditable === 'true' ||
|| !~['up', 'down', 'left', 'right'].indexOf(code) !~['up', 'down', 'left', 'right'].indexOf(code)
) { ) {
return; return;
} }
e.preventDefault(); e.preventDefault();
const ns = this.props.classPrefix; const ns = this.props.classPrefix;
let el:HTMLElement = (e.target as HTMLElement).closest(`.${ns}Field--quickEditable`) as HTMLElement || document.querySelector(`.${ns}Field--quickEditable`); let el: HTMLElement =
((e.target as HTMLElement).closest(`.${ns}Field--quickEditable`) as HTMLElement) ||
document.querySelector(`.${ns}Field--quickEditable`);
if (!el) { if (!el) {
return; return;
} }
let table = el.closest('table'); let table = el.closest('table');
if (!table) { if (!table) {
return; return;
} }
let current = table.querySelector(`.${ns}Field--quickEditable:focus`) as HTMLTableDataCellElement; let current = table.querySelector(`.${ns}Field--quickEditable:focus`) as HTMLTableDataCellElement;
if (!current) { if (!current) {
let dom = table.querySelector(`.${ns}Field--quickEditable[tabindex]`) as HTMLElement; let dom = table.querySelector(`.${ns}Field--quickEditable[tabindex]`) as HTMLElement;
dom && dom.focus(); dom && dom.focus();
} else { } else {
let prevTr, nextTr, prevTd, nextTd; let prevTr, nextTr, prevTd, nextTd;
switch (code) { switch (code) {
case 'up': case 'up':
prevTr = (current.parentNode as HTMLElement).previousSibling as HTMLTableCellElement; prevTr = (current.parentNode as HTMLElement).previousSibling as HTMLTableCellElement;
if (prevTr) { if (prevTr) {
let index = current.cellIndex; let index = current.cellIndex;
(prevTr.children[index] as HTMLElement).focus(); (prevTr.children[index] as HTMLElement).focus();
} }
break; break;
case 'down': case 'down':
nextTr = (current.parentNode as HTMLElement).nextSibling as HTMLTableCellElement; nextTr = (current.parentNode as HTMLElement).nextSibling as HTMLTableCellElement;
if (nextTr) { if (nextTr) {
let index = current.cellIndex; let index = current.cellIndex;
(nextTr.children[index] as HTMLElement).focus(); (nextTr.children[index] as HTMLElement).focus();
} }
break; break;
case 'left': case 'left':
prevTd = current.previousElementSibling as HTMLTableCellElement; prevTd = current.previousElementSibling as HTMLTableCellElement;
while (prevTd) { while (prevTd) {
if (matches(prevTd, `.${ns}Field--quickEditable[tabindex]`)) { if (matches(prevTd, `.${ns}Field--quickEditable[tabindex]`)) {
break; break;
} }
prevTd = prevTd.previousElementSibling; prevTd = prevTd.previousElementSibling;
} }
if (prevTd) { if (prevTd) {
(prevTd as HTMLElement).focus(); (prevTd as HTMLElement).focus();
} else if ((current.parentNode as HTMLElement).previousSibling) { } else if ((current.parentNode as HTMLElement).previousSibling) {
let tds = ((current.parentNode as HTMLElement).previousSibling as HTMLElement).querySelectorAll(`.${ns}Field--quickEditable[tabindex]`); let tds = ((current.parentNode as HTMLElement)
.previousSibling as HTMLElement).querySelectorAll(
if (tds.length) { `.${ns}Field--quickEditable[tabindex]`
(tds[tds.length - 1] as HTMLElement).focus(); );
}
} if (tds.length) {
break; (tds[tds.length - 1] as HTMLElement).focus();
case 'right': }
nextTd = current.nextSibling; }
while (nextTd) { break;
if (matches(nextTd, `.${ns}Field--quickEditable[tabindex]`)) { case 'right':
break; nextTd = current.nextSibling;
} while (nextTd) {
if (matches(nextTd, `.${ns}Field--quickEditable[tabindex]`)) {
nextTd = nextTd.nextSibling; break;
} }
if (nextTd) { nextTd = nextTd.nextSibling;
(nextTd as HTMLElement).focus(); }
} else if ((current.parentNode as HTMLElement).nextSibling) {
nextTd = ((current.parentNode as HTMLElement).nextSibling as HTMLElement).querySelector(`.${ns}Field--quickEditable[tabindex]`); if (nextTd) {
(nextTd as HTMLElement).focus();
if (nextTd) { } else if ((current.parentNode as HTMLElement).nextSibling) {
nextTd.focus(); nextTd = ((current.parentNode as HTMLElement).nextSibling as HTMLElement).querySelector(
} `.${ns}Field--quickEditable[tabindex]`
} );
break;
} if (nextTd) {
nextTd.focus();
}
}
break;
}
} }
} }
@ -198,14 +202,12 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
// this.closeQuickEdit(); // this.closeQuickEdit();
// } // }
overlayRef(ref:any) { overlayRef(ref: any) {
this.overlay = ref; this.overlay = ref;
} }
handleAction(e:any, action:Action, ctx:object) { handleAction(e: any, action: Action, ctx: object) {
const { const {onAction} = this.props;
onAction
} = this.props;
if (action.actionType === 'cancel' || action.actionType === 'close') { if (action.actionType === 'cancel' || action.actionType === 'close') {
this.closeQuickEdit(); this.closeQuickEdit();
@ -215,11 +217,8 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
onAction && onAction(e, action, ctx); onAction && onAction(e, action, ctx);
} }
handleSubmit(values:object) { handleSubmit(values: object) {
const { const {onQuickChange, quickEdit} = this.props;
onQuickChange,
quickEdit
} = this.props;
this.closeQuickEdit(); this.closeQuickEdit();
onQuickChange(values, (quickEdit as QuickEditConfig).saveImmediately); onQuickChange(values, (quickEdit as QuickEditConfig).saveImmediately);
@ -228,7 +227,7 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
openQuickEdit() { openQuickEdit() {
currentOpened = this; currentOpened = this;
this.setState({ this.setState({
isOpened: true isOpened: true,
}); });
} }
@ -238,21 +237,21 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
} }
currentOpened = null; currentOpened = null;
const ns = this.props.classPrefix; const ns = this.props.classPrefix;
this.setState({ this.setState(
isOpened: false {
}, () => { isOpened: false,
let el = findDOMNode(this) as HTMLElement; },
let table = el.closest('table') as HTMLElement; () => {
(table && table.querySelectorAll(`td.${ns}Field--quickEditable:focus`).length || el) && el.focus(); let el = findDOMNode(this) as HTMLElement;
}); let table = el.closest('table') as HTMLElement;
((table && table.querySelectorAll(`td.${ns}Field--quickEditable:focus`).length) || el) &&
el.focus();
}
);
} }
buildSchema() { buildSchema() {
const { const {quickEdit, name, label} = this.props;
quickEdit,
name,
label
} = this.props;
let schema; let schema;
@ -266,18 +265,23 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
type: 'text', type: 'text',
name, name,
placeholder: label, placeholder: label,
label: false label: false,
} },
] ],
}; };
} else if (quickEdit) { } else if (quickEdit) {
if ((quickEdit.controls && !~['combo', 'group', 'panel', 'fieldSet'].indexOf((quickEdit as any).type)) || quickEdit.tabs || quickEdit.fieldSet) { if (
(quickEdit.controls &&
!~['combo', 'group', 'panel', 'fieldSet'].indexOf((quickEdit as any).type)) ||
quickEdit.tabs ||
quickEdit.fieldSet
) {
schema = { schema = {
title: '', title: '',
autoFocus: (quickEdit as QuickEditConfig).mode !== 'inline', autoFocus: (quickEdit as QuickEditConfig).mode !== 'inline',
mode: (quickEdit as QuickEditConfig).mode === 'inline' ? 'inline' : 'normal', mode: (quickEdit as QuickEditConfig).mode === 'inline' ? 'inline' : 'normal',
...quickEdit, ...quickEdit,
type: 'form' type: 'form',
}; };
} else { } else {
schema = { schema = {
@ -292,9 +296,9 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
name: quickEdit.name || name, name: quickEdit.name || name,
placeholder: label, placeholder: label,
label: false, label: false,
...quickEdit ...quickEdit,
} },
] ],
}; };
} }
} }
@ -303,26 +307,29 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
schema = { schema = {
...schema, ...schema,
wrapWithPanel: (quickEdit as QuickEditConfig).mode !== 'inline', wrapWithPanel: (quickEdit as QuickEditConfig).mode !== 'inline',
actions: (quickEdit as QuickEditConfig).mode === 'inline' ? [] : [ actions:
{ (quickEdit as QuickEditConfig).mode === 'inline'
type: 'button', ? []
label: '取消', : [
actionType: 'cancel' {
}, type: 'button',
label: '取消',
actionType: 'cancel',
},
{ {
label: '确认', label: '确认',
type: 'submit', type: 'submit',
primary: true primary: true,
} },
] ],
} };
} }
return schema || 'error'; return schema || 'error';
} }
handleKeyUp(e:Event) { handleKeyUp(e: Event) {
const code = keycode(e); const code = keycode(e);
if (code === 'space' && !~['INPUT', 'TEXTAREA'].indexOf((e.target as HTMLElement).tagName)) { if (code === 'space' && !~['INPUT', 'TEXTAREA'].indexOf((e.target as HTMLElement).tagName)) {
e.preventDefault(); e.preventDefault();
@ -332,13 +339,7 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
} }
renderPopOver() { renderPopOver() {
let { let {quickEdit, render, popOverContainer, classPrefix: ns, classnames: cx} = this.props;
quickEdit,
render,
popOverContainer,
classPrefix: ns,
classnames: cx
} = this.props;
const content = ( const content = (
<div className={cx((quickEdit as QuickEditConfig).className)}> <div className={cx((quickEdit as QuickEditConfig).className)}>
@ -346,7 +347,7 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
onSubmit: this.handleSubmit, onSubmit: this.handleSubmit,
onAction: this.handleAction, onAction: this.handleAction,
onChange: null, onChange: null,
popOverContainer: popOverContainer ? () => this.overlay : null popOverContainer: popOverContainer ? () => this.overlay : null,
})} })}
</div> </div>
); );
@ -375,20 +376,10 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
} }
render() { render() {
const { const {onQuickChange, quickEdit, quickEditEnabled, className, classnames: cx, render, noHoc} = this.props;
onQuickChange,
quickEdit,
quickEditEnabled,
className,
classnames: cx,
render,
noHoc
} = this.props;
if (!quickEdit || !onQuickChange || quickEditEnabled === false || noHoc) { if (!quickEdit || !onQuickChange || quickEditEnabled === false || noHoc) {
return ( return <Component {...this.props} />;
<Component {...this.props} />
);
} }
if ((quickEdit as QuickEditConfig).mode === 'inline') { if ((quickEdit as QuickEditConfig).mode === 'inline') {
@ -397,22 +388,27 @@ export const HocQuickEdit = (config:Partial<QuickEditConfig> = {}) => (Component
{render('inline-form', this.buildSchema(), { {render('inline-form', this.buildSchema(), {
wrapperComponent: 'div', wrapperComponent: 'div',
className: cx('Form--quickEdit'), className: cx('Form--quickEdit'),
onChange: (values:object) => onQuickChange(values, (quickEdit as QuickEditConfig).saveImmediately) onChange: (values: object) =>
onQuickChange(values, (quickEdit as QuickEditConfig).saveImmediately),
})} })}
</Component> </Component>
) );
} else { } else {
return ( return (
<Component <Component
{...this.props} {...this.props}
className={cx(`Field--quickEditable`, className, { className={cx(`Field--quickEditable`, className, {
'in': this.state.isOpened in: this.state.isOpened,
})} })}
tabIndex={(quickEdit as QuickEditConfig).focusable === false ? undefined : '0'} tabIndex={(quickEdit as QuickEditConfig).focusable === false ? undefined : '0'}
onKeyUp={this.handleKeyUp} onKeyUp={this.handleKeyUp}
> >
<Component {...this.props} wrapperComponent={''} noHoc /> <Component {...this.props} wrapperComponent={''} noHoc />
<i key="edit-btn" className={cx("Field-quickEditBtn fa fa-edit")} onClick={this.openQuickEdit} /> <i
key="edit-btn"
className={cx('Field-quickEditBtn fa fa-edit')}
onClick={this.openQuickEdit}
/>
{this.state.isOpened ? this.renderPopOver() : null} {this.state.isOpened ? this.renderPopOver() : null}
</Component> </Component>
); );

View File

@ -1,18 +1,26 @@
import * as React from "react"; import * as React from 'react';
import { Renderer, RendererProps } from "../factory"; import {Renderer, RendererProps} from '../factory';
import { Api, SchemaNode, Schema, Action } from "../types"; import {Api, SchemaNode, Schema, Action} from '../types';
import * as cx from "classnames"; import * as cx from 'classnames';
import TooltipWrapper from '../components/TooltipWrapper'; import TooltipWrapper from '../components/TooltipWrapper';
import { filter } from "../utils/tpl"; import {filter} from '../utils/tpl';
export function filterContents(tooltip:string | undefined | {title?: string; content?: string; body?: string}, data:any) { export function filterContents(
tooltip: string | undefined | {title?: string; content?: string; body?: string},
data: any
) {
if (typeof tooltip === 'string') { if (typeof tooltip === 'string') {
return filter(tooltip, data); return filter(tooltip, data);
} else if (tooltip) { } else if (tooltip) {
return tooltip.title ? { return tooltip.title
title: filter(tooltip.title, data), ? {
content: tooltip.content || tooltip.body ? filter(tooltip.content || tooltip.body || '', data) : undefined, title: filter(tooltip.title, data),
} : (tooltip.content || tooltip.body ? filter(tooltip.content || tooltip.body || '', data) : undefined); content:
tooltip.content || tooltip.body ? filter(tooltip.content || tooltip.body || '', data) : undefined,
}
: tooltip.content || tooltip.body
? filter(tooltip.content || tooltip.body || '', data)
: undefined;
} }
return tooltip; return tooltip;
} }
@ -45,7 +53,7 @@ export default class Remark extends React.Component<RemarkProps> {
classPrefix: ns, classPrefix: ns,
classnames: cx, classnames: cx,
content, content,
data data,
} = this.props; } = this.props;
return ( return (
@ -53,14 +61,14 @@ export default class Remark extends React.Component<RemarkProps> {
classPrefix={ns} classPrefix={ns}
classnames={cx} classnames={cx}
tooltip={filterContents(tooltip || content, data)} tooltip={filterContents(tooltip || content, data)}
placement={tooltip && tooltip.placement || placement} placement={(tooltip && tooltip.placement) || placement}
rootClose={tooltip && tooltip.rootClose || rootClose} rootClose={(tooltip && tooltip.rootClose) || rootClose}
trigger={tooltip && tooltip.trigger || trigger} trigger={(tooltip && tooltip.trigger) || trigger}
container={container} container={container}
delay={tooltip && tooltip.delay} delay={tooltip && tooltip.delay}
> >
<div className={cx(`Remark`, tooltip && tooltip.className || className || `Remark--warning`)}> <div className={cx(`Remark`, (tooltip && tooltip.className) || className || `Remark--warning`)}>
<i className={cx('Remark-icon', tooltip && tooltip.icon || icon)} /> <i className={cx('Remark-icon', (tooltip && tooltip.icon) || icon)} />
</div> </div>
</TooltipWrapper> </TooltipWrapper>
); );
@ -69,6 +77,6 @@ export default class Remark extends React.Component<RemarkProps> {
@Renderer({ @Renderer({
test: /(^|\/)remark$/, test: /(^|\/)remark$/,
name: 'remark' name: 'remark',
}) })
export class RemarkRenderer extends Remark {} export class RemarkRenderer extends Remark {}

View File

@ -1,23 +1,13 @@
import * as React from 'react'; import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { import {Api, SchemaNode, ApiObject, RendererData} from '../types';
Api, import {filter, evalExpression} from '../utils/tpl';
SchemaNode,
ApiObject,
RendererData
} from '../types';
import {
filter, evalExpression
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
import Scoped, { ScopedContext, IScopedContext } from '../Scoped'; import Scoped, {ScopedContext, IScopedContext} from '../Scoped';
import { observer } from 'mobx-react'; import {observer} from 'mobx-react';
import { isApiOutdated } from '../utils/api'; import {isApiOutdated} from '../utils/api';
export interface ServiceProps extends RendererProps { export interface ServiceProps extends RendererProps {
api?: Api; api?: Api;
@ -34,15 +24,11 @@ export default class Service extends React.Component<ServiceProps> {
timer: NodeJS.Timeout; timer: NodeJS.Timeout;
mounted: boolean; mounted: boolean;
static defaultProps:Partial<ServiceProps> = { static defaultProps: Partial<ServiceProps> = {};
};
static propsList:Array<string> = [ static propsList: Array<string> = [];
];
constructor(props:ServiceProps) { constructor(props: ServiceProps) {
super(props); super(props);
this.handleQuery = this.handleQuery.bind(this); this.handleQuery = this.handleQuery.bind(this);
@ -52,40 +38,36 @@ export default class Service extends React.Component<ServiceProps> {
} }
componentDidMount() { componentDidMount() {
const { const {schemaApi, initFetchSchema, api, initFetch, store} = this.props;
schemaApi,
initFetchSchema,
api,
initFetch,
store
} = this.props;
this.mounted = true; this.mounted = true;
if (schemaApi && initFetchSchema !== false && (!(schemaApi as ApiObject).sendOn || evalExpression((schemaApi as ApiObject).sendOn as string, store.data))) { if (
store schemaApi &&
.fetchSchema(schemaApi, store.data) initFetchSchema !== false &&
.then(this.initInterval); (!(schemaApi as ApiObject).sendOn || evalExpression((schemaApi as ApiObject).sendOn as string, store.data))
) {
store.fetchSchema(schemaApi, store.data).then(this.initInterval);
} }
if (api && initFetch !== false && (!(api as ApiObject).sendOn || evalExpression((api as ApiObject).sendOn as string, store.data))) { if (
store api &&
.fetchInitData(api, store.data) initFetch !== false &&
.then(this.initInterval); (!(api as ApiObject).sendOn || evalExpression((api as ApiObject).sendOn as string, store.data))
) {
store.fetchInitData(api, store.data).then(this.initInterval);
} }
} }
componentDidUpdate(prevProps:ServiceProps) { componentDidUpdate(prevProps: ServiceProps) {
const props = this.props; const props = this.props;
const store = props.store; const store = props.store;
isApiOutdated(prevProps.api, props.api, prevProps.data, props.data) && store isApiOutdated(prevProps.api, props.api, prevProps.data, props.data) &&
.fetchData(props.api as Api, store.data) store.fetchData(props.api as Api, store.data).then(this.initInterval);
.then(this.initInterval);
isApiOutdated(prevProps.schemaApi, props.schemaApi, prevProps.data, props.data) && store isApiOutdated(prevProps.schemaApi, props.schemaApi, prevProps.data, props.data) &&
.fetchSchema(props.schemaApi as Api, store.data) store.fetchSchema(props.schemaApi as Api, store.data).then(this.initInterval);
.then(this.initInterval);
} }
componentWillUnmount() { componentWillUnmount() {
@ -94,46 +76,40 @@ export default class Service extends React.Component<ServiceProps> {
} }
initInterval(value: any) { initInterval(value: any) {
const { const {interval, silentPolling, stopAutoRefreshWhen, data} = this.props;
interval,
silentPolling,
stopAutoRefreshWhen,
data
} = this.props;
interval interval &&
&& this.mounted this.mounted &&
&& (!stopAutoRefreshWhen || !evalExpression(stopAutoRefreshWhen, data)) (!stopAutoRefreshWhen || !evalExpression(stopAutoRefreshWhen, data)) &&
&& (this.timer = setTimeout(silentPolling ? this.silentReload : this.reload, Math.max(interval, 3000))); (this.timer = setTimeout(silentPolling ? this.silentReload : this.reload, Math.max(interval, 3000)));
return value; return value;
} }
reload(subpath?: string, query?: any, ctx?: RendererData, silent?: boolean) { reload(subpath?: string, query?: any, ctx?: RendererData, silent?: boolean) {
if (query) { if (query) {
return this.receive(query); return this.receive(query);
} }
const { const {schemaApi, fetchSchema, api, fetch, store} = this.props;
schemaApi,
fetchSchema,
api,
fetch,
store
} = this.props;
clearTimeout(this.timer); clearTimeout(this.timer);
if (schemaApi && fetchSchema !== false && (!(schemaApi as ApiObject).sendOn || evalExpression((schemaApi as ApiObject).sendOn as string, store.data))) { if (
store schemaApi &&
.fetchSchema(schemaApi, store.data) fetchSchema !== false &&
.then(this.initInterval); (!(schemaApi as ApiObject).sendOn || evalExpression((schemaApi as ApiObject).sendOn as string, store.data))
) {
store.fetchSchema(schemaApi, store.data).then(this.initInterval);
} }
if (api && fetch !== false && (!(api as ApiObject).sendOn || evalExpression((api as ApiObject).sendOn as string, store.data))) { if (
api &&
fetch !== false &&
(!(api as ApiObject).sendOn || evalExpression((api as ApiObject).sendOn as string, store.data))
) {
store store
.fetchData(api, store.data, { .fetchData(api, store.data, {
silent silent,
}) })
.then(this.initInterval); .then(this.initInterval);
} }
@ -143,66 +119,61 @@ export default class Service extends React.Component<ServiceProps> {
this.reload(target, query, undefined, true); this.reload(target, query, undefined, true);
} }
receive(values:object) { receive(values: object) {
const { const {store} = this.props;
store
} = this.props;
store.updateData(values); store.updateData(values);
this.reload(); this.reload();
} }
handleQuery(query:any) { handleQuery(query: any) {
this.receive(query); this.receive(query);
} }
renderBody() { renderBody() {
const { const {render, store, body: schema, classnames: cx} = this.props;
render,
store,
body: schema,
classnames: cx
} = this.props;
return ( return (
<div className={cx("Service-body")}> <div className={cx('Service-body')}>
{render('body', store.schema || schema, { {
key: store.schemaKey || 'body', render('body', store.schema || schema, {
onQuery: this.handleQuery key: store.schemaKey || 'body',
}) as JSX.Element} onQuery: this.handleQuery,
}) as JSX.Element
}
</div> </div>
); );
} }
render() { render() {
const { const {className, store, render, classPrefix: ns, classnames: cx} = this.props;
className,
store,
render,
classPrefix: ns,
classnames: cx
} = this.props;
return ( return (
<div <div className={cx(`${ns}Service`, className)}>
className={cx(`${ns}Service`, className)}
>
{store.error ? ( {store.error ? (
<div className={cx(`Alert Alert--danger`)}> <div className={cx(`Alert Alert--danger`)}>
<button className={cx("Alert-close")} onClick={() => store.updateMessage('')} type="button"><span>×</span></button> <button className={cx('Alert-close')} onClick={() => store.updateMessage('')} type="button">
<span>×</span>
</button>
{store.msg} {store.msg}
</div> </div>
) : null} ) : null}
{this.renderBody()} {this.renderBody()}
{store.loading ? render('info', { {store.loading
type: 'spinner', ? render(
overlay: true 'info',
}, { {
key: 'info', type: 'spinner',
size: 'lg', overlay: true,
}) : null} },
{
key: 'info',
size: 'lg',
}
)
: null}
</div> </div>
); );
} }
@ -211,7 +182,7 @@ export default class Service extends React.Component<ServiceProps> {
@Renderer({ @Renderer({
test: /(^|\/)service$/, test: /(^|\/)service$/,
storeType: ServiceStore.name, storeType: ServiceStore.name,
name: 'service' name: 'service',
}) })
export class ServiceRenderer extends Service { export class ServiceRenderer extends Service {
static contextType = ScopedContext; static contextType = ScopedContext;
@ -227,4 +198,4 @@ export class ServiceRenderer extends Service {
const scoped = this.context as IScopedContext; const scoped = this.context as IScopedContext;
scoped.unRegisterComponent(this); scoped.unRegisterComponent(this);
} }
}; }

View File

@ -1,9 +1,8 @@
import Spinner from "../components/Spinner"; import Spinner from '../components/Spinner';
import { Renderer } from "../factory"; import {Renderer} from '../factory';
@Renderer({ @Renderer({
test: /(^|\/)spinner$/, test: /(^|\/)spinner$/,
name: 'spinner' name: 'spinner',
}) })
export class SpinnerRenderer extends Spinner {} export class SpinnerRenderer extends Spinner {}

View File

@ -1,17 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { import {Api, SchemaNode, PlainObject} from '../types';
Api, import {filter} from '../utils/tpl';
SchemaNode,
PlainObject
} from '../types';
import {
filter
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
export interface StatusProps extends RendererProps { export interface StatusProps extends RendererProps {
@ -21,11 +12,11 @@ export interface StatusProps extends RendererProps {
} }
export class StatusField extends React.Component<StatusProps, object> { export class StatusField extends React.Component<StatusProps, object> {
static defaultProps:Partial<StatusProps> = { static defaultProps: Partial<StatusProps> = {
placeholder: '-', placeholder: '-',
map: { map: {
0: 'fa fa-times text-danger', 0: 'fa fa-times text-danger',
1: 'fa fa-check text-success' 1: 'fa fa-check text-success',
}, },
labelMap: { labelMap: {
// 0: '失败', // 0: '失败',
@ -34,19 +25,12 @@ export class StatusField extends React.Component<StatusProps, object> {
}; };
render() { render() {
const { const {className, placeholder, map, labelMap, classnames: cx, data} = this.props;
className,
placeholder,
map,
labelMap,
classnames: cx,
data
} = this.props;
let value = this.props.value; let value = this.props.value;
let viewValue:React.ReactNode = <span className="text-muted">{placeholder}</span>; let viewValue: React.ReactNode = <span className="text-muted">{placeholder}</span>;
let wrapClassName:string = ''; let wrapClassName: string = '';
if (value !== undefined && value !== "" && map) { if (value !== undefined && value !== '' && map) {
if (typeof value === 'boolean') { if (typeof value === 'boolean') {
value = value ? 1 : 0; value = value ? 1 : 0;
} else if (/^\d+$/.test(value)) { } else if (/^\d+$/.test(value)) {
@ -54,23 +38,24 @@ export class StatusField extends React.Component<StatusProps, object> {
} }
wrapClassName = `StatusField--${value}`; wrapClassName = `StatusField--${value}`;
viewValue = (<i className={cx("StatusField-icon", map[value])} key="icon" />); viewValue = <i className={cx('StatusField-icon', map[value])} key="icon" />;
if (labelMap && labelMap[value]) { if (labelMap && labelMap[value]) {
viewValue = [viewValue, (<span className={cx("StatusField-label")} key="label">{filter(labelMap[value], data)}</span>)]; viewValue = [
viewValue,
<span className={cx('StatusField-label')} key="label">
{filter(labelMap[value], data)}
</span>,
];
} }
} }
return ( return <span className={cx('StatusField', wrapClassName, className)}>{viewValue}</span>;
<span className={cx('StatusField', wrapClassName, className)}>
{viewValue}
</span>
);
} }
} }
@Renderer({ @Renderer({
test: /(^|\/)status$/, test: /(^|\/)status$/,
name: 'status' name: 'status',
}) })
export class StatusFieldRenderer extends StatusField {}; export class StatusFieldRenderer extends StatusField {}

View File

@ -1,17 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { import {Api, SchemaNode, PlainObject} from '../types';
Api, import {filter} from '../utils/tpl';
SchemaNode,
PlainObject
} from '../types';
import {
filter
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
import Switch from '../components/Switch'; import Switch from '../components/Switch';
@ -24,34 +15,32 @@ export interface SwitchProps extends RendererProps {
} }
export class SwitchField extends React.Component<SwitchProps, object> { export class SwitchField extends React.Component<SwitchProps, object> {
static defaultProps:Partial<SwitchProps> = { static defaultProps: Partial<SwitchProps> = {
placeholder: '-', placeholder: '-',
trueValue: true, trueValue: true,
falseValue: false, falseValue: false,
readOnly: true, readOnly: true,
saveImmediately: false saveImmediately: false,
}; };
constructor(props:SwitchProps) { constructor(props: SwitchProps) {
super(props); super(props);
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
} }
handleChange(checked:boolean) { handleChange(checked: boolean) {
const { const {onQuickChange, name, trueValue, falseValue, saveImmediately, readOnly, disabled} = this.props;
onQuickChange,
name,
trueValue,
falseValue,
saveImmediately,
readOnly,
disabled
} = this.props;
onQuickChange && !readOnly && !disabled && onQuickChange({ onQuickChange &&
[name as string]: checked ? trueValue : falseValue !readOnly &&
}, saveImmediately); !disabled &&
onQuickChange(
{
[name as string]: checked ? trueValue : falseValue,
},
saveImmediately
);
} }
render() { render() {
@ -64,19 +53,19 @@ export class SwitchField extends React.Component<SwitchProps, object> {
falseValue, falseValue,
onQuickChange, onQuickChange,
option, option,
disabled disabled,
} = this.props; } = this.props;
let viewValue:React.ReactNode = <span className="text-muted">{placeholder}</span>; let viewValue: React.ReactNode = <span className="text-muted">{placeholder}</span>;
let showOption = false; let showOption = false;
if (value == trueValue || value == falseValue) { if (value == trueValue || value == falseValue) {
showOption = !!option; showOption = !!option;
viewValue = ( viewValue = (
<Switch <Switch
inline inline
classPrefix={ns} classPrefix={ns}
checked={value == trueValue} checked={value == trueValue}
onChange={this.handleChange} onChange={this.handleChange}
disabled={disabled || !onQuickChange} disabled={disabled || !onQuickChange}
/> />
@ -94,6 +83,6 @@ export class SwitchField extends React.Component<SwitchProps, object> {
@Renderer({ @Renderer({
test: /(^|\/)switch$/, test: /(^|\/)switch$/,
name: 'switch' name: 'switch',
}) })
export class SwitchFieldRenderer extends SwitchField {}; export class SwitchFieldRenderer extends SwitchField {}

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,12 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {ServiceStore, IServiceStore} from '../store/service';
RendererProps import {Api, SchemaNode, Schema, Action} from '../types';
} from '../factory'; import {filter, evalExpression} from '../utils/tpl';
import { ServiceStore, IServiceStore } from '../store/service'; import {Tabs as BsTabs, TabContainer, TabContent, TabPane, NavItem, Nav, Tab} from 'react-bootstrap';
import {
Api,
SchemaNode,
Schema,
Action
} from '../types';
import {
filter,
evalExpression
} from '../utils/tpl';
import {
Tabs as BsTabs,
TabContainer,
TabContent,
TabPane,
NavItem,
Nav,
Tab
} from 'react-bootstrap';
import cx = require('classnames'); import cx = require('classnames');
import find = require('lodash/find'); import find = require('lodash/find');
import { isVisible } from '../utils/helper'; import {isVisible} from '../utils/helper';
import findIndex = require('lodash/findIndex'); import findIndex = require('lodash/findIndex');
export type TabProps = Schema & { export type TabProps = Schema & {
@ -50,12 +31,11 @@ export interface TabsState {
} }
export default class Tabs extends React.Component<TabsProps, TabsState> { export default class Tabs extends React.Component<TabsProps, TabsState> {
static defaultProps: Partial<TabsProps> = { static defaultProps: Partial<TabsProps> = {
className: '', className: '',
mode: '', mode: '',
mountOnEnter: true, mountOnEnter: true,
unmountOnExit: false unmountOnExit: false,
}; };
id = Date.now() + ''; id = Date.now() + '';
@ -71,13 +51,13 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
} else if (location && Array.isArray(tabs)) { } else if (location && Array.isArray(tabs)) {
const hash = location.hash.substring(1); const hash = location.hash.substring(1);
const tab: TabProps = find(tabs, tab => tab.hash === hash) as TabProps; const tab: TabProps = find(tabs, tab => tab.hash === hash) as TabProps;
activeKey = tab && tab.hash ? tab.hash : (tabs[0] && tabs[0].hash || 0); activeKey = tab && tab.hash ? tab.hash : (tabs[0] && tabs[0].hash) || 0;
} }
this.state = { this.state = {
prevKey: undefined, prevKey: undefined,
activeKey: activeKey activeKey: activeKey,
} };
this.handleSelect = this.handleSelect.bind(this); this.handleSelect = this.handleSelect.bind(this);
this.currentIndex = this.currentIndex.bind(this); this.currentIndex = this.currentIndex.bind(this);
@ -97,7 +77,7 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
if (tab && tab.hash && tab.hash !== this.state.activeKey) { if (tab && tab.hash && tab.hash !== this.state.activeKey) {
this.setState({ this.setState({
activeKey: tab.hash, activeKey: tab.hash,
prevKey: this.state.activeKey prevKey: this.state.activeKey,
}); });
} }
} else if (props.tabs !== nextProps.tabs) { } else if (props.tabs !== nextProps.tabs) {
@ -111,53 +91,54 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
} }
if (tab) { if (tab) {
activeKey = tab.hash activeKey = tab.hash;
} else if (!nextProps.tabs || !nextProps.tabs.some((item, index) => item.hash ? item.hash === activeKey : index === activeKey)) { } else if (
activeKey = nextProps.tabs && nextProps.tabs[0] && nextProps.tabs[0].hash || 0; !nextProps.tabs ||
!nextProps.tabs.some((item, index) => (item.hash ? item.hash === activeKey : index === activeKey))
) {
activeKey = (nextProps.tabs && nextProps.tabs[0] && nextProps.tabs[0].hash) || 0;
} }
this.setState({ this.setState({
prevKey: undefined, prevKey: undefined,
activeKey: activeKey activeKey: activeKey,
}); });
} }
} }
handleSelect(key: any) { handleSelect(key: any) {
const { const {env} = this.props;
env
} = this.props;
// 是 hash需要更新到地址栏 // 是 hash需要更新到地址栏
if (typeof key === 'string' && env) { if (typeof key === 'string' && env) {
env.updateLocation(`#${key}`) env.updateLocation(`#${key}`);
} else if (typeof this.state.prevKey === 'string' && env) { } else if (typeof this.state.prevKey === 'string' && env) {
env.updateLocation(`#`); env.updateLocation(`#`);
} }
this.setState({ this.setState({
activeKey: key, activeKey: key,
prevKey: this.state.activeKey prevKey: this.state.activeKey,
}); });
} }
switchTo(index: number) { switchTo(index: number) {
const { const {tabs} = this.props;
tabs
} = this.props;
Array.isArray(tabs) && tabs[index] && this.setState({ Array.isArray(tabs) &&
activeKey: tabs[index].hash || index tabs[index] &&
}) this.setState({
activeKey: tabs[index].hash || index,
});
} }
currentIndex(): number { currentIndex(): number {
const { const {tabs} = this.props;
tabs
} = this.props;
return Array.isArray(tabs) return Array.isArray(tabs)
? findIndex(tabs, (tab: TabProps, index) => tab.hash ? tab.hash === this.state.activeKey : index === this.state.activeKey) ? findIndex(tabs, (tab: TabProps, index) =>
tab.hash ? tab.hash === this.state.activeKey : index === this.state.activeKey
)
: -1; : -1;
} }
@ -173,7 +154,7 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
render, render,
data, data,
mode: dMode, mode: dMode,
tabsMode tabsMode,
} = this.props; } = this.props;
if (!Array.isArray(tabs)) { if (!Array.isArray(tabs)) {
@ -186,9 +167,13 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
return ( return (
<TabContainer <TabContainer
id={this.id} id={this.id}
className={cx(`Tabs`, { className={cx(
[`Tabs--${mode}`]: mode `Tabs`,
}, className)} {
[`Tabs--${mode}`]: mode,
},
className
)}
activeKey={this.state.activeKey} activeKey={this.state.activeKey}
onSelect={this.handleSelect} onSelect={this.handleSelect}
> >
@ -199,9 +184,15 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
className={cx('Tabs-link')} className={cx('Tabs-link')}
key={index} key={index}
eventKey={tab.hash || index} eventKey={tab.hash || index}
disabled={tab.disabled || tab.disabledOn && evalExpression(tab.disabledOn, data)} disabled={tab.disabled || (tab.disabledOn && evalExpression(tab.disabledOn, data))}
> >
{tab.icon ? (<div><i className={tab.icon} /> {tab.title}</div>) : tab.title} {tab.icon ? (
<div>
<i className={tab.icon} /> {tab.title}
</div>
) : (
tab.title
)}
</NavItem> </NavItem>
))} ))}
</Nav> </Nav>
@ -218,7 +209,9 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
mountOnEnter={mountOnEnter} mountOnEnter={mountOnEnter}
unmountOnExit={typeof tab.reload === 'boolean' ? tab.reload : tab.unmountOnExit} unmountOnExit={typeof tab.reload === 'boolean' ? tab.reload : tab.unmountOnExit}
> >
{tabRender ? tabRender(tab, this.props) : render(`tab/${index}`, tab.tab || tab.body || '')} {tabRender
? tabRender(tab, this.props)
: render(`tab/${index}`, tab.tab || tab.body || '')}
</TabPane> </TabPane>
))} ))}
</TabContent> </TabContent>
@ -230,6 +223,6 @@ export default class Tabs extends React.Component<TabsProps, TabsState> {
@Renderer({ @Renderer({
test: /(^|\/)tabs$/, test: /(^|\/)tabs$/,
name: 'tabs' name: 'tabs',
}) })
export class TabsRenderer extends Tabs { } export class TabsRenderer extends Tabs {}

View File

@ -1,12 +1,9 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {ServiceStore, IServiceStore} from '../store/service';
RendererProps
} from '../factory';
import { ServiceStore, IServiceStore } from '../store/service';
import * as cx from 'classnames'; import * as cx from 'classnames';
import getExprProperties from '../utils/filter-schema'; import getExprProperties from '../utils/filter-schema';
import { Api, Payload } from '../types'; import {Api, Payload} from '../types';
import update = require('react-addons-update'); import update = require('react-addons-update');
export interface TaskProps extends RendererProps { export interface TaskProps extends RendererProps {
@ -15,7 +12,7 @@ export interface TaskProps extends RendererProps {
checkApi: Api; checkApi: Api;
submitApi: Api; submitApi: Api;
reSubmitApi: Api; reSubmitApi: Api;
tableClassName?: string; tableClassName?: string;
taskNameLabel?: string; taskNameLabel?: string;
operationLabel?: string; operationLabel?: string;
@ -40,12 +37,12 @@ export interface TaskItem {
label?: string; label?: string;
key?: string; key?: string;
remark?: string; remark?: string;
status?:any; status?: any;
} }
export interface TaskState { export interface TaskState {
error?: string; error?: string;
items: Array<TaskItem> items: Array<TaskItem>;
} }
export default class Task extends React.Component<TaskProps, TaskState> { export default class Task extends React.Component<TaskProps, TaskState> {
@ -68,141 +65,150 @@ export default class Task extends React.Component<TaskProps, TaskState> {
errorStatusCode: 3, errorStatusCode: 3,
finishStatusCode: 4, finishStatusCode: 4,
canRetryStatusCode: 5, canRetryStatusCode: 5,
interval: 3000 interval: 3000,
}; };
timer:any; timer: any;
constructor(props:TaskProps) { constructor(props: TaskProps) {
super(props); super(props);
this.state = { this.state = {
items: props.items ? props.items.concat() : [] items: props.items ? props.items.concat() : [],
}; };
this.handleLoaded = this.handleLoaded.bind(this); this.handleLoaded = this.handleLoaded.bind(this);
this.tick = this.tick.bind(this); this.tick = this.tick.bind(this);
} }
componentWillReceiveProps(nextProps:TaskProps) { componentWillReceiveProps(nextProps: TaskProps) {
const props = this.props; const props = this.props;
if (props.items !== nextProps.items) { if (props.items !== nextProps.items) {
this.setState({ this.setState({
items: nextProps.items ? nextProps.items.concat() : [] items: nextProps.items ? nextProps.items.concat() : [],
}); });
} }
} }
componentDidMount() { componentDidMount() {
this.tick(!!this.props.checkApi); this.tick(!!this.props.checkApi);
} }
componentWillUnmount() { componentWillUnmount() {
clearTimeout(this.timer); clearTimeout(this.timer);
} }
tick(force = false) { tick(force = false) {
const { const {loadingStatusCode, data, interval, checkApi, env} = this.props;
loadingStatusCode,
data,
interval,
checkApi,
env
} = this.props;
const items = this.state.items; const items = this.state.items;
// 如果每个 task 都完成了, 则不需要取查看状态. // 如果每个 task 都完成了, 则不需要取查看状态.
if (!force && !items.some(item => item.status === loadingStatusCode)) { if (!force && !items.some(item => item.status === loadingStatusCode)) {
return; return;
} }
if (interval && !checkApi) { if (interval && !checkApi) {
return alert('checkApi 没有设置, 不能及时获取任务状态'); return alert('checkApi 没有设置, 不能及时获取任务状态');
} }
env && env.fetcher(checkApi, data) env &&
.then(this.handleLoaded) env
.catch(e => this.setState({ error: e })) .fetcher(checkApi, data)
.then(this.handleLoaded)
.catch(e => this.setState({error: e}));
} }
handleLoaded(ret:Payload) { handleLoaded(ret: Payload) {
if (!Array.isArray(ret.data)) { if (!Array.isArray(ret.data)) {
return alert('返回格式不正确, 期望 response.data 为数组, 包含每个 task 的状态信息'); return alert('返回格式不正确, 期望 response.data 为数组, 包含每个 task 的状态信息');
} }
this.setState({ this.setState({
items: ret.data items: ret.data,
}); });
const interval = this.props.interval; const interval = this.props.interval;
clearTimeout(this.timer); clearTimeout(this.timer);
this.timer = setTimeout(this.tick, interval); this.timer = setTimeout(this.tick, interval);
} }
submitTask(item:TaskItem, index:number, retry = false) { submitTask(item: TaskItem, index: number, retry = false) {
const { const {submitApi, reSubmitApi, loadingStatusCode, errorStatusCode, data, env} = this.props;
submitApi,
reSubmitApi,
loadingStatusCode,
errorStatusCode,
data,
env
} = this.props;
if (!retry && !submitApi) { if (!retry && !submitApi) {
return alert('submitApi 没有配置'); return alert('submitApi 没有配置');
} else if (retry && !reSubmitApi) { } else if (retry && !reSubmitApi) {
return alert('reSubmitApi 没有配置'); return alert('reSubmitApi 没有配置');
} }
this.setState(update(this.state, { this.setState(
items: { update(this.state, {
$splice: [ items: {
[index, 1, { $splice: [
...item, [
status: loadingStatusCode index,
}] 1,
] {
} ...item,
} as any)); status: loadingStatusCode,
},
env && env.fetcher(retry ? reSubmitApi : submitApi, { ],
...data, ],
...item },
}) } as any)
.then((ret:Payload) => { );
if (ret && ret.data) {
if (Array.isArray(ret.data)) { env &&
this.handleLoaded(ret); env
} else { .fetcher(retry ? reSubmitApi : submitApi, {
const items = this.state.items.map(item => item.key === ret.data.key ? { ...data,
...item, ...item,
...ret.data })
} : item); .then((ret: Payload) => {
this.handleLoaded({ if (ret && ret.data) {
...ret, if (Array.isArray(ret.data)) {
data: items this.handleLoaded(ret);
}); } else {
} const items = this.state.items.map(item =>
return; item.key === ret.data.key
} ? {
...item,
clearTimeout(this.timer); ...ret.data,
this.timer = setTimeout(this.tick, 4); }
}) : item
.catch(e => this.setState(update(this.state, { );
items: { this.handleLoaded({
$splice: [ ...ret,
[index, 1, { data: items,
...item, });
status: errorStatusCode, }
remark: e.message || e return;
}] }
]
} clearTimeout(this.timer);
} as any))) this.timer = setTimeout(this.tick, 4);
})
.catch(e =>
this.setState(
update(this.state, {
items: {
$splice: [
[
index,
1,
{
...item,
status: errorStatusCode,
remark: e.message || e,
},
],
],
},
} as any)
)
);
} }
render() { render() {
const { const {
className, className,
@ -220,11 +226,11 @@ export default class Task extends React.Component<TaskProps, TaskState> {
readyStatusCode, readyStatusCode,
loadingStatusCode, loadingStatusCode,
canRetryStatusCode, canRetryStatusCode,
render render,
} = this.props; } = this.props;
const items = this.state.items; const items = this.state.items;
const error = this.state.error; const error = this.state.error;
return ( return (
<div className={className}> <div className={className}>
<table className={tableClassName}> <table className={tableClassName}>
@ -237,36 +243,48 @@ export default class Task extends React.Component<TaskProps, TaskState> {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{error ? ( {error ? (
<tr> <tr>
<td colSpan={4}><div className="text-danger">{error}</div></td> <td colSpan={4}>
</tr> <div className="text-danger">{error}</div>
) : items.map((item, key) => ( </td>
<tr key={key}> </tr>
<td>{item.label}</td> ) : (
<td> items.map((item, key) => (
{item.status == loadingStatusCode ? ( <tr key={key}>
<i className="fa fa-spinner fa-spin fa-2x fa-fw" /> <td>{item.label}</td>
) : item.status == canRetryStatusCode ? ( <td>
<a {item.status == loadingStatusCode ? (
onClick={() => this.submitTask(item, key, true)} <i className="fa fa-spinner fa-spin fa-2x fa-fw" />
className={cx('btn', retryBtnClassName || btnClassName)} ) : item.status == canRetryStatusCode ? (
> <a
{retryBtnText || btnText} onClick={() => this.submitTask(item, key, true)}
</a> className={cx('btn', retryBtnClassName || btnClassName)}
) : ( >
<a {retryBtnText || btnText}
onClick={() => this.submitTask(item, key)} </a>
className={cx('btn', btnClassName, { disabled: item.status !== readyStatusCode })} ) : (
> <a
{btnText} onClick={() => this.submitTask(item, key)}
</a> className={cx('btn', btnClassName, {
)} disabled: item.status !== readyStatusCode,
</td> })}
<td><span className={cx('label', statusLabelMap && statusLabelMap[item.status || 0])}>{statusTextMap && statusTextMap[item.status || 0]}</span></td> >
<td>{item.remark ? render(`${key}/remark`, item.remark): null}</td> {btnText}
</tr> </a>
))} )}
</td>
<td>
<span
className={cx('label', statusLabelMap && statusLabelMap[item.status || 0])}
>
{statusTextMap && statusTextMap[item.status || 0]}
</span>
</td>
<td>{item.remark ? render(`${key}/remark`, item.remark) : null}</td>
</tr>
))
)}
</tbody> </tbody>
</table> </table>
</div> </div>
@ -276,6 +294,6 @@ export default class Task extends React.Component<TaskProps, TaskState> {
@Renderer({ @Renderer({
test: /(^|\/)tasks$/, test: /(^|\/)tasks$/,
name: 'tasks' name: 'tasks',
}) })
export class TaskRenderer extends Task { } export class TaskRenderer extends Task {}

View File

@ -1,14 +1,9 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {filter} from '../utils/tpl';
RendererProps
} from '../factory';
import {
filter
} from '../utils/tpl';
import * as cx from 'classnames'; import * as cx from 'classnames';
import { anyChanged } from '../utils/helper'; import {anyChanged} from '../utils/helper';
import { escapeHtml } from '../utils/tpl-builtin'; import {escapeHtml} from '../utils/tpl-builtin';
export interface TplProps extends RendererProps { export interface TplProps extends RendererProps {
className?: string; className?: string;
@ -22,48 +17,32 @@ export interface TplProps extends RendererProps {
} }
export class Tpl extends React.Component<TplProps, object> { export class Tpl extends React.Component<TplProps, object> {
static defaultProps:Partial<TplProps> = { static defaultProps: Partial<TplProps> = {
inline: true, inline: true,
placeholder: '', placeholder: '',
value: '' value: '',
}; };
dom:any; dom: any;
constructor(props:TplProps) { constructor(props: TplProps) {
super(props); super(props);
this.htmlRef = this.htmlRef.bind(this); this.htmlRef = this.htmlRef.bind(this);
} }
componentDidUpdate(prevProps: TplProps) {
componentDidUpdate(prevProps:TplProps) { if (anyChanged(['data', 'tpl', 'html', 'text', 'raw', 'value'], this.props, prevProps)) {
if (anyChanged([
'data',
'tpl',
'html',
'text',
'raw',
'value'
], this.props, prevProps)) {
this._render(); this._render();
} }
} }
htmlRef(dom:any) { htmlRef(dom: any) {
this.dom = dom; this.dom = dom;
this._render(); this._render();
} }
getContent() { getContent() {
const { const {tpl, html, text, raw, value, data, placeholder} = this.props;
tpl,
html,
text,
raw,
value,
data,
placeholder
} = this.props;
if (raw) { if (raw) {
return raw; return raw;
@ -74,7 +53,11 @@ export class Tpl extends React.Component<TplProps, object> {
} else if (text) { } else if (text) {
return escapeHtml(filter(text, data)); return escapeHtml(filter(text, data));
} else { } else {
return (value == null || value === '' ? `<span class="text-muted">${placeholder}</span>` : (typeof value === 'string' ? value: JSON.stringify(value))); return value == null || value === ''
? `<span class="text-muted">${placeholder}</span>`
: typeof value === 'string'
? value
: JSON.stringify(value);
} }
} }
@ -87,23 +70,16 @@ export class Tpl extends React.Component<TplProps, object> {
} }
render() { render() {
const { const {className, wrapperComponent, inline, classnames: cx} = this.props;
className,
wrapperComponent,
inline,
classnames: cx
} = this.props;
const Component = wrapperComponent || (inline ? 'span' : 'div'); const Component = wrapperComponent || (inline ? 'span' : 'div');
return ( return <Component children={this.getContent()} ref={this.htmlRef} className={cx('TplField', className)} />;
<Component children={this.getContent()} ref={this.htmlRef} className={cx('TplField', className)} />
);
} }
} }
@Renderer({ @Renderer({
test: /(^|\/)(?:tpl|html)$/, test: /(^|\/)(?:tpl|html)$/,
name: 'tpl' name: 'tpl',
}) })
export class TplRenderer extends Tpl {}; export class TplRenderer extends Tpl {}

View File

@ -1,8 +1,7 @@
import * as React from "react"; import * as React from 'react';
import { Renderer, RendererProps } from "../factory"; import {Renderer, RendererProps} from '../factory';
import { Api, SchemaNode, Schema, Action } from "../types"; import {Api, SchemaNode, Schema, Action} from '../types';
import * as cx from "classnames"; import * as cx from 'classnames';
export type Row = Schema & { export type Row = Schema & {
rowClassName?: string; rowClassName?: string;
@ -15,45 +14,37 @@ export interface HBoxProps extends RendererProps {
} }
export default class VBox extends React.Component<HBoxProps, object> { export default class VBox extends React.Component<HBoxProps, object> {
static propsList: Array<string> = ["rows"]; static propsList: Array<string> = ['rows'];
static defaultProps: Partial<HBoxProps> = {}; static defaultProps: Partial<HBoxProps> = {};
renderChild(region:string, node:Schema) { renderChild(region: string, node: Schema) {
const { const {render} = this.props;
render
} = this.props;
return render(region, node); return render(region, node);
} }
renderCell(row: Row, key: any) { renderCell(row: Row, key: any) {
const { const {classPrefix: ns} = this.props;
classPrefix: ns
} = this.props;
return ( return (
<div <div className={cx(`${ns}Vbox-cell`, (row as Row).cellClassName)}>
className={cx(`${ns}Vbox-cell`, (row as Row).cellClassName)}
>
{this.renderChild(`row/${key}`, row)} {this.renderChild(`row/${key}`, row)}
</div> </div>
); );
} }
render() { render() {
const { const {className, rows, classPrefix: ns} = this.props;
className,
rows,
classPrefix: ns
} = this.props;
return ( return (
<div className={cx(`${ns}Vbox`, className)}> <div className={cx(`${ns}Vbox`, className)}>
{Array.isArray(rows) ? rows.map((row, key) => {Array.isArray(rows)
<div className={cx('row-row', (row as Row).rowClassName)} key={key}> ? rows.map((row, key) => (
{this.renderCell(row, key)} <div className={cx('row-row', (row as Row).rowClassName)} key={key}>
</div> {this.renderCell(row, key)}
) : null} </div>
))
: null}
</div> </div>
); );
} }
@ -61,6 +52,6 @@ export default class VBox extends React.Component<HBoxProps, object> {
@Renderer({ @Renderer({
test: /(^|\/)vbox$/, test: /(^|\/)vbox$/,
name: 'vbox' name: 'vbox',
}) })
export class VBoxRenderer extends VBox {} export class VBoxRenderer extends VBox {}

View File

@ -5,19 +5,20 @@
/* eslint fecs-indent: [0, "space", 2, 2] */ /* eslint fecs-indent: [0, "space", 2, 2] */
import * as React from 'react'; import * as React from 'react';
import { Player, Shortcut, BigPlayButton } from 'video-react'; import {Player, Shortcut, BigPlayButton} from 'video-react';
import { padArr } from '../utils/helper'; import {padArr} from '../utils/helper';
import * as cx from 'classnames'; import * as cx from 'classnames';
import { import {Renderer, RendererProps} from '../factory';
Renderer, import {resolveVariable} from '../utils/tpl-builtin';
RendererProps import {filter} from '../utils/tpl';
} from '../factory';
import { resolveVariable } from '../utils/tpl-builtin';
import { filter } from '../utils/tpl';
// import css // import css
import "video-react/dist/video-react.css"; import 'video-react/dist/video-react.css';
const str2seconds: (str: string) => number = str => str.split(':').reverse().reduce((seconds, value, index) => seconds + (parseInt(value, 10) || 0) * Math.pow(60, index), 0); const str2seconds: (str: string) => number = str =>
str
.split(':')
.reverse()
.reduce((seconds, value, index) => seconds + (parseInt(value, 10) || 0) * Math.pow(60, index), 0);
export interface FlvSourceProps { export interface FlvSourceProps {
src?: string; src?: string;
@ -36,18 +37,21 @@ let currentPlaying: any = null;
export class FlvSource extends React.Component<FlvSourceProps, any> { export class FlvSource extends React.Component<FlvSourceProps, any> {
flvPlayer: any; flvPlayer: any;
componentDidMount() { componentDidMount() {
let { src, video, config, manager, isLive, autoPlay, actions } = this.props; let {src, video, config, manager, isLive, autoPlay, actions} = this.props;
(require as any)(['flv.js'], (flvjs: any) => { (require as any)(['flv.js'], (flvjs: any) => {
// load hls video source base on hls.js // load hls video source base on hls.js
if (flvjs.isSupported()) { if (flvjs.isSupported()) {
video = video || manager.video && manager.video.video; video = video || (manager.video && manager.video.video);
let flvPlayer = flvjs.createPlayer({ let flvPlayer = flvjs.createPlayer(
type: 'flv', {
url: src, type: 'flv',
isLive: isLive url: src,
}, config); isLive: isLive,
},
config
);
flvPlayer.attachMediaElement(video); flvPlayer.attachMediaElement(video);
this.flvPlayer = flvPlayer; this.flvPlayer = flvPlayer;
let loaded = false; let loaded = false;
@ -99,12 +103,7 @@ export class FlvSource extends React.Component<FlvSourceProps, any> {
} }
render() { render() {
return ( return <source src={this.props.src} type={this.props.type || 'video/x-flv'} />;
<source
src={this.props.src}
type={this.props.type || 'video/x-flv'}
/>
);
} }
} }
@ -118,20 +117,20 @@ export interface HlsSourceProps {
autoPlay?: boolean; autoPlay?: boolean;
actions?: any; actions?: any;
order?: number; order?: number;
}; }
export class HlsSource extends React.Component<HlsSourceProps, any> { export class HlsSource extends React.Component<HlsSourceProps, any> {
hls: any; hls: any;
componentDidMount() { componentDidMount() {
let { src, video, config, manager, isLive, autoPlay, actions } = this.props; let {src, video, config, manager, isLive, autoPlay, actions} = this.props;
(require as any)(['hls.js'], (Hls: any) => { (require as any)(['hls.js'], (Hls: any) => {
// load hls video source base on hls.js // load hls video source base on hls.js
if (Hls.isSupported()) { if (Hls.isSupported()) {
video = video || manager.video && manager.video.video; video = video || (manager.video && manager.video.video);
let hls = this.hls = new Hls({ let hls = (this.hls = new Hls({
autoStartLoad: false autoStartLoad: false,
}); }));
hls.attachMedia(video); hls.attachMedia(video);
hls.loadSource(src); hls.loadSource(src);
@ -167,12 +166,7 @@ export class HlsSource extends React.Component<HlsSourceProps, any> {
} }
render() { render() {
return ( return <source src={this.props.src} type={this.props.type || 'application/x-mpegURL'} />;
<source
src={this.props.src}
type={this.props.type || 'application/x-mpegURL'}
/>
);
} }
} }
@ -187,14 +181,14 @@ export interface VideoProps extends RendererProps {
export interface VideoState { export interface VideoState {
posterInfo?: any; posterInfo?: any;
videoState?: any; videoState?: any;
}; }
export default class Video extends React.Component<VideoProps, VideoState> { export default class Video extends React.Component<VideoProps, VideoState> {
static defaultProps = { static defaultProps = {
columnsCount: 8, columnsCount: 8,
isLive: false, isLive: false,
jumpFrame: true, jumpFrame: true,
aspectRatio: "auto" aspectRatio: 'auto',
}; };
frameDom: any; frameDom: any;
@ -207,7 +201,7 @@ export default class Video extends React.Component<VideoProps, VideoState> {
this.state = { this.state = {
posterInfo: null, posterInfo: null,
videoState: {} videoState: {},
}; };
this.frameRef = this.frameRef.bind(this); this.frameRef = this.frameRef.bind(this);
@ -223,8 +217,8 @@ export default class Video extends React.Component<VideoProps, VideoState> {
this.setState({ this.setState({
posterInfo: { posterInfo: {
width: image.width, width: image.width,
height: image.height height: image.height,
} },
}); });
image = image.onload = null; image = image.onload = null;
}; };
@ -248,7 +242,7 @@ export default class Video extends React.Component<VideoProps, VideoState> {
player.subscribeToStateChange((state: any) => { player.subscribeToStateChange((state: any) => {
this.setState({ this.setState({
videoState: state videoState: state,
}); });
if (!state.paused) { if (!state.paused) {
@ -259,7 +253,6 @@ export default class Video extends React.Component<VideoProps, VideoState> {
currentPlaying = player; currentPlaying = player;
} }
if (!this.frameDom || !this.times) { if (!this.frameDom || !this.times) {
return; return;
} }
@ -268,7 +261,7 @@ export default class Video extends React.Component<VideoProps, VideoState> {
const times = this.times; const times = this.times;
const len = times.length; const len = times.length;
while (index < len) { while (index < len) {
if (times[index - 1] && state.currentTime <= (times[index + 1] - (times[index + 1] - times[index]) / 2)) { if (times[index - 1] && state.currentTime <= times[index + 1] - (times[index + 1] - times[index]) / 2) {
break; break;
} else if (state.currentTime <= times[index]) { } else if (state.currentTime <= times[index]) {
break; break;
@ -284,7 +277,7 @@ export default class Video extends React.Component<VideoProps, VideoState> {
} }
moveCursorToIndex(index: number) { moveCursorToIndex(index: number) {
const { classPrefix: ns } = this.props; const {classPrefix: ns} = this.props;
if (!this.frameDom || !this.cursorDom) { if (!this.frameDom || !this.cursorDom) {
return; return;
} }
@ -295,7 +288,12 @@ export default class Video extends React.Component<VideoProps, VideoState> {
const item = items[index]; const item = items[index];
const frameRect = this.frameDom.getBoundingClientRect(); const frameRect = this.frameDom.getBoundingClientRect();
const rect = item.getBoundingClientRect(); const rect = item.getBoundingClientRect();
this.cursorDom.setAttribute("style", `width: ${rect.width - 4}px; height: ${rect.height - 4}px; left: ${rect.left + 2 - frameRect.left}px; top: ${rect.top + 2 - frameRect.top}px;`); this.cursorDom.setAttribute(
'style',
`width: ${rect.width - 4}px; height: ${rect.height - 4}px; left: ${rect.left +
2 -
frameRect.left}px; top: ${rect.top + 2 - frameRect.top}px;`
);
} }
} }
@ -316,14 +314,7 @@ export default class Video extends React.Component<VideoProps, VideoState> {
} }
renderFrames() { renderFrames() {
let { let {frames, framesClassName, columnsCount, data, jumpFrame, classPrefix: ns} = this.props;
frames,
framesClassName,
columnsCount,
data,
jumpFrame,
classPrefix: ns
} = this.props;
if (typeof frames === 'string' && frames[0] === '$') { if (typeof frames === 'string' && frames[0] === '$') {
frames = resolveVariable(frames, data); frames = resolveVariable(frames, data);
@ -334,7 +325,7 @@ export default class Video extends React.Component<VideoProps, VideoState> {
} }
const items: Array<object> = []; const items: Array<object> = [];
const times: Array<number> = this.times = []; const times: Array<number> = (this.times = []);
Object.keys(frames).forEach(time => { Object.keys(frames).forEach(time => {
if (!frames[time]) { if (!frames[time]) {
return; return;
@ -344,7 +335,7 @@ export default class Video extends React.Component<VideoProps, VideoState> {
items.push({ items.push({
time: time, time: time,
src: frames[time] src: frames[time],
}); });
}); });
@ -366,15 +357,21 @@ export default class Video extends React.Component<VideoProps, VideoState> {
<div className="pull-in-xxs" key={i}> <div className="pull-in-xxs" key={i}>
<div className={`${ns}Hbox ${ns}Video-frameItem`}> <div className={`${ns}Hbox ${ns}Video-frameItem`}>
{items.map((item, key) => ( {items.map((item, key) => (
<div className={`${ns}Hbox-col Wrapper--xxs ${ns}Video-frame`} key={key} onClick={() => this.jumpToIndex(i * (columnsCount as number) + key)}> <div
className={`${ns}Hbox-col Wrapper--xxs ${ns}Video-frame`}
key={key}
onClick={() => this.jumpToIndex(i * (columnsCount as number) + key)}
>
<img className="w-full" alt="poster" src={item.src} /> <img className="w-full" alt="poster" src={item.src} />
<div className={`${ns}Text--center`}>{item.time}</div> <div className={`${ns}Text--center`}>{item.time}</div>
</div> </div>
))} ))}
{/* 补充空白 */restCount ? blankArray.map((_, index) => ( {/* 补充空白 */ restCount
<div className={`${ns}Hbox-col Wrapper--xxs`} key={`blank_${index}`} /> ? blankArray.map((_, index) => (
)) : null} <div className={`${ns}Hbox-col Wrapper--xxs`} key={`blank_${index}`} />
))
: null}
</div> </div>
</div> </div>
); );
@ -398,18 +395,18 @@ export default class Video extends React.Component<VideoProps, VideoState> {
videoType, videoType,
playerClassName, playerClassName,
classPrefix: ns, classPrefix: ns,
aspectRatio aspectRatio,
} = this.props; } = this.props;
let source = this.props.src || name && data && (data as any)[name] || amisConfig && amisConfig.value; let source = this.props.src || (name && data && (data as any)[name]) || (amisConfig && amisConfig.value);
const videoState = this.state.videoState; const videoState = this.state.videoState;
let highlight = videoState.duration && minVideoDuration && videoState.duration < minVideoDuration; let highlight = videoState.duration && minVideoDuration && videoState.duration < minVideoDuration;
let src = filter(source, data); let src = filter(source, data);
let sourceNode; let sourceNode;
if (src && /\.flv(?:$|\?)/.test(src) && isLive || videoType === 'video/x-flv') { if ((src && /\.flv(?:$|\?)/.test(src) && isLive) || videoType === 'video/x-flv') {
sourceNode = <FlvSource autoPlay={autoPlay} order={999.0} isLive={isLive} src={src} />; sourceNode = <FlvSource autoPlay={autoPlay} order={999.0} isLive={isLive} src={src} />;
} else if (src && /\.m3u8(?:$|\?)/.test(src) || videoType === 'application/x-mpegURL') { } else if ((src && /\.m3u8(?:$|\?)/.test(src)) || videoType === 'application/x-mpegURL') {
sourceNode = <HlsSource autoPlay={autoPlay} order={999.0} src={src} />; sourceNode = <HlsSource autoPlay={autoPlay} order={999.0} src={src} />;
} else { } else {
sourceNode = <source src={src} />; sourceNode = <source src={src} />;
@ -430,25 +427,21 @@ export default class Video extends React.Component<VideoProps, VideoState> {
<Shortcut disabled /> <Shortcut disabled />
</Player> </Player>
{highlight ? (<p className={`m-t-xs ${ns}Text--danger`}> {minVideoDuration} </p>) : null} {highlight ? <p className={`m-t-xs ${ns}Text--danger`}> {minVideoDuration} </p> : null}
</div> </div>
); );
} }
renderPosterAndPlayer() { renderPosterAndPlayer() {
let { let {poster, data, locals, minPosterDimension, classPrefix: ns} = this.props;
poster,
data,
locals,
minPosterDimension,
classPrefix: ns
} = this.props;
const posterInfo = this.state.posterInfo || {}; const posterInfo = this.state.posterInfo || {};
let dimensionClassName = ''; let dimensionClassName = '';
if (posterInfo && minPosterDimension if (
&& (minPosterDimension.width || minPosterDimension.height) posterInfo &&
&& (minPosterDimension.width > posterInfo.width || minPosterDimension.height > posterInfo.height) minPosterDimension &&
(minPosterDimension.width || minPosterDimension.height) &&
(minPosterDimension.width > posterInfo.width || minPosterDimension.height > posterInfo.height)
) { ) {
dimensionClassName = `${ns}Text--danger`; dimensionClassName = `${ns}Text--danger`;
} }
@ -458,24 +451,39 @@ export default class Video extends React.Component<VideoProps, VideoState> {
<div className={`${ns}Hbox`}> <div className={`${ns}Hbox`}>
<div className={`${ns}Hbox-col`}> <div className={`${ns}Hbox-col`}>
<div className="Wrapper--xs"> <div className="Wrapper--xs">
<img onLoad={this.onImageLoaded as any} className="w-full" alt="poster" src={filter(poster, data)} /> <img
<p className="m-t-xs"> <span className={dimensionClassName}>{posterInfo.width || '-'} x {posterInfo.height || '-'}</span> onLoad={this.onImageLoaded as any}
{dimensionClassName ? (<span> <span className={`${ns}Text--danger`}>{minPosterDimension.width || '-'} x {minPosterDimension.height || '-'}</span></span>) : null} className="w-full"
alt="poster"
src={filter(poster, data)}
/>
<p className="m-t-xs">
{' '}
<span className={dimensionClassName}>
{posterInfo.width || '-'} x {posterInfo.height || '-'}
</span>
{dimensionClassName ? (
<span>
{' '}
{' '}
<span className={`${ns}Text--danger`}>
{minPosterDimension.width || '-'} x {minPosterDimension.height || '-'}
</span>
</span>
) : null}
</p> </p>
</div> </div>
</div> </div>
<div className={`${ns}Hbox-col`}><div className="Wrapper--xs">{this.renderPlayer()}</div></div> <div className={`${ns}Hbox-col`}>
<div className="Wrapper--xs">{this.renderPlayer()}</div>
</div>
</div> </div>
</div> </div>
); );
} }
render() { render() {
let { let {splitPoster, className, classPrefix: ns} = this.props;
splitPoster,
className,
classPrefix: ns
} = this.props;
return ( return (
<div className={cx(`${ns}Video`, className)} onClick={this.onClick as any}> <div className={cx(`${ns}Video`, className)} onClick={this.onClick as any}>
@ -486,9 +494,8 @@ export default class Video extends React.Component<VideoProps, VideoState> {
} }
} }
@Renderer({ @Renderer({
test: /(^|\/)video$/, test: /(^|\/)video$/,
name: 'video' name: 'video',
}) })
export class VideoRenderer extends Video { }; export class VideoRenderer extends Video {}

View File

@ -1,27 +1,16 @@
import * as React from 'react'; import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import Scoped, { ScopedContext, IScopedContext } from '../Scoped'; import Scoped, {ScopedContext, IScopedContext} from '../Scoped';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {ServiceStore, IServiceStore} from '../store/service'; import {ServiceStore, IServiceStore} from '../store/service';
import { import {Api, SchemaNode, Schema, Action} from '../types';
Api, import {filter, evalExpression} from '../utils/tpl';
SchemaNode,
Schema,
Action
} from '../types';
import {
filter,
evalExpression
} from '../utils/tpl';
import cx = require('classnames'); import cx = require('classnames');
import { observer } from 'mobx-react'; import {observer} from 'mobx-react';
import { createObject, until } from '../utils/helper'; import {createObject, until} from '../utils/helper';
import { buildApi, isValidApi, isApiOutdated } from '../utils/api'; import {buildApi, isValidApi, isApiOutdated} from '../utils/api';
import { IFormStore } from '../store/form'; import {IFormStore} from '../store/form';
export type TabProps = Schema & { export type TabProps = Schema & {
title?: string; // 标题 title?: string; // 标题
@ -40,7 +29,7 @@ export interface WizardProps extends RendererProps {
actionNextSaveLabel?: string; actionNextSaveLabel?: string;
actionFinishLabel?: string; actionFinishLabel?: string;
mode?: 'horizontal' | 'vertical'; mode?: 'horizontal' | 'vertical';
onFinished: (values:object, action:any) => any; onFinished: (values: object, action: any) => any;
} }
export interface WizardState { export interface WizardState {
@ -48,8 +37,7 @@ export interface WizardState {
} }
export default class Wizard extends React.Component<WizardProps, WizardState> { export default class Wizard extends React.Component<WizardProps, WizardState> {
static defaultProps: Partial<WizardProps> = {
static defaultProps:Partial<WizardProps>= {
mode: 'horizontal', // vertical mode: 'horizontal', // vertical
readOnly: false, readOnly: false,
messages: {}, messages: {},
@ -57,29 +45,29 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
actionPrevLabel: '上一步', actionPrevLabel: '上一步',
actionNextLabel: '下一步', actionNextLabel: '下一步',
actionNextSaveLabel: '保存并下一步', actionNextSaveLabel: '保存并下一步',
actionFinishLabel: '完成' actionFinishLabel: '完成',
}; };
static propsList: Array<string> = [ static propsList: Array<string> = [
"steps", 'steps',
"mode", 'mode',
"messages", 'messages',
"actionClassName", 'actionClassName',
"actionPrevLabel", 'actionPrevLabel',
"actionNextLabel", 'actionNextLabel',
"actionNextSaveLabel", 'actionNextSaveLabel',
"actionFinishLabel", 'actionFinishLabel',
"onFinished" 'onFinished',
]; ];
dom:any; dom: any;
form:any; form: any;
asyncCancel:() => void; asyncCancel: () => void;
constructor(props:WizardProps) { constructor(props: WizardProps) {
super(props); super(props);
this.state = { this.state = {
currentStep: -1 // init 完后会设置成 1 currentStep: -1, // init 完后会设置成 1
}; };
this.handleAction = this.handleAction.bind(this); this.handleAction = this.handleAction.bind(this);
@ -100,11 +88,8 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
initFinishedField, initFinishedField,
store, store,
data, data,
messages: { messages: {fetchSuccess, fetchFailed},
fetchSuccess, onInit,
fetchFailed
},
onInit
} = this.props; } = this.props;
if (initApi && initFetch !== false && (!initApi.sendOn || evalExpression(initApi.sendOn, data))) { if (initApi && initFetch !== false && (!initApi.sendOn || evalExpression(initApi.sendOn, data))) {
@ -117,44 +102,45 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
return; return;
} }
return until(() => store.checkRemote(initAsyncApi, store.data) return until(
, (ret:any) => ret && ret[initFinishedField || 'finished'] () => store.checkRemote(initAsyncApi, store.data),
, (cancel) => this.asyncCancel = cancel); (ret: any) => ret && ret[initFinishedField || 'finished'],
} cancel => (this.asyncCancel = cancel)
);
},
}) })
.then(value => { .then(value => {
onInit && onInit(store.data); onInit && onInit(store.data);
if (value && value.data && typeof value.data.step === 'number') { if (value && value.data && typeof value.data.step === 'number') {
this.setState({ this.setState({
currentStep: value.data.step currentStep: value.data.step,
}); });
} else { } else {
this.setState({ this.setState({
currentStep: 1 currentStep: 1,
}); });
} }
return value; return value;
}); });
} else { } else {
this.setState({ this.setState(
currentStep: 1 {
}, () => onInit && onInit(store.data)); currentStep: 1,
},
() => onInit && onInit(store.data)
);
} }
} }
componentDidUpdate(prevProps:WizardProps) { componentDidUpdate(prevProps: WizardProps) {
const props = this.props; const props = this.props;
const { const {store, fetchSuccess, fetchFailed} = props;
store,
fetchSuccess,
fetchFailed
} = props;
if (isApiOutdated(prevProps.initApi, props.initApi, prevProps.data, props.data)) { if (isApiOutdated(prevProps.initApi, props.initApi, prevProps.data, props.data)) {
store.fetchData(props.initApi, store.data, { store.fetchData(props.initApi, store.data, {
successMessage: fetchSuccess, successMessage: fetchSuccess,
errorMessage: fetchFailed errorMessage: fetchFailed,
}); });
} }
} }
@ -163,16 +149,16 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
this.asyncCancel && this.asyncCancel(); this.asyncCancel && this.asyncCancel();
} }
gotoStep(index:number) { gotoStep(index: number) {
const steps = this.props.steps || []; const steps = this.props.steps || [];
index = Math.max(Math.min(steps.length, index), 1); index = Math.max(Math.min(steps.length, index), 1);
this.setState({ this.setState({
currentStep: index currentStep: index,
}); });
} }
formRef(ref:any) { formRef(ref: any) {
if (ref) { if (ref) {
while (ref && ref.getWrappedInstance) { while (ref && ref.getWrappedInstance) {
ref = ref.getWrappedInstance(); ref = ref.getWrappedInstance();
@ -183,15 +169,15 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
} }
} }
submitToTarget(target:string, values:object) { submitToTarget(target: string, values: object) {
throw new Error('Please implements this!'); throw new Error('Please implements this!');
} }
reloadTarget(target:string, data:any) { reloadTarget(target: string, data: any) {
throw new Error('Please implements this!'); throw new Error('Please implements this!');
} }
domRef(ref:any) { domRef(ref: any) {
this.dom = ref; this.dom = ref;
} }
@ -199,50 +185,37 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
return this.dom; return this.dom;
} }
handleAction(e: React.UIEvent<any> | void, action: Action, data: object, throwErrors?:boolean) { handleAction(e: React.UIEvent<any> | void, action: Action, data: object, throwErrors?: boolean) {
const { const {onAction, store} = this.props;
onAction,
store
} = this.props;
if (action.actionType === 'next' || action.type === 'submit') { if (action.actionType === 'next' || action.type === 'submit') {
this.form.doAction({ this.form.doAction(
...action, {
actionType: 'submit' ...action,
}, data); actionType: 'submit',
},
data
);
} else if (action.actionType === 'prev') { } else if (action.actionType === 'prev') {
this.gotoStep(this.state.currentStep - 1); this.gotoStep(this.state.currentStep - 1);
} else if (action.type === 'reset') { } else if (action.type === 'reset') {
this.form.reset(); this.form.reset();
} else if (action.actionType === 'dialog') { } else if (action.actionType === 'dialog') {
store.openDialog(data); store.openDialog(data);
} else if (onAction) { } else if (onAction) {
onAction(e, action, data); onAction(e, action, data);
} }
} }
handleChange(values:object) { handleChange(values: object) {
const { const {store} = this.props;
store
} = this.props;
store.updateData(values); store.updateData(values);
} }
// 接管里面 form 的提交,不能直接让 form 提交,因为 wizard 自己需要知道进度。 // 接管里面 form 的提交,不能直接让 form 提交,因为 wizard 自己需要知道进度。
handleSubmit(values:object, action:Action) { handleSubmit(values: object, action: Action) {
const { const {store, steps, api, asyncApi, finishedField, target, redirect, reload, env, onFinished} = this.props;
store,
steps,
api,
asyncApi,
finishedField,
target,
redirect,
reload,
env,
onFinished
} = this.props;
const step = steps[this.state.currentStep - 1]; const step = steps[this.state.currentStep - 1];
store.updateData(values); store.updateData(values);
@ -250,9 +223,10 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
if (this.state.currentStep < steps.length) { if (this.state.currentStep < steps.length) {
let finnalAsyncApi = action.asyncApi || asyncApi; let finnalAsyncApi = action.asyncApi || asyncApi;
finnalAsyncApi && store.updateData({ finnalAsyncApi &&
[finishedField || 'finished']: false store.updateData({
}); [finishedField || 'finished']: false,
});
if (step.api || action.api) { if (step.api || action.api) {
store store
@ -261,30 +235,34 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
if (!finnalAsyncApi || store.data[finishedField || 'finished']) { if (!finnalAsyncApi || store.data[finishedField || 'finished']) {
return; return;
} }
return until(() => store.checkRemote(finnalAsyncApi as Api, store.data) return until(
, (ret:any) => ret && ret[finishedField || 'finished'] () => store.checkRemote(finnalAsyncApi as Api, store.data),
, (cancel) => this.asyncCancel = cancel); (ret: any) => ret && ret[finishedField || 'finished'],
} cancel => (this.asyncCancel = cancel)
);
},
}) })
.then(() => this.gotoStep(this.state.currentStep + 1)) .then(() => this.gotoStep(this.state.currentStep + 1))
.catch(e => { .catch(e => {
// do nothing // do nothing
}) });
} else { } else {
this.gotoStep(this.state.currentStep + 1); this.gotoStep(this.state.currentStep + 1);
} }
} else { // 最后一步 } else {
// 最后一步
if (target) { if (target) {
this.submitToTarget(target, store.data); this.submitToTarget(target, store.data);
} else if (action.api || step.api || api) { } else if (action.api || step.api || api) {
let finnalAsyncApi = action.asyncApi || step.asyncApi || asyncApi; let finnalAsyncApi = action.asyncApi || step.asyncApi || asyncApi;
finnalAsyncApi && store.updateData({ finnalAsyncApi &&
[finishedField || 'finished']: false store.updateData({
}); [finishedField || 'finished']: false,
});
const formStore = this.form ? this.form.props.store as IFormStore : store; const formStore = this.form ? (this.form.props.store as IFormStore) : store;
store.markSaving(true); store.markSaving(true);
formStore formStore
@ -293,11 +271,13 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
if (!finnalAsyncApi || store.data[finishedField || 'finished']) { if (!finnalAsyncApi || store.data[finishedField || 'finished']) {
return; return;
} }
return until(() => store.checkRemote(finnalAsyncApi as Api, store.data) return until(
, (ret:any) => ret && ret[finishedField || 'finished'] () => store.checkRemote(finnalAsyncApi as Api, store.data),
, (cancel) => this.asyncCancel = cancel); (ret: any) => ret && ret[finishedField || 'finished'],
} cancel => (this.asyncCancel = cancel)
);
},
}) })
.then(value => { .then(value => {
store.markSaving(false); store.markSaving(false);
@ -305,13 +285,13 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
// 如果是 false 后面的操作就不执行 // 如果是 false 后面的操作就不执行
return value; return value;
} }
if (redirect) { if (redirect) {
env.updateLocation(filter(redirect, store.data)); env.updateLocation(filter(redirect, store.data));
} else if (reload) { } else if (reload) {
this.reloadTarget(reload, store.data); this.reloadTarget(reload, store.data);
} }
return value; return value;
}) })
.catch(e => { .catch(e => {
@ -324,10 +304,8 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
return false; return false;
} }
handleDialogConfirm(values: object[], action:Action, ctx:any, targets:Array<any>) { handleDialogConfirm(values: object[], action: Action, ctx: any, targets: Array<any>) {
const { const {store} = this.props;
store
} = this.props;
if (action.mergeData && values.length === 1 && values[0] && targets[0].props.type === 'form') { if (action.mergeData && values.length === 1 && values[0] && targets[0].props.type === 'form') {
store.updateData(values[0]); store.updateData(values[0]);
@ -337,48 +315,44 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
} }
handleDialogClose() { handleDialogClose() {
const { const {store} = this.props;
store
} = this.props;
store.closeDialog(); store.closeDialog();
} }
renderSteps() { renderSteps() {
const { const {steps, store, mode, classPrefix: ns} = this.props;
steps,
store,
mode,
classPrefix: ns
} = this.props;
const currentStep = this.state.currentStep; const currentStep = this.state.currentStep;
return ( return (
<div className={`${ns}Wizard-steps clearfix ${ns}Wizard--${mode}`} id="form-wizard"> <div className={`${ns}Wizard-steps clearfix ${ns}Wizard--${mode}`} id="form-wizard">
{Array.isArray(steps) && steps.length ? ( {Array.isArray(steps) && steps.length ? (
<ul> <ul>
{steps.map((step, key) => { {steps.map((step, key) => {
const canJump = isJumpable(step, key, currentStep, store.data); const canJump = isJumpable(step, key, currentStep, store.data);
return ( return (
<li <li
key={key} key={key}
className={cx({ className={cx({
'is-complete': canJump, 'is-complete': canJump,
'is-active': currentStep === key + 1 'is-active': currentStep === key + 1,
})}
onClick={() => canJump ? this.gotoStep(key + 1) : null}
>
<span
className={cx("Badge", {
// 'Badge--success': canJump && currentStep != key + 1,
'Badge--info': currentStep === key + 1 || canJump && currentStep != key + 1
})} })}
>{key + 1}</span> onClick={() => (canJump ? this.gotoStep(key + 1) : null)}
{step.title || step.label || `${key + 1}`} >
</li> <span
); className={cx('Badge', {
})} // 'Badge--success': canJump && currentStep != key + 1,
</ul> 'Badge--info':
currentStep === key + 1 || (canJump && currentStep != key + 1),
})}
>
{key + 1}
</span>
{step.title || step.label || `${key + 1}`}
</li>
);
})}
</ul>
) : null} ) : null}
</div> </div>
); );
@ -403,7 +377,7 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
if (!Array.isArray(steps)) { if (!Array.isArray(steps)) {
return null; return null;
} }
const currentStepIndex = this.state.currentStep; const currentStepIndex = this.state.currentStep;
const nextStep = steps[currentStepIndex]; const nextStep = steps[currentStepIndex];
const prevStep = steps[currentStepIndex - 2]; const prevStep = steps[currentStepIndex - 2];
@ -415,58 +389,62 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
} }
const prevCanJump = prevStep ? isJumpable(prevStep, currentStepIndex - 2, currentStepIndex, store.data) : false; const prevCanJump = prevStep ? isJumpable(prevStep, currentStepIndex - 2, currentStepIndex, store.data) : false;
if (step.actions && Array.isArray(step.actions)) { if (step.actions && Array.isArray(step.actions)) {
return step.actions.length ? ( return step.actions.length ? (
<div className={cx('Panel-footer')}> <div className={cx('Panel-footer')}>
{step.actions.map((action:Action, index:number) => render(`action/${index}`, action, { {step.actions.map((action: Action, index: number) =>
key: index, render(`action/${index}`, action, {
onAction: this.handleAction, key: index,
disabled: action.disabled onAction: this.handleAction,
|| waiting disabled:
|| disabled action.disabled ||
|| action.actionType === 'prev' && !prevCanJump waiting ||
|| action.actionType === 'next' && readOnly && (!!step.api || !nextStep) disabled ||
}))} (action.actionType === 'prev' && !prevCanJump) ||
(action.actionType === 'next' && readOnly && (!!step.api || !nextStep)),
})
)}
</div> </div>
) : null; ) : null;
} }
return ( return (
<div className={cx('Panel-footer')}> <div className={cx('Panel-footer')}>
{render(`prev-btn`, { {render(
type: 'button', `prev-btn`,
label: actionPrevLabel, {
actionType: 'prev', type: 'button',
className: actionClassName label: actionPrevLabel,
}, { actionType: 'prev',
disabled: waiting || !prevCanJump || disabled, className: actionClassName,
onAction: this.handleAction },
})} {
disabled: waiting || !prevCanJump || disabled,
{render(`next-btn`, { onAction: this.handleAction,
type: 'button', }
label: !nextStep ? actionFinishLabel : !step.api ? actionNextLabel : actionNextSaveLabel, )}
actionType: 'next',
primary: !nextStep || !!step.api, {render(
className: actionClassName `next-btn`,
}, { {
disabled: waiting || disabled || readOnly && (!!step.api || !nextStep), type: 'button',
onAction: this.handleAction label: !nextStep ? actionFinishLabel : !step.api ? actionNextLabel : actionNextSaveLabel,
})} actionType: 'next',
primary: !nextStep || !!step.api,
className: actionClassName,
},
{
disabled: waiting || disabled || (readOnly && (!!step.api || !nextStep)),
onAction: this.handleAction,
}
)}
</div> </div>
); );
} }
render() { render() {
const { const {className, steps, render, store, mode, classPrefix: ns} = this.props;
className,
steps,
render,
store,
mode,
classPrefix: ns
} = this.props;
const currentStep = this.state.currentStep; const currentStep = this.state.currentStep;
const step = Array.isArray(steps) ? steps[currentStep - 1] : null; const step = Array.isArray(steps) ? steps[currentStep - 1] : null;
@ -475,57 +453,74 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
<div ref={this.domRef} className={cx(`${ns}Panel ${ns}Panel--default ${ns}Wizard`, className)}> <div ref={this.domRef} className={cx(`${ns}Panel ${ns}Panel--default ${ns}Wizard`, className)}>
{this.renderSteps()} {this.renderSteps()}
<div className={`${ns}Wizard-stepContent clearfix`}> <div className={`${ns}Wizard-stepContent clearfix`}>
{step ? render('body', { {step ? (
...step, render(
type: 'form', 'body',
wrapWithPanel: false, {
...step,
type: 'form',
wrapWithPanel: false,
// 接口相关需要外部来接管 // 接口相关需要外部来接管
api: null api: null,
}, { },
key: this.state.currentStep, {
ref: this.formRef, key: this.state.currentStep,
onSubmit: this.handleSubmit, ref: this.formRef,
onAction: this.handleAction, onSubmit: this.handleSubmit,
disabled: store.loading, onAction: this.handleAction,
popOverContainer: this.getPopOverContainer, disabled: store.loading,
onChange: this.handleChange popOverContainer: this.getPopOverContainer,
}) : currentStep === -1 ? '初始中。。' : ( onChange: this.handleChange,
}
)
) : currentStep === -1 ? (
'初始中。。'
) : (
<p className="text-danger"></p> <p className="text-danger"></p>
)} )}
</div> </div>
{render('dialog', { {render(
...(store.action as Action) && (store.action as Action).dialog as object, 'dialog',
type: 'dialog' {
}, { ...((store.action as Action) && ((store.action as Action).dialog as object)),
key: 'dialog', type: 'dialog',
data: store.dialogData, },
onConfirm: this.handleDialogConfirm, {
onClose: this.handleDialogClose, key: 'dialog',
show: store.dialogOpen data: store.dialogData,
})} onConfirm: this.handleDialogConfirm,
onClose: this.handleDialogClose,
show: store.dialogOpen,
}
)}
{this.renderActions()} {this.renderActions()}
{store.loading ? render('spinner', { {store.loading
type: 'spinner', ? render('spinner', {
overlay: true, type: 'spinner',
size: 'lg' overlay: true,
}) : null} size: 'lg',
})
: null}
</div> </div>
); );
} }
} }
function isJumpable(step:any, index:number, currentStep:number, data:any) { function isJumpable(step: any, index: number, currentStep: number, data: any) {
let canJump = false; let canJump = false;
if (step && step.hasOwnProperty('jumpable')) { if (step && step.hasOwnProperty('jumpable')) {
canJump = step.jumpable; canJump = step.jumpable;
} else if (step && step.jumpableOn) { } else if (step && step.jumpableOn) {
canJump = evalExpression(step.jumpableOn, createObject(data, { canJump = evalExpression(
currentStep step.jumpableOn,
})); createObject(data, {
currentStep,
})
);
} else { } else {
canJump = index + 1 < currentStep; canJump = index + 1 < currentStep;
} }
@ -537,7 +532,7 @@ function isJumpable(step:any, index:number, currentStep:number, data:any) {
test: /(^|\/)wizard$/, test: /(^|\/)wizard$/,
storeType: ServiceStore.name, storeType: ServiceStore.name,
name: 'wizard', name: 'wizard',
isolateScope: true isolateScope: true,
}) })
export class WizardRenderer extends Wizard { export class WizardRenderer extends Wizard {
static contextType = ScopedContext; static contextType = ScopedContext;
@ -556,17 +551,17 @@ export class WizardRenderer extends Wizard {
return this.handleAction(undefined, action, data, throwErrors); return this.handleAction(undefined, action, data, throwErrors);
} }
submitToTarget(target:string, values:object) { submitToTarget(target: string, values: object) {
const scoped = this.context as IScopedContext; const scoped = this.context as IScopedContext;
scoped.send(target, values); scoped.send(target, values);
} }
reloadTarget(target:string, data:any) { reloadTarget(target: string, data: any) {
const scoped = this.context as IScopedContext; const scoped = this.context as IScopedContext;
scoped.reload(target, data); scoped.reload(target, data);
} }
handleDialogConfirm(values: object[], action:Action, ctx:any, targets:Array<any>) { handleDialogConfirm(values: object[], action: Action, ctx: any, targets: Array<any>) {
super.handleDialogConfirm(values, action, ctx, targets); super.handleDialogConfirm(values, action, ctx, targets);
const store = this.props.store; const store = this.props.store;

View File

@ -1,49 +1,35 @@
import * as React from 'react'; import * as React from 'react';
import { import {Renderer, RendererProps} from '../factory';
Renderer,
RendererProps
} from '../factory';
import {SchemaNode} from '../types'; import {SchemaNode} from '../types';
import * as cx from 'classnames'; import * as cx from 'classnames';
export interface WrapperProps extends RendererProps { export interface WrapperProps extends RendererProps {
body?: SchemaNode; body?: SchemaNode;
className?: string; className?: string;
children?: JSX.Element | ((props?:any) => JSX.Element); children?: JSX.Element | ((props?: any) => JSX.Element);
size?: 'xs' | 'sm' | 'md' | 'lg' | 'none'; size?: 'xs' | 'sm' | 'md' | 'lg' | 'none';
} }
export default class Wrapper extends React.Component<WrapperProps, object> { export default class Wrapper extends React.Component<WrapperProps, object> {
static propsList: Array<string> = [ static propsList: Array<string> = ['body', 'className', 'children', 'size'];
"body", static defaultProps: Partial<WrapperProps> = {
"className",
"children",
"size"
];
static defaultProps:Partial<WrapperProps>= {
className: 'bg-white', className: 'bg-white',
}; };
renderBody():JSX.Element | null { renderBody(): JSX.Element | null {
const { const {children, body, render} = this.props;
children,
body,
render
} = this.props;
return children ? ( return children
typeof children === 'function' ? children(this.props) as JSX.Element : children as JSX.Element ? typeof children === 'function'
) : body ? ( ? (children(this.props) as JSX.Element)
render('body', body) as JSX.Element : (children as JSX.Element)
) : null; : body
? (render('body', body) as JSX.Element)
: null;
} }
render() { render() {
const { const {className, size, classnames: cx} = this.props;
className,
size,
classnames: cx
} = this.props;
return ( return (
<div <div
@ -57,6 +43,6 @@ export default class Wrapper extends React.Component<WrapperProps, object> {
@Renderer({ @Renderer({
test: /(^|\/)wrapper$/, test: /(^|\/)wrapper$/,
name: 'wrapper' name: 'wrapper',
}) })
export class WrapperRenderer extends Wrapper {}; export class WrapperRenderer extends Wrapper {}

View File

@ -11,35 +11,35 @@ const bsMapping: {
} = { } = {
level: 'bsStyle', level: 'bsStyle',
classPrefix: 'bsClass', classPrefix: 'bsClass',
size: 'bsSize' size: 'bsSize',
}; };
/** /**
* bootstrap bs * bootstrap bs
* *
* @param {Object} rawProps * @param {Object} rawProps
* @return {Object} * @return {Object}
*/ */
export const props2BsProps = (rawProps: { [propName: string]: any; }) => { export const props2BsProps = (rawProps: {[propName: string]: any}) => {
let props: { [propName: string]: any; } = {}; let props: {[propName: string]: any} = {};
Object.keys(rawProps).forEach(key => props[bsMapping[key] || key] = rawProps[key]); Object.keys(rawProps).forEach(key => (props[bsMapping[key] || key] = rawProps[key]));
return props; return props;
}; };
/** /**
* props2BsProps hoc * props2BsProps hoc
* *
* @param {*} ComposedComponent * @param {*} ComposedComponent
* @return {Component} * @return {Component}
*/ */
export const props2BsPropsHoc: (ComposedComponent: React.ComponentType<any>) => React.ComponentType<any> = (ComposedComponent) => { export const props2BsPropsHoc: (
ComposedComponent: React.ComponentType<any>
) => React.ComponentType<any> = ComposedComponent => {
class BsComponent extends React.Component<any> { class BsComponent extends React.Component<any> {
render() { render() {
return ( return <ComposedComponent {...props2BsProps(this.props)} />;
<ComposedComponent {...props2BsProps(this.props)} />
);
} }
} }
@ -63,15 +63,13 @@ function getContainerDimensions(containerNode: any) {
width = window.innerWidth; width = window.innerWidth;
height = window.innerHeight; height = window.innerHeight;
scroll = scroll = getScrollTop(ownerDocument(containerNode).documentElement) || getScrollTop(containerNode);
getScrollTop(ownerDocument(containerNode).documentElement) ||
getScrollTop(containerNode);
} else { } else {
({ width, height } = getOffset(containerNode)); ({width, height} = getOffset(containerNode));
scroll = getScrollTop(containerNode); scroll = getScrollTop(containerNode);
} }
return { width, height, scroll }; return {width, height, scroll};
} }
function getTopDelta(top: any, overlayHeight: any, container: any, padding: any) { function getTopDelta(top: any, overlayHeight: any, container: any, padding: any) {
@ -107,16 +105,14 @@ function getLeftDelta(left: any, overlayWidth: any, container: any, padding: any
return 0; return 0;
} }
export function calculatePosition( export function calculatePosition(placement: any, overlayNode: any, target: any, container: any, padding: any) {
placement: any, overlayNode: any, target: any, container: any, padding: any
) {
const childOffset = container.tagName === 'BODY' ? getOffset(target) : getPosition(target, container); const childOffset = container.tagName === 'BODY' ? getOffset(target) : getPosition(target, container);
const { const {height: overlayHeight, width: overlayWidth} = getOffset(overlayNode);
height: overlayHeight,
width: overlayWidth
} = getOffset(overlayNode);
let positionLeft = 0, positionTop = 0, arrowOffsetLeft: any = '', arrowOffsetTop: any = ''; let positionLeft = 0,
positionTop = 0,
arrowOffsetLeft: any = '',
arrowOffsetTop: any = '';
if (~placement.indexOf('-')) { if (~placement.indexOf('-')) {
const tests = placement.split(/\s+/); const tests = placement.split(/\s+/);
@ -127,14 +123,18 @@ export function calculatePosition(
myX = myX || atX; myX = myX || atX;
myY = myY || atY; myY = myY || atY;
positionLeft = atX === 'left' positionLeft =
? childOffset.left : atX === 'right' atX === 'left'
? (childOffset.left + childOffset.width) ? childOffset.left
: (childOffset.left + childOffset.width / 2); : atX === 'right'
positionTop = atY === 'top' ? childOffset.left + childOffset.width
? childOffset.top : atY === 'bottom' : childOffset.left + childOffset.width / 2;
? (childOffset.top + childOffset.height) positionTop =
: (childOffset.top + childOffset.height / 2); atY === 'top'
? childOffset.top
: atY === 'bottom'
? childOffset.top + childOffset.height
: childOffset.top + childOffset.height / 2;
positionLeft -= myX === 'left' ? 0 : myX === 'right' ? overlayWidth : overlayWidth / 2; positionLeft -= myX === 'left' ? 0 : myX === 'right' ? overlayWidth : overlayWidth / 2;
positionTop -= myY === 'top' ? 0 : myY === 'bottom' ? overlayHeight : overlayHeight / 2; positionTop -= myY === 'top' ? 0 : myY === 'bottom' ? overlayHeight : overlayHeight / 2;
@ -146,12 +146,14 @@ export function calculatePosition(
x: clip.x + positionLeft - childOffset.left, x: clip.x + positionLeft - childOffset.left,
y: clip.y + positionTop - childOffset.top, y: clip.y + positionTop - childOffset.top,
width: overlayWidth, width: overlayWidth,
height: overlayHeight height: overlayHeight,
} };
if ( if (
transformed.x > 0 && (transformed.x + transformed.width) < window.innerWidth transformed.x > 0 &&
&& transformed.y > 0 && (transformed.y + transformed.height) < window.innerHeight transformed.x + transformed.width < window.innerWidth &&
transformed.y > 0 &&
transformed.y + transformed.height < window.innerHeight
) { ) {
break; break;
} }
@ -170,12 +172,10 @@ export function calculatePosition(
} }
positionTop = childOffset.top + (childOffset.height - overlayHeight) / 2; positionTop = childOffset.top + (childOffset.height - overlayHeight) / 2;
const topDelta = getTopDelta( const topDelta = getTopDelta(positionTop, overlayHeight, container, padding);
positionTop, overlayHeight, container, padding
);
positionTop += topDelta; positionTop += topDelta;
arrowOffsetTop = 50 * (1 - 2 * topDelta / overlayHeight) + '%'; arrowOffsetTop = 50 * (1 - (2 * topDelta) / overlayHeight) + '%';
} else if (placement === 'top' || placement === 'bottom') { } else if (placement === 'top' || placement === 'bottom') {
// atY = placement; // atY = placement;
// atX = myX = 'center'; // atX = myX = 'center';
@ -187,22 +187,18 @@ export function calculatePosition(
} }
positionLeft = childOffset.left + (childOffset.width - overlayWidth) / 2; positionLeft = childOffset.left + (childOffset.width - overlayWidth) / 2;
const leftDelta = getLeftDelta( const leftDelta = getLeftDelta(positionLeft, overlayWidth, container, padding);
positionLeft, overlayWidth, container, padding
);
positionLeft += leftDelta; positionLeft += leftDelta;
arrowOffsetLeft = 50 * (1 - 2 * leftDelta / overlayHeight) + '%'; arrowOffsetLeft = 50 * (1 - (2 * leftDelta) / overlayHeight) + '%';
} else if (placement = 'center') { } else if ((placement = 'center')) {
// atX = atY = myX = myY = 'center'; // atX = atY = myX = myY = 'center';
positionLeft = childOffset.left + (childOffset.width - overlayWidth) / 2; positionLeft = childOffset.left + (childOffset.width - overlayWidth) / 2;
positionTop = childOffset.top + (childOffset.height - overlayHeight) / 2; positionTop = childOffset.top + (childOffset.height - overlayHeight) / 2;
arrowOffsetLeft = arrowOffsetTop = void 0; arrowOffsetLeft = arrowOffsetTop = void 0;
} else { } else {
throw new Error( throw new Error(`calcOverlayPosition(): No such placement of "${placement}" found.`);
`calcOverlayPosition(): No such placement of "${placement}" found.`
);
} }
return { positionLeft, positionTop, arrowOffsetLeft, arrowOffsetTop }; return {positionLeft, positionTop, arrowOffsetLeft, arrowOffsetTop};
} }