+ );
+ }
- componentDidMount() {
- document.addEventListener(`click`, this.handleHideFilter);
- }
+ componentDidMount() {
+ document.addEventListener(`click`, this.handleHideFilter);
+ }
- handleAdd(event) {
- const {onAdd} = this.props;
- onAdd && onAdd(event);
- }
+ handleAdd(event) {
+ const { onAdd } = this.props;
+ onAdd && onAdd(event);
+ }
- handleChange(event) {
- const {onChange} = this.props;
+ handleChange(event) {
+ const { onChange } = this.props;
- event.stopPropagation();
+ event.stopPropagation();
- const value = event.target.value;
+ const value = event.target.value;
- this.setState({value});
+ this.setState({ value });
- onChange && onChange(value, this.state.categories, event);
- }
+ onChange && onChange(value, this.state.categories, event);
+ }
- handleInput(event) {
- const {onInput} = this.props;
+ handleInput(event) {
+ const { onInput } = this.props;
- event.stopPropagation();
+ event.stopPropagation();
- const value = event.target.value;
+ const value = event.target.value;
- this.setState({value});
+ this.setState({ value });
- onInput && onInput(value, this.state.categories, event);
- }
+ onInput && onInput(value, this.state.categories, event);
+ }
- handleReset(event) {
- const {onInput, onChange} = this.props;
- const value = '';
+ handleReset(event) {
+ const { onInput, onChange } = this.props;
+ const value = '';
- this.setState({value});
+ this.setState({ value });
- onInput && onInput(value, this.state.categories, event);
- onChange && onChange(value, this.state.categories, event);
- }
+ onInput && onInput(value, this.state.categories, event);
+ onChange && onChange(value, this.state.categories, event);
+ }
- handleShowFilter() {
- this.setState({
- filterShow: !this.state.filterShow
- });
- }
+ handleShowFilter() {
+ this.setState({
+ filterShow: !this.state.filterShow,
+ });
+ }
- handleHideFilter() {
- this.setState({
- filterShow: false
- });
- }
+ handleHideFilter() {
+ this.setState({
+ filterShow: false,
+ });
+ }
- handleCheckBoxChange(checked, name, event) {
- const {onInput, onChange} = this.props;
+ handleCheckBoxChange(checked, name, event) {
+ const { onInput, onChange } = this.props;
- let categories = this.state.categories;
- let index = categories.indexOf(name);
+ let categories = this.state.categories;
+ let index = categories.indexOf(name);
- if (checked && index === -1) {
- categories.push(name);
- } else if (!checked && index > -1) {
- categories.splice(index, 1);
- } else {
- console.warn(`SearchField: handleCheckBoxChange error.`);
- return;
- }
+ if (checked && index === -1) {
+ categories.push(name);
+ } else if (!checked && index > -1) {
+ categories.splice(index, 1);
+ } else {
+ console.warn(`SearchField: handleCheckBoxChange error.`);
+ return;
+ }
- const value = this.state.value;
+ const value = this.state.value;
- this.setState({categories}, () => {
- onInput && onInput(value, categories, event);
- onChange && onChange(value, categories, event);
- });
- }
+ this.setState({ categories }, () => {
+ onInput && onInput(value, categories, event);
+ onChange && onChange(value, categories, event);
+ });
+ }
- stopPropagation(event) {
- event.nativeEvent.stopImmediatePropagation();
- }
+ stopPropagation(event) {
+ event.nativeEvent.stopImmediatePropagation();
+ }
}
SearchField.propTypes = {
- className: PropTypes.string,
- style: PropTypes.object,
- value: PropTypes.string,
- data: PropTypes.array,
- placeholder: PropTypes.string,
- showAddButton: PropTypes.bool,
- showFilterButton: PropTypes.bool,
- onAdd: PropTypes.func,
- onChange: PropTypes.func,
- onInput: PropTypes.func
+ className: PropTypes.string,
+ style: PropTypes.object,
+ value: PropTypes.string,
+ data: PropTypes.array,
+ placeholder: PropTypes.string,
+ showAddButton: PropTypes.bool,
+ showFilterButton: PropTypes.bool,
+ onAdd: PropTypes.func,
+ onChange: PropTypes.func,
+ onInput: PropTypes.func,
};
SearchField.defaultProps = {
- className: null,
- style: null,
- value: '',
- data: [],
- placeholder: 'Enter a keyword',
- showAddButton: false,
- showFilterButton: false,
- onAdd: null,
- onChange: null,
- onInput: null
+ className: null,
+ style: null,
+ value: '',
+ data: [],
+ placeholder: 'Enter a keyword',
+ showAddButton: false,
+ showFilterButton: false,
+ onAdd: null,
+ onChange: null,
+ onInput: null,
};
-export default SearchField;
\ No newline at end of file
+export default SearchField;
diff --git a/web/src/ui/index.js b/web/src/ui/index.js
index 867226cc1756eb69c0509fcc694e1038d0a3940d..9a5c6c66130895d97e303d31d3772cf4a2dd61e9 100644
--- a/web/src/ui/index.js
+++ b/web/src/ui/index.js
@@ -3,7 +3,7 @@
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file.
- *
+ *
* For more information, please visit: https://github.com/tengge1/ShadowEditor
* You can also visit: https://gitee.com/tengge1/ShadowEditor
*/
@@ -115,4 +115,4 @@ export { default as Photo } from './window/Photo.jsx';
export { default as Prompt } from './window/Prompt.jsx';
export { default as Toast } from './window/Toast.jsx';
export { default as Video } from './window/Video.jsx';
-export { default as Window } from './window/Window.jsx';
\ No newline at end of file
+export { default as Window } from './window/Window.jsx';
diff --git a/web/src/ui/timeline/Timeline.jsx b/web/src/ui/timeline/Timeline.jsx
index fdc7b009c8829be13cad77685b9648d2fea38950..08a0c13b83470f2f60df5a7b4eb2ee2bef6b9072 100644
--- a/web/src/ui/timeline/Timeline.jsx
+++ b/web/src/ui/timeline/Timeline.jsx
@@ -3,7 +3,7 @@
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file.
- *
+ *
* For more information, please visit: https://github.com/tengge1/ShadowEditor
* You can also visit: https://gitee.com/tengge1/ShadowEditor
*/
@@ -16,402 +16,689 @@ import IconButton from '../form/IconButton.jsx';
import Toolbar from '../toolbar/Toolbar.jsx';
import ToolbarSeparator from '../toolbar/ToolbarSeparator.jsx';
import ToolbarFiller from '../toolbar/ToolbarFiller.jsx';
+import global from '../../global';
+import { Input } from '../index';
+import TimeUtils from '../../utils/TimeUtils';
+import { throttle } from '../../utils/functionalUtils';
/**
* 时间轴
* @author tengge / https://github.com/tengge1
*/
class Timeline extends React.Component {
- constructor(props) {
- super(props);
-
- this.duration = 120; // 持续时长(秒)
- this.scale = 30; // 尺寸,1秒=30像素
- this.time = 0; // 当前时间
- this.speed = 16; // 当前速度
-
- this.canvasRef = React.createRef();
- this.layersRef = React.createRef();
- this.leftRef = React.createRef();
- this.rightRef = React.createRef();
- this.sliderRef = React.createRef();
-
- this.handleAddLayer = this.handleAddLayer.bind(this, props.onAddLayer);
- this.handleEditLayer = this.handleEditLayer.bind(this, props.onEditLayer);
- this.handleDeleteLayer = this.handleDeleteLayer.bind(this, props.onDeleteLayer);
- this.commitDeleteLayer = this.commitDeleteLayer.bind(this);
-
- this.handleSelectedLayerChange = this.handleSelectedLayerChange.bind(this, props.onSelectedLayerChange);
-
- this.handleBackward = this.handleBackward.bind(this);
- this.handlePlay = this.handlePlay.bind(this);
- this.handlePause = this.handlePause.bind(this);
- this.handleForward = this.handleForward.bind(this);
- this.handleStop = this.handleStop.bind(this);
-
- this.handleClick = this.handleClick.bind(this, props.onClickAnimation);
- this.handleDoubleClick = this.handleDoubleClick.bind(this, props.onAddAnimation);
- this.handleRightScroll = this.handleRightScroll.bind(this);
-
- this.handleDragStart = this.handleDragStart.bind(this);
- this.handleDragEnd = this.handleDragEnd.bind(this);
- this.handleDragEnter = this.handleDragEnter.bind(this);
- this.handleDragOver = this.handleDragOver.bind(this);
- this.handleDragLeave = this.handleDragLeave.bind(this);
- this.handleDrop = this.handleDrop.bind(this, props.onDropAnimation);
- }
-
- render() {
- const {className, style, animations, selectedLayer, selected} = this.props;
-
- return
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {animations.map(layer => {
- return
-
-
-
;
- })}
-
-
- {animations.map(layer => {
- return
- {layer.animations.map(animation => {
- return
{animation.name}
;
- })}
-
;
- })}
-
-
-
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ // 动画时长
+ duration: 120,
+ // 每秒钟像素数
+ scale: 30,
+ // 当前动画时间
+ time: 0,
+ // 速度
+ speed: 16,
+ // 是否处于修改结束时间状态
+ isEditorTime: false,
+ // 最长的动画时长
+ maxAnimationTime: 3600,
+ // 是否处于播放动画状态
+ isPlay: false,
+ };
+
+ this.canvasRef = React.createRef();
+ this.layersRef = React.createRef();
+ this.leftRef = React.createRef();
+ this.rightRef = React.createRef();
+ this.sliderRef = React.createRef();
+
+ this.handleAddLayer = this.handleAddLayer.bind(this, props.onAddLayer);
+ this.handleEditLayer = this.handleEditLayer.bind(this, props.onEditLayer);
+ this.handleDeleteLayer = this.handleDeleteLayer.bind(this, props.onDeleteLayer);
+ this.commitDeleteLayer = this.commitDeleteLayer.bind(this);
+
+ this.handleSelectedLayerChange = this.handleSelectedLayerChange.bind(this, props.onSelectedLayerChange);
+
+ this.handleBackward = this.handleBackward.bind(this);
+ this.handlePlay = this.handlePlay.bind(this);
+ this.handlePause = this.handlePause.bind(this);
+ this.handleForward = this.handleForward.bind(this);
+ this.handleStop = this.handleStop.bind(this);
+
+ this.handleClick = this.handleClick.bind(this, props.onClickAnimation);
+ this.handleDoubleClick = this.handleDoubleClick.bind(this, props.onAddAnimation);
+ this.handleRightScroll = this.handleRightScroll.bind(this);
+
+ this.handleDragStart = this.handleDragStart.bind(this);
+ this.handleDragEnd = this.handleDragEnd.bind(this);
+ this.handleDragEnter = this.handleDragEnter.bind(this);
+ this.handleDragOver = this.handleDragOver.bind(this);
+ this.handleDragLeave = this.handleDragLeave.bind(this);
+ this.handleDrop = this.handleDrop.bind(this, props.onDropAnimation);
+ this.editorEndTime = this.editorEndTime.bind(this);
+ this.handleChange = this.handleChange.bind(this);
+ this.handleBlur = this.handleBlur.bind(this);
+ this.scrollTimeLine = this.scrollTimeLine.bind(this);
+ this.playEndCallback = this.playEndCallback.bind(this);
+ }
+
+ render() {
+ const { className, style, animations, selectedLayer, selected } = this.props;
+
+ return (
+
+
+
+
+
+
+
+ {this.state.isPlay ? : }
+
+
+
+
+
+
+ {_t('EndTime')}:{this.state.isEditorTime ? : {TimeUtils.formatSeconds(this.state.duration)}}
+
+ {/* */}
+
+
+
+
+
+
+
+ {animations.map(layer => {
+ return (
+
+
+
+
+ );
+ })}
+
+
+ {animations.map(layer => {
+ return (
+
+ {layer.animations.map(animation => {
+ return (
+
+ {animation.name}
+
+ );
+ })}
+
+ );
+ })}
-
;
+ {/* 开始分界线 */}
+
+
+ {/* 动画进度条 */}
+
+
+
+
+ );
+ }
+
+ componentDidMount() {
+ // 渲染时间轴
+ this.renderTimeline();
+ }
+
+ /**
+ * 渲染时间轴
+ * @param {number} durationEdit 指定的时间轴时长
+ * @param {number} scaleEdit 指定的每秒钟跨度px
+ */
+ renderTimeline(durationEdit, scaleEdit) {
+ // 获取默认的duration和scale
+ let { duration, scale } = this.state;
+
+ // 如果接收到的了自定义的duration就使用自定义的
+ if (durationEdit) {
+ duration = durationEdit;
}
- componentDidMount() {
- this.renderTimeline();
+ // 如果接收到的了自定义的scale就使用自定义的
+ if (scaleEdit) {
+ scale = scaleEdit;
}
- renderTimeline() {
- const {duration, scale} = this;
-
- const width = duration * scale; // 画布宽度
- const scale5 = scale / 5; // 0.2秒像素数
- const margin = 0; // 时间轴前后间距
-
- const canvas = this.canvasRef.current;
-
- canvas.style.width = width + margin * 2 + 'px';
- canvas.width = canvas.clientWidth;
- canvas.height = 32;
-
- const context = canvas.getContext('2d');
-
- // 时间轴背景
- context.fillStyle = '#fafafa';
- context.fillRect(0, 0, canvas.width, canvas.height);
-
- // 时间轴刻度
- context.strokeStyle = '#555';
- context.beginPath();
-
- for (let i = margin; i <= width + margin; i += scale) { // 绘制每一秒
- for (let j = 0; j < 5; j++) { // 绘制每个小格
- if (j === 0) { // 长刻度
- context.moveTo(i + scale5 * j, 22);
- context.lineTo(i + scale5 * j, 30);
- } else { // 短刻度
- context.moveTo(i + scale5 * j, 26);
- context.lineTo(i + scale5 * j, 30);
- }
- }
- }
-
- context.stroke();
-
- // 时间轴文字
- context.font = '12px Arial';
- context.fillStyle = '#888';
-
- for (let i = 0; i <= duration; i += 2) { // 对于每两秒
- let minute = Math.floor(i / 60);
- let second = Math.floor(i % 60);
-
- let text = (minute > 0 ? minute + ':' : '') + ('0' + second).slice(-2);
-
- if (i === 0) {
- context.textAlign = 'left';
- } else if (i === duration) {
- context.textAlign = 'right';
- } else {
- context.textAlign = 'center';
- }
-
- context.fillText(text, margin + i * scale, 16);
+ const width = duration * scale; // 画布宽度
+ const scale5 = scale / 5;
+ const margin = 0; // 时间轴前后间距
+
+ // 时间轴画布实例
+ const canvas = this.canvasRef.current;
+
+ // 计算画布宽度(3600)
+ canvas.style.width = width + margin * 2 + 'px';
+ canvas.width = canvas.clientWidth;
+ canvas.height = 32;
+
+ const context = canvas.getContext('2d');
+
+ // 时间轴背景
+ context.fillStyle = '#fafafa';
+
+ // 清空时间轴
+ context.fillRect(0, 0, canvas.width, canvas.height);
+
+ // 时间轴刻度
+ context.strokeStyle = '#555';
+ context.beginPath();
+
+ for (let i = margin; i <= width + margin; i += scale) {
+ for (let j = 0; j < 5; j++) {
+ // 绘制每个小格
+ if (j === 0) {
+ // 长刻度
+ context.moveTo(i + scale5 * j, 22);
+ context.lineTo(i + scale5 * j, 30);
+ } else {
+ // 短刻度
+ context.moveTo(i + scale5 * j, 26);
+ context.lineTo(i + scale5 * j, 30);
}
+ }
}
- handleAddLayer(onAddLayer, event) {
- onAddLayer && onAddLayer(event);
- }
-
- handleEditLayer(onEditLayer, event) {
- const {selectedLayer} = this.props;
-
- onEditLayer && onEditLayer(selectedLayer, event);
- }
-
- handleDeleteLayer(onDeleteLayer, event) {
- const {selectedLayer} = this.props;
-
- onDeleteLayer && onDeleteLayer(selectedLayer, event);
- }
-
- commitDeleteLayer() {
+ context.stroke();
- }
-
- handleSelectedLayerChange(onSelectedLayerChange, value, name, event) {
- onSelectedLayerChange && onSelectedLayerChange(value ? name : null, event);
- }
+ // 时间轴刻度字体样式设置
+ context.font = '12px Arial';
+ context.fillStyle = '#888';
- handleBackward(event) {
+ // 处理每进度的2%就绘制一次文字刻度
+ for (let i = 0; i <= duration; i += duration / 60) {
+ // 转化秒与分钟
+ let minute = Math.floor(i / 60);
+ let second = Math.floor(i % 60);
- }
+ // 生成要在画布上绘制的文本。如果有经过的分钟数,则将其添加到文本中,后跟冒号(':')。然后,使用 '0' + second 将秒数转换为字符串,并使用 .slice(-2) 截取最后两个字符,确保秒数始终显示为两位数字。最终生成的文本存储在 text 变量中。
+ let text = (minute > 0 ? minute + ':' : '') + ('0' + second).slice(-2);
- handlePlay(event) {
+ // 设置文字的样式
+ if (i === 0) {
+ context.textAlign = 'left';
+ } else if (i === duration) {
+ context.textAlign = 'right';
+ } else {
+ context.textAlign = 'center';
+ }
+ context.fillText(text, margin + i * scale, 16);
}
+ }
- handlePause(event) {
+ /**
+ * 结束时间修改触发
+ * @param {string} value 修改后的结束事件
+ */
+ handleChange(value) {
+ // 根据输入的时间将分钟:秒数的格式转换为对应的秒数
+ let duration = TimeUtils.timeToSeconds(value);
- }
+ // 如果修改得到的秒数为0,就阻止修改
+ if (duration === 0) return;
- handleForward(event) {
+ // 判断动画时长是否超出最大时长
+ if (duration >= this.state.maxAnimationTime) {
+ // 设置最长动画时长为1个小时
+ duration = this.state.maxAnimationTime;
+ global.app.toast(_t('The maximum animation duration is 1 hour'), 'warn');
}
- handleStop(event) {
-
+ // 更新步长
+ let scale = 3600 / duration;
+
+ // 更新秒数与步长
+ this.setState({
+ duration,
+ scale,
+ });
+
+ // 重新渲染时间轴
+ this.renderTimeline(duration, scale);
+ }
+
+ /**
+ * 结束时间弹框取消焦点触发
+ */
+ handleBlur() {
+ this.setState({
+ isEditorTime: false,
+ });
+ }
+
+ /**
+ * 点击结束时间,将结束时间替换为文本框
+ */
+ editorEndTime() {
+ this.setState({
+ isEditorTime: true,
+ });
+ }
+
+ /**
+ * 添加动画层
+ * @param {function} onAddLayer 动画层回调
+ * @param {object} event 事件对象
+ */
+ handleAddLayer(onAddLayer, event) {
+ onAddLayer && onAddLayer(event);
+ }
+
+ /**
+ * 修改动画层信息
+ * @param {function} onEditLayer 修改动画层回调
+ * @param {object} event 事件对象
+ */
+ handleEditLayer(onEditLayer, event) {
+ const { selectedLayer } = this.props;
+
+ onEditLayer && onEditLayer(selectedLayer, event);
+ }
+
+ /**
+ * 删除动画层
+ * @param {function} onDeleteLayer 删除动画层回调
+ * @param {object} event 事件对象
+ */
+ handleDeleteLayer(onDeleteLayer, event) {
+ const { selectedLayer } = this.props;
+
+ onDeleteLayer && onDeleteLayer(selectedLayer, event);
+ }
+
+ commitDeleteLayer() {}
+
+ /**
+ * 选择动画层
+ * @param {function} onSelectedLayerChange 选择动画层回调
+ * @param {boolean} value 是否选中东湖层
+ * @param {string} name 动画层uuid
+ * @param {object} event 事件对象
+ */
+ handleSelectedLayerChange(onSelectedLayerChange, value, name, event) {
+ console.log(value, name, 'handleSelectedLayerChange');
+ onSelectedLayerChange && onSelectedLayerChange(value ? name : null, event);
+ }
+
+ /**
+ * 快退函数
+ */
+ handleBackward() {
+ if (this.state.time === 0) return;
+ this.setState({
+ time: this.state.time >= 0 ? this.state.time - 1 : 0,
+ });
+
+ // 更新时间轴滚动与分割线移动
+ this.scrollTimeLine();
+ }
+
+ /**
+ * 播放函数
+ */
+ handlePlay() {
+ if (this.state.isPlay) {
+ global.app.toast('动画正在播放,请勿重复操作', 'warn');
+ return;
}
- handleClick(onClickAnimation, event) {
- const type = event.target.getAttribute('data-type');
-
- if (type !== 'animation') {
- return;
+ // 滚动条移动至起点
+ this.rightRef.current.scrollTo({
+ left: 0,
+ });
+
+ // 设置动画播放函数为正在播放
+ this.setState({
+ isPlay: true,
+ });
+
+ // 开启动画
+ global.app.editor.actions &&
+ global.app.editor.actions.forEach(item => {
+ item.action.play();
+ });
+
+ // animate.Timeline在每次渲染器渲染时执行
+ global.app.on(`animate.Timeline`, clock => {
+ if (this.state.isPlay) {
+ // 判断动画播放时长是否超出时间轴总时长
+ if (!(this.state.time >= this.state.duration)) {
+ // 累计时间,设置播放动画状态
+ this.setState({
+ time: this.state.time + clock.deltaTime,
+ });
+
+ if (global.app.editor.actions && global.app.editor.actions.length !== 0) {
+ global.app.editor.actions.forEach(item => {
+ // item.mixer.update(clock.deltaTime);
+ item.action.time = this.state.time;
+ // console.log(item.action.time, 'item.action.time');
+ item.mixer.update(0);
+ });
+ }
+
+ // 更新时间轴滚动与分割线移动
+ this.scrollTimeLine();
+ } else {
+ // 调用播放结束回调
+ this.playEndCallback();
}
-
- const pid = event.target.getAttribute('data-pid');
- const id = event.target.getAttribute('data-id');
-
- onClickAnimation && onClickAnimation(id, pid, event);
+ }
+ });
+ }
+
+ /**
+ * 播放结束回调
+ */
+ playEndCallback() {
+ // 到达最终时间
+ // 滚动条移动至起点
+ this.rightRef.current.scrollTo({
+ left: 0,
+ });
+
+ // 到达最终时间
+ // 累计时间,设置播放动画状态
+ this.setState({
+ time: 0,
+ isPlay: false,
+ });
+
+ // 重置时间轴
+ this.sliderRef.current.style.left = '100px';
+
+ // 重置所有动画
+ global.app.editor.actions &&
+ global.app.editor.actions.forEach(item => {
+ item.action.reset();
+ });
+
+ // 给animate.Timeline绑定一个空函数,覆盖之前绑定的函数
+ global.app.on(`animate.Timeline`, () => {});
+ }
+
+ /**
+ * 动画时间面板滚动节流函数
+ */
+ throttledScrollTo = throttle(left => {
+ this.rightRef.current.scrollTo({
+ behavior: 'smooth',
+ left: left,
+ });
+ }, 200);
+
+ /**
+ * 暂停函数
+ */
+ handlePause() {
+ console.log('暂停');
+ this.setState({
+ isPlay: false,
+ });
+ }
+
+ /**
+ * 快进函数
+ */
+ handleForward() {
+ this.setState({
+ time: this.state.time >= this.state.duration ? this.state.duration : this.state.time + 1,
+ });
+
+ // 当前播放的时间等于或者大于结束时间时,调用播放结束回调
+ if (this.state.time >= this.state.duration) {
+ this.playEndCallback();
}
- handleDoubleClick(onAddAnimation, event) {
- const type = event.target.getAttribute('data-type');
-
- if (type !== 'layer') {
- return;
- }
-
- const layerID = event.target.getAttribute('data-id');
-
- const beginTime = event.nativeEvent.offsetX / this.scale;
- const endTime = beginTime + 2;
-
- onAddAnimation && onAddAnimation(layerID, beginTime, endTime, event);
+ // 更新时间轴滚动与分割线移动
+ this.scrollTimeLine();
+ }
+
+ /**
+ * 结束函数
+ */
+ handleStop() {
+ this.setState({
+ isPlay: false,
+ time: 0,
+ });
+
+ // 重置时间轴
+ this.sliderRef.current.style.left = '100px';
+
+ // 重置所有动画
+ global.app.editor.actions &&
+ global.app.editor.actions.forEach(item => {
+ item.action.reset();
+ });
+
+ // 因为有200毫秒的节流,所以这里应该延迟200毫秒之后再重置会起点
+ setTimeout(() => {
+ // 滚动条移动至起点
+ this.rightRef.current.scrollTo({
+ left: 0,
+ });
+ }, 200);
+ }
+
+ /**
+ * 滚动条滚动或者时间轴分割线移动函数
+ */
+ scrollTimeLine() {
+ if (this.state.time * this.state.scale >= 2278) {
+ this.sliderRef.current.style.left = 100 + this.state.time * this.state.scale - 2280 + 'px';
+ } else {
+ this.throttledScrollTo(this.state.time * this.state.scale);
}
-
- handleRightScroll(scroll) {
- let left = this.leftRef.current;
- let canvas = this.canvasRef.current;
-
- left.scrollTop = event.target.scrollTop;
- canvas.style.left = `${100 - event.target.scrollLeft}px`;
+ }
+
+ /**
+ * 点击动画层中的动画
+ * @param {function} onClickAnimation 点击动画回调函数
+ * @param {object} event 事件对象
+ */
+ handleClick(onClickAnimation, event) {
+ const type = event.target.getAttribute('data-type');
+
+ if (type !== 'animation') {
+ return;
}
- handleDragStart(event) {
- const type = event.target.getAttribute('data-type');
+ const pid = event.target.getAttribute('data-pid');
+ const id = event.target.getAttribute('data-id');
- if (type !== 'animation') {
- return;
- }
+ onClickAnimation && onClickAnimation(id, pid, event);
+ }
- const id = event.target.getAttribute('data-id');
- const pid = event.target.getAttribute('data-pid');
+ /**
+ * 双击动画层
+ * @param {function} onAddAnimation 双击动画层回调
+ * @param {object} event 事件对象
+ */
+ handleDoubleClick(onAddAnimation, event) {
+ const type = event.target.getAttribute('data-type');
- event.nativeEvent.dataTransfer.setData('id', id);
- event.nativeEvent.dataTransfer.setData('pid', pid);
- event.nativeEvent.dataTransfer.setData('offsetX', event.nativeEvent.offsetX);
+ if (type !== 'layer') {
+ return;
}
- handleDragEnd(event) {
- event.nativeEvent.dataTransfer.clearData();
- }
+ const layerID = event.target.getAttribute('data-id');
- handleDragEnter(event) {
- event.preventDefault();
- }
+ const beginTime = event.nativeEvent.offsetX / this.state.scale;
+ const endTime = beginTime + 2;
- handleDragOver(event) {
- event.preventDefault();
- }
+ onAddAnimation && onAddAnimation(layerID, beginTime, endTime, event);
+ }
- handleDragLeave(event) {
- event.preventDefault();
- }
-
- handleDrop(onDropAnimation, event) {
- const type = event.target.getAttribute('data-type');
-
- if (type !== 'layer') {
- return;
- }
-
- const id = event.nativeEvent.dataTransfer.getData('id');
- const oldLayerID = event.nativeEvent.dataTransfer.getData('pid');
- const offsetX = event.nativeEvent.dataTransfer.getData('offsetX');
+ /**
+ * 时间轴滚动条滚动函数
+ * @param {object} scroll 事件对象
+ */
+ handleRightScroll(scroll) {
+ let left = this.leftRef.current;
+ let canvas = this.canvasRef.current;
- const newLayerID = event.target.getAttribute('data-id');
+ left.scrollTop = event.target.scrollTop;
+ canvas.style.left = `${100 - event.target.scrollLeft}px`;
+ }
- const beginTime = (event.nativeEvent.offsetX - offsetX) / this.scale;
+ /**
+ * 拖拽动画起始函数
+ * @param {object} event 事件对象
+ */
+ handleDragStart(event) {
+ const type = event.target.getAttribute('data-type');
- onDropAnimation && onDropAnimation(id, oldLayerID, newLayerID, beginTime, event);
+ if (type !== 'animation') {
+ return;
}
- parseTime(time) {
- let minute = `0${parseInt(time / 60)}`;
- let second = `0${parseInt(time % 60)}`;
- return `${minute.substr(minute.length - 2, 2)}:${second.substr(second.length - 2, 2)}`;
+ const id = event.target.getAttribute('data-id');
+ const pid = event.target.getAttribute('data-pid');
+
+ event.nativeEvent.dataTransfer.setData('id', id);
+ event.nativeEvent.dataTransfer.setData('pid', pid);
+ event.nativeEvent.dataTransfer.setData('offsetX', event.nativeEvent.offsetX);
+ }
+
+ /**
+ * 拖拽动画结束函数
+ * @param {object} event 事件对象
+ */
+ handleDragEnd(event) {
+ event.nativeEvent.dataTransfer.clearData();
+ }
+
+ /**
+ * 监听拖拽函数,阻止默认行为
+ * @param {object} event 事件对象
+ */
+ handleDragEnter(event) {
+ event.preventDefault();
+ }
+
+ /**
+ * 监听拖拽函数,阻止默认行为
+ * @param {object} event 事件对象
+ */
+ handleDragOver(event) {
+ event.preventDefault();
+ }
+
+ /**
+ * 监听拖拽函数,阻止默认行为
+ * @param {object} event 事件对象
+ */
+ handleDragLeave(event) {
+ event.preventDefault();
+ }
+
+ /**
+ * 拖拽处理函数
+ * @param {function} onDropAnimation 处理函数回调
+ * @param {object} event 事件对象
+ */
+ handleDrop(onDropAnimation, event) {
+ const type = event.target.getAttribute('data-type');
+
+ if (type !== 'layer') {
+ return;
}
- parseSpeed(speed) {
- return speed;
- }
+ const id = event.nativeEvent.dataTransfer.getData('id');
+ const oldLayerID = event.nativeEvent.dataTransfer.getData('pid');
+ const offsetX = event.nativeEvent.dataTransfer.getData('offsetX');
+
+ const newLayerID = event.target.getAttribute('data-id');
+
+ const beginTime = (event.nativeEvent.offsetX - offsetX) / this.state.scale;
+
+ onDropAnimation && onDropAnimation(id, oldLayerID, newLayerID, beginTime, event);
+ }
+
+ /**
+ * 格式化秒数为分钟:秒数的格式
+ * @param {number} time 时长
+ * @returns 处理之后的事件
+ */
+ parseTime(time) {
+ let minute = `0${parseInt(time / 60)}`;
+ let second = `0${parseInt(time % 60)}`;
+ return `${minute.substr(minute.length - 2, 2)}:${second.substr(second.length - 2, 2)}`;
+ }
+
+ /**
+ * 格式化播放速度
+ * @param {number} speed 速度
+ * @returns 速度
+ */
+ parseSpeed(speed) {
+ return speed;
+ }
}
Timeline.propTypes = {
- className: PropTypes.string,
- style: PropTypes.object,
- animations: PropTypes.array,
- selectedLayer: PropTypes.string,
- selected: PropTypes.string,
-
- onAddLayer: PropTypes.func,
- onEditLayer: PropTypes.func,
- onDeleteLayer: PropTypes.func,
- onSelectedLayerChange: PropTypes.func,
-
- onAddAnimation: PropTypes.func,
- onDropAnimation: PropTypes.func,
- onClickAnimation: PropTypes.func
+ className: PropTypes.string,
+ style: PropTypes.object,
+ animations: PropTypes.array,
+ selectedLayer: PropTypes.string,
+ selected: PropTypes.string,
+
+ onAddLayer: PropTypes.func,
+ onEditLayer: PropTypes.func,
+ onDeleteLayer: PropTypes.func,
+ onSelectedLayerChange: PropTypes.func,
+
+ onAddAnimation: PropTypes.func,
+ onDropAnimation: PropTypes.func,
+ onClickAnimation: PropTypes.func,
};
Timeline.defaultProps = {
- className: null,
- style: null,
- animations: [],
- selectedLayer: null,
- selected: null,
-
- onAddLayer: null,
- onEditLayer: null,
- onDeleteLayer: null,
- onSelectedLayerChange: null,
-
- onAddAnimation: null,
- onDropAnimation: null,
- onClickAnimation: null
+ className: null,
+ style: null,
+ animations: [],
+ selectedLayer: null,
+ selected: null,
+
+ onAddLayer: null,
+ onEditLayer: null,
+ onDeleteLayer: null,
+ onSelectedLayerChange: null,
+
+ onAddAnimation: null,
+ onDropAnimation: null,
+ onClickAnimation: null,
};
-export default Timeline;
\ No newline at end of file
+export default Timeline;
diff --git a/web/src/ui/tree/css/Tree.css b/web/src/ui/tree/css/Tree.css
index 2622fed2b2ebed2dcefa8a52b2b8396b29702051..09f33388fc465f17cfb1fcb42fbf1a88185046cb 100644
--- a/web/src/ui/tree/css/Tree.css
+++ b/web/src/ui/tree/css/Tree.css
@@ -11,21 +11,25 @@
.Tree {
list-style: none;
+ min-width: 100%;
margin: 0;
padding: 0;
font: 12px 'Microsoft YaHei';
line-height: 18px;
+ float: left;
--icon-plus: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAMAAADXT/YiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCNzY5QkUzNTgzNzVFOTExOEU2NkEzOTNDMkUxQ0UzNiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGNjVEQzExQzc2NDYxMUU5OEMxN0UxQ0QyRDMwMjk0NyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGNjVEQzExQjc2NDYxMUU5OEMxN0UxQ0QyRDMwMjk0NyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI4NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI3NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+GSEyxQAAAI1QTFRFCQsNxr6u39vSDhIWExgca4ehM0FNIyw1Q1Rla4egZYCZTmN11tHG2NPJKzdBZH6WPU5cX3iPSl5wWHCF9fXxFBofDREUUmd7z8i7VWyBRlhp6unjHCQqGSAmLTpF7e3nHygw5eHaXXaMJzE5CAoM0sy/5OHZ3NjP8PDswrio/f379/f1////AAAA////NGgXgAAAAC90Uk5T/////////////////////////////////////////////////////////////wBapTj3AAAAWUlEQVR42iTBBxKCQBAEwAExIYiIBFGScIFd9/7/POqKbrhjnBf18+Jw/e9OyIhIiPiOHzML2+mM90u88IubtYN8tCnRapNKopoDHipYPcD189hF1eI2AQYAwn4J7uCjPfoAAAAASUVORK5CYII=);
--icon-minus: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAMAAADXT/YiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCNzY5QkUzNTgzNzVFOTExOEU2NkEzOTNDMkUxQ0UzNiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowRTM3OTM2RDc2NDcxMUU5OUYyREZCNzdBMzZGQTU0QSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowRTM3OTM2Qzc2NDcxMUU5OUYyREZCNzdBMzZGQTU0QSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI4NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI3NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+7URHdgAAAHtQTFRFDxMW9fXxJC01KzdBJC42XHWLTWFzXHSLb4yoTWF0DhIWdJSw6unjVWt/KzhBNEFOVGt/xr6uz8i7dJOvNEJO5eHab42n39vSFRof7e3nHCMqY32W0sy/5OHZ3NjPPExaaoaf8PDs2NPJwrio/f379/f1AAAA////////MX4KXQAAACl0Uk5T/////////////////////////////////////////////////////wBS9CCHAAAATUlEQVR42iTBBRKAIAAEwLO7i1ZQlP+/0EF34ar84ElfOEzPb8d6feyMw3p6icCD29tCZFpTWkvF0EhljBEjQSqG0yvh9q6NGYF7BRgAle0Iqns528wAAAAASUVORK5CYII=);
--icon-node: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAPCAMAAADjyg5GAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCNzY5QkUzNTgzNzVFOTExOEU2NkEzOTNDMkUxQ0UzNiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2MTQyNjRFRDc2NDYxMUU5QTdGRjlBOUM1MTgxQUEyNCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2MTQyNjRFQzc2NDYxMUU5QTdGRjlBOUM1MTgxQUEyNCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI4NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI3NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+2JwdnQAAAUdQTFRF2tra39/f1tbWysrK49zf8vLy3d3dxsbG7OzstbCyz8/P4+Hiz8nMzc3NvL282NjYiIeI+/v78Ors4dvd4dvempaXyMjI1tPV8vDxwry/t7K0xMLD3Nzci4eJycnJ3NXY4uLi8/Pzd3d31tHTcW5vz87Pzs7O0s7QsbGx2dPW6OLl6efo9PPz8ertpqam493gu7u7x8fH4tvegn6A3dfZxcXFpqKjycPG29TX5N3gwcHB1M7R1c/R2dnZ9fT08PDwxsDDvr290NDQwcDA6eLl2tPW19fX0dDQ9u/yxb/C4eHh8Ons19HU7ufq1dXV9Ozvwbu+u7a4ysTG8/Hy29zb/Pz839jbsq2vgX1/5+bnx8HEpqGj0MrMrqiqvb293NbZmJiY8/LztrO0jIyM+ff4zs/O7u7u9O3w7ezt/f39/v7+////////roPGVgAAAG10Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AC221EsAAADFSURBVHjaYsgBgqwcbmEhQSCVwwDkZeZwi3j46ORkg7lZOdpG6WHiIEkgNytH1dPfWspcAqw4KycjQiUg0MFGPgWoGshNNIvmjIpxddGyy8lmABoULBnEo2npyxOZkwUymdVd2YLFO14aZFR2GoMfn6O+rkmSEz9HNgMvKwO7RrIoS3iCgZu9HAObKSMDM6exrIAeF6MVE9AoNeYQ9VilOAVeQ0WQRTkyYqnOTBzsTEDngh2ZY8vGwBWK8EKOFyNYOAcgwADsYjo583VUugAAAABJRU5ErkJggg==);
--folder-open: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAANCAMAAACXZR4WAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCNzY5QkUzNTgzNzVFOTExOEU2NkEzOTNDMkUxQ0UzNiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGN0MzMTAyMDc2NDUxMUU5OTAzM0U1RjFGODU1RURCQSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGN0MzMTAxRjc2NDUxMUU5OTAzM0U1RjFGODU1RURCQSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI4NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI3NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+orCXfgAAAJZQTFRFzJk0zc3NoG4Iy5gz+vv8yZYx//+lxcXFmmgCnGoEnmwG2r9spHIMvYol/dVwp3UPmWcByaAhtYIdsH4Yt7e3yJUw9/CJt4Qfxpoa/9eE4rNDo3EL0dHRrHoUs4Ebwo8qonAK4sFQ8OR6uoci17hA/+uEz6ImpXMN/+R//9t1/++JxMTE//iTxpoZ//+c//+Z////////7K5NWgAAADJ0Uk5T/////////////////////////////////////////////////////////////////wANUJjvAAAAgElEQVR42kzLVxLCMAxFUTk9oYXeW+gY2dL+N4ecGA9n9HXnCZgZIE7n7IEcESXJMQQg49DgspwcphLiLkgS+x1wSp/AjHLAP+fbVgNa/2FsD9dRG377BQ4rDYz27WXjTdkG8+xk13shQcrLa1Z17sLD6Z+iqixmSgIrHSj+CjAAb/seguUxx1gAAAAASUVORK5CYII=);
--folder-close: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAANCAMAAABBwMRzAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpCNzY5QkUzNTgzNzVFOTExOEU2NkEzOTNDMkUxQ0UzNiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpEQTA0NkVFNDc2NDUxMUU5ODVCQ0E0NDExMUZBOTUzQiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpEQTA0NkVFMzc2NDUxMUU5ODVCQ0E0NDExMUZBOTUzQiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkI4NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkI3NjlCRTM1ODM3NUU5MTE4RTY2QTM5M0MyRTFDRTM2Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+eRolvgAAAIFQTFRF09PTmWcBy5gzwo8q27dxnmwGs4EbuocitYIdvYol//+6zMzMnGoEsH4YyZYxoG4Io3ELp3UPqHYQrHoUt4QfwI0opXMNq3kTx5QvyJUw1tbWrnwWwMDAmmgCxZItzJk027dS/9Rv//iT//+c/9t1/+R//++Jy8vL//////+Z////MJO0LgAAACt0Uk5T////////////////////////////////////////////////////////ACPJp9AAAABxSURBVHjaTM5XEoJAEEXRR44CYk4MmHpe73+BYpV2cf7u34Wqc2Fz0h+o896XZfRvF49fcZFt83SlCP3C5oyGD8N1hSMXOsHAu2EriPg2TAQ7vgxrQcGn4U2QcTIMKuh8cTn0+65N6uAKzItioB8BBgD8YxgHc9UPOwAAAABJRU5ErkJggg==);
- overflow-y: auto;
+ overflow: auto;
}
.Tree .node {
background: #fff;
- box-sizing: border-box;
+ box-sizing: content-box;
+ word-break: keep-all;
+ white-space: nowrap;
}
.Tree .node.selected {
diff --git a/web/src/ui/window/Confirm.jsx b/web/src/ui/window/Confirm.jsx
index fde236f78ea758a1692913aa0b91e65d2c479e02..2e339d0bbfc5801e9936bd75705082d75c37cb32 100644
--- a/web/src/ui/window/Confirm.jsx
+++ b/web/src/ui/window/Confirm.jsx
@@ -3,7 +3,7 @@
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file.
- *
+ *
* For more information, please visit: https://github.com/tengge1/ShadowEditor
* You can also visit: https://gitee.com/tengge1/ShadowEditor
*/
@@ -20,72 +20,67 @@ import Button from '../form/Button.jsx';
* 询问框
*/
class Confirm extends React.Component {
- constructor(props) {
- super(props);
+ constructor(props) {
+ super(props);
- this.handleOK = this.handleOK.bind(this, props.onOK);
- this.handleCancel = this.handleCancel.bind(this, props.onCancel);
- this.handleClose = this.handleClose.bind(this, props.onClose);
- }
+ this.handleOK = this.handleOK.bind(this, props.onOK);
+ this.handleCancel = this.handleCancel.bind(this, props.onCancel);
+ this.handleClose = this.handleClose.bind(this, props.onClose);
+ }
- render() {
- const {className, style, title, children, hidden, mask, okText, cancelText} = this.props;
+ render() {
+ const { className, style, title, children, hidden, mask, okText, cancelText } = this.props;
- return
- {children}
-
-
-
-
- ;
- }
+ return (
+
+ {children}
+
+
+
+
+
+ );
+ }
- handleOK(onOK, event) {
- onOK && onOK(event);
- }
+ handleOK(onOK, event) {
+ onOK && onOK(event);
+ }
- handleCancel(onCancel, event) {
- onCancel && onCancel(event);
- }
+ handleCancel(onCancel, event) {
+ onCancel && onCancel(event);
+ }
- handleClose(onClose, event) {
- onClose && onClose(event);
- }
+ handleClose(onClose, event) {
+ onClose && onClose(event);
+ }
}
Confirm.propTypes = {
- className: PropTypes.string,
- style: PropTypes.object,
- title: PropTypes.string,
- children: PropTypes.node,
- hidden: PropTypes.bool,
- mask: PropTypes.bool,
- okText: PropTypes.string,
- cancelText: PropTypes.string,
- onOK: PropTypes.func,
- onCancel: PropTypes.func,
- onClose: PropTypes.func
+ className: PropTypes.string,
+ style: PropTypes.object,
+ title: PropTypes.string,
+ children: PropTypes.node,
+ hidden: PropTypes.bool,
+ mask: PropTypes.bool,
+ okText: PropTypes.string,
+ cancelText: PropTypes.string,
+ onOK: PropTypes.func,
+ onCancel: PropTypes.func,
+ onClose: PropTypes.func,
};
Confirm.defaultProps = {
- className: null,
- style: null,
- title: 'Confirm',
- children: null,
- hidden: false,
- mask: false,
- okText: 'OK',
- cancelText: 'Cancel',
- onOK: null,
- onCancel: null,
- onClose: null
+ className: null,
+ style: null,
+ title: 'Confirm',
+ children: null,
+ hidden: false,
+ mask: false,
+ okText: 'OK',
+ cancelText: 'Cancel',
+ onOK: null,
+ onCancel: null,
+ onClose: null,
};
-export default Confirm;
\ No newline at end of file
+export default Confirm;
diff --git a/web/src/utils/Ajax.js b/web/src/utils/Ajax.js
index 2341335f98aba82d24ceedd6b4a2ddc47d791d65..ccb97ebf8bb6c350e1c225975624f971b675d6dd 100644
--- a/web/src/utils/Ajax.js
+++ b/web/src/utils/Ajax.js
@@ -3,7 +3,7 @@
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file.
- *
+ *
* For more information, please visit: https://github.com/tengge1/ShadowEditor
* You can also visit: https://gitee.com/tengge1/ShadowEditor
*/
@@ -15,62 +15,66 @@ import MIMETypeUtils from './MIMETypeUtils';
* @param {Object} params 参数
*/
function ajax(params) {
- const url = params.url || '';
- const method = params.method || 'GET';
- const data = params.data || null;
- const callback = params.callback || null;
-
- const xhr = new XMLHttpRequest();
- xhr.open(method, url, true);
- xhr.onreadystatechange = function () {
- if (xhr.readyState === 4) {
- var data = xhr.responseText;
- typeof callback === 'function' && callback(data);
- }
- };
-
- if (data === null) { // 不需要POST数据
- xhr.send(null);
- return;
+ const url = params.url || '';
+ const method = params.method || 'GET';
+ const data = params.data || null;
+ const callback = params.callback || null;
+
+ const xhr = new XMLHttpRequest();
+ xhr.open(method, url, true);
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4) {
+ var data = xhr.responseText;
+ typeof callback === 'function' && callback(data);
+ }
+ };
+
+ if (data === null) {
+ // 不需要POST数据
+ xhr.send(null);
+ return;
+ }
+
+ // 判断是发送表单还是上传文件
+ // 由于API Controller只能序列化Content-Type为`application/x-www-form-urlencoded`的数据,所以发送表单和上传文件只能二选一。
+ // 否则报错:"No MediaTypeFormatter is available to read an object of type 'EditTextureModel' from content with media type 'multipart/form-data'.
+ var hasFile = false,
+ name;
+
+ for (name in data) {
+ if (data[name] instanceof Blob) {
+ hasFile = true;
+ break;
}
+ }
+
+ if (hasFile) {
+ // 上传文件
+ var formData = new FormData();
- // 判断是发送表单还是上传文件
- // 由于API Controller只能序列化Content-Type为`application/x-www-form-urlencoded`的数据,所以发送表单和上传文件只能二选一。
- // 否则报错:"No MediaTypeFormatter is available to read an object of type 'EditTextureModel' from content with media type 'multipart/form-data'.
- var hasFile = false, name;
+ for (name in data) {
+ if (data[name] instanceof File) {
+ formData.append(name, data[name]);
+ } else if (data[name] instanceof Blob) {
+ formData.append(name, data[name], `${data[name].name}.${MIMETypeUtils.getExtension(data[name].type)}`);
+ }
+ }
+ xhr.send(formData);
+ } else {
+ // 发送表单
+ var bodies = [];
for (name in data) {
- if (data[name] instanceof Blob) {
- hasFile = true;
- break;
- }
+ bodies.push(name + '=' + encodeURIComponent(data[name]));
}
- if (hasFile) { // 上传文件
- var formData = new FormData();
-
- for (name in data) {
- if (data[name] instanceof File) {
- formData.append(name, data[name]);
- } else if (data[name] instanceof Blob) {
- formData.append(name, data[name], `${data[name].name}.${MIMETypeUtils.getExtension(data[name].type)}`);
- }
- }
-
- xhr.send(formData);
- } else { // 发送表单
- var bodies = [];
- for (name in data) {
- bodies.push(name + '=' + encodeURIComponent(data[name]));
- }
-
- var body = bodies.join('&');
- if (body.length) {
- xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
- }
-
- xhr.send(body);
+ var body = bodies.join('&');
+ if (body.length) {
+ xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
}
+
+ xhr.send(body);
+ }
}
/**
@@ -79,10 +83,10 @@ function ajax(params) {
* @param {Function} callback 回调函数
*/
function get(url, callback) {
- ajax({
- url: url,
- callback: callback
- });
+ ajax({
+ url: url,
+ callback: callback,
+ });
}
/**
@@ -91,12 +95,12 @@ function get(url, callback) {
* @param {Function} callback 回调函数
*/
function getJson(url, callback) {
- ajax({
- url: url,
- callback: function (data) {
- typeof callback === 'function' && callback(JSON.parse(data));
- }
- });
+ ajax({
+ url: url,
+ callback: function (data) {
+ typeof callback === 'function' && callback(JSON.parse(data));
+ },
+ });
}
/**
@@ -106,25 +110,25 @@ function getJson(url, callback) {
* @param {Function} callback 回调函数
*/
function post(url, data, callback) {
- const _data = typeof data === 'function' ? null : data;
- const _callback = typeof data === 'function' ? data : callback;
-
- ajax({
- url: url,
- method: 'POST',
- data: _data,
- callback: _callback
- });
+ const _data = typeof data === 'function' ? null : data;
+ const _callback = typeof data === 'function' ? data : callback;
+
+ ajax({
+ url: url,
+ method: 'POST',
+ data: _data,
+ callback: _callback,
+ });
}
/**
* Ajax
*/
const Ajax = {
- ajax: ajax,
- get: get,
- getJson: getJson,
- post: post
+ ajax: ajax,
+ get: get,
+ getJson: getJson,
+ post: post,
};
-export default Ajax;
\ No newline at end of file
+export default Ajax;
diff --git a/web/src/utils/LanguageLoader.js b/web/src/utils/LanguageLoader.js
index 0c8132e6a75210e2d9e6b42b717f64ad1ee6c091..8bda1fbfc6bec1b4965560c0dd2d4db5ffb75715 100644
--- a/web/src/utils/LanguageLoader.js
+++ b/web/src/utils/LanguageLoader.js
@@ -3,63 +3,65 @@
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file.
- *
+ *
* For more information, please visit: https://github.com/tengge1/ShadowEditor
* You can also visit: https://gitee.com/tengge1/ShadowEditor
*/
-import {Backend, i18next} from '../third_party';
+import { Backend, i18next } from '../third_party';
/**
* 语言加载器
* @author tengge / https://github.com/tengge1
*/
class LanguageLoader {
- constructor() {
- window.i18next = i18next;
- window._t = i18next.t.bind(i18next);
- }
+ constructor() {
+ window.i18next = i18next;
+ window._t = i18next.t.bind(i18next);
+ }
- load() {
- let lang = window.localStorage.getItem('lang');
+ load() {
+ let lang = window.localStorage.getItem('lang');
- if (!lang) {
- let language = window.navigator.language.toLocaleLowerCase();
+ if (!lang) {
+ let language = window.navigator.language.toLocaleLowerCase();
- if (language === 'zh-cn') {
- lang = 'zh-CN';
- } else {
- lang = 'en-US';
- }
- window.localStorage.setItem('lang', lang);
- }
+ if (language === 'zh-cn') {
+ lang = 'zh-CN';
+ } else {
+ lang = 'en-US';
+ }
+ window.localStorage.setItem('lang', lang);
+ }
- return new Promise(resolve => {
- i18next.use(Backend)
- .init({
- lng: lang,
- debug: false,
+ return new Promise(resolve => {
+ i18next.use(Backend).init(
+ {
+ lng: lang,
+ debug: false,
- whitelist: ['en-US', 'zh-CN', 'zh-TW', 'ja-JP', 'ko-KR', 'ru-RU', 'fr-FR'],
+ whitelist: ['en-US', 'zh-CN', 'zh-TW', 'ja-JP', 'ko-KR', 'ru-RU', 'fr-FR'],
- backend: {
- // for all available options read the backend's repository readme file
- loadPath: 'locales/{{lng}}.json'
- },
+ backend: {
+ // for all available options read the backend's repository readme file
+ loadPath: 'locales/{{lng}}.json',
+ },
- // allow keys to be phrases having `:`, `.`
- nsSeparator: false,
- keySeparator: false,
+ // allow keys to be phrases having `:`, `.`
+ nsSeparator: false,
+ keySeparator: false,
- // do not load a fallback
- fallbackLng: false
- }, (err) => {
- if (err) {
- console.warn(err);
- }
- resolve();
- });
- });
- }
+ // do not load a fallback
+ fallbackLng: false,
+ },
+ err => {
+ if (err) {
+ console.warn(err);
+ }
+ resolve();
+ }
+ );
+ });
+ }
}
-export default LanguageLoader;
\ No newline at end of file
+export default LanguageLoader;
diff --git a/web/src/utils/Server.js b/web/src/utils/Server.js
index 6d3a0cd1f70d8df3bd3b0270ffbad1114e9d43b0..74f04f8a66390deac6b23ca1cec07aa9e681cbcb 100644
--- a/web/src/utils/Server.js
+++ b/web/src/utils/Server.js
@@ -3,7 +3,7 @@
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file.
- *
+ *
* For more information, please visit: https://github.com/tengge1/ShadowEditor
* You can also visit: https://gitee.com/tengge1/ShadowEditor
*/
@@ -14,117 +14,122 @@ import global from '../global';
* @author tengge / https://github.com/tengge1
*/
class Server {
- constructor(server) {
- this.origin = server;
-
- this.enableAuthority = false; // 是否开启权限
- this.initialized = false; // 系统是否初始化
-
- this.isLogin = false; // 是否登录
- this.username = ''; // 登录用户名
- this.name = ''; // 登录姓名
-
- this.roleName = ''; // 角色名称
- this.deptName = ''; // 机构名称
- this.authorities = []; // 权限列表
-
- this.isAdmin = false; // 是否是管理员
-
- this.enableRemoteEdit = false;
- this.webSocketServerPort = 5000;
- }
-
- load() {
- return new Promise(resolve => {
- fetch(`${this.origin}/api/Config/Get`).then(response => {
- response.json().then(obj => {
- if (obj.Code !== 200) {
- global.app.toast(_t(obj.Msg), 'warn');
- return;
- }
- this.enableAuthority = obj.Data.EnableAuthority;
- this.initialized = obj.Data.Initialized;
-
- this.isLogin = obj.Data.IsLogin;
- this.username = obj.Data.Username;
- this.name = obj.Data.Name;
-
- this.roleName = obj.Data.RoleName;
- this.deptName = obj.Data.DeptName;
- this.authorities = obj.Data.OperatingAuthorities;
-
- this.isAdmin = this.roleName === 'Administrator';
-
- this.enableRemoteEdit = obj.Data.EnableRemoteEdit;
- this.webSocketServerPort = obj.Data.WebSocketServerPort;
- resolve();
- }).catch(e => {
- console.warn(e);
- global.app.toast(_t('Server configuration acquisition failed.'), 'error');
- resolve();
- });
- }).catch(e => {
- console.warn(e);
- global.app.toast(_t('Server configuration acquisition failed.'), 'error');
- resolve();
+ constructor(server) {
+ this.origin = server;
+
+ this.enableAuthority = false; // 是否开启权限
+ this.initialized = false; // 系统是否初始化
+
+ this.isLogin = false; // 是否登录
+ this.username = ''; // 登录用户名
+ this.name = ''; // 登录姓名
+
+ this.roleName = ''; // 角色名称
+ this.deptName = ''; // 机构名称
+ this.authorities = []; // 权限列表
+
+ this.isAdmin = false; // 是否是管理员
+
+ this.enableRemoteEdit = false;
+ this.webSocketServerPort = 5000;
+ }
+
+ load() {
+ return new Promise(resolve => {
+ fetch(`${this.origin}/api/Config/Get`)
+ .then(response => {
+ response
+ .json()
+ .then(obj => {
+ if (obj.Code !== 200) {
+ global.app.toast(_t(obj.Msg), 'warn');
+ return;
+ }
+ this.enableAuthority = obj.Data.EnableAuthority;
+ this.initialized = obj.Data.Initialized;
+
+ this.isLogin = obj.Data.IsLogin;
+ this.username = obj.Data.Username;
+ this.name = obj.Data.Name;
+
+ this.roleName = obj.Data.RoleName;
+ this.deptName = obj.Data.DeptName;
+ this.authorities = obj.Data.OperatingAuthorities;
+
+ this.isAdmin = this.roleName === 'Administrator';
+
+ this.enableRemoteEdit = obj.Data.EnableRemoteEdit;
+ this.webSocketServerPort = obj.Data.WebSocketServerPort;
+ resolve();
+ })
+ .catch(e => {
+ console.warn(e);
+ global.app.toast(_t('Server configuration acquisition failed.'), 'error');
+ resolve();
});
+ })
+ .catch(e => {
+ console.warn(e);
+ global.app.toast(_t('Server configuration acquisition failed.'), 'error');
+ resolve();
});
- }
-
- login(username, password) {
- return new Promise(resolve => {
- fetch(`${this.origin}/api/Login/Login`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded'
- },
- body: `Username=${username}&Password=${password}`
- }).then(response => {
- response.json().then(obj => {
- if (obj.Code !== 200) {
- global.app.toast(_t(obj.Msg), 'warn');
- resolve(false);
- return;
- }
- this.isLogin = true;
- this.username = obj.Data.Username;
- this.name = obj.Data.Name;
-
- // TODO: 登录后返回所有信息
- // this.roleName = ''; // 角色名称
- // this.deptName = ''; // 机构名称
- // this.authorities = []; // 权限列表
- // this.authorities = obj.Data.OperatingAuthorities;
-
- // this.isAdmin = false; // 是否是管理员
-
- global.app.call('login', this);
- resolve(true);
- });
- });
+ });
+ }
+
+ login(username, password) {
+ return new Promise(resolve => {
+ fetch(`${this.origin}/api/Login/Login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: `Username=${username}&Password=${password}`,
+ }).then(response => {
+ response.json().then(obj => {
+ if (obj.Code !== 200) {
+ global.app.toast(_t(obj.Msg), 'warn');
+ resolve(false);
+ return;
+ }
+ this.isLogin = true;
+ this.username = obj.Data.Username;
+ this.name = obj.Data.Name;
+
+ // TODO: 登录后返回所有信息
+ // this.roleName = ''; // 角色名称
+ // this.deptName = ''; // 机构名称
+ // this.authorities = []; // 权限列表
+ // this.authorities = obj.Data.OperatingAuthorities;
+
+ // this.isAdmin = false; // 是否是管理员
+
+ global.app.call('login', this);
+ resolve(true);
});
- }
-
- logout() {
- return new Promise(resolve => {
- fetch(`${this.origin}/api/Login/Logout`, {
- method: 'POST'
- }).then(response => {
- response.json().then(obj => {
- if (obj.Code !== 200) {
- global.app.toast(_t(obj.Msg), 'warn');
- resolve(false);
- return;
- }
- this.isLogin = false;
- this.username = '';
- this.name = '';
- global.app.call('logout', this);
- resolve(true);
- });
- });
+ });
+ });
+ }
+
+ logout() {
+ return new Promise(resolve => {
+ fetch(`${this.origin}/api/Login/Logout`, {
+ method: 'POST',
+ }).then(response => {
+ response.json().then(obj => {
+ if (obj.Code !== 200) {
+ global.app.toast(_t(obj.Msg), 'warn');
+ resolve(false);
+ return;
+ }
+ this.isLogin = false;
+ this.username = '';
+ this.name = '';
+ global.app.call('logout', this);
+ resolve(true);
});
- }
+ });
+ });
+ }
}
-export default Server;
\ No newline at end of file
+export default Server;
diff --git a/web/src/utils/Storage.js b/web/src/utils/Storage.js
index 257827087e7770896d7050c19dcd565c22dbb8f7..cdb5aac4145db5a34cb81472303353169020768a 100644
--- a/web/src/utils/Storage.js
+++ b/web/src/utils/Storage.js
@@ -3,7 +3,7 @@
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file.
- *
+ *
* For more information, please visit: https://github.com/tengge1/ShadowEditor
* You can also visit: https://gitee.com/tengge1/ShadowEditor
*/
@@ -13,124 +13,124 @@ import global from '../global';
* 存储类
*/
class Storage {
- constructor() {
- // 向本地存储写入默认配置,并提供快捷访问方法
- const defaultConfigs = {
- debug: false, // 调试模式
- autoSave: false, // 自动保存
-
- // 视图相关
- assetsPanelShow: true,
- sidebarShow: true,
- toolbarShow: true,
- timelinePanelShow: true,
- statusBarShow: true,
-
- // 帮助器
- showStats: true, // 性能监视器
- showGrid: true, // 网格
- showViewHelper: true, // 视角帮助器
- showCamera: false,
- showPointLight: true,
- showDirectionalLight: true,
- showSpotLight: true,
- showHemisphereLight: true,
- showRectAreaLight: true,
- showSkeleton: false,
-
- // 选中效果
- selectMode: 'whole', // whole: 选择整体;part: 选择部分。
- selectedColor: '#ff6600', // unity3d: #ff6600
- selectedThickness: 4,
-
- // 高亮效果
- hoverEnabled: false, // 高亮效果
- hoveredColor: '#ffff00',
-
- // 编辑模式
- addMode: 'center', // 添加模式:center: 添加到场景中心;click: 点击场景添加。
- controlMode: 'EditorControls' // 控制器模式:EditorControls: 编辑器控制器;FreeControls: 自由控制器。
- };
-
- let configs = this._getConfigs();
-
- Object.entries(defaultConfigs).forEach(n => {
- if (configs[n[0]] === undefined) {
- configs[n[0]] = n[1];
- }
-
- Object.defineProperty(this, n[0], {
- get: () => {
- return this.get(n[0]);
- },
- set: value => {
- return this.set(n[0], value);
- }
- });
- });
-
- this._setConfigs(configs);
- }
-
- /**
- * 获取本地存储键值
- * @param {String} key 键
- * @returns {Object} 值,不存储返回undefined
- */
- get(key) {
- let configs = this._getConfigs();
- return configs[key];
+ constructor() {
+ // 向本地存储写入默认配置,并提供快捷访问方法
+ const defaultConfigs = {
+ debug: false, // 调试模式
+ autoSave: false, // 自动保存
+
+ // 视图相关
+ assetsPanelShow: true,
+ sidebarShow: true,
+ toolbarShow: true,
+ timelinePanelShow: true,
+ statusBarShow: true,
+
+ // 帮助器
+ showStats: true, // 性能监视器
+ showGrid: true, // 网格
+ showViewHelper: true, // 视角帮助器
+ showCamera: false,
+ showPointLight: true,
+ showDirectionalLight: true,
+ showSpotLight: true,
+ showHemisphereLight: true,
+ showRectAreaLight: true,
+ showSkeleton: false,
+
+ // 选中效果
+ selectMode: 'whole', // whole: 选择整体;part: 选择部分。
+ selectedColor: '#ff6600', // unity3d: #ff6600
+ selectedThickness: 4,
+
+ // 高亮效果
+ hoverEnabled: false, // 高亮效果
+ hoveredColor: '#ffff00',
+
+ // 编辑模式
+ addMode: 'center', // 添加模式:center: 添加到场景中心;click: 点击场景添加。
+ controlMode: 'EditorControls', // 控制器模式:EditorControls: 编辑器控制器;FreeControls: 自由控制器。
+ };
+
+ let configs = this._getConfigs();
+
+ Object.entries(defaultConfigs).forEach(n => {
+ if (configs[n[0]] === undefined) {
+ configs[n[0]] = n[1];
+ }
+
+ Object.defineProperty(this, n[0], {
+ get: () => {
+ return this.get(n[0]);
+ },
+ set: value => {
+ return this.set(n[0], value);
+ },
+ });
+ });
+
+ this._setConfigs(configs);
+ }
+
+ /**
+ * 获取本地存储键值
+ * @param {String} key 键
+ * @returns {Object} 值,不存储返回undefined
+ */
+ get(key) {
+ let configs = this._getConfigs();
+ return configs[key];
+ }
+
+ /**
+ * 设置本地存储
+ * @param {String} key 键
+ * @param {String} value 值
+ */
+ set(key, value) {
+ let configs = this._getConfigs();
+ configs[key] = value;
+ this._setConfigs(configs);
+ if (global.app.call) {
+ global.app.call(`storageChanged`, this, key, value);
+ } else {
+ console.warn(`Storage: EventDispatcher has not been created.`);
}
+ }
- /**
- * 设置本地存储
- * @param {String} key 键
- * @param {String} value 值
- */
- set(key, value) {
- let configs = this._getConfigs();
- configs[key] = value;
- this._setConfigs(configs);
- if (global.app.call) {
- global.app.call(`storageChanged`, this, key, value);
- } else {
- console.warn(`Storage: EventDispatcher has not been created.`);
- }
+ setConfigs(configs) {
+ if (typeof configs !== 'object') {
+ console.warn(`Storage: configs should be an object.`);
+ return;
}
-
- setConfigs(configs) {
- if (typeof configs !== 'object') {
- console.warn(`Storage: configs should be an object.`);
- return;
- }
- let _configs = this._getConfigs();
- Object.keys(configs).forEach(n => {
- _configs[n] = configs[n];
- });
- this._setConfigs(_configs);
- }
-
- remove(key) {
- let configs = this._getConfigs();
- delete configs[key];
- this._setConfigs(configs);
+ let _configs = this._getConfigs();
+ Object.keys(configs).forEach(n => {
+ _configs[n] = configs[n];
+ });
+ this._setConfigs(_configs);
+ }
+
+ remove(key) {
+ let configs = this._getConfigs();
+ delete configs[key];
+ this._setConfigs(configs);
+ }
+
+ clear() {
+ window.localStorage.removeItem('configs');
+ }
+
+ _getConfigs() {
+ let configs = window.localStorage.getItem('configs');
+ if (!configs) {
+ configs = '{}';
}
+ return JSON.parse(configs);
+ }
- clear() {
- window.localStorage.removeItem('configs');
- }
-
- _getConfigs() {
- let configs = window.localStorage.getItem('configs');
- if (!configs) {
- configs = '{}';
- }
- return JSON.parse(configs);
- }
-
- _setConfigs(configs) {
- window.localStorage.setItem('configs', JSON.stringify(configs));
- }
+ _setConfigs(configs) {
+ window.localStorage.setItem('configs', JSON.stringify(configs));
+ }
}
-export default Storage;
\ No newline at end of file
+export default Storage;
diff --git a/web/src/utils/TimeUtils.js b/web/src/utils/TimeUtils.js
index c663e15af99a6613ef51da984ee258d5ee54240b..0ca72a84a28d6f1360ccafdf488af7638dae8c92 100644
--- a/web/src/utils/TimeUtils.js
+++ b/web/src/utils/TimeUtils.js
@@ -3,7 +3,7 @@
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file.
- *
+ *
* For more information, please visit: https://github.com/tengge1/ShadowEditor
* You can also visit: https://gitee.com/tengge1/ShadowEditor
*/
@@ -12,28 +12,64 @@
* @author tengge / https://github.com/tengge1
*/
const TimeUtils = {
- getDateTime: function (format = 'yyyyMMddHHmmss') {
- let date = new Date();
- let year = date.getFullYear();
- let month = `00${date.getMonth() + 1}`;
- let day = `00${date.getDate()}`;
- let hour = `00${date.getHours()}`;
- let minute = `00${date.getMinutes()}`;
- let second = `00${date.getSeconds()}`;
-
- month = month.substr(month.length - 2, 2);
- day = day.substr(day.length - 2, 2);
- hour = hour.substr(hour.length - 2, 2);
- minute = minute.substr(minute.length - 2, 2);
- second = second.substr(second.length - 2, 2);
-
- return format.replace('yyyy', year)
- .replace('MM', month)
- .replace('dd', day)
- .replace('HH', hour)
- .replace('mm', minute)
- .replace('ss', second);
+ /**
+ * 格式化时间
+ * @param {string} format 事件格式化的格式
+ * @returns 格式化之后的时间
+ */
+ getDateTime: function (format = 'yyyyMMddHHmmss') {
+ let date = new Date();
+ let year = date.getFullYear();
+ let month = `00${date.getMonth() + 1}`;
+ let day = `00${date.getDate()}`;
+ let hour = `00${date.getHours()}`;
+ let minute = `00${date.getMinutes()}`;
+ let second = `00${date.getSeconds()}`;
+
+ month = month.substr(month.length - 2, 2);
+ day = day.substr(day.length - 2, 2);
+ hour = hour.substr(hour.length - 2, 2);
+ minute = minute.substr(minute.length - 2, 2);
+ second = second.substr(second.length - 2, 2);
+
+ return format.replace('yyyy', year).replace('MM', month).replace('dd', day).replace('HH', hour).replace('mm', minute).replace('ss', second);
+ },
+
+ /**
+ * 将秒数转换为hour:minute:seconds的格式
+ * @param {number} seconds 秒数
+ * @returns hour:minute:seconds格式的字符串
+ */
+ formatSeconds: function (seconds) {
+ var hours = Math.floor(seconds / 3600);
+ var minutes = Math.floor((seconds % 3600) / 60);
+ var seconds = seconds % 60;
+
+ var formattedTime = (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
+
+ if (hours > 0) {
+ formattedTime = (hours < 10 ? '0' : '') + hours + ':' + formattedTime;
+ }
+
+ return formattedTime;
+ },
+
+ /**
+ * 将hour:minute:seconds的格式时间转换为秒数
+ * @param {string} timeString hour:minute:seconds格式的时间
+ * @returns 返回此格式的总秒数
+ */
+ timeToSeconds: function (timeString) {
+ var parts = timeString.split(':');
+ var seconds = parseInt(parts.pop(), 10);
+ var minutes = parts.length > 0 ? parseInt(parts.pop(), 10) : 0;
+ var hours = parts.length > 0 ? parseInt(parts.pop(), 10) : 0;
+
+ if (isNaN(hours)) {
+ hours = 0;
}
+ return hours * 3600 + minutes * 60 + seconds;
+ },
};
-export default TimeUtils;
\ No newline at end of file
+export default TimeUtils;
diff --git a/web/src/utils/functionalUtils.js b/web/src/utils/functionalUtils.js
new file mode 100644
index 0000000000000000000000000000000000000000..63f15912896b7e2a95475a6901778b3efbdd62ce
--- /dev/null
+++ b/web/src/utils/functionalUtils.js
@@ -0,0 +1,34 @@
+/*
+ * @Author: wangzhiyu
+ * @Date: 2023-10-11 15:47:36
+ * @LastEditors: wangzhiyu
+ * @LastEditTime: 2023-10-11 15:48:43
+ */
+
+/**
+ * 节流函数
+ * @param {function} func
+ * @param {number} delay
+ * @returns 返回节流处理后的函数
+ */
+export function throttle(func, delay) {
+ let timeoutId;
+ let lastExecTime = 0;
+
+ return function (...args) {
+ const currentTime = Date.now();
+ const remainingTime = delay - (currentTime - lastExecTime);
+
+ clearTimeout(timeoutId);
+
+ if (remainingTime <= 0) {
+ func.apply(this, args);
+ lastExecTime = currentTime;
+ } else {
+ timeoutId = setTimeout(() => {
+ func.apply(this, args);
+ lastExecTime = Date.now();
+ }, remainingTime);
+ }
+ };
+}
diff --git a/web/src/worker/MyWorker.js b/web/src/worker/MyWorker.js
index 5cc844d92ec530ad72c9c8166e8bb74252c62723..070e40d486ccbbc1063fb8812dcedd10433109b3 100644
--- a/web/src/worker/MyWorker.js
+++ b/web/src/worker/MyWorker.js
@@ -1,3 +1,3 @@
self.onmessage = e => {
- console.log(e);
-};
\ No newline at end of file
+ console.log(e);
+};
diff --git a/web/view.html b/web/view.html
index caae053b43ce25fc06cd646d90319d74b4fa0c38..8519d2d9a4330e93d4cb1df516750713855f6782 100644
--- a/web/view.html
+++ b/web/view.html
@@ -1,28 +1,27 @@
-
-
-
-
Shadow Editor Viewer
-
-
+
+
+
No Name
+
+
-
+
-
+
@@ -41,59 +40,60 @@
-
-
-
\ No newline at end of file
+
+