parent
db104c20ce
commit
5df5fb5be6
@ -0,0 +1,53 @@
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
|
||||
import Spreadsheet from './x-sheet';
|
||||
import { css } from '@nocobase/client';
|
||||
import { demoData } from './x-sheet/demo';
|
||||
|
||||
export type SheetRef = {
|
||||
getData: () => any;
|
||||
};
|
||||
|
||||
export type SheetProps = {
|
||||
data?: any;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
const Sheet = forwardRef<SheetRef, SheetProps>(({ data }, ref) => {
|
||||
const workbookRef = useRef<Spreadsheet>(null);
|
||||
const containerRef = useRef(null);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getData,
|
||||
}));
|
||||
|
||||
const getData = () => {
|
||||
if (!workbookRef.current) {
|
||||
throw new Error('Workbook is not initialized');
|
||||
}
|
||||
return workbookRef.current.getData();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const workbook = new Spreadsheet('#sheet', {
|
||||
view: {
|
||||
height: () => containerRef.current.offsetHeight,
|
||||
width: () => containerRef.current.offsetWidth,
|
||||
},
|
||||
}).loadData(demoData);
|
||||
workbookRef.current = workbook;
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
id="sheet"
|
||||
className={css`
|
||||
height: 800px;
|
||||
`}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
Sheet.displayName = 'Sheet';
|
||||
|
||||
export default Sheet;
|
@ -1,3 +1,8 @@
|
||||
export * from './GroupBlockConfigure/GroupBlockConfigure';
|
||||
export * from './PDFViewer';
|
||||
export * from './Sheet';
|
||||
export * from './SignatureInput';
|
||||
export * from './SignaturePad';
|
||||
export * from './custom-components/CustomAssociatedField';
|
||||
export * from './custom-components/CustomComponentDispatcher';
|
||||
export * from './custom-components/CustomField';
|
||||
@ -7,7 +12,3 @@ export * from './fields/Expression';
|
||||
export * from './system/MobileLink';
|
||||
export * from './system/Notifications';
|
||||
export * from './system/OnlineUserProvider';
|
||||
export * from './PDFViewer';
|
||||
export * from './SignatureInput';
|
||||
export * from './SignaturePad';
|
||||
export * from './GroupBlockConfigure/GroupBlockConfigure';
|
||||
|
@ -0,0 +1,11 @@
|
||||
/* eslint no-bitwise: "off" */
|
||||
/*
|
||||
v: int value
|
||||
digit: bit len of v
|
||||
flag: true or false
|
||||
*/
|
||||
const bitmap = (v, digit, flag) => {
|
||||
const b = 1 << digit;
|
||||
return flag ? (v | b) : (v ^ b);
|
||||
};
|
||||
export default bitmap;
|
@ -0,0 +1,39 @@
|
||||
// src: include chars: [0-9], +, -, *, /
|
||||
// // 9+(3-1)*3+10/2 => 9 3 1-3*+ 10 2/+
|
||||
const infix2suffix = (src) => {
|
||||
const operatorStack = [];
|
||||
const stack = [];
|
||||
for (let i = 0; i < src.length; i += 1) {
|
||||
const c = src.charAt(i);
|
||||
if (c !== ' ') {
|
||||
if (c >= '0' && c <= '9') {
|
||||
stack.push(c);
|
||||
} else if (c === ')') {
|
||||
let c1 = operatorStack.pop();
|
||||
while (c1 !== '(') {
|
||||
stack.push(c1);
|
||||
c1 = operatorStack.pop();
|
||||
}
|
||||
} else {
|
||||
// priority: */ > +-
|
||||
if (operatorStack.length > 0 && (c === '+' || c === '-')) {
|
||||
const last = operatorStack[operatorStack.length - 1];
|
||||
if (last === '*' || last === '/') {
|
||||
while (operatorStack.length > 0) {
|
||||
stack.push(operatorStack.pop());
|
||||
}
|
||||
}
|
||||
}
|
||||
operatorStack.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (operatorStack.length > 0) {
|
||||
stack.push(operatorStack.pop());
|
||||
}
|
||||
return stack;
|
||||
};
|
||||
|
||||
export default {
|
||||
infix2suffix,
|
||||
};
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 170 KiB |
@ -0,0 +1,137 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" width="262px" height="72px" viewBox="0 0 262 72" preserveAspectRatio="none">
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(0,0)"><path fill="#000000" fill-rule="evenodd" d="M11.5656391,4.43436088 L9,7 L16,7 L16,0 L13.0418424,2.95815758 C11.5936787,1.73635959 9.72260775,1 7.67955083,1 C4.22126258,1 1.25575599,3.10984908 0,6 L2,7 C2.93658775,4.60974406 5.12943697,3.08011229 7.67955083,3 C9.14881247,3.0528747 10.4994783,3.57862053 11.5656391,4.43436088 Z" transform="matrix(-1 0 0 1 17 5)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(18,0)"><path fill="#000000" fill-rule="evenodd" d="M11.5656391,4.43436088 L9,7 L16,7 L16,0 L13.0418424,2.95815758 C11.5936787,1.73635959 9.72260775,1 7.67955083,1 C4.22126258,1 1.25575599,3.10984908 0,6 L2,7 C2.93658775,4.60974406 5.12943697,3.08011229 7.67955083,3 C9.14881247,3.0528747 10.4994783,3.57862053 11.5656391,4.43436088 Z" transform="translate(1 5)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(36,0)"><path fill="#000000" fill-rule="evenodd" d="M13,14 L3,14 L3,11 L0,11 L0,6.00591905 C0,4.89808055 0.894513756,4 1.99406028,4 L14.0059397,4 C15.1072288,4 16,4.88655484 16,6.00591905 L16,11 L13,11 L13,14 Z M5,9 L11,9 L11,12 L5,12 L5,9 Z M3,0 L13,0 L13,3 L3,3 L3,0 Z M12,6 L14,6 L14,8 L12,8 L12,6 Z" transform="translate(1 2)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(54,0)"><path fill="#000000" fill-rule="evenodd" d="M9,0 L1,0 C0.45,0 0,0.45 0,1 L0,4 C0,4.55 0.45,5 1,5 L9,5 C9.55,5 10,4.55 10,4 L10,3 L11,3 L11,6 L4,6 L4,14 L6,14 L6,8 L13,8 L13,2 L10,2 L10,1 C10,0.45 9.55,0 9,0 Z" transform="translate(3 2)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(72,0)"><path fill="#000000" fill-rule="evenodd" d="M0.27,1.55 L5.43,6.7 L3,12 L5.5,12 L7.14,8.42 L11.73,13 L13,11.73 L1.55,0.27 L0.27,1.55 L0.27,1.55 Z M3.82,0 L5.82,2 L7.58,2 L7.03,3.21 L8.74,4.92 L10.08,2 L14,2 L14,0 L3.82,0 L3.82,0 Z" transform="translate(2 3)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(90,0)"><path fill="#000000" fill-rule="evenodd" d="M9,3.5 C9,1.57 7.43,0 5.5,0 L1.77635684e-15,0 L1.77635684e-15,12 L6.25,12 C8.04,12 9.5,10.54 9.5,8.75 C9.5,7.45 8.73,6.34 7.63,5.82 C8.46,5.24 9,4.38 9,3.5 Z M5,2 C5.82999992,2 6.5,2.67 6.5,3.5 C6.5,4.33 5.82999992,5 5,5 L3,5 L3,2 L5,2 Z M3,10 L3,7 L5.5,7 C6.32999992,7 7,7.67 7,8.5 C7,9.33 6.32999992,10 5.5,10 L3,10 Z" transform="translate(4 3)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(108,0)"><polygon fill="#000000" fill-rule="evenodd" points="4 0 4 2 6.58 2 2.92 10 0 10 0 12 8 12 8 10 5.42 10 9.08 2 12 2 12 0" transform="translate(3 3)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(126,0)"><path fill="#000000" d="M6,12 C8.76,12 11,9.76 11,7 L11,0 L9,0 L9,7 C9,8.75029916 7.49912807,10 6,10 C4.50087193,10 3,8.75837486 3,7 L3,0 L1,0 L1,7 C1,9.76 3.24,12 6,12 Z M0,13 L0,15 L12,15 L12,13 L0,13 Z" transform="translate(3 3)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(144,0)"><path fill="#010101" fill-rule="evenodd" d="M2.8875,3.06 C2.8875,2.6025 2.985,2.18625 3.18375,1.8075 C3.3825,1.42875 3.66,1.10625 4.02,0.84 C4.38,0.57375 4.80375,0.3675 5.29875,0.22125 C5.79375,0.075 6.33375,0 6.92625,0 C7.53375,0 8.085,0.0825 8.58,0.25125 C9.075,0.42 9.49875,0.6525 9.85125,0.95625 C10.20375,1.25625 10.47375,1.6125 10.665,2.02875 C10.85625,2.44125 10.95,2.895 10.95,3.38625 L8.6925,3.38625 C8.6925,3.1575 8.655,2.94375 8.58375,2.74875 C8.5125,2.55 8.4,2.38125 8.25,2.2425 C8.1,2.10375 7.9125,1.99125 7.6875,1.91625 C7.4625,1.8375 7.19625,1.8 6.88875,1.8 C6.5925,1.8 6.3375,1.83375 6.11625,1.8975 C5.89875,1.96125 5.71875,2.05125 5.57625,2.1675 C5.43375,2.28375 5.325,2.41875 5.25375,2.5725 C5.1825,2.72625 5.145,2.895 5.145,3.0675 C5.145,3.4275 5.32875,3.73125 5.69625,3.975 C5.71780203,3.98908066 5.73942012,4.00311728 5.76118357,4.01733315 C6.02342923,4.18863185 6.5,4.5 7,5 L4,5 C4,5 3.21375,4.37625 3.17625,4.30875 C2.985,3.9525 2.8875,3.53625 2.8875,3.06 Z M14,6 L0,6 L0,8 L7.21875,8 C7.35375,8.0525 7.51875,8.105 7.63125,8.15375 C7.90875,8.2775 8.12625,8.40875 8.28375,8.53625 C8.44125,8.6675 8.54625,8.81 8.6025,8.96 C8.65875,9.11375 8.685,9.28625 8.685,9.47375 C8.685,9.65 8.65125,9.815 8.58375,9.965 C8.51625,10.11875 8.41125,10.25 8.2725,10.35875 C8.13375,10.4675 7.95375,10.55375 7.74,10.6175 C7.5225,10.68125 7.27125,10.71125 6.97875,10.71125 C6.6525,10.71125 6.35625,10.6775 6.09,10.61375 C5.82375,10.55 5.59875,10.445 5.41125,10.3025 C5.22375,10.16 5.0775,9.9725 4.9725,9.74375 C4.8675,9.515 4.78125,9.17 4.78125,9 L2.55,9 C2.55,9.2525 2.61,9.6875 2.72625,10.025 C2.8425,10.3625 3.0075,10.66625 3.21375,10.9325 C3.42,11.19875 3.6675,11.4275 3.94875,11.6225 C4.23,11.8175 4.53375,11.9825 4.86375,12.11 C5.19375,12.24125 5.535,12.33875 5.89875,12.39875 C6.25875,12.4625 6.6225,12.4925 6.9825,12.4925 C7.5825,12.4925 8.13,12.425 8.6175,12.28625 C9.105,12.1475 9.525,11.94875 9.87,11.69375 C10.215,11.435 10.48125,11.12 10.6725,10.74125 C10.86375,10.3625 10.95375,9.935 10.95375,9.455 C10.95375,9.005 10.875,8.6 10.72125,8.24375 C10.68375,8.1575 10.6425,8.075 10.59375,7.9925 L14,8 L14,6 Z" transform="translate(2 3)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(162,0)"><path fill="#000000" fill-rule="evenodd" d="M7,0 L5,0 L0.5,12 L2.5,12 L3.62,9 L8.37,9 L9.49,12 L11.49,12 L7,0 L7,0 Z M4.38,7 L6,2.67 L7.62,7 L4.38,7 L4.38,7 Z" transform="translate(3 1)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(180,0)"><g fill="none" fill-rule="evenodd">
|
||||
<path fill="#000000" d="M14.5,8.87 C14.5,8.87 13,10.49 13,11.49 C13,12.32 13.67,12.99 14.5,12.99 C15.33,12.99 16,12.32 16,11.49 C16,10.5 14.5,8.87 14.5,8.87 L14.5,8.87 Z M12.71,6.79 L5.91,0 L4.85,1.06 L6.44,2.65 L2.29,6.79 C1.9,7.18 1.9,7.81 2.29,8.2 L6.79,12.7 C6.99,12.9 7.24,13 7.5,13 C7.76,13 8.01,12.9 8.21,12.71 L12.71,8.21 C13.1,7.82 13.1,7.18 12.71,6.79 L12.71,6.79 Z M4.21,7 L7.5,3.71 L10.79,7 L4.21,7 L4.21,7 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(198,0)"><path fill="#000000" fill-rule="evenodd" d="M3,6 L1,6 L1,2 L8,2 L8,4 L3,4 L3,6 Z M10,4 L10,2 L17,2 L17,6 L15,6 L15,4 L10,4 Z M10,14 L15,14 L15,12 L17,12 L17,16 L10,16 L10,14 Z M1,12 L3,12 L3,14 L8,14 L8,16 L1,16 L1,12 Z M1,8 L5,8 L5,6 L8,9 L5,12 L5,10 L1,10 L1,8 Z M10,9 L13,6 L13,8 L17,8 L17,10 L13,10 L13,12 L10,9 Z"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(216,0)"><path fill="#000000" fill-rule="evenodd" d="M0,14 L10,14 L10,12 L0,12 L0,14 Z M10,4 L0,4 L0,6 L10,6 L10,4 Z M0,0 L0,2 L14,2 L14,0 L0,0 Z M0,10 L14,10 L14,8 L0,8 L0,10 Z" transform="translate(2 2)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(234,0)"><path fill="#000000" fill-rule="evenodd" d="M2,12 L2,14 L12,14 L12,12 L2,12 Z M2,4 L2,6 L12,6 L12,4 L2,4 Z M0,10 L14,10 L14,8 L0,8 L0,10 Z M0,0 L0,2 L14,2 L14,0 L0,0 Z" transform="translate(2 2)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(0,18)"><path fill="#000000" fill-rule="evenodd" d="M4,14 L14,14 L14,12 L4,12 L4,14 Z M0,10 L14,10 L14,8 L0,8 L0,10 Z M0,0 L0,2 L14,2 L14,0 L0,0 Z M4,6 L14,6 L14,4 L4,4 L4,6 Z" transform="translate(2 2)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(18,18)"><path fill="#000000" fill-rule="evenodd" d="M0,0 L0,2 L12,2 L12,0 L0,0 L0,0 Z M2.5,7 L5,7 L5,14 L7,14 L7,7 L9.5,7 L6,3.5 L2.5,7 L2.5,7 Z" transform="translate(3 2)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(36,18)"><path fill="#000000" fill-rule="evenodd" d="M9.5,3 L7,3 L7,0 L5,0 L5,3 L2.5,3 L6,6.5 L9.5,3 L9.5,3 Z M0,8 L0,10 L12,10 L12,8 L0,8 L0,8 Z M2.5,15 L5,15 L5,18 L7,18 L7,15 L9.5,15 L6,11.5 L2.5,15 L2.5,15 Z" transform="translate(3)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(54,18)"><path fill="#000000" fill-rule="evenodd" d="M9.5,7 L7,7 L7,0 L5,0 L5,7 L2.5,7 L6,10.5 L9.5,7 L9.5,7 Z M0,12 L0,14 L12,14 L12,12 L0,12 L0,12 Z" transform="translate(3 2)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(72,18)"><path fill="#000000" fill-rule="evenodd" d="M14,0 L0,0 L0,2 L14,2 L14,0 Z M0,12 L4,12 L4,10 L0,10 L0,12 Z M11.5,5 L0,5 L0,7 L11.75,7 C12.58,7 13.25,7.67 13.25,8.5 C13.25,9.33 12.58,10 11.75,10 L9,10 L9,8 L6,11 L9,14 L9,12 L11.5,12 C13.43,12 15,10.43 15,8.5 C15,6.57 13.43,5 11.5,5 Z" transform="translate(2 3)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(90,18)"><path fill="#000000" fill-rule="evenodd" d="M0,0 L0,1 L6,7 L6,12 L8,11 L8,7 L14,1 L14,0 L0,0 Z M4,3 L10,3 L7,6 L4,3 Z" transform="translate(2 3)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(108,18)"><polygon fill="#000000" fill-rule="evenodd" points="10 0 0 0 0 1.8 5.5 7 0 12.2 0 14 10 14 10 12 3.1 12 8 7 3.1 2 10 2" transform="translate(4 2)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(126,18)"><polygon fill="#000000" fill-rule="evenodd" points="0 0 4 4 8 0" transform="translate(5 7)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(144,18)"><polygon fill="#000000" fill-rule="evenodd" points="-2 2 2 6 6 2" transform="rotate(-90 8 3)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(162,18)"><path fill="#000000" fill-rule="evenodd" d="M1.9,4 C1.9,2.84 2.84,1.9 4,1.9 L8,1.9 L8,0 L4,0 C1.79,0 0,1.79 0,4 C0,6.21 1.79,8 4,8 L8,8 L8,6.1 L4,6.1 C2.84,6.1 1.9,5.16 1.9,4 L1.9,4 Z M14,0 L10,0 L10,1.9 L14,1.9 C15.16,1.9 16.1,2.84 16.1,4 C16.1,5.16 15.16,6.1 14,6.1 L10,6.1 L10,8 L14,8 C16.21,8 18,6.21 18,4 C18,1.79 16.21,0 14,0 L14,0 Z M6,5 L12,5 L12,3 L6,3 L6,5 L6,5 Z" transform="translate(0 5)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(180,18)"><path fill="#000000" fill-rule="evenodd" d="M15,0 C15.55,0 16,0.45 16,1 L16,15 C16,15.55 15.55,16 15,16 L1,16 C0.45,16 0,15.55 0,15 L0,1 C0,0.45 0.45,0 1,0 L15,0 Z M2,2 L2,14 L14,14 L14,2 L2,2 Z M6,12 L4,12 L4,7 L6,7 L6,12 L6,12 Z M9,12 L7,12 L7,4 L9,4 L9,12 L9,12 Z M12,12 L10,12 L10,8 L12,8 L12,12 L12,12 Z" transform="translate(1 1)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(198,18)"><g fill="none" fill-rule="evenodd">
|
||||
<path stroke="#000000" d="M1.5 3.5H16.5V15.5H1.5z"/>
|
||||
<path fill="#000000" d="M6 8H7V15H6z"/>
|
||||
<path fill="#D8D8D8" d="M2 4H16V7H2z"/>
|
||||
<path fill="#000000" d="M2 7H16V8H2zM2 11H16V12H2z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(216,18)"><path fill="#000000" fill-rule="evenodd" d="M2,0.5 C1.17,0.5 0.5,1.17 0.5,2 C0.5,2.83 1.17,3.5 2,3.5 C2.83,3.5 3.5,2.83 3.5,2 C3.5,1.17 2.83,0.5 2,0.5 L2,0.5 Z M12,0.5 C11.17,0.5 10.5,1.17 10.5,2 C10.5,2.83 11.17,3.5 12,3.5 C12.83,3.5 13.5,2.83 13.5,2 C13.5,1.17 12.83,0.5 12,0.5 L12,0.5 Z M7,0.5 C6.17,0.5 5.5,1.17 5.5,2 C5.5,2.83 6.17,3.5 7,3.5 C7.83,3.5 8.5,2.83 8.5,2 C8.5,1.17 7.83,0.5 7,0.5 L7,0.5 Z" transform="translate(2 7)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(234,18)"><path fill="#000000" fill-rule="evenodd" d="M6,4 L6,0 L4,0 L4,4 L0,4 L0,6 L4,6 L4,10 L6,10 L6,6 L10,6 L10,4 L6,4 Z" transform="translate(4 4)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(0,36)"><path fill="#000000" fill-rule="evenodd" d="M0,0 L0,14 L14,14 L14,0 L0,0 L0,0 Z M6,12 L2,12 L2,8 L6,8 L6,12 L6,12 Z M6,6 L2,6 L2,2 L6,2 L6,6 L6,6 Z M12,12 L8,12 L8,8 L12,8 L12,12 L12,12 Z M12,6 L8,6 L8,2 L12,2 L12,6 L12,6 Z" transform="translate(2 2)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(18,36)">
|
||||
<g fill="#000000" fill-rule="evenodd" transform="translate(2 2)">
|
||||
<path d="M0,14 L2,14 L2,12 L0,12 L0,14 L0,14 Z M2,3 L0,3 L0,5 L2,5 L2,3 L2,3 Z M3,14 L5,14 L5,12 L3,12 L3,14 L3,14 Z M11,0 L9,0 L9,2 L11,2 L11,0 L11,0 Z M2,0 L0,0 L0,2 L2,2 L2,0 L2,0 Z M5,0 L3,0 L3,2 L5,2 L5,0 L5,0 Z M0,11 L2,11 L2,9 L0,9 L0,11 L0,11 Z M9,14 L11,14 L11,12 L9,12 L9,14 L9,14 Z M12,0 L12,2 L14,2 L14,0 L12,0 L12,0 Z M12,5 L14,5 L14,3 L12,3 L12,5 L12,5 Z M12,14 L14,14 L14,12 L12,12 L12,14 L12,14 Z M12,11 L14,11 L14,9 L12,9 L12,11 L12,11 Z" opacity=".54"/>
|
||||
<polygon points="8 0 6 0 6 6 0 6 0 8 6 8 6 14 8 14 8 8 14 8 14 6 8 6"/>
|
||||
</g>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(36,36)">
|
||||
<g fill="#000000" fill-rule="evenodd" transform="translate(2 2)">
|
||||
<path d="M6,14 L8,14 L8,12 L6,12 L6,14 L6,14 Z M3,2 L5,2 L5,0 L3,0 L3,2 L3,2 Z M6,11 L8,11 L8,9 L6,9 L6,11 L6,11 Z M3,14 L5,14 L5,12 L3,12 L3,14 L3,14 Z M0,5 L2,5 L2,3 L0,3 L0,5 L0,5 Z M0,14 L2,14 L2,12 L0,12 L0,14 L0,14 Z M0,2 L2,2 L2,0 L0,0 L0,2 L0,2 Z M0,11 L2,11 L2,9 L0,9 L0,11 L0,11 Z M12,11 L14,11 L14,9 L12,9 L12,11 L12,11 Z M12,14 L14,14 L14,12 L12,12 L12,14 L12,14 Z M12,5 L14,5 L14,3 L12,3 L12,5 L12,5 Z M12,0 L12,2 L14,2 L14,0 L12,0 L12,0 Z M6,2 L8,2 L8,0 L6,0 L6,2 L6,2 Z M9,2 L11,2 L11,0 L9,0 L9,2 L9,2 Z M6,5 L8,5 L8,3 L6,3 L6,5 L6,5 Z M9,14 L11,14 L11,12 L9,12 L9,14 L9,14 Z" opacity=".54"/>
|
||||
<polygon points="0 8 14 8 14 6 0 6"/>
|
||||
</g>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(54,36)">
|
||||
<g fill="#000000" fill-rule="evenodd" transform="translate(2 2)">
|
||||
<path d="M3,14 L5,14 L5,12 L3,12 L3,14 L3,14 Z M0,5 L2,5 L2,3 L0,3 L0,5 L0,5 Z M0,2 L2,2 L2,0 L0,0 L0,2 L0,2 Z M3,8 L5,8 L5,6 L3,6 L3,8 L3,8 Z M3,2 L5,2 L5,0 L3,0 L3,2 L3,2 Z M0,14 L2,14 L2,12 L0,12 L0,14 L0,14 Z M0,8 L2,8 L2,6 L0,6 L0,8 L0,8 Z M0,11 L2,11 L2,9 L0,9 L0,11 L0,11 Z M12,0 L12,2 L14,2 L14,0 L12,0 L12,0 Z M12,8 L14,8 L14,6 L12,6 L12,8 L12,8 Z M12,14 L14,14 L14,12 L12,12 L12,14 L12,14 Z M12,5 L14,5 L14,3 L12,3 L12,5 L12,5 Z M12,11 L14,11 L14,9 L12,9 L12,11 L12,11 Z M9,14 L11,14 L11,12 L9,12 L9,14 L9,14 Z M9,8 L11,8 L11,6 L9,6 L9,8 L9,8 Z M9,2 L11,2 L11,0 L9,0 L9,2 L9,2 Z" opacity=".54"/>
|
||||
<polygon points="6 14 8 14 8 0 6 0"/>
|
||||
</g>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(72,36)">
|
||||
<g fill="#000000" fill-rule="evenodd" transform="translate(2 2)">
|
||||
<path d="M8,3 L6,3 L6,5 L8,5 L8,3 L8,3 Z M11,6 L9,6 L9,8 L11,8 L11,6 L11,6 Z M8,6 L6,6 L6,8 L8,8 L8,6 L8,6 Z M8,9 L6,9 L6,11 L8,11 L8,9 L8,9 Z M5,6 L3,6 L3,8 L5,8 L5,6 L5,6 Z" opacity=".54"/>
|
||||
<path d="M0,0 L14,0 L14,14 L0,14 L0,0 Z M12,12 L12,2 L2,2 L2,12 L12,12 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(90,36)">
|
||||
<g fill="#000000" fill-rule="evenodd" transform="translate(2 2)">
|
||||
<path d="M6,8 L8,8 L8,6 L6,6 L6,8 L6,8 Z M6,5 L8,5 L8,3 L6,3 L6,5 L6,5 Z M6,11 L8,11 L8,9 L6,9 L6,11 L6,11 Z M6,14 L8,14 L8,12 L6,12 L6,14 L6,14 Z M3,14 L5,14 L5,12 L3,12 L3,14 L3,14 Z M3,2 L5,2 L5,0 L3,0 L3,2 L3,2 Z M3,8 L5,8 L5,6 L3,6 L3,8 L3,8 Z M12,14 L14,14 L14,12 L12,12 L12,14 L12,14 Z M12,8 L14,8 L14,6 L12,6 L12,8 L12,8 Z M12,11 L14,11 L14,9 L12,9 L12,11 L12,11 Z M12,5 L14,5 L14,3 L12,3 L12,5 L12,5 Z M6,2 L8,2 L8,0 L6,0 L6,2 L6,2 Z M12,0 L12,2 L14,2 L14,0 L12,0 L12,0 Z M9,14 L11,14 L11,12 L9,12 L9,14 L9,14 Z M9,8 L11,8 L11,6 L9,6 L9,8 L9,8 Z M9,2 L11,2 L11,0 L9,0 L9,2 L9,2 Z" opacity=".54"/>
|
||||
<polygon points="0 14 2 14 2 0 0 0"/>
|
||||
</g>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(108,36)">
|
||||
<g fill="#000000" fill-rule="evenodd" transform="translate(2 2)">
|
||||
<path d="M3,8 L5,8 L5,6 L3,6 L3,8 L3,8 Z M0,14 L2,14 L2,12 L0,12 L0,14 L0,14 Z M6,14 L8,14 L8,12 L6,12 L6,14 L6,14 Z M6,11 L8,11 L8,9 L6,9 L6,11 L6,11 Z M3,14 L5,14 L5,12 L3,12 L3,14 L3,14 Z M0,11 L2,11 L2,9 L0,9 L0,11 L0,11 Z M6,8 L8,8 L8,6 L6,6 L6,8 L6,8 Z M0,5 L2,5 L2,3 L0,3 L0,5 L0,5 Z M0,8 L2,8 L2,6 L0,6 L0,8 L0,8 Z M12,8 L14,8 L14,6 L12,6 L12,8 L12,8 Z M12,11 L14,11 L14,9 L12,9 L12,11 L12,11 Z M12,5 L14,5 L14,3 L12,3 L12,5 L12,5 Z M6,5 L8,5 L8,3 L6,3 L6,5 L6,5 Z M9,14 L11,14 L11,12 L9,12 L9,14 L9,14 Z M9,8 L11,8 L11,6 L9,6 L9,8 L9,8 Z M12,14 L14,14 L14,12 L12,12 L12,14 L12,14 Z" opacity=".54"/>
|
||||
<polygon points="0 0 0 2 14 2 14 0"/>
|
||||
</g>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(126,36)">
|
||||
<g fill="#000000" fill-rule="evenodd" transform="translate(2 2)">
|
||||
<path d="M0,2 L2,2 L2,0 L0,0 L0,2 L0,2 Z M3,2 L5,2 L5,0 L3,0 L3,2 L3,2 Z M3,8 L5,8 L5,6 L3,6 L3,8 L3,8 Z M3,14 L5,14 L5,12 L3,12 L3,14 L3,14 Z M0,5 L2,5 L2,3 L0,3 L0,5 L0,5 Z M0,8 L2,8 L2,6 L0,6 L0,8 L0,8 Z M0,14 L2,14 L2,12 L0,12 L0,14 L0,14 Z M0,11 L2,11 L2,9 L0,9 L0,11 L0,11 Z M9,8 L11,8 L11,6 L9,6 L9,8 L9,8 Z M6,14 L8,14 L8,12 L6,12 L6,14 L6,14 Z M9,14 L11,14 L11,12 L9,12 L9,14 L9,14 Z M6,2 L8,2 L8,0 L6,0 L6,2 L6,2 Z M9,2 L11,2 L11,0 L9,0 L9,2 L9,2 Z M6,11 L8,11 L8,9 L6,9 L6,11 L6,11 Z M6,5 L8,5 L8,3 L6,3 L6,5 L6,5 Z M6,8 L8,8 L8,6 L6,6 L6,8 L6,8 Z" opacity=".54"/>
|
||||
<polygon points="12 0 12 14 14 14 14 0"/>
|
||||
</g>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(144,36)">
|
||||
<g fill="#000000" fill-rule="evenodd" transform="translate(2 2)">
|
||||
<path d="M5,0 L3,0 L3,2 L5,2 L5,0 L5,0 Z M8,6 L6,6 L6,8 L8,8 L8,6 L8,6 Z M8,9 L6,9 L6,11 L8,11 L8,9 L8,9 Z M11,6 L9,6 L9,8 L11,8 L11,6 L11,6 Z M5,6 L3,6 L3,8 L5,8 L5,6 L5,6 Z M11,0 L9,0 L9,2 L11,2 L11,0 L11,0 Z M8,3 L6,3 L6,5 L8,5 L8,3 L8,3 Z M8,0 L6,0 L6,2 L8,2 L8,0 L8,0 Z M2,9 L0,9 L0,11 L2,11 L2,9 L2,9 Z M12,11 L14,11 L14,9 L12,9 L12,11 L12,11 Z M12,5 L14,5 L14,3 L12,3 L12,5 L12,5 Z M12,8 L14,8 L14,6 L12,6 L12,8 L12,8 Z M12,0 L12,2 L14,2 L14,0 L12,0 L12,0 Z M2,0 L0,0 L0,2 L2,2 L2,0 L2,0 Z M2,3 L0,3 L0,5 L2,5 L2,3 L2,3 Z M2,6 L0,6 L0,8 L2,8 L2,6 L2,6 Z" opacity=".54"/>
|
||||
<polygon points="0 14 14 14 14 12 0 12"/>
|
||||
</g>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(162,36)"><path fill="#000000" fill-rule="evenodd" d="M6,14 L8,14 L8,12 L6,12 L6,14 L6,14 Z M3,8 L5,8 L5,6 L3,6 L3,8 L3,8 Z M3,2 L5,2 L5,0 L3,0 L3,2 L3,2 Z M6,11 L8,11 L8,9 L6,9 L6,11 L6,11 Z M3,14 L5,14 L5,12 L3,12 L3,14 L3,14 Z M0,5 L2,5 L2,3 L0,3 L0,5 L0,5 Z M0,14 L2,14 L2,12 L0,12 L0,14 L0,14 Z M0,2 L2,2 L2,0 L0,0 L0,2 L0,2 Z M0,8 L2,8 L2,6 L0,6 L0,8 L0,8 Z M6,8 L8,8 L8,6 L6,6 L6,8 L6,8 Z M0,11 L2,11 L2,9 L0,9 L0,11 L0,11 Z M12,11 L14,11 L14,9 L12,9 L12,11 L12,11 Z M12,14 L14,14 L14,12 L12,12 L12,14 L12,14 Z M12,8 L14,8 L14,6 L12,6 L12,8 L12,8 Z M12,5 L14,5 L14,3 L12,3 L12,5 L12,5 Z M12,0 L12,2 L14,2 L14,0 L12,0 L12,0 Z M6,2 L8,2 L8,0 L6,0 L6,2 L6,2 Z M9,2 L11,2 L11,0 L9,0 L9,2 L9,2 Z M6,5 L8,5 L8,3 L6,3 L6,5 L6,5 Z M9,14 L11,14 L11,12 L9,12 L9,14 L9,14 Z M9,8 L11,8 L11,6 L9,6 L9,8 L9,8 Z" transform="translate(2 2)" opacity=".54"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(180,36)"><path fill="#000000" fill-rule="evenodd" d="M6.5,3.62 L0,10.12 L0,13 L2.88,13 L9.38,6.5 L6.5,3.62 Z M11.85,4.02 C12.05,3.82 12.05,3.51 11.85,3.31 L9.68,1.14 C9.48,0.94 9.17,0.94 8.97,1.14 L7.62,2.5 L10.5,5.38 L11.85,4.02 L11.85,4.02 Z" transform="translate(4)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(198,36)"><path fill="#000000" fill-rule="evenodd" d="M0,0 L14,0 L14,2 L0,2 L0,0 Z M0,4 L6,4 L6,6 L0,6 L0,4 Z M0,8 L2,8 L2,10 L0,10 L0,8 Z M8,4 L14,4 L14,6 L8,6 L8,4 Z M4,8 L6,8 L6,10 L4,10 L4,8 Z M8,8 L10,8 L10,10 L8,10 L8,8 Z M12,8 L14,8 L14,10 L12,10 L12,8 Z" transform="translate(2 4)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(216,36)"><polygon fill="#000000" fill-rule="evenodd" points="-2 2 2 6 6 2" transform="rotate(90 3 10)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(234,36)"><polygon fill="#000000" fill-rule="evenodd" points="7.53 1.53 6.47 .47 4 2.94 1.53 .47 .47 1.53 2.94 4 .47 6.47 1.53 7.53 4 5.06 6.47 7.53 7.53 6.47 5.06 4" transform="translate(5 5)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(0,54)"><polygon fill="#000000" fill-rule="evenodd" points="8.44 .44 5 3.88 1.56 .44 .5 1.5 5 6 9.5 1.5" transform="translate(4 6)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(18,54)"><polygon fill="#000000" fill-rule="evenodd" points="5 0 .5 4.5 1.56 5.56 5 2.12 8.44 5.56 9.5 4.5" transform="translate(4 6)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(36,54)"><polygon fill="#000000" fill-rule="evenodd" points="8.44 .44 5 3.88 1.56 .44 .5 1.5 5 6 9.5 1.5" transform="rotate(90 4 8)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(54,54)"><polygon fill="#000000" fill-rule="evenodd" points="5 0 .5 4.5 1.56 5.56 5 2.12 8.44 5.56 9.5 4.5" transform="rotate(90 4 8)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(72,54)"><polygon fill="#000000" fill-rule="evenodd" points="12 5 3.125 5 7.06 1.06 6 0 0 6 6 12 7.06 10.94 3.125 7 12 7" transform="matrix(-1 0 0 1 15 3)"/>
|
||||
</g>
|
||||
<g xmlns="http://www.w3.org/2000/svg" transform="translate(90,54)"><polygon fill="#000000" fill-rule="evenodd" points="12 5 3.125 5 7.06 1.06 6 0 0 6 6 12 7.06 10.94 3.125 7 12 7" transform="translate(3 3)"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,403 @@
|
||||
/* global window */
|
||||
function dpr() {
|
||||
return window.devicePixelRatio || 1;
|
||||
}
|
||||
|
||||
function thinLineWidth() {
|
||||
return dpr() - 0.5;
|
||||
}
|
||||
|
||||
function npx(px) {
|
||||
return parseInt(px * dpr(), 10);
|
||||
}
|
||||
|
||||
function npxLine(px) {
|
||||
const n = npx(px);
|
||||
return n > 0 ? n - 0.5 : 0.5;
|
||||
}
|
||||
|
||||
class DrawBox {
|
||||
constructor(x, y, w, h, padding = 0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = w;
|
||||
this.height = h;
|
||||
this.padding = padding;
|
||||
this.bgcolor = '#ffffff';
|
||||
// border: [width, style, color]
|
||||
this.borderTop = null;
|
||||
this.borderRight = null;
|
||||
this.borderBottom = null;
|
||||
this.borderLeft = null;
|
||||
}
|
||||
|
||||
setBorders({
|
||||
top, bottom, left, right,
|
||||
}) {
|
||||
if (top) this.borderTop = top;
|
||||
if (right) this.borderRight = right;
|
||||
if (bottom) this.borderBottom = bottom;
|
||||
if (left) this.borderLeft = left;
|
||||
}
|
||||
|
||||
innerWidth() {
|
||||
return this.width - (this.padding * 2) - 2;
|
||||
}
|
||||
|
||||
innerHeight() {
|
||||
return this.height - (this.padding * 2) - 2;
|
||||
}
|
||||
|
||||
textx(align) {
|
||||
const { width, padding } = this;
|
||||
let { x } = this;
|
||||
if (align === 'left') {
|
||||
x += padding;
|
||||
} else if (align === 'center') {
|
||||
x += width / 2;
|
||||
} else if (align === 'right') {
|
||||
x += width - padding;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
texty(align, h) {
|
||||
const { height, padding } = this;
|
||||
let { y } = this;
|
||||
if (align === 'top') {
|
||||
y += padding;
|
||||
} else if (align === 'middle') {
|
||||
y += height / 2 - h / 2;
|
||||
} else if (align === 'bottom') {
|
||||
y += height - padding - h;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
topxys() {
|
||||
const { x, y, width } = this;
|
||||
return [[x, y], [x + width, y]];
|
||||
}
|
||||
|
||||
rightxys() {
|
||||
const {
|
||||
x, y, width, height,
|
||||
} = this;
|
||||
return [[x + width, y], [x + width, y + height]];
|
||||
}
|
||||
|
||||
bottomxys() {
|
||||
const {
|
||||
x, y, width, height,
|
||||
} = this;
|
||||
return [[x, y + height], [x + width, y + height]];
|
||||
}
|
||||
|
||||
leftxys() {
|
||||
const {
|
||||
x, y, height,
|
||||
} = this;
|
||||
return [[x, y], [x, y + height]];
|
||||
}
|
||||
}
|
||||
|
||||
function drawFontLine(type, tx, ty, align, valign, blheight, blwidth) {
|
||||
const floffset = { x: 0, y: 0 };
|
||||
if (type === 'underline') {
|
||||
if (valign === 'bottom') {
|
||||
floffset.y = 0;
|
||||
} else if (valign === 'top') {
|
||||
floffset.y = -(blheight + 2);
|
||||
} else {
|
||||
floffset.y = -blheight / 2;
|
||||
}
|
||||
} else if (type === 'strike') {
|
||||
if (valign === 'bottom') {
|
||||
floffset.y = blheight / 2;
|
||||
} else if (valign === 'top') {
|
||||
floffset.y = -((blheight / 2) + 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (align === 'center') {
|
||||
floffset.x = blwidth / 2;
|
||||
} else if (align === 'right') {
|
||||
floffset.x = blwidth;
|
||||
}
|
||||
this.line(
|
||||
[tx - floffset.x, ty - floffset.y],
|
||||
[tx - floffset.x + blwidth, ty - floffset.y],
|
||||
);
|
||||
}
|
||||
|
||||
class Draw {
|
||||
constructor(el, width, height) {
|
||||
this.el = el;
|
||||
this.ctx = el.getContext('2d');
|
||||
this.resize(width, height);
|
||||
this.ctx.scale(dpr(), dpr());
|
||||
}
|
||||
|
||||
resize(width, height) {
|
||||
// console.log('dpr:', dpr);
|
||||
this.el.style.width = `${width}px`;
|
||||
this.el.style.height = `${height}px`;
|
||||
this.el.width = npx(width);
|
||||
this.el.height = npx(height);
|
||||
}
|
||||
|
||||
clear() {
|
||||
const { width, height } = this.el;
|
||||
this.ctx.clearRect(0, 0, width, height);
|
||||
return this;
|
||||
}
|
||||
|
||||
attr(options) {
|
||||
Object.assign(this.ctx, options);
|
||||
return this;
|
||||
}
|
||||
|
||||
save() {
|
||||
this.ctx.save();
|
||||
this.ctx.beginPath();
|
||||
return this;
|
||||
}
|
||||
|
||||
restore() {
|
||||
this.ctx.restore();
|
||||
return this;
|
||||
}
|
||||
|
||||
beginPath() {
|
||||
this.ctx.beginPath();
|
||||
return this;
|
||||
}
|
||||
|
||||
translate(x, y) {
|
||||
this.ctx.translate(npx(x), npx(y));
|
||||
return this;
|
||||
}
|
||||
|
||||
scale(x, y) {
|
||||
this.ctx.scale(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
clearRect(x, y, w, h) {
|
||||
this.ctx.clearRect(x, y, w, h);
|
||||
return this;
|
||||
}
|
||||
|
||||
fillRect(x, y, w, h) {
|
||||
this.ctx.fillRect(npx(x) - 0.5, npx(y) - 0.5, npx(w), npx(h));
|
||||
return this;
|
||||
}
|
||||
|
||||
fillText(text, x, y) {
|
||||
this.ctx.fillText(text, npx(x), npx(y));
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
txt: render text
|
||||
box: DrawBox
|
||||
attr: {
|
||||
align: left | center | right
|
||||
valign: top | middle | bottom
|
||||
color: '#333333',
|
||||
strike: false,
|
||||
font: {
|
||||
name: 'Arial',
|
||||
size: 14,
|
||||
bold: false,
|
||||
italic: false,
|
||||
}
|
||||
}
|
||||
textWrap: text wrapping
|
||||
*/
|
||||
text(mtxt, box, attr = {}, textWrap = true) {
|
||||
const { ctx } = this;
|
||||
const {
|
||||
align, valign, font, color, strike, underline,
|
||||
} = attr;
|
||||
const tx = box.textx(align);
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
this.attr({
|
||||
textAlign: align,
|
||||
textBaseline: valign,
|
||||
font: `${font.italic ? 'italic' : ''} ${font.bold ? 'bold' : ''} ${npx(font.size)}px ${font.name}`,
|
||||
fillStyle: color,
|
||||
strokeStyle: color,
|
||||
});
|
||||
const txts = `${mtxt}`.split('\n');
|
||||
const biw = box.innerWidth();
|
||||
const ntxts = [];
|
||||
txts.forEach((it) => {
|
||||
const txtWidth = ctx.measureText(it).width;
|
||||
if (textWrap && txtWidth > npx(biw)) {
|
||||
let textLine = { w: 0, len: 0, start: 0 };
|
||||
for (let i = 0; i < it.length; i += 1) {
|
||||
if (textLine.w >= npx(biw)) {
|
||||
ntxts.push(it.substr(textLine.start, textLine.len));
|
||||
textLine = { w: 0, len: 0, start: i };
|
||||
}
|
||||
textLine.len += 1;
|
||||
textLine.w += ctx.measureText(it[i]).width + 1;
|
||||
}
|
||||
if (textLine.len > 0) {
|
||||
ntxts.push(it.substr(textLine.start, textLine.len));
|
||||
}
|
||||
} else {
|
||||
ntxts.push(it);
|
||||
}
|
||||
});
|
||||
const txtHeight = (ntxts.length - 1) * (font.size + 2);
|
||||
let ty = box.texty(valign, txtHeight);
|
||||
ntxts.forEach((txt) => {
|
||||
const txtWidth = ctx.measureText(txt).width;
|
||||
this.fillText(txt, tx, ty);
|
||||
if (strike) {
|
||||
drawFontLine.call(this, 'strike', tx, ty, align, valign, font.size, txtWidth);
|
||||
}
|
||||
if (underline) {
|
||||
drawFontLine.call(this, 'underline', tx, ty, align, valign, font.size, txtWidth);
|
||||
}
|
||||
ty += font.size + 2;
|
||||
});
|
||||
ctx.restore();
|
||||
return this;
|
||||
}
|
||||
|
||||
border(style, color) {
|
||||
const { ctx } = this;
|
||||
ctx.lineWidth = thinLineWidth;
|
||||
ctx.strokeStyle = color;
|
||||
// console.log('style:', style);
|
||||
if (style === 'medium') {
|
||||
ctx.lineWidth = npx(2) - 0.5;
|
||||
} else if (style === 'thick') {
|
||||
ctx.lineWidth = npx(3);
|
||||
} else if (style === 'dashed') {
|
||||
ctx.setLineDash([npx(3), npx(2)]);
|
||||
} else if (style === 'dotted') {
|
||||
ctx.setLineDash([npx(1), npx(1)]);
|
||||
} else if (style === 'double') {
|
||||
ctx.setLineDash([npx(2), 0]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
line(...xys) {
|
||||
const { ctx } = this;
|
||||
if (xys.length > 1) {
|
||||
ctx.beginPath();
|
||||
const [x, y] = xys[0];
|
||||
ctx.moveTo(npxLine(x), npxLine(y));
|
||||
for (let i = 1; i < xys.length; i += 1) {
|
||||
const [x1, y1] = xys[i];
|
||||
ctx.lineTo(npxLine(x1), npxLine(y1));
|
||||
}
|
||||
ctx.stroke();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
strokeBorders(box) {
|
||||
const { ctx } = this;
|
||||
ctx.save();
|
||||
// border
|
||||
const {
|
||||
borderTop, borderRight, borderBottom, borderLeft,
|
||||
} = box;
|
||||
if (borderTop) {
|
||||
this.border(...borderTop);
|
||||
// console.log('box.topxys:', box.topxys());
|
||||
this.line(...box.topxys());
|
||||
}
|
||||
if (borderRight) {
|
||||
this.border(...borderRight);
|
||||
this.line(...box.rightxys());
|
||||
}
|
||||
if (borderBottom) {
|
||||
this.border(...borderBottom);
|
||||
this.line(...box.bottomxys());
|
||||
}
|
||||
if (borderLeft) {
|
||||
this.border(...borderLeft);
|
||||
this.line(...box.leftxys());
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
dropdown(box) {
|
||||
const { ctx } = this;
|
||||
const {
|
||||
x, y, width, height,
|
||||
} = box;
|
||||
const sx = x + width - 15;
|
||||
const sy = y + height - 15;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(npx(sx), npx(sy));
|
||||
ctx.lineTo(npx(sx + 8), npx(sy));
|
||||
ctx.lineTo(npx(sx + 4), npx(sy + 6));
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, .45)';
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
error(box) {
|
||||
const { ctx } = this;
|
||||
const { x, y, width } = box;
|
||||
const sx = x + width - 1;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(npx(sx - 8), npx(y - 1));
|
||||
ctx.lineTo(npx(sx), npx(y - 1));
|
||||
ctx.lineTo(npx(sx), npx(y + 8));
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = 'rgba(255, 0, 0, .65)';
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
frozen(box) {
|
||||
const { ctx } = this;
|
||||
const { x, y, width } = box;
|
||||
const sx = x + width - 1;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(npx(sx - 8), npx(y - 1));
|
||||
ctx.lineTo(npx(sx), npx(y - 1));
|
||||
ctx.lineTo(npx(sx), npx(y + 8));
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = 'rgba(0, 255, 0, .85)';
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
rect(box, dtextcb) {
|
||||
const { ctx } = this;
|
||||
const {
|
||||
x, y, width, height, bgcolor,
|
||||
} = box;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = bgcolor || '#fff';
|
||||
ctx.rect(npxLine(x + 1), npxLine(y + 1), npx(width - 2), npx(height - 2));
|
||||
ctx.clip();
|
||||
ctx.fill();
|
||||
dtextcb();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
export default {};
|
||||
export {
|
||||
Draw,
|
||||
DrawBox,
|
||||
thinLineWidth,
|
||||
npx,
|
||||
};
|
@ -0,0 +1,92 @@
|
||||
class Draw {
|
||||
constructor(el) {
|
||||
this.el = el;
|
||||
this.ctx = el.getContext('2d');
|
||||
}
|
||||
|
||||
clear() {
|
||||
const { width, height } = this.el;
|
||||
this.ctx.clearRect(0, 0, width, height);
|
||||
return this;
|
||||
}
|
||||
|
||||
attr(m) {
|
||||
Object.assign(this.ctx, m);
|
||||
return this;
|
||||
}
|
||||
|
||||
save() {
|
||||
this.ctx.save();
|
||||
this.ctx.beginPath();
|
||||
return this;
|
||||
}
|
||||
|
||||
restore() {
|
||||
this.ctx.restore();
|
||||
return this;
|
||||
}
|
||||
|
||||
beginPath() {
|
||||
this.ctx.beginPath();
|
||||
return this;
|
||||
}
|
||||
|
||||
closePath() {
|
||||
this.ctx.closePath();
|
||||
return this;
|
||||
}
|
||||
|
||||
measureText(text) {
|
||||
return this.ctx.measureText(text);
|
||||
}
|
||||
|
||||
rect(x, y, width, height) {
|
||||
this.ctx.rect(x, y, width, height);
|
||||
return this;
|
||||
}
|
||||
|
||||
scale(x, y) {
|
||||
this.ctx.scale(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
rotate(angle) {
|
||||
this.ctx.rotate(angle);
|
||||
return this;
|
||||
}
|
||||
|
||||
translate(x, y) {
|
||||
this.ctx.translate(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
transform(a, b, c, d, e) {
|
||||
this.ctx.transform(a, b, c, d, e);
|
||||
return this;
|
||||
}
|
||||
|
||||
fillRect(x, y, w, h) {
|
||||
this.ctx.fillRect(x, y, w, h);
|
||||
return this;
|
||||
}
|
||||
|
||||
strokeRect(x, y, w, h) {
|
||||
this.ctx.strokeRect(x, y, w, h);
|
||||
return this;
|
||||
}
|
||||
|
||||
fillText(text, x, y, maxWidth) {
|
||||
this.ctx.fillText(text, x, y, maxWidth);
|
||||
return this;
|
||||
}
|
||||
|
||||
strokeText(text, x, y, maxWidth) {
|
||||
this.ctx.strokeText(text, x, y, maxWidth);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export default {};
|
||||
export {
|
||||
Draw,
|
||||
};
|
@ -0,0 +1,60 @@
|
||||
import { h } from './element';
|
||||
import Icon from './icon';
|
||||
import DropdownColor from './dropdown_color';
|
||||
import DropdownLineType from './dropdown_linetype';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
function buildTable(...trs) {
|
||||
return h('table', '').child(
|
||||
h('tbody', '').children(...trs),
|
||||
);
|
||||
}
|
||||
|
||||
function buildTd(iconName) {
|
||||
return h('td', '').child(
|
||||
h('div', `${cssPrefix}-border-palette-cell`).child(
|
||||
new Icon(`border-${iconName}`),
|
||||
).on('click', () => {
|
||||
this.mode = iconName;
|
||||
const { mode, style, color } = this;
|
||||
this.change({ mode, style, color });
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export default class BorderPalette {
|
||||
constructor() {
|
||||
this.color = '#000';
|
||||
this.style = 'thin';
|
||||
this.mode = 'all';
|
||||
this.change = () => {};
|
||||
this.ddColor = new DropdownColor('line-color', this.color);
|
||||
this.ddColor.change = (color) => {
|
||||
this.color = color;
|
||||
};
|
||||
this.ddType = new DropdownLineType(this.style);
|
||||
this.ddType.change = ([s]) => {
|
||||
this.style = s;
|
||||
};
|
||||
this.el = h('div', `${cssPrefix}-border-palette`);
|
||||
const table = buildTable(
|
||||
h('tr', '').children(
|
||||
h('td', `${cssPrefix}-border-palette-left`).child(
|
||||
buildTable(
|
||||
h('tr', '').children(
|
||||
...['all', 'inside', 'horizontal', 'vertical', 'outside'].map(it => buildTd.call(this, it)),
|
||||
),
|
||||
h('tr', '').children(
|
||||
...['left', 'top', 'right', 'bottom', 'none'].map(it => buildTd.call(this, it)),
|
||||
),
|
||||
),
|
||||
),
|
||||
h('td', `${cssPrefix}-border-palette-right`).children(
|
||||
h('div', `${cssPrefix}-toolbar-btn`).child(this.ddColor.el),
|
||||
h('div', `${cssPrefix}-toolbar-btn`).child(this.ddType.el),
|
||||
),
|
||||
),
|
||||
);
|
||||
this.el.child(table);
|
||||
}
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
import { h } from './element';
|
||||
import { bindClickoutside, unbindClickoutside } from './event';
|
||||
import { cssPrefix } from '../config';
|
||||
import Icon from './icon';
|
||||
import FormInput from './form_input';
|
||||
import Dropdown from './dropdown';
|
||||
// Record: temp not used
|
||||
// import { xtoast } from './message';
|
||||
import { tf } from '../locale/locale';
|
||||
|
||||
class DropdownMore extends Dropdown {
|
||||
constructor(click) {
|
||||
const icon = new Icon('ellipsis');
|
||||
super(icon, 'auto', false, 'top-left');
|
||||
this.contentClick = click;
|
||||
}
|
||||
|
||||
reset(items) {
|
||||
const eles = items.map((it, i) => h('div', `${cssPrefix}-item`)
|
||||
.css('width', '150px')
|
||||
.css('font-weight', 'normal')
|
||||
.on('click', () => {
|
||||
this.contentClick(i);
|
||||
this.hide();
|
||||
})
|
||||
.child(it));
|
||||
this.setContentChildren(...eles);
|
||||
}
|
||||
|
||||
setTitle() {}
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{ key: 'delete', title: tf('contextmenu.deleteSheet') },
|
||||
];
|
||||
|
||||
function buildMenuItem(item) {
|
||||
return h('div', `${cssPrefix}-item`)
|
||||
.child(item.title())
|
||||
.on('click', () => {
|
||||
this.itemClick(item.key);
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
|
||||
function buildMenu() {
|
||||
return menuItems.map(it => buildMenuItem.call(this, it));
|
||||
}
|
||||
|
||||
class ContextMenu {
|
||||
constructor() {
|
||||
this.el = h('div', `${cssPrefix}-contextmenu`)
|
||||
.css('width', '160px')
|
||||
.children(...buildMenu.call(this))
|
||||
.hide();
|
||||
this.itemClick = () => {};
|
||||
}
|
||||
|
||||
hide() {
|
||||
const { el } = this;
|
||||
el.hide();
|
||||
unbindClickoutside(el);
|
||||
}
|
||||
|
||||
setOffset(offset) {
|
||||
const { el } = this;
|
||||
el.offset(offset);
|
||||
el.show();
|
||||
bindClickoutside(el);
|
||||
}
|
||||
}
|
||||
|
||||
export default class Bottombar {
|
||||
constructor(addFunc = () => {},
|
||||
swapFunc = () => {},
|
||||
deleteFunc = () => {},
|
||||
updateFunc = () => {}) {
|
||||
this.swapFunc = swapFunc;
|
||||
this.updateFunc = updateFunc;
|
||||
this.dataNames = [];
|
||||
this.activeEl = null;
|
||||
this.deleteEl = null;
|
||||
this.items = [];
|
||||
this.moreEl = new DropdownMore((i) => {
|
||||
this.clickSwap2(this.items[i]);
|
||||
});
|
||||
this.contextMenu = new ContextMenu();
|
||||
this.contextMenu.itemClick = deleteFunc;
|
||||
this.el = h('div', `${cssPrefix}-bottombar`).children(
|
||||
this.contextMenu.el,
|
||||
this.menuEl = h('ul', `${cssPrefix}-menu`).child(
|
||||
h('li', '').children(
|
||||
new Icon('add').on('click', () => {
|
||||
addFunc();
|
||||
}),
|
||||
h('span', '').child(this.moreEl),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
addItem(name, active, options) {
|
||||
this.dataNames.push(name);
|
||||
const item = h('li', active ? 'active' : '').child(name);
|
||||
item.on('click', () => {
|
||||
this.clickSwap2(item);
|
||||
}).on('contextmenu', (evt) => {
|
||||
if (options.mode === 'read') return;
|
||||
const { offsetLeft, offsetHeight } = evt.target;
|
||||
this.contextMenu.setOffset({ left: offsetLeft, bottom: offsetHeight + 1 });
|
||||
this.deleteEl = item;
|
||||
}).on('dblclick', () => {
|
||||
if (options.mode === 'read') return;
|
||||
const v = item.html();
|
||||
const input = new FormInput('auto', '');
|
||||
input.val(v);
|
||||
input.input.on('blur', ({ target }) => {
|
||||
const { value } = target;
|
||||
const nindex = this.dataNames.findIndex(it => it === v);
|
||||
this.renameItem(nindex, value);
|
||||
/*
|
||||
this.dataNames.splice(nindex, 1, value);
|
||||
this.moreEl.reset(this.dataNames);
|
||||
item.html('').child(value);
|
||||
this.updateFunc(nindex, value);
|
||||
*/
|
||||
});
|
||||
item.html('').child(input.el);
|
||||
input.focus();
|
||||
});
|
||||
if (active) {
|
||||
this.clickSwap(item);
|
||||
}
|
||||
this.items.push(item);
|
||||
this.menuEl.child(item);
|
||||
this.moreEl.reset(this.dataNames);
|
||||
}
|
||||
|
||||
renameItem(index, value) {
|
||||
this.dataNames.splice(index, 1, value);
|
||||
this.moreEl.reset(this.dataNames);
|
||||
this.items[index].html('').child(value);
|
||||
this.updateFunc(index, value);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.items.forEach((it) => {
|
||||
this.menuEl.removeChild(it.el);
|
||||
});
|
||||
this.items = [];
|
||||
this.dataNames = [];
|
||||
this.moreEl.reset(this.dataNames);
|
||||
}
|
||||
|
||||
deleteItem() {
|
||||
const { activeEl, deleteEl } = this;
|
||||
if (this.items.length > 1) {
|
||||
const index = this.items.findIndex(it => it === deleteEl);
|
||||
this.items.splice(index, 1);
|
||||
this.dataNames.splice(index, 1);
|
||||
this.menuEl.removeChild(deleteEl.el);
|
||||
this.moreEl.reset(this.dataNames);
|
||||
if (activeEl === deleteEl) {
|
||||
const [f] = this.items;
|
||||
this.activeEl = f;
|
||||
this.activeEl.toggle();
|
||||
return [index, 0];
|
||||
}
|
||||
return [index, -1];
|
||||
}
|
||||
return [-1];
|
||||
}
|
||||
|
||||
clickSwap2(item) {
|
||||
const index = this.items.findIndex(it => it === item);
|
||||
this.clickSwap(item);
|
||||
this.activeEl.toggle();
|
||||
this.swapFunc(index);
|
||||
}
|
||||
|
||||
clickSwap(item) {
|
||||
if (this.activeEl !== null) {
|
||||
this.activeEl.toggle();
|
||||
}
|
||||
this.activeEl = item;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import { Element } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
import { t } from '../locale/locale';
|
||||
|
||||
export default class Button extends Element {
|
||||
// type: primary
|
||||
constructor(title, type = '') {
|
||||
super('div', `${cssPrefix}-button ${type}`);
|
||||
this.child(t(`button.${title}`));
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
import { h } from './element';
|
||||
import Icon from './icon';
|
||||
import { t } from '../locale/locale';
|
||||
|
||||
function addMonth(date, step) {
|
||||
date.setMonth(date.getMonth() + step);
|
||||
}
|
||||
|
||||
function weekday(date, index) {
|
||||
const d = new Date(date);
|
||||
d.setDate(index - date.getDay() + 1);
|
||||
return d;
|
||||
}
|
||||
|
||||
function monthDays(year, month, cdate) {
|
||||
// the first day of month
|
||||
const startDate = new Date(year, month, 1, 23, 59, 59);
|
||||
const datess = [[], [], [], [], [], []];
|
||||
for (let i = 0; i < 6; i += 1) {
|
||||
for (let j = 0; j < 7; j += 1) {
|
||||
const index = i * 7 + j;
|
||||
const d = weekday(startDate, index);
|
||||
const disabled = d.getMonth() !== month;
|
||||
// console.log('d:', d, ', cdate:', cdate);
|
||||
const active = d.getMonth() === cdate.getMonth() && d.getDate() === cdate.getDate();
|
||||
datess[i][j] = { d, disabled, active };
|
||||
}
|
||||
}
|
||||
return datess;
|
||||
}
|
||||
|
||||
export default class Calendar {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
this.cvalue = new Date(value);
|
||||
|
||||
this.headerLeftEl = h('div', 'calendar-header-left');
|
||||
this.bodyEl = h('tbody', '');
|
||||
this.buildAll();
|
||||
this.el = h('div', 'x-spreadsheet-calendar')
|
||||
.children(
|
||||
h('div', 'calendar-header').children(
|
||||
this.headerLeftEl,
|
||||
h('div', 'calendar-header-right').children(
|
||||
h('a', 'calendar-prev')
|
||||
.on('click.stop', () => this.prev())
|
||||
.child(new Icon('chevron-left')),
|
||||
h('a', 'calendar-next')
|
||||
.on('click.stop', () => this.next())
|
||||
.child(new Icon('chevron-right')),
|
||||
),
|
||||
),
|
||||
h('table', 'calendar-body').children(
|
||||
h('thead', '').child(
|
||||
h('tr', '').children(
|
||||
...t('calendar.weeks').map(week => h('th', 'cell').child(week)),
|
||||
),
|
||||
),
|
||||
this.bodyEl,
|
||||
),
|
||||
);
|
||||
this.selectChange = () => {};
|
||||
}
|
||||
|
||||
setValue(value) {
|
||||
this.value = value;
|
||||
this.cvalue = new Date(value);
|
||||
this.buildAll();
|
||||
}
|
||||
|
||||
prev() {
|
||||
const { value } = this;
|
||||
addMonth(value, -1);
|
||||
this.buildAll();
|
||||
}
|
||||
|
||||
next() {
|
||||
const { value } = this;
|
||||
addMonth(value, 1);
|
||||
this.buildAll();
|
||||
}
|
||||
|
||||
buildAll() {
|
||||
this.buildHeaderLeft();
|
||||
this.buildBody();
|
||||
}
|
||||
|
||||
buildHeaderLeft() {
|
||||
const { value } = this;
|
||||
this.headerLeftEl.html(`${t('calendar.months')[value.getMonth()]} ${value.getFullYear()}`);
|
||||
}
|
||||
|
||||
buildBody() {
|
||||
const { value, cvalue, bodyEl } = this;
|
||||
const mDays = monthDays(value.getFullYear(), value.getMonth(), cvalue);
|
||||
const trs = mDays.map((it) => {
|
||||
const tds = it.map((it1) => {
|
||||
let cls = 'cell';
|
||||
if (it1.disabled) cls += ' disabled';
|
||||
if (it1.active) cls += ' active';
|
||||
return h('td', '').child(
|
||||
h('div', cls)
|
||||
.on('click.stop', () => {
|
||||
this.selectChange(it1.d);
|
||||
})
|
||||
.child(it1.d.getDate().toString()),
|
||||
);
|
||||
});
|
||||
return h('tr', '').children(...tds);
|
||||
});
|
||||
bodyEl.html('').children(...trs);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import { h } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
const themeColorPlaceHolders = ['#ffffff', '#000100', '#e7e5e6', '#445569', '#5b9cd6', '#ed7d31', '#a5a5a5', '#ffc001', '#4371c6', '#71ae47'];
|
||||
|
||||
const themeColors = [
|
||||
['#f2f2f2', '#7f7f7f', '#d0cecf', '#d5dce4', '#deeaf6', '#fce5d5', '#ededed', '#fff2cd', '#d9e2f3', '#e3efd9'],
|
||||
['#d8d8d8', '#595959', '#afabac', '#adb8ca', '#bdd7ee', '#f7ccac', '#dbdbdb', '#ffe59a', '#b3c6e7', '#c5e0b3'],
|
||||
['#bfbfbf', '#3f3f3f', '#756f6f', '#8596b0', '#9cc2e6', '#f4b184', '#c9c9c9', '#fed964', '#8eaada', '#a7d08c'],
|
||||
['#a5a5a5', '#262626', '#3a3839', '#333f4f', '#2e75b5', '#c45a10', '#7b7b7b', '#bf8e01', '#2f5596', '#538136'],
|
||||
['#7f7f7f', '#0c0c0c', '#171516', '#222a35', '#1f4e7a', '#843c0a', '#525252', '#7e6000', '#203864', '#365624'],
|
||||
];
|
||||
|
||||
const standardColors = ['#c00000', '#fe0000', '#fdc101', '#ffff01', '#93d051', '#00b04e', '#01b0f1', '#0170c1', '#012060', '#7030a0'];
|
||||
|
||||
function buildTd(bgcolor) {
|
||||
return h('td', '').child(
|
||||
h('div', `${cssPrefix}-color-palette-cell`)
|
||||
.on('click.stop', () => this.change(bgcolor))
|
||||
.css('background-color', bgcolor),
|
||||
);
|
||||
}
|
||||
|
||||
export default class ColorPalette {
|
||||
constructor() {
|
||||
this.el = h('div', `${cssPrefix}-color-palette`);
|
||||
this.change = () => {};
|
||||
const table = h('table', '').children(
|
||||
h('tbody', '').children(
|
||||
h('tr', `${cssPrefix}-theme-color-placeholders`).children(
|
||||
...themeColorPlaceHolders.map(color => buildTd.call(this, color)),
|
||||
),
|
||||
...themeColors.map(it => h('tr', `${cssPrefix}-theme-colors`).children(
|
||||
...it.map(color => buildTd.call(this, color)),
|
||||
)),
|
||||
h('tr', `${cssPrefix}-standard-colors`).children(
|
||||
...standardColors.map(color => buildTd.call(this, color)),
|
||||
),
|
||||
),
|
||||
);
|
||||
this.el.child(table);
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
import { h } from './element';
|
||||
import { bindClickoutside, unbindClickoutside } from './event';
|
||||
import { cssPrefix } from '../config';
|
||||
import { tf } from '../locale/locale';
|
||||
|
||||
const menuItems = [
|
||||
{ key: 'copy', title: tf('contextmenu.copy'), label: 'Ctrl+C' },
|
||||
{ key: 'cut', title: tf('contextmenu.cut'), label: 'Ctrl+X' },
|
||||
{ key: 'paste', title: tf('contextmenu.paste'), label: 'Ctrl+V' },
|
||||
{ key: 'paste-value', title: tf('contextmenu.pasteValue'), label: 'Ctrl+Shift+V' },
|
||||
{ key: 'paste-format', title: tf('contextmenu.pasteFormat'), label: 'Ctrl+Alt+V' },
|
||||
{ key: 'divider' },
|
||||
{ key: 'insert-row', title: tf('contextmenu.insertRow') },
|
||||
{ key: 'insert-column', title: tf('contextmenu.insertColumn') },
|
||||
{ key: 'divider' },
|
||||
{ key: 'delete-row', title: tf('contextmenu.deleteRow') },
|
||||
{ key: 'delete-column', title: tf('contextmenu.deleteColumn') },
|
||||
{ key: 'delete-cell-text', title: tf('contextmenu.deleteCellText') },
|
||||
{ key: 'hide', title: tf('contextmenu.hide') },
|
||||
{ key: 'divider' },
|
||||
{ key: 'validation', title: tf('contextmenu.validation') },
|
||||
{ key: 'divider' },
|
||||
{ key: 'cell-printable', title: tf('contextmenu.cellprintable') },
|
||||
{ key: 'cell-non-printable', title: tf('contextmenu.cellnonprintable') },
|
||||
{ key: 'divider' },
|
||||
{ key: 'cell-editable', title: tf('contextmenu.celleditable') },
|
||||
{ key: 'cell-non-editable', title: tf('contextmenu.cellnoneditable') },
|
||||
];
|
||||
|
||||
function buildMenuItem(item) {
|
||||
if (item.key === 'divider') {
|
||||
return h('div', `${cssPrefix}-item divider`);
|
||||
}
|
||||
return h('div', `${cssPrefix}-item`)
|
||||
.on('click', () => {
|
||||
this.itemClick(item.key);
|
||||
this.hide();
|
||||
})
|
||||
.children(
|
||||
item.title(),
|
||||
h('div', 'label').child(item.label || ''),
|
||||
);
|
||||
}
|
||||
|
||||
function buildMenu() {
|
||||
return menuItems.map(it => buildMenuItem.call(this, it));
|
||||
}
|
||||
|
||||
export default class ContextMenu {
|
||||
constructor(viewFn, isHide = false) {
|
||||
this.menuItems = buildMenu.call(this);
|
||||
this.el = h('div', `${cssPrefix}-contextmenu`)
|
||||
.children(...this.menuItems)
|
||||
.hide();
|
||||
this.viewFn = viewFn;
|
||||
this.itemClick = () => {};
|
||||
this.isHide = isHide;
|
||||
this.setMode('range');
|
||||
}
|
||||
|
||||
// row-col: the whole rows or the whole cols
|
||||
// range: select range
|
||||
setMode(mode) {
|
||||
const hideEl = this.menuItems[12];
|
||||
if (mode === 'row-col') {
|
||||
hideEl.show();
|
||||
} else {
|
||||
hideEl.hide();
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
const { el } = this;
|
||||
el.hide();
|
||||
unbindClickoutside(el);
|
||||
}
|
||||
|
||||
setPosition(x, y) {
|
||||
if (this.isHide) return;
|
||||
const { el } = this;
|
||||
const { width } = el.show().offset();
|
||||
const view = this.viewFn();
|
||||
const vhf = view.height / 2;
|
||||
let left = x;
|
||||
if (view.width - x <= width) {
|
||||
left -= width;
|
||||
}
|
||||
el.css('left', `${left}px`);
|
||||
if (y > vhf) {
|
||||
el.css('bottom', `${view.height - y}px`)
|
||||
.css('max-height', `${y}px`)
|
||||
.css('top', 'auto');
|
||||
} else {
|
||||
el.css('top', `${y}px`)
|
||||
.css('max-height', `${view.height - y}px`)
|
||||
.css('bottom', 'auto');
|
||||
}
|
||||
bindClickoutside(el);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import Calendar from './calendar';
|
||||
import { h } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class Datepicker {
|
||||
constructor() {
|
||||
this.calendar = new Calendar(new Date());
|
||||
this.el = h('div', `${cssPrefix}-datepicker`).child(
|
||||
this.calendar.el,
|
||||
).hide();
|
||||
}
|
||||
|
||||
setValue(date) {
|
||||
// console.log(':::::::', date, typeof date, date instanceof string);
|
||||
const { calendar } = this;
|
||||
if (typeof date === 'string') {
|
||||
// console.log(/^\d{4}-\d{1,2}-\d{1,2}$/.test(date));
|
||||
if (/^\d{4}-\d{1,2}-\d{1,2}$/.test(date)) {
|
||||
calendar.setValue(new Date(date.replace(new RegExp('-', 'g'), '/')));
|
||||
}
|
||||
} else if (date instanceof Date) {
|
||||
calendar.setValue(date);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
change(cb) {
|
||||
this.calendar.selectChange = (d) => {
|
||||
cb(d);
|
||||
this.hide();
|
||||
};
|
||||
}
|
||||
|
||||
show() {
|
||||
this.el.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.el.hide();
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
import { Element, h } from './element';
|
||||
import { bindClickoutside, unbindClickoutside } from './event';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class Dropdown extends Element {
|
||||
constructor(title, width, showArrow, placement, ...children) {
|
||||
super('div', `${cssPrefix}-dropdown ${placement}`);
|
||||
this.title = title;
|
||||
this.change = () => {};
|
||||
this.headerClick = () => {};
|
||||
if (typeof title === 'string') {
|
||||
this.title = h('div', `${cssPrefix}-dropdown-title`).child(title);
|
||||
} else if (showArrow) {
|
||||
this.title.addClass('arrow-left');
|
||||
}
|
||||
this.contentEl = h('div', `${cssPrefix}-dropdown-content`)
|
||||
.css('width', width)
|
||||
.hide();
|
||||
|
||||
this.setContentChildren(...children);
|
||||
|
||||
this.headerEl = h('div', `${cssPrefix}-dropdown-header`);
|
||||
this.headerEl.on('click', () => {
|
||||
if (this.contentEl.css('display') !== 'block') {
|
||||
this.show();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
}).children(
|
||||
this.title,
|
||||
showArrow ? h('div', `${cssPrefix}-icon arrow-right`).child(
|
||||
h('div', `${cssPrefix}-icon-img arrow-down`),
|
||||
) : '',
|
||||
);
|
||||
this.children(this.headerEl, this.contentEl);
|
||||
}
|
||||
|
||||
setContentChildren(...children) {
|
||||
this.contentEl.html('');
|
||||
if (children.length > 0) {
|
||||
this.contentEl.children(...children);
|
||||
}
|
||||
}
|
||||
|
||||
setTitle(title) {
|
||||
this.title.html(title);
|
||||
this.hide();
|
||||
}
|
||||
|
||||
show() {
|
||||
const { contentEl } = this;
|
||||
contentEl.show();
|
||||
this.parent().active();
|
||||
bindClickoutside(this.parent(), () => {
|
||||
this.hide();
|
||||
});
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.parent().active(false);
|
||||
this.contentEl.hide();
|
||||
unbindClickoutside(this.parent());
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import Dropdown from './dropdown';
|
||||
import { h } from './element';
|
||||
import Icon from './icon';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
function buildItemWithIcon(iconName) {
|
||||
return h('div', `${cssPrefix}-item`).child(new Icon(iconName));
|
||||
}
|
||||
|
||||
export default class DropdownAlign extends Dropdown {
|
||||
constructor(aligns, align) {
|
||||
const icon = new Icon(`align-${align}`);
|
||||
const naligns = aligns.map(it => buildItemWithIcon(`align-${it}`)
|
||||
.on('click', () => {
|
||||
this.setTitle(it);
|
||||
this.change(it);
|
||||
}));
|
||||
super(icon, 'auto', true, 'bottom-left', ...naligns);
|
||||
}
|
||||
|
||||
setTitle(align) {
|
||||
this.title.setName(`align-${align}`);
|
||||
this.hide();
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import Dropdown from './dropdown';
|
||||
import Icon from './icon';
|
||||
import BorderPalette from './border_palette';
|
||||
|
||||
export default class DropdownBorder extends Dropdown {
|
||||
constructor() {
|
||||
const icon = new Icon('border-all');
|
||||
const borderPalette = new BorderPalette();
|
||||
borderPalette.change = (v) => {
|
||||
this.change(v);
|
||||
this.hide();
|
||||
};
|
||||
super(icon, 'auto', false, 'bottom-left', borderPalette.el);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import Dropdown from './dropdown';
|
||||
import Icon from './icon';
|
||||
import ColorPalette from './color_palette';
|
||||
|
||||
export default class DropdownColor extends Dropdown {
|
||||
constructor(iconName, color) {
|
||||
const icon = new Icon(iconName)
|
||||
.css('height', '16px')
|
||||
.css('border-bottom', `3px solid ${color}`);
|
||||
const colorPalette = new ColorPalette();
|
||||
colorPalette.change = (v) => {
|
||||
this.setTitle(v);
|
||||
this.change(v);
|
||||
};
|
||||
super(icon, 'auto', false, 'bottom-left', colorPalette.el);
|
||||
}
|
||||
|
||||
setTitle(color) {
|
||||
this.title.css('border-color', color);
|
||||
this.hide();
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import Dropdown from './dropdown';
|
||||
import { h } from './element';
|
||||
import { baseFonts } from '../core/font';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class DropdownFont extends Dropdown {
|
||||
constructor() {
|
||||
const nfonts = baseFonts.map(it => h('div', `${cssPrefix}-item`)
|
||||
.on('click', () => {
|
||||
this.setTitle(it.title);
|
||||
this.change(it);
|
||||
})
|
||||
.child(it.title));
|
||||
super(baseFonts[0].title, '160px', true, 'bottom-left', ...nfonts);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import Dropdown from './dropdown';
|
||||
import { h } from './element';
|
||||
import { fontSizes } from '../core/font';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class DropdownFontSize extends Dropdown {
|
||||
constructor() {
|
||||
const nfontSizes = fontSizes.map(it => h('div', `${cssPrefix}-item`)
|
||||
.on('click', () => {
|
||||
this.setTitle(`${it.pt}`);
|
||||
this.change(it);
|
||||
})
|
||||
.child(`${it.pt}`));
|
||||
super('10', '60px', true, 'bottom-left', ...nfontSizes);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import Dropdown from './dropdown';
|
||||
import { h } from './element';
|
||||
import { baseFormats } from '../core/format';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class DropdownFormat extends Dropdown {
|
||||
constructor() {
|
||||
let nformats = baseFormats.slice(0);
|
||||
nformats.splice(2, 0, { key: 'divider' });
|
||||
nformats.splice(8, 0, { key: 'divider' });
|
||||
nformats = nformats.map((it) => {
|
||||
const item = h('div', `${cssPrefix}-item`);
|
||||
if (it.key === 'divider') {
|
||||
item.addClass('divider');
|
||||
} else {
|
||||
item.child(it.title())
|
||||
.on('click', () => {
|
||||
this.setTitle(it.title());
|
||||
this.change(it);
|
||||
});
|
||||
if (it.label) item.child(h('div', 'label').html(it.label));
|
||||
}
|
||||
return item;
|
||||
});
|
||||
super('Normal', '220px', true, 'bottom-left', ...nformats);
|
||||
}
|
||||
|
||||
setTitle(key) {
|
||||
for (let i = 0; i < baseFormats.length; i += 1) {
|
||||
if (baseFormats[i].key === key) {
|
||||
this.title.html(baseFormats[i].title());
|
||||
}
|
||||
}
|
||||
this.hide();
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import Dropdown from './dropdown';
|
||||
import Icon from './icon';
|
||||
import { h } from './element';
|
||||
import { baseFormulas } from '../core/formula';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class DropdownFormula extends Dropdown {
|
||||
constructor() {
|
||||
const nformulas = baseFormulas.map(it => h('div', `${cssPrefix}-item`)
|
||||
.on('click', () => {
|
||||
this.hide();
|
||||
this.change(it);
|
||||
})
|
||||
.child(it.key));
|
||||
super(new Icon('formula'), '180px', true, 'bottom-left', ...nformulas);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
import Dropdown from './dropdown';
|
||||
import { h } from './element';
|
||||
import Icon from './icon';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
const lineTypes = [
|
||||
['thin', '<svg xmlns="http://www.w3.org/2000/svg" width="50" height="1" style="user-select: none;"><line x1="0" y1="0.5" x2="50" y2="0.5" stroke-width="1" stroke="black" style="user-select: none;"></line></svg>'],
|
||||
['medium', '<svg xmlns="http://www.w3.org/2000/svg" width="50" height="2" style="user-select: none;"><line x1="0" y1="1.0" x2="50" y2="1.0" stroke-width="2" stroke="black" style="user-select: none;"></line></svg>'],
|
||||
['thick', '<svg xmlns="http://www.w3.org/2000/svg" width="50" height="3" style="user-select: none;"><line x1="0" y1="1.5" x2="50" y2="1.5" stroke-width="3" stroke="black" style="user-select: none;"></line></svg>'],
|
||||
['dashed', '<svg xmlns="http://www.w3.org/2000/svg" width="50" height="1" style="user-select: none;"><line x1="0" y1="0.5" x2="50" y2="0.5" stroke-width="1" stroke="black" stroke-dasharray="2" style="user-select: none;"></line></svg>'],
|
||||
['dotted', '<svg xmlns="http://www.w3.org/2000/svg" width="50" height="1" style="user-select: none;"><line x1="0" y1="0.5" x2="50" y2="0.5" stroke-width="1" stroke="black" stroke-dasharray="1" style="user-select: none;"></line></svg>'],
|
||||
// ['double', '<svg xmlns="http://www.w3.org/2000/svg" width="50" height="3" style="user-select: none;"><line x1="0" y1="0.5" x2="50" y2="0.5" stroke-width="1" stroke="black" style="user-select: none;"></line><line x1="0" y1="2.5" x2="50" y2="2.5" stroke-width="1" stroke="black" style="user-select: none;"></line></svg>'],
|
||||
];
|
||||
|
||||
export default class DropdownLineType extends Dropdown {
|
||||
constructor(type) {
|
||||
const icon = new Icon('line-type');
|
||||
let beforei = 0;
|
||||
const lineTypeEls = lineTypes.map((it, iti) => h('div', `${cssPrefix}-item state ${type === it[0] ? 'checked' : ''}`)
|
||||
.on('click', () => {
|
||||
lineTypeEls[beforei].toggle('checked');
|
||||
lineTypeEls[iti].toggle('checked');
|
||||
beforei = iti;
|
||||
this.hide();
|
||||
this.change(it);
|
||||
})
|
||||
.child(
|
||||
h('div', `${cssPrefix}-line-type`).html(it[1]),
|
||||
));
|
||||
|
||||
super(icon, 'auto', false, 'bottom-left', ...lineTypeEls);
|
||||
}
|
||||
}
|
@ -0,0 +1,283 @@
|
||||
//* global window */
|
||||
import { h } from './element';
|
||||
import Suggest from './suggest';
|
||||
import Datepicker from './datepicker';
|
||||
import { cssPrefix } from '../config';
|
||||
// import { mouseMoveUp } from '../event';
|
||||
|
||||
function resetTextareaSize() {
|
||||
const { inputText } = this;
|
||||
if (!/^\s*$/.test(inputText)) {
|
||||
const {
|
||||
textlineEl, textEl, areaOffset,
|
||||
} = this;
|
||||
const txts = inputText.split('\n');
|
||||
const maxTxtSize = Math.max(...txts.map(it => it.length));
|
||||
const tlOffset = textlineEl.offset();
|
||||
const fontWidth = tlOffset.width / inputText.length;
|
||||
const tlineWidth = (maxTxtSize + 1) * fontWidth + 5;
|
||||
const maxWidth = this.viewFn().width - areaOffset.left - fontWidth;
|
||||
let h1 = txts.length;
|
||||
if (tlineWidth > areaOffset.width) {
|
||||
let twidth = tlineWidth;
|
||||
if (tlineWidth > maxWidth) {
|
||||
twidth = maxWidth;
|
||||
h1 += parseInt(tlineWidth / maxWidth, 10);
|
||||
h1 += (tlineWidth % maxWidth) > 0 ? 1 : 0;
|
||||
}
|
||||
textEl.css('width', `${twidth}px`);
|
||||
}
|
||||
h1 *= this.rowHeight;
|
||||
if (h1 > areaOffset.height) {
|
||||
textEl.css('height', `${h1}px`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function insertText({ target }, itxt) {
|
||||
const { value, selectionEnd } = target;
|
||||
const ntxt = `${value.slice(0, selectionEnd)}${itxt}${value.slice(selectionEnd)}`;
|
||||
target.value = ntxt;
|
||||
target.setSelectionRange(selectionEnd + 1, selectionEnd + 1);
|
||||
|
||||
this.inputText = ntxt;
|
||||
this.textlineEl.html(ntxt);
|
||||
resetTextareaSize.call(this);
|
||||
}
|
||||
|
||||
function keydownEventHandler(evt) {
|
||||
const { keyCode, altKey } = evt;
|
||||
if (keyCode !== 13 && keyCode !== 9) evt.stopPropagation();
|
||||
if (keyCode === 13 && altKey) {
|
||||
insertText.call(this, evt, '\n');
|
||||
evt.stopPropagation();
|
||||
}
|
||||
if (keyCode === 13 && !altKey) evt.preventDefault();
|
||||
}
|
||||
|
||||
function inputEventHandler(evt) {
|
||||
const v = evt.target.value;
|
||||
// console.log(evt, 'v:', v);
|
||||
const { suggest, textlineEl, validator } = this;
|
||||
const { cell } = this;
|
||||
if (cell !== null) {
|
||||
if (('editable' in cell && cell.editable === true) || (cell.editable === undefined)) {
|
||||
this.inputText = v;
|
||||
if (validator) {
|
||||
if (validator.type === 'list') {
|
||||
suggest.search(v);
|
||||
} else {
|
||||
suggest.hide();
|
||||
}
|
||||
} else {
|
||||
const start = v.lastIndexOf('=');
|
||||
if (start !== -1) {
|
||||
suggest.search(v.substring(start + 1));
|
||||
} else {
|
||||
suggest.hide();
|
||||
}
|
||||
}
|
||||
textlineEl.html(v);
|
||||
resetTextareaSize.call(this);
|
||||
this.change('input', v);
|
||||
} else {
|
||||
evt.target.value = cell.text || '';
|
||||
}
|
||||
} else {
|
||||
this.inputText = v;
|
||||
if (validator) {
|
||||
if (validator.type === 'list') {
|
||||
suggest.search(v);
|
||||
} else {
|
||||
suggest.hide();
|
||||
}
|
||||
} else {
|
||||
const start = v.lastIndexOf('=');
|
||||
if (start !== -1) {
|
||||
suggest.search(v.substring(start + 1));
|
||||
} else {
|
||||
suggest.hide();
|
||||
}
|
||||
}
|
||||
textlineEl.html(v);
|
||||
resetTextareaSize.call(this);
|
||||
this.change('input', v);
|
||||
}
|
||||
}
|
||||
|
||||
function setTextareaRange(position) {
|
||||
const { el } = this.textEl;
|
||||
setTimeout(() => {
|
||||
el.focus();
|
||||
el.setSelectionRange(position, position);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function setText(text, position) {
|
||||
const { textEl, textlineEl } = this;
|
||||
// firefox bug
|
||||
textEl.el.blur();
|
||||
|
||||
textEl.val(text);
|
||||
textlineEl.html(text);
|
||||
setTextareaRange.call(this, position);
|
||||
}
|
||||
|
||||
function suggestItemClick(it) {
|
||||
const { inputText, validator } = this;
|
||||
let position = 0;
|
||||
if (validator && validator.type === 'list') {
|
||||
this.inputText = it;
|
||||
position = this.inputText.length;
|
||||
} else {
|
||||
const start = inputText.lastIndexOf('=');
|
||||
const sit = inputText.substring(0, start + 1);
|
||||
let eit = inputText.substring(start + 1);
|
||||
if (eit.indexOf(')') !== -1) {
|
||||
eit = eit.substring(eit.indexOf(')'));
|
||||
} else {
|
||||
eit = '';
|
||||
}
|
||||
this.inputText = `${sit + it.key}(`;
|
||||
// console.log('inputText:', this.inputText);
|
||||
position = this.inputText.length;
|
||||
this.inputText += `)${eit}`;
|
||||
}
|
||||
setText.call(this, this.inputText, position);
|
||||
}
|
||||
|
||||
function resetSuggestItems() {
|
||||
this.suggest.setItems(this.formulas);
|
||||
}
|
||||
|
||||
function dateFormat(d) {
|
||||
let month = d.getMonth() + 1;
|
||||
let date = d.getDate();
|
||||
if (month < 10) month = `0${month}`;
|
||||
if (date < 10) date = `0${date}`;
|
||||
return `${d.getFullYear()}-${month}-${date}`;
|
||||
}
|
||||
|
||||
export default class Editor {
|
||||
constructor(formulas, viewFn, rowHeight) {
|
||||
this.viewFn = viewFn;
|
||||
this.rowHeight = rowHeight;
|
||||
this.formulas = formulas;
|
||||
this.suggest = new Suggest(formulas, (it) => {
|
||||
suggestItemClick.call(this, it);
|
||||
});
|
||||
this.datepicker = new Datepicker();
|
||||
this.datepicker.change((d) => {
|
||||
// console.log('d:', d);
|
||||
this.setText(dateFormat(d));
|
||||
this.clear();
|
||||
});
|
||||
this.areaEl = h('div', `${cssPrefix}-editor-area`)
|
||||
.children(
|
||||
this.textEl = h('textarea', '')
|
||||
.on('input', evt => inputEventHandler.call(this, evt))
|
||||
.on('paste.stop', () => {})
|
||||
.on('keydown', evt => keydownEventHandler.call(this, evt)),
|
||||
this.textlineEl = h('div', 'textline'),
|
||||
this.suggest.el,
|
||||
this.datepicker.el,
|
||||
)
|
||||
.on('mousemove.stop', () => {})
|
||||
.on('mousedown.stop', () => {});
|
||||
this.el = h('div', `${cssPrefix}-editor`)
|
||||
.child(this.areaEl).hide();
|
||||
this.suggest.bindInputEvents(this.textEl);
|
||||
|
||||
this.areaOffset = null;
|
||||
this.freeze = { w: 0, h: 0 };
|
||||
this.cell = null;
|
||||
this.inputText = '';
|
||||
this.change = () => {};
|
||||
}
|
||||
|
||||
setFreezeLengths(width, height) {
|
||||
this.freeze.w = width;
|
||||
this.freeze.h = height;
|
||||
}
|
||||
|
||||
clear() {
|
||||
// const { cell } = this;
|
||||
// const cellText = (cell && cell.text) || '';
|
||||
if (this.inputText !== '') {
|
||||
this.change('finished', this.inputText);
|
||||
}
|
||||
this.cell = null;
|
||||
this.areaOffset = null;
|
||||
this.inputText = '';
|
||||
this.el.hide();
|
||||
this.textEl.val('');
|
||||
this.textlineEl.html('');
|
||||
resetSuggestItems.call(this);
|
||||
this.datepicker.hide();
|
||||
}
|
||||
|
||||
setOffset(offset, suggestPosition = 'top') {
|
||||
const {
|
||||
textEl, areaEl, suggest, freeze, el,
|
||||
} = this;
|
||||
if (offset) {
|
||||
this.areaOffset = offset;
|
||||
const {
|
||||
left, top, width, height, l, t,
|
||||
} = offset;
|
||||
// console.log('left:', left, ',top:', top, ', freeze:', freeze);
|
||||
const elOffset = { left: 0, top: 0 };
|
||||
// top left
|
||||
if (freeze.w > l && freeze.h > t) {
|
||||
//
|
||||
} else if (freeze.w < l && freeze.h < t) {
|
||||
elOffset.left = freeze.w;
|
||||
elOffset.top = freeze.h;
|
||||
} else if (freeze.w > l) {
|
||||
elOffset.top = freeze.h;
|
||||
} else if (freeze.h > t) {
|
||||
elOffset.left = freeze.w;
|
||||
}
|
||||
el.offset(elOffset);
|
||||
areaEl.offset({ left: left - elOffset.left - 0.8, top: top - elOffset.top - 0.8 });
|
||||
textEl.offset({ width: width - 9 + 0.8, height: height - 3 + 0.8 });
|
||||
const sOffset = { left: 0 };
|
||||
sOffset[suggestPosition] = height;
|
||||
suggest.setOffset(sOffset);
|
||||
suggest.hide();
|
||||
}
|
||||
}
|
||||
|
||||
setCell(cell, validator) {
|
||||
if (cell && cell.editable === false) return;
|
||||
|
||||
// console.log('::', validator);
|
||||
const { el, datepicker, suggest } = this;
|
||||
el.show();
|
||||
this.cell = cell;
|
||||
const text = (cell && cell.text) || '';
|
||||
this.setText(text);
|
||||
|
||||
this.validator = validator;
|
||||
if (validator) {
|
||||
const { type } = validator;
|
||||
if (type === 'date') {
|
||||
datepicker.show();
|
||||
if (!/^\s*$/.test(text)) {
|
||||
datepicker.setValue(text);
|
||||
}
|
||||
}
|
||||
if (type === 'list') {
|
||||
suggest.setItems(validator.values());
|
||||
suggest.search('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setText(text) {
|
||||
this.inputText = text;
|
||||
// console.log('text>>:', text);
|
||||
setText.call(this, text, text.length);
|
||||
resetTextareaSize.call(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,275 @@
|
||||
/* global document */
|
||||
/* global window */
|
||||
class Element {
|
||||
constructor(tag, className = '') {
|
||||
if (typeof tag === 'string') {
|
||||
this.el = document.createElement(tag);
|
||||
this.el.className = className;
|
||||
} else {
|
||||
this.el = tag;
|
||||
}
|
||||
this.data = {};
|
||||
}
|
||||
|
||||
data(key, value) {
|
||||
if (value !== undefined) {
|
||||
this.data[key] = value;
|
||||
return this;
|
||||
}
|
||||
return this.data[key];
|
||||
}
|
||||
|
||||
on(eventNames, handler) {
|
||||
const [fen, ...oen] = eventNames.split('.');
|
||||
let eventName = fen;
|
||||
if (eventName === 'mousewheel' && /Firefox/i.test(window.navigator.userAgent)) {
|
||||
eventName = 'DOMMouseScroll';
|
||||
}
|
||||
this.el.addEventListener(eventName, (evt) => {
|
||||
handler(evt);
|
||||
for (let i = 0; i < oen.length; i += 1) {
|
||||
const k = oen[i];
|
||||
if (k === 'left' && evt.button !== 0) {
|
||||
return;
|
||||
}
|
||||
if (k === 'right' && evt.button !== 2) {
|
||||
return;
|
||||
}
|
||||
if (k === 'stop') {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
offset(value) {
|
||||
if (value !== undefined) {
|
||||
Object.keys(value).forEach((k) => {
|
||||
this.css(k, `${value[k]}px`);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
const {
|
||||
offsetTop, offsetLeft, offsetHeight, offsetWidth,
|
||||
} = this.el;
|
||||
return {
|
||||
top: offsetTop,
|
||||
left: offsetLeft,
|
||||
height: offsetHeight,
|
||||
width: offsetWidth,
|
||||
};
|
||||
}
|
||||
|
||||
scroll(v) {
|
||||
const { el } = this;
|
||||
if (v !== undefined) {
|
||||
if (v.left !== undefined) {
|
||||
el.scrollLeft = v.left;
|
||||
}
|
||||
if (v.top !== undefined) {
|
||||
el.scrollTop = v.top;
|
||||
}
|
||||
}
|
||||
return { left: el.scrollLeft, top: el.scrollTop };
|
||||
}
|
||||
|
||||
box() {
|
||||
return this.el.getBoundingClientRect();
|
||||
}
|
||||
|
||||
parent() {
|
||||
return new Element(this.el.parentNode);
|
||||
}
|
||||
|
||||
children(...eles) {
|
||||
if (arguments.length === 0) {
|
||||
return this.el.childNodes;
|
||||
}
|
||||
eles.forEach(ele => this.child(ele));
|
||||
return this;
|
||||
}
|
||||
|
||||
removeChild(el) {
|
||||
this.el.removeChild(el);
|
||||
}
|
||||
|
||||
/*
|
||||
first() {
|
||||
return this.el.firstChild;
|
||||
}
|
||||
|
||||
last() {
|
||||
return this.el.lastChild;
|
||||
}
|
||||
|
||||
remove(ele) {
|
||||
return this.el.removeChild(ele);
|
||||
}
|
||||
|
||||
prepend(ele) {
|
||||
const { el } = this;
|
||||
if (el.children.length > 0) {
|
||||
el.insertBefore(ele, el.firstChild);
|
||||
} else {
|
||||
el.appendChild(ele);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
prev() {
|
||||
return this.el.previousSibling;
|
||||
}
|
||||
|
||||
next() {
|
||||
return this.el.nextSibling;
|
||||
}
|
||||
*/
|
||||
|
||||
child(arg) {
|
||||
let ele = arg;
|
||||
if (typeof arg === 'string') {
|
||||
ele = document.createTextNode(arg);
|
||||
} else if (arg instanceof Element) {
|
||||
ele = arg.el;
|
||||
}
|
||||
this.el.appendChild(ele);
|
||||
return this;
|
||||
}
|
||||
|
||||
contains(ele) {
|
||||
return this.el.contains(ele);
|
||||
}
|
||||
|
||||
className(v) {
|
||||
if (v !== undefined) {
|
||||
this.el.className = v;
|
||||
return this;
|
||||
}
|
||||
return this.el.className;
|
||||
}
|
||||
|
||||
addClass(name) {
|
||||
this.el.classList.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
hasClass(name) {
|
||||
return this.el.classList.contains(name);
|
||||
}
|
||||
|
||||
removeClass(name) {
|
||||
this.el.classList.remove(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
toggle(cls = 'active') {
|
||||
return this.toggleClass(cls);
|
||||
}
|
||||
|
||||
toggleClass(name) {
|
||||
return this.el.classList.toggle(name);
|
||||
}
|
||||
|
||||
active(flag = true, cls = 'active') {
|
||||
if (flag) this.addClass(cls);
|
||||
else this.removeClass(cls);
|
||||
return this;
|
||||
}
|
||||
|
||||
checked(flag = true) {
|
||||
this.active(flag, 'checked');
|
||||
return this;
|
||||
}
|
||||
|
||||
disabled(flag = true) {
|
||||
if (flag) this.addClass('disabled');
|
||||
else this.removeClass('disabled');
|
||||
return this;
|
||||
}
|
||||
|
||||
// key, value
|
||||
// key
|
||||
// {k, v}...
|
||||
attr(key, value) {
|
||||
if (value !== undefined) {
|
||||
this.el.setAttribute(key, value);
|
||||
} else {
|
||||
if (typeof key === 'string') {
|
||||
return this.el.getAttribute(key);
|
||||
}
|
||||
Object.keys(key).forEach((k) => {
|
||||
this.el.setAttribute(k, key[k]);
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
removeAttr(key) {
|
||||
this.el.removeAttribute(key);
|
||||
return this;
|
||||
}
|
||||
|
||||
html(content) {
|
||||
if (content !== undefined) {
|
||||
this.el.innerHTML = content;
|
||||
return this;
|
||||
}
|
||||
return this.el.innerHTML;
|
||||
}
|
||||
|
||||
val(v) {
|
||||
if (v !== undefined) {
|
||||
this.el.value = v;
|
||||
return this;
|
||||
}
|
||||
return this.el.value;
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.el.focus();
|
||||
}
|
||||
|
||||
cssRemoveKeys(...keys) {
|
||||
keys.forEach(k => this.el.style.removeProperty(k));
|
||||
return this;
|
||||
}
|
||||
|
||||
// css( propertyName )
|
||||
// css( propertyName, value )
|
||||
// css( properties )
|
||||
css(name, value) {
|
||||
if (value === undefined && typeof name !== 'string') {
|
||||
Object.keys(name).forEach((k) => {
|
||||
this.el.style[k] = name[k];
|
||||
});
|
||||
return this;
|
||||
}
|
||||
if (value !== undefined) {
|
||||
this.el.style[name] = value;
|
||||
return this;
|
||||
}
|
||||
return this.el.style[name];
|
||||
}
|
||||
|
||||
computedStyle() {
|
||||
return window.getComputedStyle(this.el, null);
|
||||
}
|
||||
|
||||
show() {
|
||||
this.css('display', 'block');
|
||||
return this;
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.css('display', 'none');
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
const h = (tag, className = '') => new Element(tag, className);
|
||||
|
||||
export {
|
||||
Element,
|
||||
h,
|
||||
};
|
@ -0,0 +1,153 @@
|
||||
/* global window */
|
||||
export function bind(target, name, fn) {
|
||||
target.addEventListener(name, fn);
|
||||
}
|
||||
export function unbind(target, name, fn) {
|
||||
target.removeEventListener(name, fn);
|
||||
}
|
||||
export function unbindClickoutside(el) {
|
||||
if (el.xclickoutside) {
|
||||
unbind(window.document.body, 'click', el.xclickoutside);
|
||||
delete el.xclickoutside;
|
||||
}
|
||||
}
|
||||
|
||||
// the left mouse button: mousedown → mouseup → click
|
||||
// the right mouse button: mousedown → contenxtmenu → mouseup
|
||||
// the right mouse button in firefox(>65.0): mousedown → contenxtmenu → mouseup → click on window
|
||||
export function bindClickoutside(el, cb) {
|
||||
el.xclickoutside = (evt) => {
|
||||
// ignore double click
|
||||
// console.log('evt:', evt);
|
||||
if (evt.detail === 2 || el.contains(evt.target)) return;
|
||||
if (cb) cb(el);
|
||||
else {
|
||||
el.hide();
|
||||
unbindClickoutside(el);
|
||||
}
|
||||
};
|
||||
bind(window.document.body, 'click', el.xclickoutside);
|
||||
}
|
||||
export function mouseMoveUp(target, movefunc, upfunc) {
|
||||
bind(target, 'mousemove', movefunc);
|
||||
const t = target;
|
||||
t.xEvtUp = (evt) => {
|
||||
// console.log('mouseup>>>');
|
||||
unbind(target, 'mousemove', movefunc);
|
||||
unbind(target, 'mouseup', target.xEvtUp);
|
||||
upfunc(evt);
|
||||
};
|
||||
bind(target, 'mouseup', target.xEvtUp);
|
||||
}
|
||||
|
||||
function calTouchDirection(spanx, spany, evt, cb) {
|
||||
let direction = '';
|
||||
// console.log('spanx:', spanx, ', spany:', spany);
|
||||
if (Math.abs(spanx) > Math.abs(spany)) {
|
||||
// horizontal
|
||||
direction = spanx > 0 ? 'right' : 'left';
|
||||
cb(direction, spanx, evt);
|
||||
} else {
|
||||
// vertical
|
||||
direction = spany > 0 ? 'down' : 'up';
|
||||
cb(direction, spany, evt);
|
||||
}
|
||||
}
|
||||
// cb = (direction, distance) => {}
|
||||
export function bindTouch(target, { move, end }) {
|
||||
let startx = 0;
|
||||
let starty = 0;
|
||||
bind(target, 'touchstart', (evt) => {
|
||||
const { pageX, pageY } = evt.touches[0];
|
||||
startx = pageX;
|
||||
starty = pageY;
|
||||
});
|
||||
bind(target, 'touchmove', (evt) => {
|
||||
if (!move) return;
|
||||
const { pageX, pageY } = evt.changedTouches[0];
|
||||
const spanx = pageX - startx;
|
||||
const spany = pageY - starty;
|
||||
if (Math.abs(spanx) > 10 || Math.abs(spany) > 10) {
|
||||
// console.log('spanx:', spanx, ', spany:', spany);
|
||||
calTouchDirection(spanx, spany, evt, move);
|
||||
startx = pageX;
|
||||
starty = pageY;
|
||||
}
|
||||
evt.preventDefault();
|
||||
});
|
||||
bind(target, 'touchend', (evt) => {
|
||||
if (!end) return;
|
||||
const { pageX, pageY } = evt.changedTouches[0];
|
||||
const spanx = pageX - startx;
|
||||
const spany = pageY - starty;
|
||||
calTouchDirection(spanx, spany, evt, end);
|
||||
});
|
||||
}
|
||||
|
||||
// eventemiter
|
||||
export function createEventEmitter() {
|
||||
const listeners = new Map();
|
||||
|
||||
function on(eventName, callback) {
|
||||
const push = () => {
|
||||
const currentListener = listeners.get(eventName);
|
||||
return (Array.isArray(currentListener)
|
||||
&& currentListener.push(callback))
|
||||
|| false;
|
||||
};
|
||||
|
||||
const create = () => listeners.set(eventName, [].concat(callback));
|
||||
|
||||
return (listeners.has(eventName)
|
||||
&& push())
|
||||
|| create();
|
||||
}
|
||||
|
||||
function fire(eventName, args) {
|
||||
const exec = () => {
|
||||
const currentListener = listeners.get(eventName);
|
||||
for (const callback of currentListener) callback.call(null, ...args);
|
||||
};
|
||||
|
||||
return listeners.has(eventName)
|
||||
&& exec();
|
||||
}
|
||||
|
||||
function removeListener(eventName, callback) {
|
||||
const remove = () => {
|
||||
const currentListener = listeners.get(eventName);
|
||||
const idx = currentListener.indexOf(callback);
|
||||
return (idx >= 0)
|
||||
&& currentListener.splice(idx, 1)
|
||||
&& listeners.get(eventName).length === 0
|
||||
&& listeners.delete(eventName);
|
||||
};
|
||||
|
||||
return listeners.has(eventName)
|
||||
&& remove();
|
||||
}
|
||||
|
||||
function once(eventName, callback) {
|
||||
const execCalllback = (...args) => {
|
||||
callback.call(null, ...args);
|
||||
removeListener(eventName, execCalllback);
|
||||
};
|
||||
|
||||
return on(eventName, execCalllback);
|
||||
}
|
||||
|
||||
function removeAllListeners() {
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
return {
|
||||
get current() {
|
||||
return listeners;
|
||||
},
|
||||
on,
|
||||
once,
|
||||
fire,
|
||||
removeListener,
|
||||
removeAllListeners,
|
||||
};
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
import { h } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
import { t } from '../locale/locale';
|
||||
|
||||
const patterns = {
|
||||
number: /(^\d+$)|(^\d+(\.\d{0,4})?$)/,
|
||||
date: /^\d{4}-\d{1,2}-\d{1,2}$/,
|
||||
};
|
||||
|
||||
// rule: { required: false, type, pattern: // }
|
||||
export default class FormField {
|
||||
constructor(input, rule, label, labelWidth) {
|
||||
this.label = '';
|
||||
this.rule = rule;
|
||||
if (label) {
|
||||
this.label = h('label', 'label').css('width', `${labelWidth}px`).html(label);
|
||||
}
|
||||
this.tip = h('div', 'tip').child('tip').hide();
|
||||
this.input = input;
|
||||
this.input.vchange = () => this.validate();
|
||||
this.el = h('div', `${cssPrefix}-form-field`)
|
||||
.children(this.label, input.el, this.tip);
|
||||
}
|
||||
|
||||
isShow() {
|
||||
return this.el.css('display') !== 'none';
|
||||
}
|
||||
|
||||
show() {
|
||||
this.el.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.el.hide();
|
||||
return this;
|
||||
}
|
||||
|
||||
val(v) {
|
||||
return this.input.val(v);
|
||||
}
|
||||
|
||||
hint(hint) {
|
||||
this.input.hint(hint);
|
||||
}
|
||||
|
||||
validate() {
|
||||
const {
|
||||
input, rule, tip, el,
|
||||
} = this;
|
||||
const v = input.val();
|
||||
if (rule.required) {
|
||||
if (/^\s*$/.test(v)) {
|
||||
tip.html(t('validation.required'));
|
||||
el.addClass('error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (rule.type || rule.pattern) {
|
||||
const pattern = rule.pattern || patterns[rule.type];
|
||||
if (!pattern.test(v)) {
|
||||
tip.html(t('validation.notMatch'));
|
||||
el.addClass('error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
el.removeClass('error');
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import { h } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class FormInput {
|
||||
constructor(width, hint) {
|
||||
this.vchange = () => {};
|
||||
this.el = h('div', `${cssPrefix}-form-input`);
|
||||
this.input = h('input', '').css('width', width)
|
||||
.on('input', evt => this.vchange(evt))
|
||||
.attr('placeholder', hint);
|
||||
this.el.child(this.input);
|
||||
}
|
||||
|
||||
focus() {
|
||||
setTimeout(() => {
|
||||
this.input.el.focus();
|
||||
}, 10);
|
||||
}
|
||||
|
||||
hint(v) {
|
||||
this.input.attr('placeholder', v);
|
||||
}
|
||||
|
||||
val(v) {
|
||||
return this.input.val(v);
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import { h } from './element';
|
||||
import Suggest from './suggest';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class FormSelect {
|
||||
constructor(key, items, width, getTitle = it => it, change = () => {}) {
|
||||
this.key = key;
|
||||
this.getTitle = getTitle;
|
||||
this.vchange = () => {};
|
||||
this.el = h('div', `${cssPrefix}-form-select`);
|
||||
this.suggest = new Suggest(items.map(it => ({ key: it, title: this.getTitle(it) })), (it) => {
|
||||
this.itemClick(it.key);
|
||||
change(it.key);
|
||||
this.vchange(it.key);
|
||||
}, width, this.el);
|
||||
this.el.children(
|
||||
this.itemEl = h('div', 'input-text').html(this.getTitle(key)),
|
||||
this.suggest.el,
|
||||
).on('click', () => this.show());
|
||||
}
|
||||
|
||||
show() {
|
||||
this.suggest.search('');
|
||||
}
|
||||
|
||||
itemClick(it) {
|
||||
this.key = it;
|
||||
this.itemEl.html(this.getTitle(it));
|
||||
}
|
||||
|
||||
val(v) {
|
||||
if (v !== undefined) {
|
||||
this.key = v;
|
||||
this.itemEl.html(this.getTitle(v));
|
||||
return this;
|
||||
}
|
||||
return this.key;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { Element, h } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class Icon extends Element {
|
||||
constructor(name) {
|
||||
super('div', `${cssPrefix}-icon`);
|
||||
this.iconNameEl = h('div', `${cssPrefix}-icon-img ${name}`);
|
||||
this.child(this.iconNameEl);
|
||||
}
|
||||
|
||||
setName(name) {
|
||||
this.iconNameEl.className(`${cssPrefix}-icon-img ${name}`);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/* global document */
|
||||
import { h } from './element';
|
||||
import Icon from './icon';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export function xtoast(title, content) {
|
||||
const el = h('div', `${cssPrefix}-toast`);
|
||||
const dimmer = h('div', `${cssPrefix}-dimmer active`);
|
||||
const remove = () => {
|
||||
document.body.removeChild(el.el);
|
||||
document.body.removeChild(dimmer.el);
|
||||
};
|
||||
|
||||
el.children(
|
||||
h('div', `${cssPrefix}-toast-header`).children(
|
||||
new Icon('close').on('click.stop', () => remove()),
|
||||
title,
|
||||
),
|
||||
h('div', `${cssPrefix}-toast-content`).html(content),
|
||||
);
|
||||
document.body.appendChild(el.el);
|
||||
document.body.appendChild(dimmer.el);
|
||||
// set offset
|
||||
const { width, height } = el.box();
|
||||
const { clientHeight, clientWidth } = document.documentElement;
|
||||
el.offset({
|
||||
left: (clientWidth - width) / 2,
|
||||
top: (clientHeight - height) / 3,
|
||||
});
|
||||
}
|
||||
|
||||
export default {};
|
@ -0,0 +1,44 @@
|
||||
/* global document */
|
||||
/* global window */
|
||||
import { h } from './element';
|
||||
import Icon from './icon';
|
||||
import { cssPrefix } from '../config';
|
||||
import { bind, unbind } from './event';
|
||||
|
||||
export default class Modal {
|
||||
constructor(title, content, width = '600px') {
|
||||
this.title = title;
|
||||
this.el = h('div', `${cssPrefix}-modal`).css('width', width).children(
|
||||
h('div', `${cssPrefix}-modal-header`).children(
|
||||
new Icon('close').on('click.stop', () => this.hide()),
|
||||
this.title,
|
||||
),
|
||||
h('div', `${cssPrefix}-modal-content`).children(...content),
|
||||
).hide();
|
||||
}
|
||||
|
||||
show() {
|
||||
// dimmer
|
||||
this.dimmer = h('div', `${cssPrefix}-dimmer active`);
|
||||
document.body.appendChild(this.dimmer.el);
|
||||
const { width, height } = this.el.show().box();
|
||||
const { clientHeight, clientWidth } = document.documentElement;
|
||||
this.el.offset({
|
||||
left: (clientWidth - width) / 2,
|
||||
top: (clientHeight - height) / 3,
|
||||
});
|
||||
window.xkeydownEsc = (evt) => {
|
||||
if (evt.keyCode === 27) {
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
bind(window, 'keydown', window.xkeydownEsc);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.el.hide();
|
||||
document.body.removeChild(this.dimmer.el);
|
||||
unbind(window, 'keydown', window.xkeydownEsc);
|
||||
delete window.xkeydownEsc;
|
||||
}
|
||||
}
|
@ -0,0 +1,227 @@
|
||||
import Modal from './modal';
|
||||
import FormInput from './form_input';
|
||||
import FormSelect from './form_select';
|
||||
import FormField from './form_field';
|
||||
import Button from './button';
|
||||
import { t } from '../locale/locale';
|
||||
import { h } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
const fieldLabelWidth = 100;
|
||||
|
||||
export default class ModalValidation extends Modal {
|
||||
constructor() {
|
||||
const mf = new FormField(
|
||||
new FormSelect('cell',
|
||||
['cell'], // cell|row|column
|
||||
'100%',
|
||||
it => t(`dataValidation.modeType.${it}`)),
|
||||
{ required: true },
|
||||
`${t('dataValidation.range')}:`,
|
||||
fieldLabelWidth,
|
||||
);
|
||||
const rf = new FormField(
|
||||
new FormInput('120px', 'E3 or E3:F12'),
|
||||
{ required: true, pattern: /^([A-Z]{1,2}[1-9]\d*)(:[A-Z]{1,2}[1-9]\d*)?$/ },
|
||||
);
|
||||
const cf = new FormField(
|
||||
new FormSelect('list',
|
||||
['list', 'number', 'date', 'phone', 'email'],
|
||||
'100%',
|
||||
it => t(`dataValidation.type.${it}`),
|
||||
it => this.criteriaSelected(it)),
|
||||
{ required: true },
|
||||
`${t('dataValidation.criteria')}:`,
|
||||
fieldLabelWidth,
|
||||
);
|
||||
|
||||
// operator
|
||||
const of = new FormField(
|
||||
new FormSelect('be',
|
||||
['be', 'nbe', 'eq', 'neq', 'lt', 'lte', 'gt', 'gte'],
|
||||
'160px',
|
||||
it => t(`dataValidation.operator.${it}`),
|
||||
it => this.criteriaOperatorSelected(it)),
|
||||
{ required: true },
|
||||
).hide();
|
||||
// min, max
|
||||
const minvf = new FormField(
|
||||
new FormInput('70px', '10'),
|
||||
{ required: true },
|
||||
).hide();
|
||||
const maxvf = new FormField(
|
||||
new FormInput('70px', '100'),
|
||||
{ required: true, type: 'number' },
|
||||
).hide();
|
||||
// value
|
||||
const svf = new FormField(
|
||||
new FormInput('120px', 'a,b,c'),
|
||||
{ required: true },
|
||||
);
|
||||
const vf = new FormField(
|
||||
new FormInput('70px', '10'),
|
||||
{ required: true, type: 'number' },
|
||||
).hide();
|
||||
|
||||
super(t('contextmenu.validation'), [
|
||||
h('div', `${cssPrefix}-form-fields`).children(
|
||||
mf.el,
|
||||
rf.el,
|
||||
),
|
||||
h('div', `${cssPrefix}-form-fields`).children(
|
||||
cf.el,
|
||||
of.el,
|
||||
minvf.el,
|
||||
maxvf.el,
|
||||
vf.el,
|
||||
svf.el,
|
||||
),
|
||||
h('div', `${cssPrefix}-buttons`).children(
|
||||
new Button('cancel').on('click', () => this.btnClick('cancel')),
|
||||
new Button('remove').on('click', () => this.btnClick('remove')),
|
||||
new Button('save', 'primary').on('click', () => this.btnClick('save')),
|
||||
),
|
||||
]);
|
||||
this.mf = mf;
|
||||
this.rf = rf;
|
||||
this.cf = cf;
|
||||
this.of = of;
|
||||
this.minvf = minvf;
|
||||
this.maxvf = maxvf;
|
||||
this.vf = vf;
|
||||
this.svf = svf;
|
||||
this.change = () => {};
|
||||
}
|
||||
|
||||
showVf(it) {
|
||||
const hint = it === 'date' ? '2018-11-12' : '10';
|
||||
const { vf } = this;
|
||||
vf.input.hint(hint);
|
||||
vf.show();
|
||||
}
|
||||
|
||||
criteriaSelected(it) {
|
||||
const {
|
||||
of, minvf, maxvf, vf, svf,
|
||||
} = this;
|
||||
if (it === 'date' || it === 'number') {
|
||||
of.show();
|
||||
minvf.rule.type = it;
|
||||
maxvf.rule.type = it;
|
||||
if (it === 'date') {
|
||||
minvf.hint('2018-11-12');
|
||||
maxvf.hint('2019-11-12');
|
||||
} else {
|
||||
minvf.hint('10');
|
||||
maxvf.hint('100');
|
||||
}
|
||||
minvf.show();
|
||||
maxvf.show();
|
||||
vf.hide();
|
||||
svf.hide();
|
||||
} else {
|
||||
if (it === 'list') {
|
||||
svf.show();
|
||||
} else {
|
||||
svf.hide();
|
||||
}
|
||||
vf.hide();
|
||||
of.hide();
|
||||
minvf.hide();
|
||||
maxvf.hide();
|
||||
}
|
||||
}
|
||||
|
||||
criteriaOperatorSelected(it) {
|
||||
if (!it) return;
|
||||
const {
|
||||
minvf, maxvf, vf,
|
||||
} = this;
|
||||
if (it === 'be' || it === 'nbe') {
|
||||
minvf.show();
|
||||
maxvf.show();
|
||||
vf.hide();
|
||||
} else {
|
||||
const type = this.cf.val();
|
||||
vf.rule.type = type;
|
||||
if (type === 'date') {
|
||||
vf.hint('2018-11-12');
|
||||
} else {
|
||||
vf.hint('10');
|
||||
}
|
||||
vf.show();
|
||||
minvf.hide();
|
||||
maxvf.hide();
|
||||
}
|
||||
}
|
||||
|
||||
btnClick(action) {
|
||||
if (action === 'cancel') {
|
||||
this.hide();
|
||||
} else if (action === 'remove') {
|
||||
this.change('remove');
|
||||
this.hide();
|
||||
} else if (action === 'save') {
|
||||
// validate
|
||||
const attrs = ['mf', 'rf', 'cf', 'of', 'svf', 'vf', 'minvf', 'maxvf'];
|
||||
for (let i = 0; i < attrs.length; i += 1) {
|
||||
const field = this[attrs[i]];
|
||||
// console.log('field:', field);
|
||||
if (field.isShow()) {
|
||||
// console.log('it:', it);
|
||||
if (!field.validate()) return;
|
||||
}
|
||||
}
|
||||
|
||||
const mode = this.mf.val();
|
||||
const ref = this.rf.val();
|
||||
const type = this.cf.val();
|
||||
const operator = this.of.val();
|
||||
let value = this.svf.val();
|
||||
if (type === 'number' || type === 'date') {
|
||||
if (operator === 'be' || operator === 'nbe') {
|
||||
value = [this.minvf.val(), this.maxvf.val()];
|
||||
} else {
|
||||
value = this.vf.val();
|
||||
}
|
||||
}
|
||||
// console.log(mode, ref, type, operator, value);
|
||||
this.change('save',
|
||||
mode,
|
||||
ref,
|
||||
{
|
||||
type, operator, required: false, value,
|
||||
});
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
// validation: { mode, ref, validator }
|
||||
setValue(v) {
|
||||
if (v) {
|
||||
const {
|
||||
mf, rf, cf, of, svf, vf, minvf, maxvf,
|
||||
} = this;
|
||||
const {
|
||||
mode, ref, validator,
|
||||
} = v;
|
||||
const {
|
||||
type, operator, value,
|
||||
} = validator || { type: 'list' };
|
||||
mf.val(mode || 'cell');
|
||||
rf.val(ref);
|
||||
cf.val(type);
|
||||
of.val(operator);
|
||||
if (Array.isArray(value)) {
|
||||
minvf.val(value[0]);
|
||||
maxvf.val(value[1]);
|
||||
} else {
|
||||
svf.val(value || '');
|
||||
vf.val(value || '');
|
||||
}
|
||||
this.criteriaSelected(type);
|
||||
this.criteriaOperatorSelected(operator);
|
||||
}
|
||||
this.show();
|
||||
}
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
/* global window document */
|
||||
import { h } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
import Button from './button';
|
||||
import { Draw } from '../canvas/draw';
|
||||
import { renderCell } from './table';
|
||||
import { t } from '../locale/locale';
|
||||
|
||||
// resolution: 72 => 595 x 842
|
||||
// 150 => 1240 x 1754
|
||||
// 200 => 1654 x 2339
|
||||
// 300 => 2479 x 3508
|
||||
// 96 * cm / 2.54 , 96 * cm / 2.54
|
||||
|
||||
const PAGER_SIZES = [
|
||||
['A3', 11.69, 16.54],
|
||||
['A4', 8.27, 11.69],
|
||||
['A5', 5.83, 8.27],
|
||||
['B4', 9.84, 13.90],
|
||||
['B5', 6.93, 9.84],
|
||||
];
|
||||
|
||||
const PAGER_ORIENTATIONS = ['landscape', 'portrait'];
|
||||
|
||||
function inches2px(inc) {
|
||||
return parseInt(96 * inc, 10);
|
||||
}
|
||||
|
||||
function btnClick(type) {
|
||||
if (type === 'cancel') {
|
||||
this.el.hide();
|
||||
} else {
|
||||
this.toPrint();
|
||||
}
|
||||
}
|
||||
|
||||
function pagerSizeChange(evt) {
|
||||
const { paper } = this;
|
||||
const { value } = evt.target;
|
||||
const ps = PAGER_SIZES[value];
|
||||
paper.w = inches2px(ps[1]);
|
||||
paper.h = inches2px(ps[2]);
|
||||
// console.log('paper:', ps, paper);
|
||||
this.preview();
|
||||
}
|
||||
function pagerOrientationChange(evt) {
|
||||
const { paper } = this;
|
||||
const { value } = evt.target;
|
||||
const v = PAGER_ORIENTATIONS[value];
|
||||
paper.orientation = v;
|
||||
this.preview();
|
||||
}
|
||||
|
||||
export default class Print {
|
||||
constructor(data) {
|
||||
this.paper = {
|
||||
w: inches2px(PAGER_SIZES[0][1]),
|
||||
h: inches2px(PAGER_SIZES[0][2]),
|
||||
padding: 50,
|
||||
orientation: PAGER_ORIENTATIONS[0],
|
||||
get width() {
|
||||
return this.orientation === 'landscape' ? this.h : this.w;
|
||||
},
|
||||
get height() {
|
||||
return this.orientation === 'landscape' ? this.w : this.h;
|
||||
},
|
||||
};
|
||||
this.data = data;
|
||||
this.el = h('div', `${cssPrefix}-print`)
|
||||
.children(
|
||||
h('div', `${cssPrefix}-print-bar`)
|
||||
.children(
|
||||
h('div', '-title').child('Print settings'),
|
||||
h('div', '-right').children(
|
||||
h('div', `${cssPrefix}-buttons`).children(
|
||||
new Button('cancel').on('click', btnClick.bind(this, 'cancel')),
|
||||
new Button('next', 'primary').on('click', btnClick.bind(this, 'next')),
|
||||
),
|
||||
),
|
||||
),
|
||||
h('div', `${cssPrefix}-print-content`)
|
||||
.children(
|
||||
this.contentEl = h('div', '-content'),
|
||||
h('div', '-sider').child(
|
||||
h('form', '').children(
|
||||
h('fieldset', '').children(
|
||||
h('label', '').child(`${t('print.size')}`),
|
||||
h('select', '').children(
|
||||
...PAGER_SIZES.map((it, index) => h('option', '').attr('value', index).child(`${it[0]} ( ${it[1]}''x${it[2]}'' )`)),
|
||||
).on('change', pagerSizeChange.bind(this)),
|
||||
),
|
||||
h('fieldset', '').children(
|
||||
h('label', '').child(`${t('print.orientation')}`),
|
||||
h('select', '').children(
|
||||
...PAGER_ORIENTATIONS.map((it, index) => h('option', '').attr('value', index).child(`${t('print.orientations')[index]}`)),
|
||||
).on('change', pagerOrientationChange.bind(this)),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
).hide();
|
||||
}
|
||||
|
||||
resetData(data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
preview() {
|
||||
const { data, paper } = this;
|
||||
const { width, height, padding } = paper;
|
||||
const iwidth = width - padding * 2;
|
||||
const iheight = height - padding * 2;
|
||||
const cr = data.contentRange();
|
||||
const pages = parseInt(cr.h / iheight, 10) + 1;
|
||||
const scale = iwidth / cr.w;
|
||||
let left = padding;
|
||||
const top = padding;
|
||||
if (scale > 1) {
|
||||
left += (iwidth - cr.w) / 2;
|
||||
}
|
||||
let ri = 0;
|
||||
let yoffset = 0;
|
||||
this.contentEl.html('');
|
||||
this.canvases = [];
|
||||
const mViewRange = {
|
||||
sri: 0,
|
||||
sci: 0,
|
||||
eri: 0,
|
||||
eci: 0,
|
||||
};
|
||||
for (let i = 0; i < pages; i += 1) {
|
||||
let th = 0;
|
||||
let yo = 0;
|
||||
const wrap = h('div', `${cssPrefix}-canvas-card`);
|
||||
const canvas = h('canvas', `${cssPrefix}-canvas`);
|
||||
this.canvases.push(canvas.el);
|
||||
const draw = new Draw(canvas.el, width, height);
|
||||
// cell-content
|
||||
draw.save();
|
||||
draw.translate(left, top);
|
||||
if (scale < 1) draw.scale(scale, scale);
|
||||
// console.log('ri:', ri, cr.eri, yoffset);
|
||||
for (; ri <= cr.eri; ri += 1) {
|
||||
const rh = data.rows.getHeight(ri);
|
||||
th += rh;
|
||||
if (th < iheight) {
|
||||
for (let ci = 0; ci <= cr.eci; ci += 1) {
|
||||
renderCell(draw, data, ri, ci, yoffset);
|
||||
mViewRange.eci = ci;
|
||||
}
|
||||
} else {
|
||||
yo = -(th - rh);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mViewRange.eri = ri;
|
||||
draw.restore();
|
||||
// merge-cell
|
||||
draw.save();
|
||||
draw.translate(left, top);
|
||||
if (scale < 1) draw.scale(scale, scale);
|
||||
const yof = yoffset;
|
||||
data.eachMergesInView(mViewRange, ({ sri, sci }) => {
|
||||
renderCell(draw, data, sri, sci, yof);
|
||||
});
|
||||
draw.restore();
|
||||
|
||||
mViewRange.sri = mViewRange.eri;
|
||||
mViewRange.sci = mViewRange.eci;
|
||||
yoffset += yo;
|
||||
this.contentEl.child(h('div', `${cssPrefix}-canvas-card-wraper`).child(wrap.child(canvas)));
|
||||
}
|
||||
this.el.show();
|
||||
}
|
||||
|
||||
toPrint() {
|
||||
this.el.hide();
|
||||
const { paper } = this;
|
||||
const iframe = h('iframe', '').hide();
|
||||
const { el } = iframe;
|
||||
window.document.body.appendChild(el);
|
||||
const { contentWindow } = el;
|
||||
const idoc = contentWindow.document;
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = `
|
||||
@page { size: ${paper.width}px ${paper.height}px; };
|
||||
canvas {
|
||||
page-break-before: auto;
|
||||
page-break-after: always;
|
||||
image-rendering: pixelated;
|
||||
};
|
||||
`;
|
||||
idoc.head.appendChild(style);
|
||||
this.canvases.forEach((it) => {
|
||||
const cn = it.cloneNode(false);
|
||||
const ctx = cn.getContext('2d');
|
||||
// ctx.imageSmoothingEnabled = true;
|
||||
ctx.drawImage(it, 0, 0);
|
||||
idoc.body.appendChild(cn);
|
||||
});
|
||||
contentWindow.print();
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
/* global window */
|
||||
import { h } from './element';
|
||||
import { mouseMoveUp } from './event';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class Resizer {
|
||||
constructor(vertical = false, minDistance) {
|
||||
this.moving = false;
|
||||
this.vertical = vertical;
|
||||
this.el = h('div', `${cssPrefix}-resizer ${vertical ? 'vertical' : 'horizontal'}`).children(
|
||||
this.unhideHoverEl = h('div', `${cssPrefix}-resizer-hover`)
|
||||
.on('dblclick.stop', evt => this.mousedblclickHandler(evt))
|
||||
.css('position', 'absolute').hide(),
|
||||
this.hoverEl = h('div', `${cssPrefix}-resizer-hover`)
|
||||
.on('mousedown.stop', evt => this.mousedownHandler(evt)),
|
||||
this.lineEl = h('div', `${cssPrefix}-resizer-line`).hide(),
|
||||
).hide();
|
||||
// cell rect
|
||||
this.cRect = null;
|
||||
this.finishedFn = null;
|
||||
this.minDistance = minDistance;
|
||||
this.unhideFn = () => {};
|
||||
}
|
||||
|
||||
showUnhide(index) {
|
||||
this.unhideIndex = index;
|
||||
this.unhideHoverEl.show();
|
||||
}
|
||||
|
||||
hideUnhide() {
|
||||
this.unhideHoverEl.hide();
|
||||
}
|
||||
|
||||
// rect : {top, left, width, height}
|
||||
// line : {width, height}
|
||||
show(rect, line) {
|
||||
const {
|
||||
moving, vertical, hoverEl, lineEl, el,
|
||||
unhideHoverEl,
|
||||
} = this;
|
||||
if (moving) return;
|
||||
this.cRect = rect;
|
||||
const {
|
||||
left, top, width, height,
|
||||
} = rect;
|
||||
el.offset({
|
||||
left: vertical ? left + width - 5 : left,
|
||||
top: vertical ? top : top + height - 5,
|
||||
}).show();
|
||||
hoverEl.offset({
|
||||
width: vertical ? 5 : width,
|
||||
height: vertical ? height : 5,
|
||||
});
|
||||
lineEl.offset({
|
||||
width: vertical ? 0 : line.width,
|
||||
height: vertical ? line.height : 0,
|
||||
});
|
||||
unhideHoverEl.offset({
|
||||
left: vertical ? 5 - width : left,
|
||||
top: vertical ? top : 5 - height,
|
||||
width: vertical ? 5 : width,
|
||||
height: vertical ? height : 5,
|
||||
});
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.el.offset({
|
||||
left: 0,
|
||||
top: 0,
|
||||
}).hide();
|
||||
this.hideUnhide();
|
||||
}
|
||||
|
||||
mousedblclickHandler() {
|
||||
if (this.unhideIndex) this.unhideFn(this.unhideIndex);
|
||||
}
|
||||
|
||||
mousedownHandler(evt) {
|
||||
let startEvt = evt;
|
||||
const {
|
||||
el, lineEl, cRect, vertical, minDistance,
|
||||
} = this;
|
||||
let distance = vertical ? cRect.width : cRect.height;
|
||||
// console.log('distance:', distance);
|
||||
lineEl.show();
|
||||
mouseMoveUp(window, (e) => {
|
||||
this.moving = true;
|
||||
if (startEvt !== null && e.buttons === 1) {
|
||||
// console.log('top:', top, ', left:', top, ', cRect:', cRect);
|
||||
if (vertical) {
|
||||
distance += e.movementX;
|
||||
if (distance > minDistance) {
|
||||
el.css('left', `${cRect.left + distance}px`);
|
||||
}
|
||||
} else {
|
||||
distance += e.movementY;
|
||||
if (distance > minDistance) {
|
||||
el.css('top', `${cRect.top + distance}px`);
|
||||
}
|
||||
}
|
||||
startEvt = e;
|
||||
}
|
||||
}, () => {
|
||||
startEvt = null;
|
||||
lineEl.hide();
|
||||
this.moving = false;
|
||||
this.hide();
|
||||
if (this.finishedFn) {
|
||||
if (distance < minDistance) distance = minDistance;
|
||||
this.finishedFn(cRect, distance);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import { h } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default class Scrollbar {
|
||||
constructor(vertical) {
|
||||
this.vertical = vertical;
|
||||
this.moveFn = null;
|
||||
this.el = h('div', `${cssPrefix}-scrollbar ${vertical ? 'vertical' : 'horizontal'}`)
|
||||
.child(this.contentEl = h('div', ''))
|
||||
.on('mousemove.stop', () => {})
|
||||
.on('scroll.stop', (evt) => {
|
||||
const { scrollTop, scrollLeft } = evt.target;
|
||||
// console.log('scrollTop:', scrollTop);
|
||||
if (this.moveFn) {
|
||||
this.moveFn(this.vertical ? scrollTop : scrollLeft, evt);
|
||||
}
|
||||
// console.log('evt:::', evt);
|
||||
});
|
||||
}
|
||||
|
||||
move(v) {
|
||||
this.el.scroll(v);
|
||||
return this;
|
||||
}
|
||||
|
||||
scroll() {
|
||||
return this.el.scroll();
|
||||
}
|
||||
|
||||
set(distance, contentDistance) {
|
||||
const d = distance - 1;
|
||||
// console.log('distance:', distance, ', contentDistance:', contentDistance);
|
||||
if (contentDistance > d) {
|
||||
const cssKey = this.vertical ? 'height' : 'width';
|
||||
// console.log('d:', d);
|
||||
this.el.css(cssKey, `${d - 15}px`).show();
|
||||
this.contentEl
|
||||
.css(this.vertical ? 'width' : 'height', '1px')
|
||||
.css(cssKey, `${contentDistance}px`);
|
||||
} else {
|
||||
this.el.hide();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,413 @@
|
||||
import { h } from './element';
|
||||
import { cssPrefix } from '../config';
|
||||
import { CellRange } from '../core/cell_range';
|
||||
|
||||
const selectorHeightBorderWidth = 2 * 2 - 1;
|
||||
let startZIndex = 10;
|
||||
|
||||
class SelectorElement {
|
||||
constructor(useHideInput = false, autoFocus = true) {
|
||||
this.useHideInput = useHideInput;
|
||||
this.autoFocus = autoFocus;
|
||||
this.inputChange = () => {};
|
||||
this.cornerEl = h('div', `${cssPrefix}-selector-corner`);
|
||||
this.areaEl = h('div', `${cssPrefix}-selector-area`)
|
||||
.child(this.cornerEl).hide();
|
||||
this.clipboardEl = h('div', `${cssPrefix}-selector-clipboard`).hide();
|
||||
this.autofillEl = h('div', `${cssPrefix}-selector-autofill`).hide();
|
||||
this.el = h('div', `${cssPrefix}-selector`)
|
||||
.css('z-index', `${startZIndex}`)
|
||||
.children(this.areaEl, this.clipboardEl, this.autofillEl)
|
||||
.hide();
|
||||
if (useHideInput) {
|
||||
this.hideInput = h('input', '')
|
||||
.on('compositionend', (evt) => {
|
||||
this.inputChange(evt.target.value);
|
||||
});
|
||||
this.el.child(this.hideInputDiv = h('div', 'hide-input').child(this.hideInput));
|
||||
this.el.child(this.hideInputDiv = h('div', 'hide-input').child(this.hideInput));
|
||||
}
|
||||
startZIndex += 1;
|
||||
}
|
||||
|
||||
setOffset(v) {
|
||||
this.el.offset(v).show();
|
||||
return this;
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.el.hide();
|
||||
return this;
|
||||
}
|
||||
|
||||
setAreaOffset(v) {
|
||||
const {
|
||||
left, top, width, height,
|
||||
} = v;
|
||||
const of = {
|
||||
width: width - selectorHeightBorderWidth + 0.8,
|
||||
height: height - selectorHeightBorderWidth + 0.8,
|
||||
left: left - 0.8,
|
||||
top: top - 0.8,
|
||||
};
|
||||
this.areaEl.offset(of).show();
|
||||
if (this.useHideInput) {
|
||||
this.hideInputDiv.offset(of);
|
||||
if (this.autoFocus) {
|
||||
this.hideInput.val('').focus();
|
||||
} else {
|
||||
this.hideInput.val('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setClipboardOffset(v) {
|
||||
const {
|
||||
left, top, width, height,
|
||||
} = v;
|
||||
this.clipboardEl.offset({
|
||||
left,
|
||||
top,
|
||||
width: width - 5,
|
||||
height: height - 5,
|
||||
});
|
||||
}
|
||||
|
||||
showAutofill(v) {
|
||||
const {
|
||||
left, top, width, height,
|
||||
} = v;
|
||||
this.autofillEl.offset({
|
||||
width: width - selectorHeightBorderWidth,
|
||||
height: height - selectorHeightBorderWidth,
|
||||
left,
|
||||
top,
|
||||
}).show();
|
||||
}
|
||||
|
||||
hideAutofill() {
|
||||
this.autofillEl.hide();
|
||||
}
|
||||
|
||||
showClipboard() {
|
||||
this.clipboardEl.show();
|
||||
}
|
||||
|
||||
hideClipboard() {
|
||||
this.clipboardEl.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function calBRAreaOffset(offset) {
|
||||
const { data } = this;
|
||||
const {
|
||||
left, top, width, height, scroll, l, t,
|
||||
} = offset;
|
||||
const ftwidth = data.freezeTotalWidth();
|
||||
const ftheight = data.freezeTotalHeight();
|
||||
let left0 = left - ftwidth;
|
||||
if (ftwidth > l) left0 -= scroll.x;
|
||||
let top0 = top - ftheight;
|
||||
if (ftheight > t) top0 -= scroll.y;
|
||||
return {
|
||||
left: left0,
|
||||
top: top0,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
|
||||
function calTAreaOffset(offset) {
|
||||
const { data } = this;
|
||||
const {
|
||||
left, width, height, l, t, scroll,
|
||||
} = offset;
|
||||
const ftwidth = data.freezeTotalWidth();
|
||||
let left0 = left - ftwidth;
|
||||
if (ftwidth > l) left0 -= scroll.x;
|
||||
return {
|
||||
left: left0, top: t, width, height,
|
||||
};
|
||||
}
|
||||
|
||||
function calLAreaOffset(offset) {
|
||||
const { data } = this;
|
||||
const {
|
||||
top, width, height, l, t, scroll,
|
||||
} = offset;
|
||||
const ftheight = data.freezeTotalHeight();
|
||||
let top0 = top - ftheight;
|
||||
// console.log('ftheight:', ftheight, ', t:', t);
|
||||
if (ftheight > t) top0 -= scroll.y;
|
||||
return {
|
||||
left: l, top: top0, width, height,
|
||||
};
|
||||
}
|
||||
|
||||
function setBRAreaOffset(offset) {
|
||||
const { br } = this;
|
||||
br.setAreaOffset(calBRAreaOffset.call(this, offset));
|
||||
}
|
||||
|
||||
function setTLAreaOffset(offset) {
|
||||
const { tl } = this;
|
||||
tl.setAreaOffset(offset);
|
||||
}
|
||||
|
||||
function setTAreaOffset(offset) {
|
||||
const { t } = this;
|
||||
t.setAreaOffset(calTAreaOffset.call(this, offset));
|
||||
}
|
||||
|
||||
function setLAreaOffset(offset) {
|
||||
const { l } = this;
|
||||
l.setAreaOffset(calLAreaOffset.call(this, offset));
|
||||
}
|
||||
|
||||
function setLClipboardOffset(offset) {
|
||||
const { l } = this;
|
||||
l.setClipboardOffset(calLAreaOffset.call(this, offset));
|
||||
}
|
||||
|
||||
function setBRClipboardOffset(offset) {
|
||||
const { br } = this;
|
||||
br.setClipboardOffset(calBRAreaOffset.call(this, offset));
|
||||
}
|
||||
|
||||
function setTLClipboardOffset(offset) {
|
||||
const { tl } = this;
|
||||
tl.setClipboardOffset(offset);
|
||||
}
|
||||
|
||||
function setTClipboardOffset(offset) {
|
||||
const { t } = this;
|
||||
t.setClipboardOffset(calTAreaOffset.call(this, offset));
|
||||
}
|
||||
|
||||
function setAllAreaOffset(offset) {
|
||||
setBRAreaOffset.call(this, offset);
|
||||
setTLAreaOffset.call(this, offset);
|
||||
setTAreaOffset.call(this, offset);
|
||||
setLAreaOffset.call(this, offset);
|
||||
}
|
||||
|
||||
function setAllClipboardOffset(offset) {
|
||||
setBRClipboardOffset.call(this, offset);
|
||||
setTLClipboardOffset.call(this, offset);
|
||||
setTClipboardOffset.call(this, offset);
|
||||
setLClipboardOffset.call(this, offset);
|
||||
}
|
||||
|
||||
export default class Selector {
|
||||
constructor(data) {
|
||||
const { autoFocus } = data.settings;
|
||||
this.inputChange = () => {};
|
||||
this.data = data;
|
||||
this.br = new SelectorElement(true, autoFocus);
|
||||
this.t = new SelectorElement();
|
||||
this.l = new SelectorElement();
|
||||
this.tl = new SelectorElement();
|
||||
this.br.inputChange = (v) => {
|
||||
this.inputChange(v);
|
||||
};
|
||||
this.br.el.show();
|
||||
this.offset = null;
|
||||
this.areaOffset = null;
|
||||
this.indexes = null;
|
||||
this.range = null;
|
||||
this.arange = null;
|
||||
this.el = h('div', `${cssPrefix}-selectors`)
|
||||
.children(
|
||||
this.tl.el,
|
||||
this.t.el,
|
||||
this.l.el,
|
||||
this.br.el,
|
||||
).hide();
|
||||
|
||||
// for performance
|
||||
this.lastri = -1;
|
||||
this.lastci = -1;
|
||||
|
||||
startZIndex += 1;
|
||||
}
|
||||
|
||||
resetData(data) {
|
||||
this.data = data;
|
||||
this.range = data.selector.range;
|
||||
this.resetAreaOffset();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.el.hide();
|
||||
}
|
||||
|
||||
resetOffset() {
|
||||
const {
|
||||
data, tl, t, l, br,
|
||||
} = this;
|
||||
const freezeHeight = data.freezeTotalHeight();
|
||||
const freezeWidth = data.freezeTotalWidth();
|
||||
if (freezeHeight > 0 || freezeWidth > 0) {
|
||||
tl.setOffset({ width: freezeWidth, height: freezeHeight });
|
||||
t.setOffset({ left: freezeWidth, height: freezeHeight });
|
||||
l.setOffset({ top: freezeHeight, width: freezeWidth });
|
||||
br.setOffset({ left: freezeWidth, top: freezeHeight });
|
||||
} else {
|
||||
tl.hide();
|
||||
t.hide();
|
||||
l.hide();
|
||||
br.setOffset({ left: 0, top: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
resetAreaOffset() {
|
||||
// console.log('offset:', offset);
|
||||
const offset = this.data.getSelectedRect();
|
||||
const coffset = this.data.getClipboardRect();
|
||||
setAllAreaOffset.call(this, offset);
|
||||
setAllClipboardOffset.call(this, coffset);
|
||||
this.resetOffset();
|
||||
}
|
||||
|
||||
resetBRTAreaOffset() {
|
||||
const offset = this.data.getSelectedRect();
|
||||
const coffset = this.data.getClipboardRect();
|
||||
setBRAreaOffset.call(this, offset);
|
||||
setTAreaOffset.call(this, offset);
|
||||
setBRClipboardOffset.call(this, coffset);
|
||||
setTClipboardOffset.call(this, coffset);
|
||||
this.resetOffset();
|
||||
}
|
||||
|
||||
resetBRLAreaOffset() {
|
||||
const offset = this.data.getSelectedRect();
|
||||
const coffset = this.data.getClipboardRect();
|
||||
setBRAreaOffset.call(this, offset);
|
||||
setLAreaOffset.call(this, offset);
|
||||
setBRClipboardOffset.call(this, coffset);
|
||||
setLClipboardOffset.call(this, coffset);
|
||||
this.resetOffset();
|
||||
}
|
||||
|
||||
set(ri, ci, indexesUpdated = true) {
|
||||
const { data } = this;
|
||||
const cellRange = data.calSelectedRangeByStart(ri, ci);
|
||||
const { sri, sci } = cellRange;
|
||||
if (indexesUpdated) {
|
||||
let [cri, cci] = [ri, ci];
|
||||
if (ri < 0) cri = 0;
|
||||
if (ci < 0) cci = 0;
|
||||
data.selector.setIndexes(cri, cci);
|
||||
this.indexes = [cri, cci];
|
||||
}
|
||||
|
||||
this.moveIndexes = [sri, sci];
|
||||
// this.sIndexes = sIndexes;
|
||||
// this.eIndexes = eIndexes;
|
||||
this.range = cellRange;
|
||||
this.resetAreaOffset();
|
||||
this.el.show();
|
||||
}
|
||||
|
||||
setEnd(ri, ci, moving = true) {
|
||||
const { data, lastri, lastci } = this;
|
||||
if (moving) {
|
||||
if (ri === lastri && ci === lastci) return;
|
||||
this.lastri = ri;
|
||||
this.lastci = ci;
|
||||
}
|
||||
this.range = data.calSelectedRangeByEnd(ri, ci);
|
||||
setAllAreaOffset.call(this, this.data.getSelectedRect());
|
||||
}
|
||||
|
||||
reset() {
|
||||
// console.log('::::', this.data);
|
||||
const { eri, eci } = this.data.selector.range;
|
||||
this.setEnd(eri, eci);
|
||||
}
|
||||
|
||||
showAutofill(ri, ci) {
|
||||
if (ri === -1 && ci === -1) return;
|
||||
// console.log('ri:', ri, ', ci:', ci);
|
||||
// const [sri, sci] = this.sIndexes;
|
||||
// const [eri, eci] = this.eIndexes;
|
||||
const {
|
||||
sri, sci, eri, eci,
|
||||
} = this.range;
|
||||
const [nri, nci] = [ri, ci];
|
||||
// const rn = eri - sri;
|
||||
// const cn = eci - sci;
|
||||
const srn = sri - ri;
|
||||
const scn = sci - ci;
|
||||
const ern = eri - ri;
|
||||
const ecn = eci - ci;
|
||||
if (scn > 0) {
|
||||
// left
|
||||
// console.log('left');
|
||||
this.arange = new CellRange(sri, nci, eri, sci - 1);
|
||||
// this.saIndexes = [sri, nci];
|
||||
// this.eaIndexes = [eri, sci - 1];
|
||||
// data.calRangeIndexes2(
|
||||
} else if (srn > 0) {
|
||||
// top
|
||||
// console.log('top');
|
||||
// nri = sri;
|
||||
this.arange = new CellRange(nri, sci, sri - 1, eci);
|
||||
// this.saIndexes = [nri, sci];
|
||||
// this.eaIndexes = [sri - 1, eci];
|
||||
} else if (ecn < 0) {
|
||||
// right
|
||||
// console.log('right');
|
||||
// nci = eci;
|
||||
this.arange = new CellRange(sri, eci + 1, eri, nci);
|
||||
// this.saIndexes = [sri, eci + 1];
|
||||
// this.eaIndexes = [eri, nci];
|
||||
} else if (ern < 0) {
|
||||
// bottom
|
||||
// console.log('bottom');
|
||||
// nri = eri;
|
||||
this.arange = new CellRange(eri + 1, sci, nri, eci);
|
||||
// this.saIndexes = [eri + 1, sci];
|
||||
// this.eaIndexes = [nri, eci];
|
||||
} else {
|
||||
// console.log('else:');
|
||||
this.arange = null;
|
||||
// this.saIndexes = null;
|
||||
// this.eaIndexes = null;
|
||||
return;
|
||||
}
|
||||
if (this.arange !== null) {
|
||||
// console.log(this.saIndexes, ':', this.eaIndexes);
|
||||
const offset = this.data.getRect(this.arange);
|
||||
offset.width += 2;
|
||||
offset.height += 2;
|
||||
const {
|
||||
br, l, t, tl,
|
||||
} = this;
|
||||
br.showAutofill(calBRAreaOffset.call(this, offset));
|
||||
l.showAutofill(calLAreaOffset.call(this, offset));
|
||||
t.showAutofill(calTAreaOffset.call(this, offset));
|
||||
tl.showAutofill(offset);
|
||||
}
|
||||
}
|
||||
|
||||
hideAutofill() {
|
||||
['br', 'l', 't', 'tl'].forEach((property) => {
|
||||
this[property].hideAutofill();
|
||||
});
|
||||
}
|
||||
|
||||
showClipboard() {
|
||||
const coffset = this.data.getClipboardRect();
|
||||
setAllClipboardOffset.call(this, coffset);
|
||||
['br', 'l', 't', 'tl'].forEach((property) => {
|
||||
this[property].showClipboard();
|
||||
});
|
||||
}
|
||||
|
||||
hideClipboard() {
|
||||
['br', 'l', 't', 'tl'].forEach((property) => {
|
||||
this[property].hideClipboard();
|
||||
});
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,141 @@
|
||||
import { h } from './element';
|
||||
import Button from './button';
|
||||
import { bindClickoutside, unbindClickoutside } from './event';
|
||||
import { cssPrefix } from '../config';
|
||||
import { t } from '../locale/locale';
|
||||
|
||||
function buildMenu(clsName) {
|
||||
return h('div', `${cssPrefix}-item ${clsName}`);
|
||||
}
|
||||
|
||||
function buildSortItem(it) {
|
||||
return buildMenu('state').child(t(`sort.${it}`))
|
||||
.on('click.stop', () => this.itemClick(it));
|
||||
}
|
||||
|
||||
function buildFilterBody(items) {
|
||||
const { filterbEl, filterValues } = this;
|
||||
filterbEl.html('');
|
||||
const itemKeys = Object.keys(items);
|
||||
itemKeys.forEach((it, index) => {
|
||||
const cnt = items[it];
|
||||
const active = filterValues.includes(it) ? 'checked' : '';
|
||||
filterbEl.child(h('div', `${cssPrefix}-item state ${active}`)
|
||||
.on('click.stop', () => this.filterClick(index, it))
|
||||
.children(it === '' ? t('filter.empty') : it, h('div', 'label').html(`(${cnt})`)));
|
||||
});
|
||||
}
|
||||
|
||||
function resetFilterHeader() {
|
||||
const { filterhEl, filterValues, values } = this;
|
||||
filterhEl.html(`${filterValues.length} / ${values.length}`);
|
||||
filterhEl.checked(filterValues.length === values.length);
|
||||
}
|
||||
|
||||
export default class SortFilter {
|
||||
constructor() {
|
||||
this.filterbEl = h('div', `${cssPrefix}-body`);
|
||||
this.filterhEl = h('div', `${cssPrefix}-header state`).on('click.stop', () => this.filterClick(0, 'all'));
|
||||
this.el = h('div', `${cssPrefix}-sort-filter`).children(
|
||||
this.sortAscEl = buildSortItem.call(this, 'asc'),
|
||||
this.sortDescEl = buildSortItem.call(this, 'desc'),
|
||||
buildMenu('divider'),
|
||||
h('div', `${cssPrefix}-filter`).children(
|
||||
this.filterhEl,
|
||||
this.filterbEl,
|
||||
),
|
||||
h('div', `${cssPrefix}-buttons`).children(
|
||||
new Button('cancel').on('click', () => this.btnClick('cancel')),
|
||||
new Button('ok', 'primary').on('click', () => this.btnClick('ok')),
|
||||
),
|
||||
).hide();
|
||||
// this.setFilters(['test1', 'test2', 'text3']);
|
||||
this.ci = null;
|
||||
this.sortDesc = null;
|
||||
this.values = null;
|
||||
this.filterValues = [];
|
||||
}
|
||||
|
||||
btnClick(it) {
|
||||
if (it === 'ok') {
|
||||
const { ci, sort, filterValues } = this;
|
||||
if (this.ok) {
|
||||
this.ok(ci, sort, 'in', filterValues);
|
||||
}
|
||||
}
|
||||
this.hide();
|
||||
}
|
||||
|
||||
itemClick(it) {
|
||||
// console.log('it:', it);
|
||||
this.sort = it;
|
||||
const { sortAscEl, sortDescEl } = this;
|
||||
sortAscEl.checked(it === 'asc');
|
||||
sortDescEl.checked(it === 'desc');
|
||||
}
|
||||
|
||||
filterClick(index, it) {
|
||||
// console.log('index:', index, it);
|
||||
const { filterbEl, filterValues, values } = this;
|
||||
const children = filterbEl.children();
|
||||
if (it === 'all') {
|
||||
if (children.length === filterValues.length) {
|
||||
this.filterValues = [];
|
||||
children.forEach(i => h(i).checked(false));
|
||||
} else {
|
||||
this.filterValues = Array.from(values);
|
||||
children.forEach(i => h(i).checked(true));
|
||||
}
|
||||
} else {
|
||||
const checked = h(children[index]).toggle('checked');
|
||||
if (checked) {
|
||||
filterValues.push(it);
|
||||
} else {
|
||||
filterValues.splice(filterValues.findIndex(i => i === it), 1);
|
||||
}
|
||||
}
|
||||
resetFilterHeader.call(this);
|
||||
}
|
||||
|
||||
// v: autoFilter
|
||||
// items: {value: cnt}
|
||||
// sort { ci, order }
|
||||
set(ci, items, filter, sort) {
|
||||
this.ci = ci;
|
||||
const { sortAscEl, sortDescEl } = this;
|
||||
if (sort !== null) {
|
||||
this.sort = sort.order;
|
||||
sortAscEl.checked(sort.asc());
|
||||
sortDescEl.checked(sort.desc());
|
||||
} else {
|
||||
this.sortDesc = null;
|
||||
sortAscEl.checked(false);
|
||||
sortDescEl.checked(false);
|
||||
}
|
||||
// this.setFilters(items, filter);
|
||||
this.values = Object.keys(items);
|
||||
this.filterValues = filter ? Array.from(filter.value) : Object.keys(items);
|
||||
buildFilterBody.call(this, items, filter);
|
||||
resetFilterHeader.call(this);
|
||||
}
|
||||
|
||||
setOffset(v) {
|
||||
this.el.offset(v).show();
|
||||
let tindex = 1;
|
||||
bindClickoutside(this.el, () => {
|
||||
if (tindex <= 0) {
|
||||
this.hide();
|
||||
}
|
||||
tindex -= 1;
|
||||
});
|
||||
}
|
||||
|
||||
show() {
|
||||
this.el.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.el.hide();
|
||||
unbindClickoutside(this.el);
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
import { h } from './element';
|
||||
import { bindClickoutside, unbindClickoutside } from './event';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
function inputMovePrev(evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
const { filterItems } = this;
|
||||
if (filterItems.length <= 0) return;
|
||||
if (this.itemIndex >= 0) filterItems[this.itemIndex].toggle();
|
||||
this.itemIndex -= 1;
|
||||
if (this.itemIndex < 0) {
|
||||
this.itemIndex = filterItems.length - 1;
|
||||
}
|
||||
filterItems[this.itemIndex].toggle();
|
||||
}
|
||||
|
||||
function inputMoveNext(evt) {
|
||||
evt.stopPropagation();
|
||||
const { filterItems } = this;
|
||||
if (filterItems.length <= 0) return;
|
||||
if (this.itemIndex >= 0) filterItems[this.itemIndex].toggle();
|
||||
this.itemIndex += 1;
|
||||
if (this.itemIndex > filterItems.length - 1) {
|
||||
this.itemIndex = 0;
|
||||
}
|
||||
filterItems[this.itemIndex].toggle();
|
||||
}
|
||||
|
||||
function inputEnter(evt) {
|
||||
evt.preventDefault();
|
||||
const { filterItems } = this;
|
||||
if (filterItems.length <= 0) return;
|
||||
evt.stopPropagation();
|
||||
if (this.itemIndex < 0) this.itemIndex = 0;
|
||||
filterItems[this.itemIndex].el.click();
|
||||
this.hide();
|
||||
}
|
||||
|
||||
function inputKeydownHandler(evt) {
|
||||
const { keyCode } = evt;
|
||||
if (evt.ctrlKey) {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
switch (keyCode) {
|
||||
case 37: // left
|
||||
evt.stopPropagation();
|
||||
break;
|
||||
case 38: // up
|
||||
inputMovePrev.call(this, evt);
|
||||
break;
|
||||
case 39: // right
|
||||
evt.stopPropagation();
|
||||
break;
|
||||
case 40: // down
|
||||
inputMoveNext.call(this, evt);
|
||||
break;
|
||||
case 13: // enter
|
||||
inputEnter.call(this, evt);
|
||||
break;
|
||||
case 9:
|
||||
inputEnter.call(this, evt);
|
||||
break;
|
||||
default:
|
||||
evt.stopPropagation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export default class Suggest {
|
||||
constructor(items, itemClick, width = '200px') {
|
||||
this.filterItems = [];
|
||||
this.items = items;
|
||||
this.el = h('div', `${cssPrefix}-suggest`).css('width', width).hide();
|
||||
this.itemClick = itemClick;
|
||||
this.itemIndex = -1;
|
||||
}
|
||||
|
||||
setOffset(v) {
|
||||
this.el.cssRemoveKeys('top', 'bottom')
|
||||
.offset(v);
|
||||
}
|
||||
|
||||
hide() {
|
||||
const { el } = this;
|
||||
this.filterItems = [];
|
||||
this.itemIndex = -1;
|
||||
el.hide();
|
||||
unbindClickoutside(this.el.parent());
|
||||
}
|
||||
|
||||
setItems(items) {
|
||||
this.items = items;
|
||||
// this.search('');
|
||||
}
|
||||
|
||||
search(word) {
|
||||
let { items } = this;
|
||||
if (!/^\s*$/.test(word)) {
|
||||
items = items.filter(it => (it.key || it).startsWith(word.toUpperCase()));
|
||||
}
|
||||
items = items.map((it) => {
|
||||
let { title } = it;
|
||||
if (title) {
|
||||
if (typeof title === 'function') {
|
||||
title = title();
|
||||
}
|
||||
} else {
|
||||
title = it;
|
||||
}
|
||||
const item = h('div', `${cssPrefix}-item`)
|
||||
.child(title)
|
||||
.on('click.stop', () => {
|
||||
this.itemClick(it);
|
||||
this.hide();
|
||||
});
|
||||
if (it.label) {
|
||||
item.child(h('div', 'label').html(it.label));
|
||||
}
|
||||
return item;
|
||||
});
|
||||
this.filterItems = items;
|
||||
if (items.length <= 0) {
|
||||
return;
|
||||
}
|
||||
const { el } = this;
|
||||
// items[0].toggle();
|
||||
el.html('').children(...items).show();
|
||||
bindClickoutside(el.parent(), () => { this.hide(); });
|
||||
}
|
||||
|
||||
bindInputEvents(input) {
|
||||
input.on('keydown', evt => inputKeydownHandler.call(this, evt));
|
||||
}
|
||||
}
|
@ -0,0 +1,373 @@
|
||||
import { stringAt } from '../core/alphabet';
|
||||
import { getFontSizePxByPt } from '../core/font';
|
||||
import _cell from '../core/cell';
|
||||
import { formulam } from '../core/formula';
|
||||
import { formatm } from '../core/format';
|
||||
|
||||
import {
|
||||
Draw, DrawBox, thinLineWidth, npx,
|
||||
} from '../canvas/draw';
|
||||
// gobal var
|
||||
const cellPaddingWidth = 5;
|
||||
const tableFixedHeaderCleanStyle = { fillStyle: '#f4f5f8' };
|
||||
const tableGridStyle = {
|
||||
fillStyle: '#fff',
|
||||
lineWidth: thinLineWidth,
|
||||
strokeStyle: '#e6e6e6',
|
||||
};
|
||||
function tableFixedHeaderStyle() {
|
||||
return {
|
||||
textAlign: 'center',
|
||||
textBaseline: 'middle',
|
||||
font: `500 ${npx(12)}px Source Sans Pro`,
|
||||
fillStyle: '#585757',
|
||||
lineWidth: thinLineWidth(),
|
||||
strokeStyle: '#e6e6e6',
|
||||
};
|
||||
}
|
||||
|
||||
function getDrawBox(data, rindex, cindex, yoffset = 0) {
|
||||
const {
|
||||
left, top, width, height,
|
||||
} = data.cellRect(rindex, cindex);
|
||||
return new DrawBox(left, top + yoffset, width, height, cellPaddingWidth);
|
||||
}
|
||||
/*
|
||||
function renderCellBorders(bboxes, translateFunc) {
|
||||
const { draw } = this;
|
||||
if (bboxes) {
|
||||
const rset = new Set();
|
||||
// console.log('bboxes:', bboxes);
|
||||
bboxes.forEach(({ ri, ci, box }) => {
|
||||
if (!rset.has(ri)) {
|
||||
rset.add(ri);
|
||||
translateFunc(ri);
|
||||
}
|
||||
draw.strokeBorders(box);
|
||||
});
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
export function renderCell(draw, data, rindex, cindex, yoffset = 0) {
|
||||
const { sortedRowMap, rows, cols } = data;
|
||||
if (rows.isHide(rindex) || cols.isHide(cindex)) return;
|
||||
let nrindex = rindex;
|
||||
if (sortedRowMap.has(rindex)) {
|
||||
nrindex = sortedRowMap.get(rindex);
|
||||
}
|
||||
|
||||
const cell = data.getCell(nrindex, cindex);
|
||||
if (cell === null) return;
|
||||
let frozen = false;
|
||||
if ('editable' in cell && cell.editable === false) {
|
||||
frozen = true;
|
||||
}
|
||||
|
||||
const style = data.getCellStyleOrDefault(nrindex, cindex);
|
||||
const dbox = getDrawBox(data, rindex, cindex, yoffset);
|
||||
dbox.bgcolor = style.bgcolor;
|
||||
if (style.border !== undefined) {
|
||||
dbox.setBorders(style.border);
|
||||
// bboxes.push({ ri: rindex, ci: cindex, box: dbox });
|
||||
draw.strokeBorders(dbox);
|
||||
}
|
||||
draw.rect(dbox, () => {
|
||||
// render text
|
||||
let cellText = '';
|
||||
if (!data.settings.evalPaused) {
|
||||
cellText = _cell.render(cell.text || '', formulam, (y, x) => (data.getCellTextOrDefault(x, y)));
|
||||
} else {
|
||||
cellText = cell.text || '';
|
||||
}
|
||||
if (style.format) {
|
||||
// console.log(data.formatm, '>>', cell.format);
|
||||
cellText = formatm[style.format].render(cellText);
|
||||
}
|
||||
const font = Object.assign({}, style.font);
|
||||
font.size = getFontSizePxByPt(font.size);
|
||||
// console.log('style:', style);
|
||||
draw.text(cellText, dbox, {
|
||||
align: style.align,
|
||||
valign: style.valign,
|
||||
font,
|
||||
color: style.color,
|
||||
strike: style.strike,
|
||||
underline: style.underline,
|
||||
}, style.textwrap);
|
||||
// error
|
||||
const error = data.validations.getError(rindex, cindex);
|
||||
if (error) {
|
||||
// console.log('error:', rindex, cindex, error);
|
||||
draw.error(dbox);
|
||||
}
|
||||
if (frozen) {
|
||||
draw.frozen(dbox);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderAutofilter(viewRange) {
|
||||
const { data, draw } = this;
|
||||
if (viewRange) {
|
||||
const { autoFilter } = data;
|
||||
if (!autoFilter.active()) return;
|
||||
const afRange = autoFilter.hrange();
|
||||
if (viewRange.intersects(afRange)) {
|
||||
afRange.each((ri, ci) => {
|
||||
const dbox = getDrawBox(data, ri, ci);
|
||||
draw.dropdown(dbox);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderContent(viewRange, fw, fh, tx, ty) {
|
||||
const { draw, data } = this;
|
||||
draw.save();
|
||||
draw.translate(fw, fh)
|
||||
.translate(tx, ty);
|
||||
|
||||
const { exceptRowSet } = data;
|
||||
// const exceptRows = Array.from(exceptRowSet);
|
||||
const filteredTranslateFunc = (ri) => {
|
||||
const ret = exceptRowSet.has(ri);
|
||||
if (ret) {
|
||||
const height = data.rows.getHeight(ri);
|
||||
draw.translate(0, -height);
|
||||
}
|
||||
return !ret;
|
||||
};
|
||||
|
||||
const exceptRowTotalHeight = data.exceptRowTotalHeight(viewRange.sri, viewRange.eri);
|
||||
// 1 render cell
|
||||
draw.save();
|
||||
draw.translate(0, -exceptRowTotalHeight);
|
||||
viewRange.each((ri, ci) => {
|
||||
renderCell(draw, data, ri, ci);
|
||||
}, ri => filteredTranslateFunc(ri));
|
||||
draw.restore();
|
||||
|
||||
|
||||
// 2 render mergeCell
|
||||
const rset = new Set();
|
||||
draw.save();
|
||||
draw.translate(0, -exceptRowTotalHeight);
|
||||
data.eachMergesInView(viewRange, ({ sri, sci, eri }) => {
|
||||
if (!exceptRowSet.has(sri)) {
|
||||
renderCell(draw, data, sri, sci);
|
||||
} else if (!rset.has(sri)) {
|
||||
rset.add(sri);
|
||||
const height = data.rows.sumHeight(sri, eri + 1);
|
||||
draw.translate(0, -height);
|
||||
}
|
||||
});
|
||||
draw.restore();
|
||||
|
||||
// 3 render autofilter
|
||||
renderAutofilter.call(this, viewRange);
|
||||
|
||||
draw.restore();
|
||||
}
|
||||
|
||||
function renderSelectedHeaderCell(x, y, w, h) {
|
||||
const { draw } = this;
|
||||
draw.save();
|
||||
draw.attr({ fillStyle: 'rgba(75, 137, 255, 0.08)' })
|
||||
.fillRect(x, y, w, h);
|
||||
draw.restore();
|
||||
}
|
||||
|
||||
// viewRange
|
||||
// type: all | left | top
|
||||
// w: the fixed width of header
|
||||
// h: the fixed height of header
|
||||
// tx: moving distance on x-axis
|
||||
// ty: moving distance on y-axis
|
||||
function renderFixedHeaders(type, viewRange, w, h, tx, ty) {
|
||||
const { draw, data } = this;
|
||||
const sumHeight = viewRange.h; // rows.sumHeight(viewRange.sri, viewRange.eri + 1);
|
||||
const sumWidth = viewRange.w; // cols.sumWidth(viewRange.sci, viewRange.eci + 1);
|
||||
const nty = ty + h;
|
||||
const ntx = tx + w;
|
||||
|
||||
draw.save();
|
||||
// draw rect background
|
||||
draw.attr(tableFixedHeaderCleanStyle);
|
||||
if (type === 'all' || type === 'left') draw.fillRect(0, nty, w, sumHeight);
|
||||
if (type === 'all' || type === 'top') draw.fillRect(ntx, 0, sumWidth, h);
|
||||
|
||||
const {
|
||||
sri, sci, eri, eci,
|
||||
} = data.selector.range;
|
||||
// console.log(data.selectIndexes);
|
||||
// draw text
|
||||
// text font, align...
|
||||
draw.attr(tableFixedHeaderStyle());
|
||||
// y-header-text
|
||||
if (type === 'all' || type === 'left') {
|
||||
data.rowEach(viewRange.sri, viewRange.eri, (i, y1, rowHeight) => {
|
||||
const y = nty + y1;
|
||||
const ii = i;
|
||||
draw.line([0, y], [w, y]);
|
||||
if (sri <= ii && ii < eri + 1) {
|
||||
renderSelectedHeaderCell.call(this, 0, y, w, rowHeight);
|
||||
}
|
||||
draw.fillText(ii + 1, w / 2, y + (rowHeight / 2));
|
||||
if (i > 0 && data.rows.isHide(i - 1)) {
|
||||
draw.save();
|
||||
draw.attr({ strokeStyle: '#c6c6c6' });
|
||||
draw.line([5, y + 5], [w - 5, y + 5]);
|
||||
draw.restore();
|
||||
}
|
||||
});
|
||||
draw.line([0, sumHeight + nty], [w, sumHeight + nty]);
|
||||
draw.line([w, nty], [w, sumHeight + nty]);
|
||||
}
|
||||
// x-header-text
|
||||
if (type === 'all' || type === 'top') {
|
||||
data.colEach(viewRange.sci, viewRange.eci, (i, x1, colWidth) => {
|
||||
const x = ntx + x1;
|
||||
const ii = i;
|
||||
draw.line([x, 0], [x, h]);
|
||||
if (sci <= ii && ii < eci + 1) {
|
||||
renderSelectedHeaderCell.call(this, x, 0, colWidth, h);
|
||||
}
|
||||
draw.fillText(stringAt(ii), x + (colWidth / 2), h / 2);
|
||||
if (i > 0 && data.cols.isHide(i - 1)) {
|
||||
draw.save();
|
||||
draw.attr({ strokeStyle: '#c6c6c6' });
|
||||
draw.line([x + 5, 5], [x + 5, h - 5]);
|
||||
draw.restore();
|
||||
}
|
||||
});
|
||||
draw.line([sumWidth + ntx, 0], [sumWidth + ntx, h]);
|
||||
draw.line([0, h], [sumWidth + ntx, h]);
|
||||
}
|
||||
draw.restore();
|
||||
}
|
||||
|
||||
function renderFixedLeftTopCell(fw, fh) {
|
||||
const { draw } = this;
|
||||
draw.save();
|
||||
// left-top-cell
|
||||
draw.attr({ fillStyle: '#f4f5f8' })
|
||||
.fillRect(0, 0, fw, fh);
|
||||
draw.restore();
|
||||
}
|
||||
|
||||
function renderContentGrid({
|
||||
sri, sci, eri, eci, w, h,
|
||||
}, fw, fh, tx, ty) {
|
||||
const { draw, data } = this;
|
||||
const { settings } = data;
|
||||
|
||||
draw.save();
|
||||
draw.attr(tableGridStyle)
|
||||
.translate(fw + tx, fh + ty);
|
||||
// const sumWidth = cols.sumWidth(sci, eci + 1);
|
||||
// const sumHeight = rows.sumHeight(sri, eri + 1);
|
||||
// console.log('sumWidth:', sumWidth);
|
||||
// draw.clearRect(0, 0, w, h);
|
||||
if (!settings.showGrid) {
|
||||
draw.restore();
|
||||
return;
|
||||
}
|
||||
// console.log('rowStart:', rowStart, ', rowLen:', rowLen);
|
||||
data.rowEach(sri, eri, (i, y, ch) => {
|
||||
// console.log('y:', y);
|
||||
if (i !== sri) draw.line([0, y], [w, y]);
|
||||
if (i === eri) draw.line([0, y + ch], [w, y + ch]);
|
||||
});
|
||||
data.colEach(sci, eci, (i, x, cw) => {
|
||||
if (i !== sci) draw.line([x, 0], [x, h]);
|
||||
if (i === eci) draw.line([x + cw, 0], [x + cw, h]);
|
||||
});
|
||||
draw.restore();
|
||||
}
|
||||
|
||||
function renderFreezeHighlightLine(fw, fh, ftw, fth) {
|
||||
const { draw, data } = this;
|
||||
const twidth = data.viewWidth() - fw;
|
||||
const theight = data.viewHeight() - fh;
|
||||
draw.save()
|
||||
.translate(fw, fh)
|
||||
.attr({ strokeStyle: 'rgba(75, 137, 255, .6)' });
|
||||
draw.line([0, fth], [twidth, fth]);
|
||||
draw.line([ftw, 0], [ftw, theight]);
|
||||
draw.restore();
|
||||
}
|
||||
|
||||
/** end */
|
||||
class Table {
|
||||
constructor(el, data) {
|
||||
this.el = el;
|
||||
this.draw = new Draw(el, data.viewWidth(), data.viewHeight());
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
resetData(data) {
|
||||
this.data = data;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
// resize canvas
|
||||
const { data } = this;
|
||||
const { rows, cols } = data;
|
||||
// fixed width of header
|
||||
const fw = cols.indexWidth;
|
||||
// fixed height of header
|
||||
const fh = rows.height;
|
||||
|
||||
this.draw.resize(data.viewWidth(), data.viewHeight());
|
||||
this.clear();
|
||||
|
||||
const viewRange = data.viewRange();
|
||||
// renderAll.call(this, viewRange, data.scroll);
|
||||
const tx = data.freezeTotalWidth();
|
||||
const ty = data.freezeTotalHeight();
|
||||
const { x, y } = data.scroll;
|
||||
// 1
|
||||
renderContentGrid.call(this, viewRange, fw, fh, tx, ty);
|
||||
renderContent.call(this, viewRange, fw, fh, -x, -y);
|
||||
renderFixedHeaders.call(this, 'all', viewRange, fw, fh, tx, ty);
|
||||
renderFixedLeftTopCell.call(this, fw, fh);
|
||||
const [fri, fci] = data.freeze;
|
||||
if (fri > 0 || fci > 0) {
|
||||
// 2
|
||||
if (fri > 0) {
|
||||
const vr = viewRange.clone();
|
||||
vr.sri = 0;
|
||||
vr.eri = fri - 1;
|
||||
vr.h = ty;
|
||||
renderContentGrid.call(this, vr, fw, fh, tx, 0);
|
||||
renderContent.call(this, vr, fw, fh, -x, 0);
|
||||
renderFixedHeaders.call(this, 'top', vr, fw, fh, tx, 0);
|
||||
}
|
||||
// 3
|
||||
if (fci > 0) {
|
||||
const vr = viewRange.clone();
|
||||
vr.sci = 0;
|
||||
vr.eci = fci - 1;
|
||||
vr.w = tx;
|
||||
renderContentGrid.call(this, vr, fw, fh, 0, ty);
|
||||
renderFixedHeaders.call(this, 'left', vr, fw, fh, 0, ty);
|
||||
renderContent.call(this, vr, fw, fh, 0, -y);
|
||||
}
|
||||
// 4
|
||||
const freezeViewRange = data.freezeViewRange();
|
||||
renderContentGrid.call(this, freezeViewRange, fw, fh, 0, 0);
|
||||
renderFixedHeaders.call(this, 'all', freezeViewRange, fw, fh, 0, 0);
|
||||
renderContent.call(this, freezeViewRange, fw, fh, 0, 0);
|
||||
// 5
|
||||
renderFreezeHighlightLine.call(this, fw, fh, tx, ty);
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.draw.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export default Table;
|
@ -0,0 +1,225 @@
|
||||
/* global window */
|
||||
import { h } from './element';
|
||||
import { bind } from './event';
|
||||
import tooltip from './tooltip';
|
||||
import DropdownFont from './dropdown_font';
|
||||
import DropdownFontSize from './dropdown_fontsize';
|
||||
import DropdownFormat from './dropdown_format';
|
||||
import DropdownFormula from './dropdown_formula';
|
||||
import DropdownColor from './dropdown_color';
|
||||
import DropdownAlign from './dropdown_align';
|
||||
import DropdownBorder from './dropdown_border';
|
||||
import Dropdown from './dropdown';
|
||||
import Icon from './icon';
|
||||
import { cssPrefix } from '../config';
|
||||
import { t } from '../locale/locale';
|
||||
|
||||
function buildIcon(name) {
|
||||
return new Icon(name);
|
||||
}
|
||||
|
||||
function buildButton(tooltipdata) {
|
||||
return h('div', `${cssPrefix}-toolbar-btn`)
|
||||
.on('mouseenter', (evt) => {
|
||||
tooltip(tooltipdata, evt.target);
|
||||
})
|
||||
.attr('data-tooltip', tooltipdata);
|
||||
}
|
||||
|
||||
function buildDivider() {
|
||||
return h('div', `${cssPrefix}-toolbar-divider`);
|
||||
}
|
||||
|
||||
function buildButtonWithIcon(tooltipdata, iconName, change = () => {}) {
|
||||
return buildButton(tooltipdata)
|
||||
.child(buildIcon(iconName))
|
||||
.on('click', () => change());
|
||||
}
|
||||
|
||||
function bindDropdownChange() {
|
||||
this.ddFormat.change = it => this.change('format', it.key);
|
||||
this.ddFont.change = it => this.change('font-name', it.key);
|
||||
this.ddFormula.change = it => this.change('formula', it.key);
|
||||
this.ddFontSize.change = it => this.change('font-size', it.pt);
|
||||
this.ddTextColor.change = it => this.change('color', it);
|
||||
this.ddFillColor.change = it => this.change('bgcolor', it);
|
||||
this.ddAlign.change = it => this.change('align', it);
|
||||
this.ddVAlign.change = it => this.change('valign', it);
|
||||
this.ddBorder.change = it => this.change('border', it);
|
||||
}
|
||||
|
||||
|
||||
function toggleChange(type) {
|
||||
let elName = type;
|
||||
const types = type.split('-');
|
||||
if (types.length > 1) {
|
||||
types.forEach((it, i) => {
|
||||
if (i === 0) elName = it;
|
||||
else elName += it[0].toUpperCase() + it.substring(1);
|
||||
});
|
||||
}
|
||||
const el = this[`${elName}El`];
|
||||
el.toggle();
|
||||
this.change(type, el.hasClass('active'));
|
||||
}
|
||||
|
||||
class DropdownMore extends Dropdown {
|
||||
constructor() {
|
||||
const icon = new Icon('ellipsis');
|
||||
const moreBtns = h('div', `${cssPrefix}-toolbar-more`);
|
||||
super(icon, 'auto', false, 'bottom-right', moreBtns);
|
||||
this.moreBtns = moreBtns;
|
||||
this.contentEl.css('max-width', '420px');
|
||||
}
|
||||
}
|
||||
|
||||
function initBtns2() {
|
||||
this.btns2 = this.btnChildren.map((it) => {
|
||||
const rect = it.box();
|
||||
const { marginLeft, marginRight } = it.computedStyle();
|
||||
return [it, rect.width + parseInt(marginLeft, 10) + parseInt(marginRight, 10)];
|
||||
});
|
||||
}
|
||||
|
||||
function moreResize() {
|
||||
const {
|
||||
el, btns, moreEl, ddMore, btns2,
|
||||
} = this;
|
||||
const { moreBtns, contentEl } = ddMore;
|
||||
el.css('width', `${this.widthFn() - 60}px`);
|
||||
const elBox = el.box();
|
||||
|
||||
let sumWidth = 160;
|
||||
let sumWidth2 = 12;
|
||||
const list1 = [];
|
||||
const list2 = [];
|
||||
btns2.forEach(([it, w], index) => {
|
||||
sumWidth += w;
|
||||
if (index === btns2.length - 1 || sumWidth < elBox.width) {
|
||||
list1.push(it);
|
||||
} else {
|
||||
sumWidth2 += w;
|
||||
list2.push(it);
|
||||
}
|
||||
});
|
||||
btns.html('').children(...list1);
|
||||
moreBtns.html('').children(...list2);
|
||||
contentEl.css('width', `${sumWidth2}px`);
|
||||
if (list2.length > 0) {
|
||||
moreEl.show();
|
||||
} else {
|
||||
moreEl.hide();
|
||||
}
|
||||
}
|
||||
|
||||
export default class Toolbar {
|
||||
constructor(data, widthFn, isHide = false) {
|
||||
this.data = data;
|
||||
this.change = () => {};
|
||||
this.widthFn = widthFn;
|
||||
const style = data.defaultStyle();
|
||||
// console.log('data:', data);
|
||||
this.ddFormat = new DropdownFormat();
|
||||
this.ddFont = new DropdownFont();
|
||||
this.ddFormula = new DropdownFormula();
|
||||
this.ddFontSize = new DropdownFontSize();
|
||||
this.ddTextColor = new DropdownColor('text-color', style.color);
|
||||
this.ddFillColor = new DropdownColor('fill-color', style.bgcolor);
|
||||
this.ddAlign = new DropdownAlign(['left', 'center', 'right'], style.align);
|
||||
this.ddVAlign = new DropdownAlign(['top', 'middle', 'bottom'], style.valign);
|
||||
this.ddBorder = new DropdownBorder();
|
||||
this.ddMore = new DropdownMore();
|
||||
this.btnChildren = [
|
||||
this.undoEl = buildButtonWithIcon(`${t('toolbar.undo')} (Ctrl+Z)`, 'undo', () => this.change('undo')),
|
||||
this.redoEl = buildButtonWithIcon(`${t('toolbar.undo')} (Ctrl+Y)`, 'redo', () => this.change('redo')),
|
||||
// this.printEl = buildButtonWithIcon('Print (Ctrl+P)', 'print', () => this.change('print')),
|
||||
this.paintformatEl = buildButtonWithIcon(`${t('toolbar.paintformat')}`, 'paintformat', () => toggleChange.call(this, 'paintformat')),
|
||||
this.clearformatEl = buildButtonWithIcon(`${t('toolbar.clearformat')}`, 'clearformat', () => this.change('clearformat')),
|
||||
buildDivider(),
|
||||
buildButton(`${t('toolbar.format')}`).child(this.ddFormat.el),
|
||||
buildDivider(),
|
||||
buildButton(`${t('toolbar.font')}`).child(this.ddFont.el),
|
||||
buildButton(`${t('toolbar.fontSize')}`).child(this.ddFontSize.el),
|
||||
buildDivider(),
|
||||
this.fontBoldEl = buildButtonWithIcon(`${t('toolbar.fontBold')} (Ctrl+B)`, 'bold', () => toggleChange.call(this, 'font-bold')),
|
||||
this.fontItalicEl = buildButtonWithIcon(`${t('toolbar.fontItalic')} (Ctrl+I)`, 'italic', () => toggleChange.call(this, 'font-italic')),
|
||||
this.underlineEl = buildButtonWithIcon(`${t('toolbar.underline')} (Ctrl+U)`, 'underline', () => toggleChange.call(this, 'underline')),
|
||||
this.strikeEl = buildButtonWithIcon(`${t('toolbar.strike')}`, 'strike', () => toggleChange.call(this, 'strike')),
|
||||
buildButton(`${t('toolbar.textColor')}`).child(this.ddTextColor.el),
|
||||
buildDivider(),
|
||||
buildButton(`${t('toolbar.fillColor')}`).child(this.ddFillColor.el),
|
||||
buildButton(`${t('toolbar.border')}`).child(this.ddBorder.el),
|
||||
this.mergeEl = buildButtonWithIcon(`${t('toolbar.merge')}`, 'merge', () => toggleChange.call(this, 'merge')),
|
||||
buildDivider(),
|
||||
buildButton(`${t('toolbar.align')}`).child(this.ddAlign.el),
|
||||
buildButton(`${t('toolbar.valign')}`).child(this.ddVAlign.el),
|
||||
this.textwrapEl = buildButtonWithIcon(`${t('toolbar.textwrap')}`, 'textwrap', () => toggleChange.call(this, 'textwrap')),
|
||||
buildDivider(),
|
||||
// this.linkEl = buildButtonWithIcon('Insert link', 'link'),
|
||||
// this.chartEl = buildButtonWithIcon('Insert chart', 'chart'),
|
||||
this.freezeEl = buildButtonWithIcon(`${t('toolbar.freeze')}`, 'freeze', () => toggleChange.call(this, 'freeze')),
|
||||
this.autofilterEl = buildButtonWithIcon(`${t('toolbar.autofilter')}`, 'autofilter', () => toggleChange.call(this, 'autofilter')),
|
||||
buildButton(`${t('toolbar.formula')}`).child(this.ddFormula.el),
|
||||
// buildDivider(),
|
||||
this.moreEl = buildButton(`${t('toolbar.more')}`).child(this.ddMore.el).hide(),
|
||||
];
|
||||
this.el = h('div', `${cssPrefix}-toolbar`);
|
||||
this.btns = h('div', `${cssPrefix}-toolbar-btns`).children(...this.btnChildren);
|
||||
this.el.child(this.btns);
|
||||
if (isHide) this.el.hide();
|
||||
bindDropdownChange.call(this);
|
||||
this.reset();
|
||||
setTimeout(() => {
|
||||
initBtns2.call(this);
|
||||
moreResize.call(this);
|
||||
}, 0);
|
||||
bind(window, 'resize', () => {
|
||||
moreResize.call(this);
|
||||
});
|
||||
}
|
||||
|
||||
paintformatActive() {
|
||||
return this.paintformatEl.hasClass('active');
|
||||
}
|
||||
|
||||
paintformatToggle() {
|
||||
this.paintformatEl.toggle();
|
||||
}
|
||||
|
||||
trigger(type) {
|
||||
toggleChange.call(this, type);
|
||||
}
|
||||
|
||||
reset() {
|
||||
const { data } = this;
|
||||
const style = data.getSelectedCellStyle();
|
||||
const cell = data.getSelectedCell();
|
||||
// console.log('canUndo:', data.canUndo());
|
||||
this.undoEl.disabled(!data.canUndo());
|
||||
this.redoEl.disabled(!data.canRedo());
|
||||
this.mergeEl.active(data.canUnmerge())
|
||||
.disabled(!data.selector.multiple());
|
||||
this.autofilterEl.active(!data.canAutofilter());
|
||||
// this.mergeEl.disabled();
|
||||
// console.log('selectedCell:', style, cell);
|
||||
const { font } = style;
|
||||
this.ddFont.setTitle(font.name);
|
||||
this.ddFontSize.setTitle(font.size);
|
||||
this.fontBoldEl.active(font.bold);
|
||||
this.fontItalicEl.active(font.italic);
|
||||
this.underlineEl.active(style.underline);
|
||||
this.strikeEl.active(style.strike);
|
||||
this.ddTextColor.setTitle(style.color);
|
||||
this.ddFillColor.setTitle(style.bgcolor);
|
||||
this.ddAlign.setTitle(style.align);
|
||||
this.ddVAlign.setTitle(style.valign);
|
||||
this.textwrapEl.active(style.textwrap);
|
||||
// console.log('freeze is Active:', data.freezeIsActive());
|
||||
this.freezeEl.active(data.freezeIsActive());
|
||||
if (cell) {
|
||||
if (cell.format) {
|
||||
this.ddFormat.setTitle(cell.format);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import DropdownItem from './dropdown_item';
|
||||
import DropdownAlign from '../dropdown_align';
|
||||
|
||||
export default class Align extends DropdownItem {
|
||||
constructor(value) {
|
||||
super('align', '', value);
|
||||
}
|
||||
|
||||
dropdown() {
|
||||
const { value } = this;
|
||||
return new DropdownAlign(['left', 'center', 'right'], value);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import ToggleItem from './toggle_item';
|
||||
|
||||
export default class Autofilter extends ToggleItem {
|
||||
constructor() {
|
||||
super('autofilter');
|
||||
}
|
||||
|
||||
setState() {}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import ToggleItem from './toggle_item';
|
||||
|
||||
export default class Bold extends ToggleItem {
|
||||
constructor() {
|
||||
super('font-bold', 'Ctrl+B');
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import DropdownItem from './dropdown_item';
|
||||
import DropdownBorder from '../dropdown_border';
|
||||
|
||||
export default class Border extends DropdownItem {
|
||||
constructor() {
|
||||
super('border');
|
||||
}
|
||||
|
||||
dropdown() {
|
||||
return new DropdownBorder();
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import IconItem from './icon_item';
|
||||
|
||||
export default class Clearformat extends IconItem {
|
||||
constructor() {
|
||||
super('clearformat');
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import Item from './item';
|
||||
|
||||
export default class DropdownItem extends Item {
|
||||
dropdown() {}
|
||||
|
||||
getValue(v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
element() {
|
||||
const { tag } = this;
|
||||
this.dd = this.dropdown();
|
||||
this.dd.change = it => this.change(tag, this.getValue(it));
|
||||
return super.element().child(
|
||||
this.dd,
|
||||
);
|
||||
}
|
||||
|
||||
setState(v) {
|
||||
if (v) {
|
||||
this.value = v;
|
||||
this.dd.setTitle(v);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import DropdownItem from './dropdown_item';
|
||||
import DropdownColor from '../dropdown_color';
|
||||
|
||||
export default class FillColor extends DropdownItem {
|
||||
constructor(color) {
|
||||
super('bgcolor', undefined, color);
|
||||
}
|
||||
|
||||
dropdown() {
|
||||
const { tag, value } = this;
|
||||
return new DropdownColor(tag, value);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import DropdownItem from './dropdown_item';
|
||||
import DropdownFont from '../dropdown_font';
|
||||
|
||||
export default class Font extends DropdownItem {
|
||||
constructor() {
|
||||
super('font-name');
|
||||
}
|
||||
|
||||
getValue(it) {
|
||||
return it.key;
|
||||
}
|
||||
|
||||
dropdown() {
|
||||
return new DropdownFont();
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import DropdownItem from './dropdown_item';
|
||||
import DropdownFontsize from '../dropdown_fontsize';
|
||||
|
||||
export default class Format extends DropdownItem {
|
||||
constructor() {
|
||||
super('font-size');
|
||||
}
|
||||
|
||||
getValue(it) {
|
||||
return it.pt;
|
||||
}
|
||||
|
||||
dropdown() {
|
||||
return new DropdownFontsize();
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import DropdownItem from './dropdown_item';
|
||||
import DropdownFormat from '../dropdown_format';
|
||||
|
||||
export default class Format extends DropdownItem {
|
||||
constructor() {
|
||||
super('format');
|
||||
}
|
||||
|
||||
getValue(it) {
|
||||
return it.key;
|
||||
}
|
||||
|
||||
dropdown() {
|
||||
return new DropdownFormat();
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import DropdownItem from './dropdown_item';
|
||||
import DropdownFormula from '../dropdown_formula';
|
||||
|
||||
export default class Format extends DropdownItem {
|
||||
constructor() {
|
||||
super('formula');
|
||||
}
|
||||
|
||||
getValue(it) {
|
||||
return it.key;
|
||||
}
|
||||
|
||||
dropdown() {
|
||||
return new DropdownFormula();
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import ToggleItem from './toggle_item';
|
||||
|
||||
export default class Freeze extends ToggleItem {
|
||||
constructor() {
|
||||
super('freeze');
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import Item from './item';
|
||||
import Icon from '../icon';
|
||||
|
||||
export default class IconItem extends Item {
|
||||
element() {
|
||||
return super.element()
|
||||
.child(new Icon(this.tag))
|
||||
.on('click', () => this.change(this.tag));
|
||||
}
|
||||
|
||||
setState(disabled) {
|
||||
this.el.disabled(disabled);
|
||||
}
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
/* global window */
|
||||
|
||||
import Align from './align';
|
||||
import Valign from './valign';
|
||||
import Autofilter from './autofilter';
|
||||
import Bold from './bold';
|
||||
import Italic from './italic';
|
||||
import Strike from './strike';
|
||||
import Underline from './underline';
|
||||
import Border from './border';
|
||||
import Clearformat from './clearformat';
|
||||
import Paintformat from './paintformat';
|
||||
import TextColor from './text_color';
|
||||
import FillColor from './fill_color';
|
||||
import FontSize from './font_size';
|
||||
import Font from './font';
|
||||
import Format from './format';
|
||||
import Formula from './formula';
|
||||
import Freeze from './freeze';
|
||||
import Merge from './merge';
|
||||
import Redo from './redo';
|
||||
import Undo from './undo';
|
||||
import Print from './print';
|
||||
import Textwrap from './textwrap';
|
||||
import More from './more';
|
||||
import Item from './item';
|
||||
|
||||
import { h } from '../element';
|
||||
import { cssPrefix } from '../../config';
|
||||
import { bind } from '../event';
|
||||
|
||||
function buildDivider() {
|
||||
return h('div', `${cssPrefix}-toolbar-divider`);
|
||||
}
|
||||
|
||||
function initBtns2() {
|
||||
this.btns2 = [];
|
||||
this.items.forEach((it) => {
|
||||
if (Array.isArray(it)) {
|
||||
it.forEach(({ el }) => {
|
||||
const rect = el.box();
|
||||
const { marginLeft, marginRight } = el.computedStyle();
|
||||
this.btns2.push([el, rect.width + parseInt(marginLeft, 10) + parseInt(marginRight, 10)]);
|
||||
});
|
||||
} else {
|
||||
const rect = it.box();
|
||||
const { marginLeft, marginRight } = it.computedStyle();
|
||||
this.btns2.push([it, rect.width + parseInt(marginLeft, 10) + parseInt(marginRight, 10)]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function moreResize() {
|
||||
const {
|
||||
el, btns, moreEl, btns2,
|
||||
} = this;
|
||||
const { moreBtns, contentEl } = moreEl.dd;
|
||||
el.css('width', `${this.widthFn()}px`);
|
||||
const elBox = el.box();
|
||||
|
||||
let sumWidth = 160;
|
||||
let sumWidth2 = 12;
|
||||
const list1 = [];
|
||||
const list2 = [];
|
||||
btns2.forEach(([it, w], index) => {
|
||||
sumWidth += w;
|
||||
if (index === btns2.length - 1 || sumWidth < elBox.width) {
|
||||
list1.push(it);
|
||||
} else {
|
||||
sumWidth2 += w;
|
||||
list2.push(it);
|
||||
}
|
||||
});
|
||||
btns.html('').children(...list1);
|
||||
moreBtns.html('').children(...list2);
|
||||
contentEl.css('width', `${sumWidth2}px`);
|
||||
if (list2.length > 0) {
|
||||
moreEl.show();
|
||||
} else {
|
||||
moreEl.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function genBtn(it) {
|
||||
const btn = new Item();
|
||||
btn.el.on('click', () => {
|
||||
if (it.onClick) it.onClick(this.data.getData(), this.data);
|
||||
});
|
||||
btn.tip = it.tip || '';
|
||||
|
||||
let { el } = it;
|
||||
|
||||
if (it.icon) {
|
||||
el = h('img').attr('src', it.icon);
|
||||
}
|
||||
|
||||
if (el) {
|
||||
const icon = h('div', `${cssPrefix}-icon`);
|
||||
icon.child(el);
|
||||
btn.el.child(icon);
|
||||
}
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
export default class Toolbar {
|
||||
constructor(data, widthFn, isHide = false) {
|
||||
this.data = data;
|
||||
this.change = () => {};
|
||||
this.widthFn = widthFn;
|
||||
this.isHide = isHide;
|
||||
const style = data.defaultStyle();
|
||||
this.items = [
|
||||
[
|
||||
this.undoEl = new Undo(),
|
||||
this.redoEl = new Redo(),
|
||||
new Print(),
|
||||
this.paintformatEl = new Paintformat(),
|
||||
this.clearformatEl = new Clearformat(),
|
||||
],
|
||||
buildDivider(),
|
||||
[
|
||||
this.formatEl = new Format(),
|
||||
],
|
||||
buildDivider(),
|
||||
[
|
||||
this.fontEl = new Font(),
|
||||
this.fontSizeEl = new FontSize(),
|
||||
],
|
||||
buildDivider(),
|
||||
[
|
||||
this.boldEl = new Bold(),
|
||||
this.italicEl = new Italic(),
|
||||
this.underlineEl = new Underline(),
|
||||
this.strikeEl = new Strike(),
|
||||
this.textColorEl = new TextColor(style.color),
|
||||
],
|
||||
buildDivider(),
|
||||
[
|
||||
this.fillColorEl = new FillColor(style.bgcolor),
|
||||
this.borderEl = new Border(),
|
||||
this.mergeEl = new Merge(),
|
||||
],
|
||||
buildDivider(),
|
||||
[
|
||||
this.alignEl = new Align(style.align),
|
||||
this.valignEl = new Valign(style.valign),
|
||||
this.textwrapEl = new Textwrap(),
|
||||
],
|
||||
buildDivider(),
|
||||
[
|
||||
this.freezeEl = new Freeze(),
|
||||
this.autofilterEl = new Autofilter(),
|
||||
this.formulaEl = new Formula(),
|
||||
],
|
||||
];
|
||||
|
||||
const { extendToolbar = {} } = data.settings;
|
||||
|
||||
if (extendToolbar.left && extendToolbar.left.length > 0) {
|
||||
this.items.unshift(buildDivider());
|
||||
const btns = extendToolbar.left.map(genBtn.bind(this));
|
||||
|
||||
this.items.unshift(btns);
|
||||
}
|
||||
if (extendToolbar.right && extendToolbar.right.length > 0) {
|
||||
this.items.push(buildDivider());
|
||||
const btns = extendToolbar.right.map(genBtn.bind(this));
|
||||
this.items.push(btns);
|
||||
}
|
||||
|
||||
this.items.push([this.moreEl = new More()]);
|
||||
|
||||
this.el = h('div', `${cssPrefix}-toolbar`);
|
||||
this.btns = h('div', `${cssPrefix}-toolbar-btns`);
|
||||
|
||||
this.items.forEach((it) => {
|
||||
if (Array.isArray(it)) {
|
||||
it.forEach((i) => {
|
||||
this.btns.child(i.el);
|
||||
i.change = (...args) => {
|
||||
this.change(...args);
|
||||
};
|
||||
});
|
||||
} else {
|
||||
this.btns.child(it.el);
|
||||
}
|
||||
});
|
||||
|
||||
this.el.child(this.btns);
|
||||
if (isHide) {
|
||||
this.el.hide();
|
||||
} else {
|
||||
this.reset();
|
||||
setTimeout(() => {
|
||||
initBtns2.call(this);
|
||||
moreResize.call(this);
|
||||
}, 0);
|
||||
bind(window, 'resize', () => {
|
||||
moreResize.call(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
paintformatActive() {
|
||||
return this.paintformatEl.active();
|
||||
}
|
||||
|
||||
paintformatToggle() {
|
||||
this.paintformatEl.toggle();
|
||||
}
|
||||
|
||||
trigger(type) {
|
||||
this[`${type}El`].click();
|
||||
}
|
||||
|
||||
resetData(data) {
|
||||
this.data = data;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this.isHide) return;
|
||||
const { data } = this;
|
||||
const style = data.getSelectedCellStyle();
|
||||
// console.log('canUndo:', data.canUndo());
|
||||
this.undoEl.setState(!data.canUndo());
|
||||
this.redoEl.setState(!data.canRedo());
|
||||
this.mergeEl.setState(data.canUnmerge(), !data.selector.multiple());
|
||||
this.autofilterEl.setState(!data.canAutofilter());
|
||||
// this.mergeEl.disabled();
|
||||
// console.log('selectedCell:', style, cell);
|
||||
const { font, format } = style;
|
||||
this.formatEl.setState(format);
|
||||
this.fontEl.setState(font.name);
|
||||
this.fontSizeEl.setState(font.size);
|
||||
this.boldEl.setState(font.bold);
|
||||
this.italicEl.setState(font.italic);
|
||||
this.underlineEl.setState(style.underline);
|
||||
this.strikeEl.setState(style.strike);
|
||||
this.textColorEl.setState(style.color);
|
||||
this.fillColorEl.setState(style.bgcolor);
|
||||
this.alignEl.setState(style.align);
|
||||
this.valignEl.setState(style.valign);
|
||||
this.textwrapEl.setState(style.textwrap);
|
||||
// console.log('freeze is Active:', data.freezeIsActive());
|
||||
this.freezeEl.setState(data.freezeIsActive());
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import ToggleItem from './toggle_item';
|
||||
|
||||
export default class Italic extends ToggleItem {
|
||||
constructor() {
|
||||
super('font-italic', 'Ctrl+I');
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import { cssPrefix } from '../../config';
|
||||
import tooltip from '../tooltip';
|
||||
import { h } from '../element';
|
||||
import { t } from '../../locale/locale';
|
||||
|
||||
export default class Item {
|
||||
// tooltip
|
||||
// tag: the subclass type
|
||||
// shortcut: shortcut key
|
||||
constructor(tag, shortcut, value) {
|
||||
this.tip = '';
|
||||
if (tag) this.tip = t(`toolbar.${tag.replace(/-[a-z]/g, c => c[1].toUpperCase())}`);
|
||||
if (shortcut) this.tip += ` (${shortcut})`;
|
||||
this.tag = tag;
|
||||
this.shortcut = shortcut;
|
||||
this.value = value;
|
||||
this.el = this.element();
|
||||
this.change = () => {};
|
||||
}
|
||||
|
||||
element() {
|
||||
const { tip } = this;
|
||||
return h('div', `${cssPrefix}-toolbar-btn`)
|
||||
.on('mouseenter', (evt) => {
|
||||
if (this.tip) tooltip(this.tip, evt.target);
|
||||
})
|
||||
.attr('data-tooltip', tip);
|
||||
}
|
||||
|
||||
setState() {}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import ToggleItem from './toggle_item';
|
||||
|
||||
export default class Merge extends ToggleItem {
|
||||
constructor() {
|
||||
super('merge');
|
||||
}
|
||||
|
||||
setState(active, disabled) {
|
||||
this.el.active(active).disabled(disabled);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import Dropdown from '../dropdown';
|
||||
import DropdownItem from './dropdown_item';
|
||||
|
||||
import { cssPrefix } from '../../config';
|
||||
import { h } from '../element';
|
||||
import Icon from '../icon';
|
||||
|
||||
class DropdownMore extends Dropdown {
|
||||
constructor() {
|
||||
const icon = new Icon('ellipsis');
|
||||
const moreBtns = h('div', `${cssPrefix}-toolbar-more`);
|
||||
super(icon, 'auto', false, 'bottom-right', moreBtns);
|
||||
this.moreBtns = moreBtns;
|
||||
this.contentEl.css('max-width', '420px');
|
||||
}
|
||||
}
|
||||
|
||||
export default class More extends DropdownItem {
|
||||
constructor() {
|
||||
super('more');
|
||||
this.el.hide();
|
||||
}
|
||||
|
||||
dropdown() {
|
||||
return new DropdownMore();
|
||||
}
|
||||
|
||||
show() {
|
||||
this.el.show();
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.el.hide();
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import ToggleItem from './toggle_item';
|
||||
|
||||
export default class Paintformat extends ToggleItem {
|
||||
constructor() {
|
||||
super('paintformat');
|
||||
}
|
||||
|
||||
setState() {}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import IconItem from './icon_item';
|
||||
|
||||
export default class Print extends IconItem {
|
||||
constructor() {
|
||||
super('print', 'Ctrl+P');
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import IconItem from './icon_item';
|
||||
|
||||
export default class Redo extends IconItem {
|
||||
constructor() {
|
||||
super('redo', 'Ctrl+Y');
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import ToggleItem from './toggle_item';
|
||||
|
||||
export default class Strike extends ToggleItem {
|
||||
constructor() {
|
||||
super('strike', 'Ctrl+U');
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import DropdownItem from './dropdown_item';
|
||||
import DropdownColor from '../dropdown_color';
|
||||
|
||||
export default class TextColor extends DropdownItem {
|
||||
constructor(color) {
|
||||
super('color', undefined, color);
|
||||
}
|
||||
|
||||
dropdown() {
|
||||
const { tag, value } = this;
|
||||
return new DropdownColor(tag, value);
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import ToggleItem from './toggle_item';
|
||||
|
||||
export default class Textwrap extends ToggleItem {
|
||||
constructor() {
|
||||
super('textwrap');
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import Item from './item';
|
||||
import Icon from '../icon';
|
||||
|
||||
export default class ToggleItem extends Item {
|
||||
element() {
|
||||
const { tag } = this;
|
||||
return super.element()
|
||||
.child(new Icon(tag))
|
||||
.on('click', () => this.click());
|
||||
}
|
||||
|
||||
click() {
|
||||
this.change(this.tag, this.toggle());
|
||||
}
|
||||
|
||||
setState(active) {
|
||||
this.el.active(active);
|
||||
}
|
||||
|
||||
toggle() {
|
||||
return this.el.toggle();
|
||||
}
|
||||
|
||||
active() {
|
||||
return this.el.hasClass('active');
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import ToggleItem from './toggle_item';
|
||||
|
||||
export default class Underline extends ToggleItem {
|
||||
constructor() {
|
||||
super('underline', 'Ctrl+U');
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import IconItem from './icon_item';
|
||||
|
||||
export default class Undo extends IconItem {
|
||||
constructor() {
|
||||
super('undo', 'Ctrl+Z');
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import DropdownItem from './dropdown_item';
|
||||
import DropdownAlign from '../dropdown_align';
|
||||
|
||||
export default class Valign extends DropdownItem {
|
||||
constructor(value) {
|
||||
super('valign', '', value);
|
||||
}
|
||||
|
||||
dropdown() {
|
||||
const { value } = this;
|
||||
return new DropdownAlign(['top', 'middle', 'bottom'], value);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/* global document */
|
||||
import { h } from './element';
|
||||
import { bind } from './event';
|
||||
import { cssPrefix } from '../config';
|
||||
|
||||
export default function tooltip(html, target) {
|
||||
if (target.classList.contains('active')) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
left, top, width, height,
|
||||
} = target.getBoundingClientRect();
|
||||
const el = h('div', `${cssPrefix}-tooltip`).html(html).show();
|
||||
document.body.appendChild(el.el);
|
||||
const elBox = el.box();
|
||||
// console.log('elBox:', elBox);
|
||||
el.css('left', `${left + (width / 2) - (elBox.width / 2)}px`)
|
||||
.css('top', `${top + height + 2}px`);
|
||||
|
||||
bind(target, 'mouseleave', () => {
|
||||
if (document.body.contains(el.el)) {
|
||||
document.body.removeChild(el.el);
|
||||
}
|
||||
});
|
||||
|
||||
bind(target, 'click', () => {
|
||||
if (document.body.contains(el.el)) {
|
||||
document.body.removeChild(el.el);
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
/* global window */
|
||||
export const cssPrefix = 'x-spreadsheet';
|
||||
export const dpr = window.devicePixelRatio || 1;
|
||||
export default {
|
||||
cssPrefix,
|
||||
dpr,
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
// font.js
|
||||
/**
|
||||
* @typedef {number} fontsizePX px for fontSize
|
||||
*/
|
||||
/**
|
||||
* @typedef {number} fontsizePT pt for fontSize
|
||||
*/
|
||||
/**
|
||||
* @typedef {object} BaseFont
|
||||
* @property {string} key inner key
|
||||
* @property {string} title title for display
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} FontSize
|
||||
* @property {fontsizePT} pt
|
||||
* @property {fontsizePX} px
|
||||
*/
|
||||
|
||||
// alphabet.js
|
||||
/**
|
||||
* @typedef {string} tagA1 A1 tag for XY-tag (0, 0)
|
||||
* @example "A1"
|
||||
*/
|
||||
/**
|
||||
* @typedef {[number, number]} tagXY
|
||||
* @example [0, 0]
|
||||
*/
|
@ -0,0 +1,91 @@
|
||||
import './_.prototypes';
|
||||
|
||||
const alphabets = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
|
||||
|
||||
/** index number 2 letters
|
||||
* @example stringAt(26) ==> 'AA'
|
||||
* @date 2019-10-10
|
||||
* @export
|
||||
* @param {number} index
|
||||
* @returns {string}
|
||||
*/
|
||||
export function stringAt(index) {
|
||||
let str = '';
|
||||
let cindex = index;
|
||||
while (cindex >= alphabets.length) {
|
||||
cindex /= alphabets.length;
|
||||
cindex -= 1;
|
||||
str += alphabets[parseInt(cindex, 10) % alphabets.length];
|
||||
}
|
||||
const last = index % alphabets.length;
|
||||
str += alphabets[last];
|
||||
return str;
|
||||
}
|
||||
|
||||
/** translate letter in A1-tag to number
|
||||
* @date 2019-10-10
|
||||
* @export
|
||||
* @param {string} str "AA" in A1-tag "AA1"
|
||||
* @returns {number}
|
||||
*/
|
||||
export function indexAt(str) {
|
||||
let ret = 0;
|
||||
for(let i = 0; i !== str.length; ++i) ret = 26 * ret + str.charCodeAt(i) - 64;
|
||||
return ret - 1;
|
||||
}
|
||||
|
||||
|
||||
// B10 => x,y
|
||||
/** translate A1-tag to XY-tag
|
||||
* @date 2019-10-10
|
||||
* @export
|
||||
* @param {tagA1} src
|
||||
* @returns {tagXY}
|
||||
*/
|
||||
export function expr2xy(src) {
|
||||
let x = '';
|
||||
let y = '';
|
||||
for (let i = 0; i < src.length; i += 1) {
|
||||
if (src.charAt(i) >= '0' && src.charAt(i) <= '9') {
|
||||
y += src.charAt(i);
|
||||
} else {
|
||||
x += src.charAt(i);
|
||||
}
|
||||
}
|
||||
return [indexAt(x), parseInt(y, 10) - 1];
|
||||
}
|
||||
|
||||
/** translate XY-tag to A1-tag
|
||||
* @example x,y => B10
|
||||
* @date 2019-10-10
|
||||
* @export
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @returns {tagA1}
|
||||
*/
|
||||
export function xy2expr(x, y) {
|
||||
return `${stringAt(x)}${y + 1}`;
|
||||
}
|
||||
|
||||
/** translate A1-tag src by (xn, yn)
|
||||
* @date 2019-10-10
|
||||
* @export
|
||||
* @param {tagA1} src
|
||||
* @param {number} xn
|
||||
* @param {number} yn
|
||||
* @returns {tagA1}
|
||||
*/
|
||||
export function expr2expr(src, xn, yn, condition = () => true) {
|
||||
if (xn === 0 && yn === 0) return src;
|
||||
const [x, y] = expr2xy(src);
|
||||
if (!condition(x, y)) return src;
|
||||
return xy2expr(x + xn, y + yn);
|
||||
}
|
||||
|
||||
export default {
|
||||
stringAt,
|
||||
indexAt,
|
||||
expr2xy,
|
||||
xy2expr,
|
||||
expr2expr,
|
||||
};
|
@ -0,0 +1,183 @@
|
||||
import { CellRange } from './cell_range';
|
||||
// operator: all|eq|neq|gt|gte|lt|lte|in|be
|
||||
// value:
|
||||
// in => []
|
||||
// be => [min, max]
|
||||
class Filter {
|
||||
constructor(ci, operator, value) {
|
||||
this.ci = ci;
|
||||
this.operator = operator;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
set(operator, value) {
|
||||
this.operator = operator;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
includes(v) {
|
||||
const { operator, value } = this;
|
||||
if (operator === 'all') {
|
||||
return true;
|
||||
}
|
||||
if (operator === 'in') {
|
||||
return value.includes(v);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
vlength() {
|
||||
const { operator, value } = this;
|
||||
if (operator === 'in') {
|
||||
return value.length;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
getData() {
|
||||
const { ci, operator, value } = this;
|
||||
return { ci, operator, value };
|
||||
}
|
||||
}
|
||||
|
||||
class Sort {
|
||||
constructor(ci, order) {
|
||||
this.ci = ci;
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
asc() {
|
||||
return this.order === 'asc';
|
||||
}
|
||||
|
||||
desc() {
|
||||
return this.order === 'desc';
|
||||
}
|
||||
}
|
||||
|
||||
export default class AutoFilter {
|
||||
constructor() {
|
||||
this.ref = null;
|
||||
this.filters = [];
|
||||
this.sort = null;
|
||||
}
|
||||
|
||||
setData({ ref, filters, sort }) {
|
||||
if (ref != null) {
|
||||
this.ref = ref;
|
||||
this.filters = filters.map(it => new Filter(it.ci, it.operator, it.value));
|
||||
if (sort) {
|
||||
this.sort = new Sort(sort.ci, sort.order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getData() {
|
||||
if (this.active()) {
|
||||
const { ref, filters, sort } = this;
|
||||
return { ref, filters: filters.map(it => it.getData()), sort };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
addFilter(ci, operator, value) {
|
||||
const filter = this.getFilter(ci);
|
||||
if (filter == null) {
|
||||
this.filters.push(new Filter(ci, operator, value));
|
||||
} else {
|
||||
filter.set(operator, value);
|
||||
}
|
||||
}
|
||||
|
||||
setSort(ci, order) {
|
||||
this.sort = order ? new Sort(ci, order) : null;
|
||||
}
|
||||
|
||||
includes(ri, ci) {
|
||||
if (this.active()) {
|
||||
return this.hrange().includes(ri, ci);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getSort(ci) {
|
||||
const { sort } = this;
|
||||
if (sort && sort.ci === ci) {
|
||||
return sort;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getFilter(ci) {
|
||||
const { filters } = this;
|
||||
for (let i = 0; i < filters.length; i += 1) {
|
||||
if (filters[i].ci === ci) {
|
||||
return filters[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
filteredRows(getCell) {
|
||||
// const ary = [];
|
||||
// let lastri = 0;
|
||||
const rset = new Set();
|
||||
const fset = new Set();
|
||||
if (this.active()) {
|
||||
const { sri, eri } = this.range();
|
||||
const { filters } = this;
|
||||
for (let ri = sri + 1; ri <= eri; ri += 1) {
|
||||
for (let i = 0; i < filters.length; i += 1) {
|
||||
const filter = filters[i];
|
||||
const cell = getCell(ri, filter.ci);
|
||||
const ctext = cell ? cell.text : '';
|
||||
if (!filter.includes(ctext)) {
|
||||
rset.add(ri);
|
||||
break;
|
||||
} else {
|
||||
fset.add(ri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { rset, fset };
|
||||
}
|
||||
|
||||
items(ci, getCell) {
|
||||
const m = {};
|
||||
if (this.active()) {
|
||||
const { sri, eri } = this.range();
|
||||
for (let ri = sri + 1; ri <= eri; ri += 1) {
|
||||
const cell = getCell(ri, ci);
|
||||
if (cell !== null && !/^\s*$/.test(cell.text)) {
|
||||
const key = cell.text;
|
||||
const cnt = (m[key] || 0) + 1;
|
||||
m[key] = cnt;
|
||||
} else {
|
||||
m[''] = (m[''] || 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
range() {
|
||||
return CellRange.valueOf(this.ref);
|
||||
}
|
||||
|
||||
hrange() {
|
||||
const r = this.range();
|
||||
r.eri = r.sri;
|
||||
return r;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.ref = null;
|
||||
this.filters = [];
|
||||
this.sort = null;
|
||||
}
|
||||
|
||||
active() {
|
||||
return this.ref !== null;
|
||||
}
|
||||
}
|
@ -0,0 +1,226 @@
|
||||
import { expr2xy, xy2expr } from './alphabet';
|
||||
import { numberCalc } from './helper';
|
||||
|
||||
// Converting infix expression to a suffix expression
|
||||
// src: AVERAGE(SUM(A1,A2), B1) + 50 + B20
|
||||
// return: [A1, A2], SUM[, B1],AVERAGE,50,+,B20,+
|
||||
const infixExprToSuffixExpr = (src) => {
|
||||
const operatorStack = [];
|
||||
const stack = [];
|
||||
let subStrs = []; // SUM, A1, B2, 50 ...
|
||||
let fnArgType = 0; // 1 => , 2 => :
|
||||
let fnArgOperator = '';
|
||||
let fnArgsLen = 1; // A1,A2,A3...
|
||||
let oldc = '';
|
||||
for (let i = 0; i < src.length; i += 1) {
|
||||
const c = src.charAt(i);
|
||||
if (c !== ' ') {
|
||||
if (c >= 'a' && c <= 'z') {
|
||||
subStrs.push(c.toUpperCase());
|
||||
} else if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || c === '.') {
|
||||
subStrs.push(c);
|
||||
} else if (c === '"') {
|
||||
i += 1;
|
||||
while (src.charAt(i) !== '"') {
|
||||
subStrs.push(src.charAt(i));
|
||||
i += 1;
|
||||
}
|
||||
stack.push(`"${subStrs.join('')}`);
|
||||
subStrs = [];
|
||||
} else if (c === '-' && /[+\-*/,(]/.test(oldc)) {
|
||||
subStrs.push(c);
|
||||
} else {
|
||||
// console.log('subStrs:', subStrs.join(''), stack);
|
||||
if (c !== '(' && subStrs.length > 0) {
|
||||
stack.push(subStrs.join(''));
|
||||
}
|
||||
if (c === ')') {
|
||||
let c1 = operatorStack.pop();
|
||||
if (fnArgType === 2) {
|
||||
// fn argument range => A1:B5
|
||||
try {
|
||||
const [ex, ey] = expr2xy(stack.pop());
|
||||
const [sx, sy] = expr2xy(stack.pop());
|
||||
// console.log('::', sx, sy, ex, ey);
|
||||
let rangelen = 0;
|
||||
for (let x = sx; x <= ex; x += 1) {
|
||||
for (let y = sy; y <= ey; y += 1) {
|
||||
stack.push(xy2expr(x, y));
|
||||
rangelen += 1;
|
||||
}
|
||||
}
|
||||
stack.push([c1, rangelen]);
|
||||
} catch (e) {
|
||||
// console.log(e);
|
||||
}
|
||||
} else if (fnArgType === 1 || fnArgType === 3) {
|
||||
if (fnArgType === 3) stack.push(fnArgOperator);
|
||||
// fn argument => A1,A2,B5
|
||||
stack.push([c1, fnArgsLen]);
|
||||
fnArgsLen = 1;
|
||||
} else {
|
||||
// console.log('c1:', c1, fnArgType, stack, operatorStack);
|
||||
while (c1 !== '(') {
|
||||
stack.push(c1);
|
||||
if (operatorStack.length <= 0) break;
|
||||
c1 = operatorStack.pop();
|
||||
}
|
||||
}
|
||||
fnArgType = 0;
|
||||
} else if (c === '=' || c === '>' || c === '<') {
|
||||
const nc = src.charAt(i + 1);
|
||||
fnArgOperator = c;
|
||||
if (nc === '=' || nc === '-') {
|
||||
fnArgOperator += nc;
|
||||
i += 1;
|
||||
}
|
||||
fnArgType = 3;
|
||||
} else if (c === ':') {
|
||||
fnArgType = 2;
|
||||
} else if (c === ',') {
|
||||
if (fnArgType === 3) {
|
||||
stack.push(fnArgOperator);
|
||||
}
|
||||
fnArgType = 1;
|
||||
fnArgsLen += 1;
|
||||
} else if (c === '(' && subStrs.length > 0) {
|
||||
// function
|
||||
operatorStack.push(subStrs.join(''));
|
||||
} else {
|
||||
// priority: */ > +-
|
||||
// console.log('xxxx:', operatorStack, c, stack);
|
||||
if (operatorStack.length > 0 && (c === '+' || c === '-')) {
|
||||
let top = operatorStack[operatorStack.length - 1];
|
||||
if (top !== '(') stack.push(operatorStack.pop());
|
||||
if (top === '*' || top === '/') {
|
||||
while (operatorStack.length > 0) {
|
||||
top = operatorStack[operatorStack.length - 1];
|
||||
if (top !== '(') stack.push(operatorStack.pop());
|
||||
else break;
|
||||
}
|
||||
}
|
||||
} else if (operatorStack.length > 0) {
|
||||
const top = operatorStack[operatorStack.length - 1];
|
||||
if (top === '*' || top === '/') stack.push(operatorStack.pop());
|
||||
}
|
||||
operatorStack.push(c);
|
||||
}
|
||||
subStrs = [];
|
||||
}
|
||||
oldc = c;
|
||||
}
|
||||
}
|
||||
if (subStrs.length > 0) {
|
||||
stack.push(subStrs.join(''));
|
||||
}
|
||||
while (operatorStack.length > 0) {
|
||||
stack.push(operatorStack.pop());
|
||||
}
|
||||
return stack;
|
||||
};
|
||||
|
||||
const evalSubExpr = (subExpr, cellRender) => {
|
||||
const [fl] = subExpr;
|
||||
let expr = subExpr;
|
||||
if (fl === '"') {
|
||||
return subExpr.substring(1);
|
||||
}
|
||||
let ret = 1;
|
||||
if (fl === '-') {
|
||||
expr = subExpr.substring(1);
|
||||
ret = -1;
|
||||
}
|
||||
if (expr[0] >= '0' && expr[0] <= '9') {
|
||||
return ret * Number(expr);
|
||||
}
|
||||
const [x, y] = expr2xy(expr);
|
||||
return ret * cellRender(x, y);
|
||||
};
|
||||
|
||||
// evaluate the suffix expression
|
||||
// srcStack: <= infixExprToSufixExpr
|
||||
// formulaMap: {'SUM': {}, ...}
|
||||
// cellRender: (x, y) => {}
|
||||
const evalSuffixExpr = (srcStack, formulaMap, cellRender, cellList) => {
|
||||
const stack = [];
|
||||
// console.log(':::::formulaMap:', formulaMap);
|
||||
for (let i = 0; i < srcStack.length; i += 1) {
|
||||
// console.log(':::>>>', srcStack[i]);
|
||||
const expr = srcStack[i];
|
||||
const fc = expr[0];
|
||||
if (expr === '+') {
|
||||
const top = stack.pop();
|
||||
stack.push(numberCalc('+', stack.pop(), top));
|
||||
} else if (expr === '-') {
|
||||
if (stack.length === 1) {
|
||||
const top = stack.pop();
|
||||
stack.push(numberCalc('*', top, -1));
|
||||
} else {
|
||||
const top = stack.pop();
|
||||
stack.push(numberCalc('-', stack.pop(), top));
|
||||
}
|
||||
} else if (expr === '*') {
|
||||
stack.push(numberCalc('*', stack.pop(), stack.pop()));
|
||||
} else if (expr === '/') {
|
||||
const top = stack.pop();
|
||||
stack.push(numberCalc('/', stack.pop(), top));
|
||||
} else if (fc === '=' || fc === '>' || fc === '<') {
|
||||
let top = stack.pop();
|
||||
if (!Number.isNaN(top)) top = Number(top);
|
||||
let left = stack.pop();
|
||||
if (!Number.isNaN(left)) left = Number(left);
|
||||
let ret = false;
|
||||
if (fc === '=') {
|
||||
ret = (left === top);
|
||||
} else if (expr === '>') {
|
||||
ret = (left > top);
|
||||
} else if (expr === '>=') {
|
||||
ret = (left >= top);
|
||||
} else if (expr === '<') {
|
||||
ret = (left < top);
|
||||
} else if (expr === '<=') {
|
||||
ret = (left <= top);
|
||||
}
|
||||
stack.push(ret);
|
||||
} else if (Array.isArray(expr)) {
|
||||
const [formula, len] = expr;
|
||||
const params = [];
|
||||
for (let j = 0; j < len; j += 1) {
|
||||
params.push(stack.pop());
|
||||
}
|
||||
stack.push(formulaMap[formula].render(params.reverse()));
|
||||
} else {
|
||||
if (cellList.includes(expr)) {
|
||||
return 0;
|
||||
}
|
||||
if ((fc >= 'a' && fc <= 'z') || (fc >= 'A' && fc <= 'Z')) {
|
||||
cellList.push(expr);
|
||||
}
|
||||
stack.push(evalSubExpr(expr, cellRender));
|
||||
cellList.pop();
|
||||
}
|
||||
// console.log('stack:', stack);
|
||||
}
|
||||
return stack[0];
|
||||
};
|
||||
|
||||
const cellRender = (src, formulaMap, getCellText, cellList = []) => {
|
||||
if (src[0] === '=') {
|
||||
const stack = infixExprToSuffixExpr(src.substring(1));
|
||||
if (stack.length <= 0) return src;
|
||||
return evalSuffixExpr(
|
||||
stack,
|
||||
formulaMap,
|
||||
(x, y) => cellRender(getCellText(x, y), formulaMap, getCellText, cellList),
|
||||
cellList,
|
||||
);
|
||||
}
|
||||
return src;
|
||||
};
|
||||
|
||||
export default {
|
||||
render: cellRender,
|
||||
};
|
||||
export {
|
||||
infixExprToSuffixExpr,
|
||||
};
|
@ -0,0 +1,224 @@
|
||||
import { xy2expr, expr2xy } from './alphabet';
|
||||
|
||||
class CellRange {
|
||||
constructor(sri, sci, eri, eci, w = 0, h = 0) {
|
||||
this.sri = sri;
|
||||
this.sci = sci;
|
||||
this.eri = eri;
|
||||
this.eci = eci;
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
}
|
||||
|
||||
set(sri, sci, eri, eci) {
|
||||
this.sri = sri;
|
||||
this.sci = sci;
|
||||
this.eri = eri;
|
||||
this.eci = eci;
|
||||
}
|
||||
|
||||
multiple() {
|
||||
return this.eri - this.sri > 0 || this.eci - this.sci > 0;
|
||||
}
|
||||
|
||||
// cell-index: ri, ci
|
||||
// cell-ref: A10
|
||||
includes(...args) {
|
||||
let [ri, ci] = [0, 0];
|
||||
if (args.length === 1) {
|
||||
[ci, ri] = expr2xy(args[0]);
|
||||
} else if (args.length === 2) {
|
||||
[ri, ci] = args;
|
||||
}
|
||||
const {
|
||||
sri, sci, eri, eci,
|
||||
} = this;
|
||||
return sri <= ri && ri <= eri && sci <= ci && ci <= eci;
|
||||
}
|
||||
|
||||
each(cb, rowFilter = () => true) {
|
||||
const {
|
||||
sri, sci, eri, eci,
|
||||
} = this;
|
||||
for (let i = sri; i <= eri; i += 1) {
|
||||
if (rowFilter(i)) {
|
||||
for (let j = sci; j <= eci; j += 1) {
|
||||
cb(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contains(other) {
|
||||
return this.sri <= other.sri
|
||||
&& this.sci <= other.sci
|
||||
&& this.eri >= other.eri
|
||||
&& this.eci >= other.eci;
|
||||
}
|
||||
|
||||
// within
|
||||
within(other) {
|
||||
return this.sri >= other.sri
|
||||
&& this.sci >= other.sci
|
||||
&& this.eri <= other.eri
|
||||
&& this.eci <= other.eci;
|
||||
}
|
||||
|
||||
// disjoint
|
||||
disjoint(other) {
|
||||
return this.sri > other.eri
|
||||
|| this.sci > other.eci
|
||||
|| other.sri > this.eri
|
||||
|| other.sci > this.eci;
|
||||
}
|
||||
|
||||
// intersects
|
||||
intersects(other) {
|
||||
return this.sri <= other.eri
|
||||
&& this.sci <= other.eci
|
||||
&& other.sri <= this.eri
|
||||
&& other.sci <= this.eci;
|
||||
}
|
||||
|
||||
// union
|
||||
union(other) {
|
||||
const {
|
||||
sri, sci, eri, eci,
|
||||
} = this;
|
||||
return new CellRange(
|
||||
other.sri < sri ? other.sri : sri,
|
||||
other.sci < sci ? other.sci : sci,
|
||||
other.eri > eri ? other.eri : eri,
|
||||
other.eci > eci ? other.eci : eci,
|
||||
);
|
||||
}
|
||||
|
||||
// intersection
|
||||
// intersection(other) {}
|
||||
|
||||
// Returns Array<CellRange> that represents that part of this that does not intersect with other
|
||||
// difference
|
||||
difference(other) {
|
||||
const ret = [];
|
||||
const addRet = (sri, sci, eri, eci) => {
|
||||
ret.push(new CellRange(sri, sci, eri, eci));
|
||||
};
|
||||
const {
|
||||
sri, sci, eri, eci,
|
||||
} = this;
|
||||
const dsr = other.sri - sri;
|
||||
const dsc = other.sci - sci;
|
||||
const der = eri - other.eri;
|
||||
const dec = eci - other.eci;
|
||||
if (dsr > 0) {
|
||||
addRet(sri, sci, other.sri - 1, eci);
|
||||
if (der > 0) {
|
||||
addRet(other.eri + 1, sci, eri, eci);
|
||||
if (dsc > 0) {
|
||||
addRet(other.sri, sci, other.eri, other.sci - 1);
|
||||
}
|
||||
if (dec > 0) {
|
||||
addRet(other.sri, other.eci + 1, other.eri, eci);
|
||||
}
|
||||
} else {
|
||||
if (dsc > 0) {
|
||||
addRet(other.sri, sci, eri, other.sci - 1);
|
||||
}
|
||||
if (dec > 0) {
|
||||
addRet(other.sri, other.eci + 1, eri, eci);
|
||||
}
|
||||
}
|
||||
} else if (der > 0) {
|
||||
addRet(other.eri + 1, sci, eri, eci);
|
||||
if (dsc > 0) {
|
||||
addRet(sri, sci, other.eri, other.sci - 1);
|
||||
}
|
||||
if (dec > 0) {
|
||||
addRet(sri, other.eci + 1, other.eri, eci);
|
||||
}
|
||||
}
|
||||
if (dsc > 0) {
|
||||
addRet(sri, sci, eri, other.sci - 1);
|
||||
if (dec > 0) {
|
||||
addRet(sri, other.eri + 1, eri, eci);
|
||||
if (dsr > 0) {
|
||||
addRet(sri, other.sci, other.sri - 1, other.eci);
|
||||
}
|
||||
if (der > 0) {
|
||||
addRet(other.sri + 1, other.sci, eri, other.eci);
|
||||
}
|
||||
} else {
|
||||
if (dsr > 0) {
|
||||
addRet(sri, other.sci, other.sri - 1, eci);
|
||||
}
|
||||
if (der > 0) {
|
||||
addRet(other.sri + 1, other.sci, eri, eci);
|
||||
}
|
||||
}
|
||||
} else if (dec > 0) {
|
||||
addRet(eri, other.eci + 1, eri, eci);
|
||||
if (dsr > 0) {
|
||||
addRet(sri, sci, other.sri - 1, other.eci);
|
||||
}
|
||||
if (der > 0) {
|
||||
addRet(other.eri + 1, sci, eri, other.eci);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size() {
|
||||
return [
|
||||
this.eri - this.sri + 1,
|
||||
this.eci - this.sci + 1,
|
||||
];
|
||||
}
|
||||
|
||||
toString() {
|
||||
const {
|
||||
sri, sci, eri, eci,
|
||||
} = this;
|
||||
let ref = xy2expr(sci, sri);
|
||||
if (this.multiple()) {
|
||||
ref = `${ref}:${xy2expr(eci, eri)}`;
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
clone() {
|
||||
const {
|
||||
sri, sci, eri, eci, w, h,
|
||||
} = this;
|
||||
return new CellRange(sri, sci, eri, eci, w, h);
|
||||
}
|
||||
|
||||
/*
|
||||
toJSON() {
|
||||
return this.toString();
|
||||
}
|
||||
*/
|
||||
|
||||
equals(other) {
|
||||
return this.eri === other.eri
|
||||
&& this.eci === other.eci
|
||||
&& this.sri === other.sri
|
||||
&& this.sci === other.sci;
|
||||
}
|
||||
|
||||
static valueOf(ref) {
|
||||
// B1:B8, B1 => 1 x 1 cell range
|
||||
const refs = ref.split(':');
|
||||
const [sci, sri] = expr2xy(refs[0]);
|
||||
let [eri, eci] = [sri, sci];
|
||||
if (refs.length > 1) {
|
||||
[eci, eri] = expr2xy(refs[1]);
|
||||
}
|
||||
return new CellRange(sri, sci, eri, eci);
|
||||
}
|
||||
}
|
||||
|
||||
export default CellRange;
|
||||
|
||||
export {
|
||||
CellRange,
|
||||
};
|
@ -0,0 +1,35 @@
|
||||
export default class Clipboard {
|
||||
constructor() {
|
||||
this.range = null; // CellRange
|
||||
this.state = 'clear';
|
||||
}
|
||||
|
||||
copy(cellRange) {
|
||||
this.range = cellRange;
|
||||
this.state = 'copy';
|
||||
return this;
|
||||
}
|
||||
|
||||
cut(cellRange) {
|
||||
this.range = cellRange;
|
||||
this.state = 'cut';
|
||||
return this;
|
||||
}
|
||||
|
||||
isCopy() {
|
||||
return this.state === 'copy';
|
||||
}
|
||||
|
||||
isCut() {
|
||||
return this.state === 'cut';
|
||||
}
|
||||
|
||||
isClear() {
|
||||
return this.state === 'clear';
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.range = null;
|
||||
this.state = 'clear';
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
import helper from './helper';
|
||||
|
||||
class Cols {
|
||||
constructor({
|
||||
len, width, indexWidth, minWidth,
|
||||
}) {
|
||||
this._ = {};
|
||||
this.len = len;
|
||||
this.width = width;
|
||||
this.indexWidth = indexWidth;
|
||||
this.minWidth = minWidth;
|
||||
}
|
||||
|
||||
setData(d) {
|
||||
if (d.len) {
|
||||
this.len = d.len;
|
||||
delete d.len;
|
||||
}
|
||||
this._ = d;
|
||||
}
|
||||
|
||||
getData() {
|
||||
const { len } = this;
|
||||
return Object.assign({ len }, this._);
|
||||
}
|
||||
|
||||
getWidth(i) {
|
||||
if (this.isHide(i)) return 0;
|
||||
const col = this._[i];
|
||||
if (col && col.width) {
|
||||
return col.width;
|
||||
}
|
||||
return this.width;
|
||||
}
|
||||
|
||||
getOrNew(ci) {
|
||||
this._[ci] = this._[ci] || {};
|
||||
return this._[ci];
|
||||
}
|
||||
|
||||
setWidth(ci, width) {
|
||||
const col = this.getOrNew(ci);
|
||||
col.width = width;
|
||||
}
|
||||
|
||||
unhide(idx) {
|
||||
let index = idx;
|
||||
while (index > 0) {
|
||||
index -= 1;
|
||||
if (this.isHide(index)) {
|
||||
this.setHide(index, false);
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
isHide(ci) {
|
||||
const col = this._[ci];
|
||||
return col && col.hide;
|
||||
}
|
||||
|
||||
setHide(ci, v) {
|
||||
const col = this.getOrNew(ci);
|
||||
if (v === true) col.hide = true;
|
||||
else delete col.hide;
|
||||
}
|
||||
|
||||
setStyle(ci, style) {
|
||||
const col = this.getOrNew(ci);
|
||||
col.style = style;
|
||||
}
|
||||
|
||||
sumWidth(min, max) {
|
||||
return helper.rangeSum(min, max, i => this.getWidth(i));
|
||||
}
|
||||
|
||||
totalWidth() {
|
||||
return this.sumWidth(0, this.len);
|
||||
}
|
||||
}
|
||||
|
||||
export default {};
|
||||
export {
|
||||
Cols,
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,76 @@
|
||||
// docs
|
||||
import './_.prototypes';
|
||||
|
||||
/** default font list
|
||||
* @type {BaseFont[]}
|
||||
*/
|
||||
const baseFonts = [
|
||||
{ key: 'Arial', title: 'Arial' },
|
||||
{ key: 'Helvetica', title: 'Helvetica' },
|
||||
{ key: 'Source Sans Pro', title: 'Source Sans Pro' },
|
||||
{ key: 'Comic Sans MS', title: 'Comic Sans MS' },
|
||||
{ key: 'Courier New', title: 'Courier New' },
|
||||
{ key: 'Verdana', title: 'Verdana' },
|
||||
{ key: 'Lato', title: 'Lato' },
|
||||
];
|
||||
|
||||
/** default fontSize list
|
||||
* @type {FontSize[]}
|
||||
*/
|
||||
const fontSizes = [
|
||||
{ pt: 7.5, px: 10 },
|
||||
{ pt: 8, px: 11 },
|
||||
{ pt: 9, px: 12 },
|
||||
{ pt: 10, px: 13 },
|
||||
{ pt: 10.5, px: 14 },
|
||||
{ pt: 11, px: 15 },
|
||||
{ pt: 12, px: 16 },
|
||||
{ pt: 14, px: 18.7 },
|
||||
{ pt: 15, px: 20 },
|
||||
{ pt: 16, px: 21.3 },
|
||||
{ pt: 18, px: 24 },
|
||||
{ pt: 22, px: 29.3 },
|
||||
{ pt: 24, px: 32 },
|
||||
{ pt: 26, px: 34.7 },
|
||||
{ pt: 36, px: 48 },
|
||||
{ pt: 42, px: 56 },
|
||||
// { pt: 54, px: 71.7 },
|
||||
// { pt: 63, px: 83.7 },
|
||||
// { pt: 72, px: 95.6 },
|
||||
];
|
||||
|
||||
/** map pt to px
|
||||
* @date 2019-10-10
|
||||
* @param {fontsizePT} pt
|
||||
* @returns {fontsizePX}
|
||||
*/
|
||||
function getFontSizePxByPt(pt) {
|
||||
for (let i = 0; i < fontSizes.length; i += 1) {
|
||||
const fontSize = fontSizes[i];
|
||||
if (fontSize.pt === pt) {
|
||||
return fontSize.px;
|
||||
}
|
||||
}
|
||||
return pt;
|
||||
}
|
||||
|
||||
/** transform baseFonts to map
|
||||
* @date 2019-10-10
|
||||
* @param {BaseFont[]} [ary=[]]
|
||||
* @returns {object}
|
||||
*/
|
||||
function fonts(ary = []) {
|
||||
const map = {};
|
||||
baseFonts.concat(ary).forEach((f) => {
|
||||
map[f.key] = f;
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
export default {};
|
||||
export {
|
||||
fontSizes,
|
||||
fonts,
|
||||
baseFonts,
|
||||
getFontSizePxByPt,
|
||||
};
|
@ -0,0 +1,110 @@
|
||||
import { tf } from '../locale/locale';
|
||||
|
||||
const formatStringRender = v => v;
|
||||
|
||||
const formatNumberRender = (v) => {
|
||||
// match "-12.1" or "12" or "12.1"
|
||||
if (/^(-?\d*.?\d*)$/.test(v)) {
|
||||
const v1 = Number(v).toFixed(2).toString();
|
||||
const [first, ...parts] = v1.split('\\.');
|
||||
return [first.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'), ...parts];
|
||||
}
|
||||
return v;
|
||||
};
|
||||
|
||||
const baseFormats = [
|
||||
{
|
||||
key: 'normal',
|
||||
title: tf('format.normal'),
|
||||
type: 'string',
|
||||
render: formatStringRender,
|
||||
},
|
||||
{
|
||||
key: 'text',
|
||||
title: tf('format.text'),
|
||||
type: 'string',
|
||||
render: formatStringRender,
|
||||
},
|
||||
{
|
||||
key: 'number',
|
||||
title: tf('format.number'),
|
||||
type: 'number',
|
||||
label: '1,000.12',
|
||||
render: formatNumberRender,
|
||||
},
|
||||
{
|
||||
key: 'percent',
|
||||
title: tf('format.percent'),
|
||||
type: 'number',
|
||||
label: '10.12%',
|
||||
render: v => `${v}%`,
|
||||
},
|
||||
{
|
||||
key: 'rmb',
|
||||
title: tf('format.rmb'),
|
||||
type: 'number',
|
||||
label: '¥10.00',
|
||||
render: v => `¥${formatNumberRender(v)}`,
|
||||
},
|
||||
{
|
||||
key: 'usd',
|
||||
title: tf('format.usd'),
|
||||
type: 'number',
|
||||
label: '$10.00',
|
||||
render: v => `$${formatNumberRender(v)}`,
|
||||
},
|
||||
{
|
||||
key: 'eur',
|
||||
title: tf('format.eur'),
|
||||
type: 'number',
|
||||
label: '€10.00',
|
||||
render: v => `€${formatNumberRender(v)}`,
|
||||
},
|
||||
{
|
||||
key: 'date',
|
||||
title: tf('format.date'),
|
||||
type: 'date',
|
||||
label: '26/09/2008',
|
||||
render: formatStringRender,
|
||||
},
|
||||
{
|
||||
key: 'time',
|
||||
title: tf('format.time'),
|
||||
type: 'date',
|
||||
label: '15:59:00',
|
||||
render: formatStringRender,
|
||||
},
|
||||
{
|
||||
key: 'datetime',
|
||||
title: tf('format.datetime'),
|
||||
type: 'date',
|
||||
label: '26/09/2008 15:59:00',
|
||||
render: formatStringRender,
|
||||
},
|
||||
{
|
||||
key: 'duration',
|
||||
title: tf('format.duration'),
|
||||
type: 'date',
|
||||
label: '24:01:00',
|
||||
render: formatStringRender,
|
||||
},
|
||||
];
|
||||
|
||||
// const formats = (ary = []) => {
|
||||
// const map = {};
|
||||
// baseFormats.concat(ary).forEach((f) => {
|
||||
// map[f.key] = f;
|
||||
// });
|
||||
// return map;
|
||||
// };
|
||||
const formatm = {};
|
||||
baseFormats.forEach((f) => {
|
||||
formatm[f.key] = f;
|
||||
});
|
||||
|
||||
export default {
|
||||
};
|
||||
export {
|
||||
formatm,
|
||||
baseFormats,
|
||||
};
|
@ -0,0 +1,98 @@
|
||||
/**
|
||||
formula:
|
||||
key
|
||||
title
|
||||
render
|
||||
*/
|
||||
/**
|
||||
* @typedef {object} Formula
|
||||
* @property {string} key
|
||||
* @property {function} title
|
||||
* @property {function} render
|
||||
*/
|
||||
import { tf } from '../locale/locale';
|
||||
import { numberCalc } from './helper';
|
||||
|
||||
/** @type {Formula[]} */
|
||||
const baseFormulas = [
|
||||
{
|
||||
key: 'SUM',
|
||||
title: tf('formula.sum'),
|
||||
render: ary => ary.reduce((a, b) => numberCalc('+', a, b), 0),
|
||||
},
|
||||
{
|
||||
key: 'AVERAGE',
|
||||
title: tf('formula.average'),
|
||||
render: ary => ary.reduce((a, b) => Number(a) + Number(b), 0) / ary.length,
|
||||
},
|
||||
{
|
||||
key: 'MAX',
|
||||
title: tf('formula.max'),
|
||||
render: ary => Math.max(...ary.map(v => Number(v))),
|
||||
},
|
||||
{
|
||||
key: 'MIN',
|
||||
title: tf('formula.min'),
|
||||
render: ary => Math.min(...ary.map(v => Number(v))),
|
||||
},
|
||||
{
|
||||
key: 'IF',
|
||||
title: tf('formula._if'),
|
||||
render: ([b, t, f]) => (b ? t : f),
|
||||
},
|
||||
{
|
||||
key: 'AND',
|
||||
title: tf('formula.and'),
|
||||
render: ary => ary.every(it => it),
|
||||
},
|
||||
{
|
||||
key: 'OR',
|
||||
title: tf('formula.or'),
|
||||
render: ary => ary.some(it => it),
|
||||
},
|
||||
{
|
||||
key: 'CONCAT',
|
||||
title: tf('formula.concat'),
|
||||
render: ary => ary.join(''),
|
||||
},
|
||||
/* support: 1 + A1 + B2 * 3
|
||||
{
|
||||
key: 'DIVIDE',
|
||||
title: tf('formula.divide'),
|
||||
render: ary => ary.reduce((a, b) => Number(a) / Number(b)),
|
||||
},
|
||||
{
|
||||
key: 'PRODUCT',
|
||||
title: tf('formula.product'),
|
||||
render: ary => ary.reduce((a, b) => Number(a) * Number(b),1),
|
||||
},
|
||||
{
|
||||
key: 'SUBTRACT',
|
||||
title: tf('formula.subtract'),
|
||||
render: ary => ary.reduce((a, b) => Number(a) - Number(b)),
|
||||
},
|
||||
*/
|
||||
];
|
||||
|
||||
const formulas = baseFormulas;
|
||||
|
||||
// const formulas = (formulaAry = []) => {
|
||||
// const formulaMap = {};
|
||||
// baseFormulas.concat(formulaAry).forEach((f) => {
|
||||
// formulaMap[f.key] = f;
|
||||
// });
|
||||
// return formulaMap;
|
||||
// };
|
||||
const formulam = {};
|
||||
baseFormulas.forEach((f) => {
|
||||
formulam[f.key] = f;
|
||||
});
|
||||
|
||||
export default {
|
||||
};
|
||||
|
||||
export {
|
||||
formulam,
|
||||
formulas,
|
||||
baseFormulas,
|
||||
};
|
@ -0,0 +1,147 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
function cloneDeep(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
const mergeDeep = (object = {}, ...sources) => {
|
||||
sources.forEach((source) => {
|
||||
Object.keys(source).forEach((key) => {
|
||||
const v = source[key];
|
||||
// console.log('k:', key, ', v:', source[key], typeof v, v instanceof Object);
|
||||
if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') {
|
||||
object[key] = v;
|
||||
} else if (typeof v !== 'function' && !Array.isArray(v) && v instanceof Object) {
|
||||
object[key] = object[key] || {};
|
||||
mergeDeep(object[key], v);
|
||||
} else {
|
||||
object[key] = v;
|
||||
}
|
||||
});
|
||||
});
|
||||
// console.log('::', object);
|
||||
return object;
|
||||
};
|
||||
|
||||
function equals(obj1, obj2) {
|
||||
const keys = Object.keys(obj1);
|
||||
if (keys.length !== Object.keys(obj2).length) return false;
|
||||
for (let i = 0; i < keys.length; i += 1) {
|
||||
const k = keys[i];
|
||||
const v1 = obj1[k];
|
||||
const v2 = obj2[k];
|
||||
if (v2 === undefined) return false;
|
||||
if (typeof v1 === 'string' || typeof v1 === 'number' || typeof v1 === 'boolean') {
|
||||
if (v1 !== v2) return false;
|
||||
} else if (Array.isArray(v1)) {
|
||||
if (v1.length !== v2.length) return false;
|
||||
for (let ai = 0; ai < v1.length; ai += 1) {
|
||||
if (!equals(v1[ai], v2[ai])) return false;
|
||||
}
|
||||
} else if (typeof v1 !== 'function' && !Array.isArray(v1) && v1 instanceof Object) {
|
||||
if (!equals(v1, v2)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
objOrAry: obejct or Array
|
||||
cb: (value, index | key) => { return value }
|
||||
*/
|
||||
const sum = (objOrAry, cb = value => value) => {
|
||||
let total = 0;
|
||||
let size = 0;
|
||||
Object.keys(objOrAry).forEach((key) => {
|
||||
total += cb(objOrAry[key], key);
|
||||
size += 1;
|
||||
});
|
||||
return [total, size];
|
||||
};
|
||||
|
||||
function deleteProperty(obj, property) {
|
||||
const oldv = obj[`${property}`];
|
||||
delete obj[`${property}`];
|
||||
return oldv;
|
||||
}
|
||||
|
||||
function rangeReduceIf(min, max, inits, initv, ifv, getv) {
|
||||
let s = inits;
|
||||
let v = initv;
|
||||
let i = min;
|
||||
for (; i < max; i += 1) {
|
||||
if (s > ifv) break;
|
||||
v = getv(i);
|
||||
s += v;
|
||||
}
|
||||
return [i, s - v, v];
|
||||
}
|
||||
|
||||
function rangeSum(min, max, getv) {
|
||||
let s = 0;
|
||||
for (let i = min; i < max; i += 1) {
|
||||
s += getv(i);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function rangeEach(min, max, cb) {
|
||||
for (let i = min; i < max; i += 1) {
|
||||
cb(i);
|
||||
}
|
||||
}
|
||||
|
||||
function arrayEquals(a1, a2) {
|
||||
if (a1.length === a2.length) {
|
||||
for (let i = 0; i < a1.length; i += 1) {
|
||||
if (a1[i] !== a2[i]) return false;
|
||||
}
|
||||
} else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function digits(a) {
|
||||
const v = `${a}`;
|
||||
let ret = 0;
|
||||
let flag = false;
|
||||
for (let i = 0; i < v.length; i += 1) {
|
||||
if (flag === true) ret += 1;
|
||||
if (v.charAt(i) === '.') flag = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function numberCalc(type, a1, a2) {
|
||||
if (Number.isNaN(a1) || Number.isNaN(a2)) {
|
||||
return a1 + type + a2;
|
||||
}
|
||||
const al1 = digits(a1);
|
||||
const al2 = digits(a2);
|
||||
const num1 = Number(a1);
|
||||
const num2 = Number(a2);
|
||||
let ret = 0;
|
||||
if (type === '-') {
|
||||
ret = num1 - num2;
|
||||
} else if (type === '+') {
|
||||
ret = num1 + num2;
|
||||
} else if (type === '*') {
|
||||
ret = num1 * num2;
|
||||
} else if (type === '/') {
|
||||
ret = num1 / num2;
|
||||
if (digits(ret) > 5) return ret.toFixed(2);
|
||||
return ret;
|
||||
}
|
||||
return ret.toFixed(Math.max(al1, al2));
|
||||
}
|
||||
|
||||
export default {
|
||||
cloneDeep,
|
||||
merge: (...sources) => mergeDeep({}, ...sources),
|
||||
equals,
|
||||
arrayEquals,
|
||||
sum,
|
||||
rangeEach,
|
||||
rangeSum,
|
||||
rangeReduceIf,
|
||||
deleteProperty,
|
||||
numberCalc,
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
// import helper from '../helper';
|
||||
|
||||
export default class History {
|
||||
constructor() {
|
||||
this.undoItems = [];
|
||||
this.redoItems = [];
|
||||
}
|
||||
|
||||
add(data) {
|
||||
this.undoItems.push(JSON.stringify(data));
|
||||
this.redoItems = [];
|
||||
}
|
||||
|
||||
canUndo() {
|
||||
return this.undoItems.length > 0;
|
||||
}
|
||||
|
||||
canRedo() {
|
||||
return this.redoItems.length > 0;
|
||||
}
|
||||
|
||||
undo(currentd, cb) {
|
||||
const { undoItems, redoItems } = this;
|
||||
if (this.canUndo()) {
|
||||
redoItems.push(JSON.stringify(currentd));
|
||||
cb(JSON.parse(undoItems.pop()));
|
||||
}
|
||||
}
|
||||
|
||||
redo(currentd, cb) {
|
||||
const { undoItems, redoItems } = this;
|
||||
if (this.canRedo()) {
|
||||
undoItems.push(JSON.stringify(currentd));
|
||||
cb(JSON.parse(redoItems.pop()));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
import { CellRange } from './cell_range';
|
||||
|
||||
class Merges {
|
||||
constructor(d = []) {
|
||||
this._ = d;
|
||||
}
|
||||
|
||||
forEach(cb) {
|
||||
this._.forEach(cb);
|
||||
}
|
||||
|
||||
deleteWithin(cr) {
|
||||
this._ = this._.filter(it => !it.within(cr));
|
||||
}
|
||||
|
||||
getFirstIncludes(ri, ci) {
|
||||
for (let i = 0; i < this._.length; i += 1) {
|
||||
const it = this._[i];
|
||||
if (it.includes(ri, ci)) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
filterIntersects(cellRange) {
|
||||
return new Merges(this._.filter(it => it.intersects(cellRange)));
|
||||
}
|
||||
|
||||
intersects(cellRange) {
|
||||
for (let i = 0; i < this._.length; i += 1) {
|
||||
const it = this._[i];
|
||||
if (it.intersects(cellRange)) {
|
||||
// console.log('intersects');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
union(cellRange) {
|
||||
let cr = cellRange;
|
||||
this._.forEach((it) => {
|
||||
if (it.intersects(cr)) {
|
||||
cr = it.union(cr);
|
||||
}
|
||||
});
|
||||
return cr;
|
||||
}
|
||||
|
||||
add(cr) {
|
||||
this.deleteWithin(cr);
|
||||
this._.push(cr);
|
||||
}
|
||||
|
||||
// type: row | column
|
||||
shift(type, index, n, cbWithin) {
|
||||
this._.forEach((cellRange) => {
|
||||
const {
|
||||
sri, sci, eri, eci,
|
||||
} = cellRange;
|
||||
const range = cellRange;
|
||||
if (type === 'row') {
|
||||
if (sri >= index) {
|
||||
range.sri += n;
|
||||
range.eri += n;
|
||||
} else if (sri < index && index <= eri) {
|
||||
range.eri += n;
|
||||
cbWithin(sri, sci, n, 0);
|
||||
}
|
||||
} else if (type === 'column') {
|
||||
if (sci >= index) {
|
||||
range.sci += n;
|
||||
range.eci += n;
|
||||
} else if (sci < index && index <= eci) {
|
||||
range.eci += n;
|
||||
cbWithin(sri, sci, 0, n);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
move(cellRange, rn, cn) {
|
||||
this._.forEach((it1) => {
|
||||
const it = it1;
|
||||
if (it.within(cellRange)) {
|
||||
it.eri += rn;
|
||||
it.sri += rn;
|
||||
it.sci += cn;
|
||||
it.eci += cn;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setData(merges) {
|
||||
this._ = merges.map(merge => CellRange.valueOf(merge));
|
||||
return this;
|
||||
}
|
||||
|
||||
getData() {
|
||||
return this._.map(merge => merge.toString());
|
||||
}
|
||||
}
|
||||
|
||||
export default {};
|
||||
export {
|
||||
Merges,
|
||||
};
|
@ -0,0 +1,356 @@
|
||||
import helper from './helper';
|
||||
import { expr2expr } from './alphabet';
|
||||
|
||||
class Rows {
|
||||
constructor({ len, height }) {
|
||||
this._ = {};
|
||||
this.len = len;
|
||||
// default row height
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
getHeight(ri) {
|
||||
if (this.isHide(ri)) return 0;
|
||||
const row = this.get(ri);
|
||||
if (row && row.height) {
|
||||
return row.height;
|
||||
}
|
||||
return this.height;
|
||||
}
|
||||
|
||||
setHeight(ri, v) {
|
||||
const row = this.getOrNew(ri);
|
||||
row.height = v;
|
||||
}
|
||||
|
||||
unhide(idx) {
|
||||
let index = idx;
|
||||
while (index > 0) {
|
||||
index -= 1;
|
||||
if (this.isHide(index)) {
|
||||
this.setHide(index, false);
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
isHide(ri) {
|
||||
const row = this.get(ri);
|
||||
return row && row.hide;
|
||||
}
|
||||
|
||||
setHide(ri, v) {
|
||||
const row = this.getOrNew(ri);
|
||||
if (v === true) row.hide = true;
|
||||
else delete row.hide;
|
||||
}
|
||||
|
||||
setStyle(ri, style) {
|
||||
const row = this.getOrNew(ri);
|
||||
row.style = style;
|
||||
}
|
||||
|
||||
sumHeight(min, max, exceptSet) {
|
||||
return helper.rangeSum(min, max, (i) => {
|
||||
if (exceptSet && exceptSet.has(i)) return 0;
|
||||
return this.getHeight(i);
|
||||
});
|
||||
}
|
||||
|
||||
totalHeight() {
|
||||
return this.sumHeight(0, this.len);
|
||||
}
|
||||
|
||||
get(ri) {
|
||||
return this._[ri];
|
||||
}
|
||||
|
||||
getOrNew(ri) {
|
||||
this._[ri] = this._[ri] || { cells: {} };
|
||||
return this._[ri];
|
||||
}
|
||||
|
||||
getCell(ri, ci) {
|
||||
const row = this.get(ri);
|
||||
if (row !== undefined && row.cells !== undefined && row.cells[ci] !== undefined) {
|
||||
return row.cells[ci];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getCellMerge(ri, ci) {
|
||||
const cell = this.getCell(ri, ci);
|
||||
if (cell && cell.merge) return cell.merge;
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
getCellOrNew(ri, ci) {
|
||||
const row = this.getOrNew(ri);
|
||||
row.cells[ci] = row.cells[ci] || {};
|
||||
return row.cells[ci];
|
||||
}
|
||||
|
||||
// what: all | text | format
|
||||
setCell(ri, ci, cell, what = 'all') {
|
||||
const row = this.getOrNew(ri);
|
||||
if (what === 'all') {
|
||||
row.cells[ci] = cell;
|
||||
} else if (what === 'text') {
|
||||
row.cells[ci] = row.cells[ci] || {};
|
||||
row.cells[ci].text = cell.text;
|
||||
} else if (what === 'format') {
|
||||
row.cells[ci] = row.cells[ci] || {};
|
||||
row.cells[ci].style = cell.style;
|
||||
if (cell.merge) row.cells[ci].merge = cell.merge;
|
||||
}
|
||||
}
|
||||
|
||||
setCellText(ri, ci, text) {
|
||||
const cell = this.getCellOrNew(ri, ci);
|
||||
if (cell.editable !== false) cell.text = text;
|
||||
}
|
||||
|
||||
// what: all | format | text
|
||||
copyPaste(srcCellRange, dstCellRange, what, autofill = false, cb = () => {}) {
|
||||
const {
|
||||
sri, sci, eri, eci,
|
||||
} = srcCellRange;
|
||||
const dsri = dstCellRange.sri;
|
||||
const dsci = dstCellRange.sci;
|
||||
const deri = dstCellRange.eri;
|
||||
const deci = dstCellRange.eci;
|
||||
const [rn, cn] = srcCellRange.size();
|
||||
const [drn, dcn] = dstCellRange.size();
|
||||
// console.log(srcIndexes, dstIndexes);
|
||||
let isAdd = true;
|
||||
let dn = 0;
|
||||
if (deri < sri || deci < sci) {
|
||||
isAdd = false;
|
||||
if (deri < sri) dn = drn;
|
||||
else dn = dcn;
|
||||
}
|
||||
for (let i = sri; i <= eri; i += 1) {
|
||||
if (this._[i]) {
|
||||
for (let j = sci; j <= eci; j += 1) {
|
||||
if (this._[i].cells && this._[i].cells[j]) {
|
||||
for (let ii = dsri; ii <= deri; ii += rn) {
|
||||
for (let jj = dsci; jj <= deci; jj += cn) {
|
||||
const nri = ii + (i - sri);
|
||||
const nci = jj + (j - sci);
|
||||
const ncell = helper.cloneDeep(this._[i].cells[j]);
|
||||
// ncell.text
|
||||
if (autofill && ncell && ncell.text && ncell.text.length > 0) {
|
||||
const { text } = ncell;
|
||||
let n = (jj - dsci) + (ii - dsri) + 2;
|
||||
if (!isAdd) {
|
||||
n -= dn + 1;
|
||||
}
|
||||
if (text[0] === '=') {
|
||||
ncell.text = text.replace(/[a-zA-Z]{1,3}\d+/g, (word) => {
|
||||
let [xn, yn] = [0, 0];
|
||||
if (sri === dsri) {
|
||||
xn = n - 1;
|
||||
// if (isAdd) xn -= 1;
|
||||
} else {
|
||||
yn = n - 1;
|
||||
}
|
||||
if (/^\d+$/.test(word)) return word;
|
||||
return expr2expr(word, xn, yn);
|
||||
});
|
||||
} else if ((rn <= 1 && cn > 1 && (dsri > eri || deri < sri))
|
||||
|| (cn <= 1 && rn > 1 && (dsci > eci || deci < sci))
|
||||
|| (rn <= 1 && cn <= 1)) {
|
||||
const result = /[\\.\d]+$/.exec(text);
|
||||
// console.log('result:', result);
|
||||
if (result !== null) {
|
||||
const index = Number(result[0]) + n - 1;
|
||||
ncell.text = text.substring(0, result.index) + index;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setCell(nri, nci, ncell, what);
|
||||
cb(nri, nci, ncell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cutPaste(srcCellRange, dstCellRange) {
|
||||
const ncellmm = {};
|
||||
this.each((ri) => {
|
||||
this.eachCells(ri, (ci) => {
|
||||
let nri = parseInt(ri, 10);
|
||||
let nci = parseInt(ci, 10);
|
||||
if (srcCellRange.includes(ri, ci)) {
|
||||
nri = dstCellRange.sri + (nri - srcCellRange.sri);
|
||||
nci = dstCellRange.sci + (nci - srcCellRange.sci);
|
||||
}
|
||||
ncellmm[nri] = ncellmm[nri] || { cells: {} };
|
||||
ncellmm[nri].cells[nci] = this._[ri].cells[ci];
|
||||
});
|
||||
});
|
||||
this._ = ncellmm;
|
||||
}
|
||||
|
||||
// src: Array<Array<String>>
|
||||
paste(src, dstCellRange) {
|
||||
if (src.length <= 0) return;
|
||||
const { sri, sci } = dstCellRange;
|
||||
src.forEach((row, i) => {
|
||||
const ri = sri + i;
|
||||
row.forEach((cell, j) => {
|
||||
const ci = sci + j;
|
||||
this.setCellText(ri, ci, cell);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
insert(sri, n = 1) {
|
||||
const ndata = {};
|
||||
this.each((ri, row) => {
|
||||
let nri = parseInt(ri, 10);
|
||||
if (nri >= sri) {
|
||||
nri += n;
|
||||
this.eachCells(ri, (ci, cell) => {
|
||||
if (cell.text && cell.text[0] === '=') {
|
||||
cell.text = cell.text.replace(/[a-zA-Z]{1,3}\d+/g, word => expr2expr(word, 0, n, (x, y) => y >= sri));
|
||||
}
|
||||
});
|
||||
}
|
||||
ndata[nri] = row;
|
||||
});
|
||||
this._ = ndata;
|
||||
this.len += n;
|
||||
}
|
||||
|
||||
delete(sri, eri) {
|
||||
const n = eri - sri + 1;
|
||||
const ndata = {};
|
||||
this.each((ri, row) => {
|
||||
const nri = parseInt(ri, 10);
|
||||
if (nri < sri) {
|
||||
ndata[nri] = row;
|
||||
} else if (ri > eri) {
|
||||
ndata[nri - n] = row;
|
||||
this.eachCells(ri, (ci, cell) => {
|
||||
if (cell.text && cell.text[0] === '=') {
|
||||
cell.text = cell.text.replace(/[a-zA-Z]{1,3}\d+/g, word => expr2expr(word, 0, -n, (x, y) => y > eri));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
this._ = ndata;
|
||||
this.len -= n;
|
||||
}
|
||||
|
||||
insertColumn(sci, n = 1) {
|
||||
this.each((ri, row) => {
|
||||
const rndata = {};
|
||||
this.eachCells(ri, (ci, cell) => {
|
||||
let nci = parseInt(ci, 10);
|
||||
if (nci >= sci) {
|
||||
nci += n;
|
||||
if (cell.text && cell.text[0] === '=') {
|
||||
cell.text = cell.text.replace(/[a-zA-Z]{1,3}\d+/g, word => expr2expr(word, n, 0, x => x >= sci));
|
||||
}
|
||||
}
|
||||
rndata[nci] = cell;
|
||||
});
|
||||
row.cells = rndata;
|
||||
});
|
||||
}
|
||||
|
||||
deleteColumn(sci, eci) {
|
||||
const n = eci - sci + 1;
|
||||
this.each((ri, row) => {
|
||||
const rndata = {};
|
||||
this.eachCells(ri, (ci, cell) => {
|
||||
const nci = parseInt(ci, 10);
|
||||
if (nci < sci) {
|
||||
rndata[nci] = cell;
|
||||
} else if (nci > eci) {
|
||||
rndata[nci - n] = cell;
|
||||
if (cell.text && cell.text[0] === '=') {
|
||||
cell.text = cell.text.replace(/[a-zA-Z]{1,3}\d+/g, word => expr2expr(word, -n, 0, x => x > eci));
|
||||
}
|
||||
}
|
||||
});
|
||||
row.cells = rndata;
|
||||
});
|
||||
}
|
||||
|
||||
// what: all | text | format | merge
|
||||
deleteCells(cellRange, what = 'all') {
|
||||
cellRange.each((i, j) => {
|
||||
this.deleteCell(i, j, what);
|
||||
});
|
||||
}
|
||||
|
||||
// what: all | text | format | merge
|
||||
deleteCell(ri, ci, what = 'all') {
|
||||
const row = this.get(ri);
|
||||
if (row !== null) {
|
||||
const cell = this.getCell(ri, ci);
|
||||
if (cell !== null && cell.editable !== false) {
|
||||
if (what === 'all') {
|
||||
delete row.cells[ci];
|
||||
} else if (what === 'text') {
|
||||
if (cell.text) delete cell.text;
|
||||
if (cell.value) delete cell.value;
|
||||
} else if (what === 'format') {
|
||||
if (cell.style !== undefined) delete cell.style;
|
||||
if (cell.merge) delete cell.merge;
|
||||
} else if (what === 'merge') {
|
||||
if (cell.merge) delete cell.merge;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
maxCell() {
|
||||
const keys = Object.keys(this._);
|
||||
const ri = keys[keys.length - 1];
|
||||
const col = this._[ri];
|
||||
if (col) {
|
||||
const { cells } = col;
|
||||
const ks = Object.keys(cells);
|
||||
const ci = ks[ks.length - 1];
|
||||
return [parseInt(ri, 10), parseInt(ci, 10)];
|
||||
}
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
each(cb) {
|
||||
Object.entries(this._).forEach(([ri, row]) => {
|
||||
cb(ri, row);
|
||||
});
|
||||
}
|
||||
|
||||
eachCells(ri, cb) {
|
||||
if (this._[ri] && this._[ri].cells) {
|
||||
Object.entries(this._[ri].cells).forEach(([ci, cell]) => {
|
||||
cb(ci, cell);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setData(d) {
|
||||
if (d.len) {
|
||||
this.len = d.len;
|
||||
delete d.len;
|
||||
}
|
||||
this._ = d;
|
||||
}
|
||||
|
||||
getData() {
|
||||
const { len } = this;
|
||||
return Object.assign({ len }, this._);
|
||||
}
|
||||
}
|
||||
|
||||
export default {};
|
||||
export {
|
||||
Rows,
|
||||
};
|
@ -0,0 +1,8 @@
|
||||
export default class Scroll {
|
||||
constructor() {
|
||||
this.x = 0; // left
|
||||
this.y = 0; // top
|
||||
this.ri = 0; // cell row-index
|
||||
this.ci = 0; // cell col-index
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
import { CellRange } from './cell_range';
|
||||
|
||||
export default class Selector {
|
||||
constructor() {
|
||||
this.range = new CellRange(0, 0, 0, 0);
|
||||
this.ri = 0;
|
||||
this.ci = 0;
|
||||
}
|
||||
|
||||
multiple() {
|
||||
return this.range.multiple();
|
||||
}
|
||||
|
||||
setIndexes(ri, ci) {
|
||||
this.ri = ri;
|
||||
this.ci = ci;
|
||||
}
|
||||
|
||||
size() {
|
||||
return this.range.size();
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
import Validator from './validator';
|
||||
import { CellRange } from './cell_range';
|
||||
|
||||
class Validation {
|
||||
constructor(mode, refs, validator) {
|
||||
this.refs = refs;
|
||||
this.mode = mode; // cell
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
includes(ri, ci) {
|
||||
const { refs } = this;
|
||||
for (let i = 0; i < refs.length; i += 1) {
|
||||
const cr = CellRange.valueOf(refs[i]);
|
||||
if (cr.includes(ri, ci)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
addRef(ref) {
|
||||
this.remove(CellRange.valueOf(ref));
|
||||
this.refs.push(ref);
|
||||
}
|
||||
|
||||
remove(cellRange) {
|
||||
const nrefs = [];
|
||||
this.refs.forEach((it) => {
|
||||
const cr = CellRange.valueOf(it);
|
||||
if (cr.intersects(cellRange)) {
|
||||
const crs = cr.difference(cellRange);
|
||||
crs.forEach(it1 => nrefs.push(it1.toString()));
|
||||
} else {
|
||||
nrefs.push(it);
|
||||
}
|
||||
});
|
||||
this.refs = nrefs;
|
||||
}
|
||||
|
||||
getData() {
|
||||
const { refs, mode, validator } = this;
|
||||
const {
|
||||
type, required, operator, value,
|
||||
} = validator;
|
||||
return {
|
||||
refs, mode, type, required, operator, value,
|
||||
};
|
||||
}
|
||||
|
||||
static valueOf({
|
||||
refs, mode, type, required, operator, value,
|
||||
}) {
|
||||
return new Validation(mode, refs, new Validator(type, required, value, operator));
|
||||
}
|
||||
}
|
||||
class Validations {
|
||||
constructor() {
|
||||
this._ = [];
|
||||
// ri_ci: errMessage
|
||||
this.errors = new Map();
|
||||
}
|
||||
|
||||
getError(ri, ci) {
|
||||
return this.errors.get(`${ri}_${ci}`);
|
||||
}
|
||||
|
||||
validate(ri, ci, text) {
|
||||
const v = this.get(ri, ci);
|
||||
const key = `${ri}_${ci}`;
|
||||
const { errors } = this;
|
||||
if (v !== null) {
|
||||
const [flag, message] = v.validator.validate(text);
|
||||
if (!flag) {
|
||||
errors.set(key, message);
|
||||
} else {
|
||||
errors.delete(key);
|
||||
}
|
||||
} else {
|
||||
errors.delete(key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// type: date|number|phone|email|list
|
||||
// validator: { required, value, operator }
|
||||
add(mode, ref, {
|
||||
type, required, value, operator,
|
||||
}) {
|
||||
const validator = new Validator(
|
||||
type, required, value, operator,
|
||||
);
|
||||
const v = this.getByValidator(validator);
|
||||
if (v !== null) {
|
||||
v.addRef(ref);
|
||||
} else {
|
||||
this._.push(new Validation(mode, [ref], validator));
|
||||
}
|
||||
}
|
||||
|
||||
getByValidator(validator) {
|
||||
for (let i = 0; i < this._.length; i += 1) {
|
||||
const v = this._[i];
|
||||
if (v.validator.equals(validator)) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get(ri, ci) {
|
||||
for (let i = 0; i < this._.length; i += 1) {
|
||||
const v = this._[i];
|
||||
if (v.includes(ri, ci)) return v;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
remove(cellRange) {
|
||||
this.each((it) => {
|
||||
it.remove(cellRange);
|
||||
});
|
||||
}
|
||||
|
||||
each(cb) {
|
||||
this._.forEach(it => cb(it));
|
||||
}
|
||||
|
||||
getData() {
|
||||
return this._.filter(it => it.refs.length > 0).map(it => it.getData());
|
||||
}
|
||||
|
||||
setData(d) {
|
||||
this._ = d.map(it => Validation.valueOf(it));
|
||||
}
|
||||
}
|
||||
|
||||
export default {};
|
||||
export {
|
||||
Validations,
|
||||
};
|
@ -0,0 +1,136 @@
|
||||
import { t } from '../locale/locale';
|
||||
import helper from './helper';
|
||||
|
||||
const rules = {
|
||||
phone: /^[1-9]\d{10}$/,
|
||||
email: /w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*/,
|
||||
};
|
||||
|
||||
function returnMessage(flag, key, ...arg) {
|
||||
let message = '';
|
||||
if (!flag) {
|
||||
message = t(`validation.${key}`, ...arg);
|
||||
}
|
||||
return [flag, message];
|
||||
}
|
||||
|
||||
export default class Validator {
|
||||
// operator: b|nb|eq|neq|lt|lte|gt|gte
|
||||
// type: date|number|list|phone|email
|
||||
constructor(type, required, value, operator) {
|
||||
this.required = required;
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
this.operator = operator;
|
||||
this.message = '';
|
||||
}
|
||||
|
||||
parseValue(v) {
|
||||
const { type } = this;
|
||||
if (type === 'date') {
|
||||
return new Date(v);
|
||||
}
|
||||
if (type === 'number') {
|
||||
return Number(v);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
equals(other) {
|
||||
let flag = this.type === other.type
|
||||
&& this.required === other.required
|
||||
&& this.operator === other.operator;
|
||||
if (flag) {
|
||||
if (Array.isArray(this.value)) {
|
||||
flag = helper.arrayEquals(this.value, other.value);
|
||||
} else {
|
||||
flag = this.value === other.value;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
values() {
|
||||
return this.value.split(',');
|
||||
}
|
||||
|
||||
validate(v) {
|
||||
const {
|
||||
required, operator, value, type,
|
||||
} = this;
|
||||
if (required && /^\s*$/.test(v)) {
|
||||
return returnMessage(false, 'required');
|
||||
}
|
||||
if (/^\s*$/.test(v)) return [true];
|
||||
if (rules[type] && !rules[type].test(v)) {
|
||||
return returnMessage(false, 'notMatch');
|
||||
}
|
||||
if (type === 'list') {
|
||||
return returnMessage(this.values().includes(v), 'notIn');
|
||||
}
|
||||
if (operator) {
|
||||
const v1 = this.parseValue(v);
|
||||
if (operator === 'be') {
|
||||
const [min, max] = value;
|
||||
return returnMessage(
|
||||
v1 >= this.parseValue(min) && v1 <= this.parseValue(max),
|
||||
'between',
|
||||
min,
|
||||
max,
|
||||
);
|
||||
}
|
||||
if (operator === 'nbe') {
|
||||
const [min, max] = value;
|
||||
return returnMessage(
|
||||
v1 < this.parseValue(min) || v1 > this.parseValue(max),
|
||||
'notBetween',
|
||||
min,
|
||||
max,
|
||||
);
|
||||
}
|
||||
if (operator === 'eq') {
|
||||
return returnMessage(
|
||||
v1 === this.parseValue(value),
|
||||
'equal',
|
||||
value,
|
||||
);
|
||||
}
|
||||
if (operator === 'neq') {
|
||||
return returnMessage(
|
||||
v1 !== this.parseValue(value),
|
||||
'notEqual',
|
||||
value,
|
||||
);
|
||||
}
|
||||
if (operator === 'lt') {
|
||||
return returnMessage(
|
||||
v1 < this.parseValue(value),
|
||||
'lessThan',
|
||||
value,
|
||||
);
|
||||
}
|
||||
if (operator === 'lte') {
|
||||
return returnMessage(
|
||||
v1 <= this.parseValue(value),
|
||||
'lessThanEqual',
|
||||
value,
|
||||
);
|
||||
}
|
||||
if (operator === 'gt') {
|
||||
return returnMessage(
|
||||
v1 > this.parseValue(value),
|
||||
'greaterThan',
|
||||
value,
|
||||
);
|
||||
}
|
||||
if (operator === 'gte') {
|
||||
return returnMessage(
|
||||
v1 >= this.parseValue(value),
|
||||
'greaterThanEqual',
|
||||
value,
|
||||
);
|
||||
}
|
||||
}
|
||||
return [true];
|
||||
}
|
||||
}
|
@ -0,0 +1,597 @@
|
||||
export const demoData = [
|
||||
{
|
||||
name: 'sheet2',
|
||||
freeze: 'A1',
|
||||
styles: [
|
||||
{
|
||||
border: {
|
||||
bottom: ['thin', '#000'],
|
||||
top: ['thin', '#000'],
|
||||
left: ['thin', '#000'],
|
||||
right: ['thin', '#000'],
|
||||
},
|
||||
},
|
||||
],
|
||||
merges: ['B23:B34', 'B18:B22', 'B7:B17', 'B1:B6', 'A1:A34'],
|
||||
rows: {
|
||||
'0': {
|
||||
cells: {
|
||||
'0': {
|
||||
style: 0,
|
||||
merge: [33, 0],
|
||||
text: '临平厂房',
|
||||
},
|
||||
'1': {
|
||||
text: '一、1#厂房内支撑脚手架',
|
||||
style: 0,
|
||||
merge: [5, 0],
|
||||
},
|
||||
'2': {
|
||||
text: '1层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '8 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '6687 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'1': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '2层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '5 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '6848 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'2': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '3层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '5 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '6848 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'3': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '4层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '5 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '6338 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'4': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '5层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '5 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '6338 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'5': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '6层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '5 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '6338 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'6': {
|
||||
cells: {
|
||||
'1': {
|
||||
text: '二、2#厂房内支撑脚手架',
|
||||
style: 0,
|
||||
merge: [10, 0],
|
||||
},
|
||||
'2': {
|
||||
text: '1层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '7.90 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4149.04 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'7': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '2层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4317.28 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'8': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '3层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4317.28 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'9': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '4层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4317.28 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'10': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '5层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4317.28 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'11': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '6层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4317.28 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'12': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '7层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4317.28 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'13': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '8层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4317.28 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'14': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '9层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4317.28 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'15': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '10层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4317.28 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'16': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '屋顶层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '2.85 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4260.88 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'17': {
|
||||
cells: {
|
||||
'1': {
|
||||
text: '三、3#厂房内支撑脚手架',
|
||||
style: 0,
|
||||
merge: [4, 0],
|
||||
},
|
||||
'2': {
|
||||
text: '1层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '6.00 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '3964.34 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'18': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '2层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '5.40 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4908.63 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'19': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '3层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '6.00 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4722.95 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'20': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '4层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '6.00 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '4729.63 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'21': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '屋顶层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '3.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '250.00 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'22': {
|
||||
cells: {
|
||||
'1': {
|
||||
text: '四、中建技智选酒店内支撑脚手架',
|
||||
style: 0,
|
||||
merge: [11, 0],
|
||||
},
|
||||
'2': {
|
||||
text: '1层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '7.90 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '1626.50 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'23': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '2层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '1598.40 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'24': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '3层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '1562.40 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'25': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '4层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '1562.40 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'26': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '5层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '1562.40 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'27': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '6层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '1562.40 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'28': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '7层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '1562.40 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'29': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '8层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '1562.40 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'30': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '9层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '1562.40 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'31': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '10层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '1562.40 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'32': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '11层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '4.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '1252.40 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
'33': {
|
||||
cells: {
|
||||
'2': {
|
||||
text: '屋顶层',
|
||||
style: 0,
|
||||
},
|
||||
'3': {
|
||||
text: '3.50 ',
|
||||
style: 0,
|
||||
},
|
||||
'4': {
|
||||
text: '250.00 \r',
|
||||
style: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
len: 40,
|
||||
},
|
||||
cols: {
|
||||
'1': {
|
||||
width: 200,
|
||||
},
|
||||
len: 10,
|
||||
},
|
||||
validations: [],
|
||||
autofilter: {},
|
||||
},
|
||||
];
|
201
packages/plugins/@hera/plugin-core/src/client/components/x-sheet/index.d.ts
vendored
Normal file
201
packages/plugins/@hera/plugin-core/src/client/components/x-sheet/index.d.ts
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
export interface ExtendToolbarOption {
|
||||
tip?: string;
|
||||
el?: HTMLElement;
|
||||
icon?: string;
|
||||
onClick?: (data: object, sheet: object) => void
|
||||
}
|
||||
export interface Options {
|
||||
mode?: 'edit' | 'read';
|
||||
showToolbar?: boolean;
|
||||
showGrid?: boolean;
|
||||
showContextmenu?: boolean;
|
||||
showBottomBar?: boolean;
|
||||
extendToolbar?: {
|
||||
left?: ExtendToolbarOption[],
|
||||
right?: ExtendToolbarOption[],
|
||||
};
|
||||
autoFocus?: boolean;
|
||||
view?: {
|
||||
height: () => number;
|
||||
width: () => number;
|
||||
};
|
||||
row?: {
|
||||
len: number;
|
||||
height: number;
|
||||
};
|
||||
col?: {
|
||||
len: number;
|
||||
width: number;
|
||||
indexWidth: number;
|
||||
minWidth: number;
|
||||
};
|
||||
style?: {
|
||||
bgcolor: string;
|
||||
align: 'left' | 'center' | 'right';
|
||||
valign: 'top' | 'middle' | 'bottom';
|
||||
textwrap: boolean;
|
||||
strike: boolean;
|
||||
underline: boolean;
|
||||
color: string;
|
||||
font: {
|
||||
name: 'Helvetica';
|
||||
size: number;
|
||||
bold: boolean;
|
||||
italic: false;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export type CELL_SELECTED = 'cell-selected';
|
||||
export type CELLS_SELECTED = 'cells-selected';
|
||||
export type CELL_EDITED = 'cell-edited';
|
||||
|
||||
export type CellMerge = [number, number];
|
||||
|
||||
export interface SpreadsheetEventHandler {
|
||||
(
|
||||
envt: CELL_SELECTED,
|
||||
callback: (cell: Cell, rowIndex: number, colIndex: number) => void
|
||||
): void;
|
||||
(
|
||||
envt: CELLS_SELECTED,
|
||||
callback: (
|
||||
cell: Cell,
|
||||
parameters: { sri: number; sci: number; eri: number; eci: number }
|
||||
) => void
|
||||
): void;
|
||||
(
|
||||
evnt: CELL_EDITED,
|
||||
callback: (text: string, rowIndex: number, colIndex: number) => void
|
||||
): void;
|
||||
}
|
||||
|
||||
export interface ColProperties {
|
||||
width?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for representing a cell
|
||||
*/
|
||||
export interface CellData {
|
||||
text: string;
|
||||
style?: number;
|
||||
merge?: CellMerge;
|
||||
}
|
||||
/**
|
||||
* Data for representing a row
|
||||
*/
|
||||
export interface RowData {
|
||||
cells: {
|
||||
[key: number]: CellData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for representing a sheet
|
||||
*/
|
||||
export interface SheetData {
|
||||
name?: string;
|
||||
freeze?: string;
|
||||
styles?: CellStyle[];
|
||||
merges?: string[];
|
||||
cols?: {
|
||||
len?: number;
|
||||
[key: number]: ColProperties;
|
||||
};
|
||||
rows?: {
|
||||
[key: number]: RowData
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for representing a spreadsheet
|
||||
*/
|
||||
export interface SpreadsheetData {
|
||||
[index: number]: SheetData;
|
||||
}
|
||||
|
||||
export interface CellStyle {
|
||||
align?: 'left' | 'center' | 'right';
|
||||
valign?: 'top' | 'middle' | 'bottom';
|
||||
font?: {
|
||||
bold?: boolean;
|
||||
}
|
||||
bgcolor?: string;
|
||||
textwrap?: boolean;
|
||||
color?: string;
|
||||
border?: {
|
||||
top?: string[];
|
||||
right?: string[];
|
||||
bottom?: string[];
|
||||
left?: string[];
|
||||
};
|
||||
}
|
||||
export interface Editor {}
|
||||
export interface Element {}
|
||||
|
||||
export interface Row {}
|
||||
export interface Table {}
|
||||
export interface Cell {}
|
||||
export interface Sheet {}
|
||||
|
||||
export default class Spreadsheet {
|
||||
constructor(container: string | HTMLElement, opts?: Options);
|
||||
on: SpreadsheetEventHandler;
|
||||
/**
|
||||
* retrieve cell
|
||||
* @param rowIndex {number} row index
|
||||
* @param colIndex {number} column index
|
||||
* @param sheetIndex {number} sheet iindex
|
||||
*/
|
||||
cell(rowIndex: number, colIndex: number, sheetIndex: number): Cell;
|
||||
/**
|
||||
* retrieve cell style
|
||||
* @param rowIndex
|
||||
* @param colIndex
|
||||
* @param sheetIndex
|
||||
*/
|
||||
cellStyle(
|
||||
rowIndex: number,
|
||||
colIndex: number,
|
||||
sheetIndex: number
|
||||
): CellStyle;
|
||||
/**
|
||||
* get/set cell text
|
||||
* @param rowIndex
|
||||
* @param colIndex
|
||||
* @param text
|
||||
* @param sheetIndex
|
||||
*/
|
||||
cellText(
|
||||
rowIndex: number,
|
||||
colIndex: number,
|
||||
text: string,
|
||||
sheetIndex?: number
|
||||
): this;
|
||||
/**
|
||||
* remove current sheet
|
||||
*/
|
||||
deleteSheet(): void;
|
||||
|
||||
/**s
|
||||
* load data
|
||||
* @param json
|
||||
*/
|
||||
loadData(json: Record<string, any>): this;
|
||||
/**
|
||||
* get data
|
||||
*/
|
||||
getData(): Record<string, any>;
|
||||
/**
|
||||
* bind handler to change event, including data change and user actions
|
||||
* @param callback
|
||||
*/
|
||||
change(callback: (json: Record<string, any>) => void): this;
|
||||
/**
|
||||
* set locale
|
||||
* @param lang
|
||||
* @param message
|
||||
*/
|
||||
static locale(lang: string, message: object): void;
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/* global window, document */
|
||||
import { h } from './component/element';
|
||||
import DataProxy from './core/data_proxy';
|
||||
import Sheet from './component/sheet';
|
||||
import Bottombar from './component/bottombar';
|
||||
import { cssPrefix } from './config';
|
||||
import { locale } from './locale/locale';
|
||||
import './index.less';
|
||||
|
||||
|
||||
class Spreadsheet {
|
||||
constructor(selectors, options = {}) {
|
||||
let targetEl = selectors;
|
||||
this.options = { showBottomBar: true, ...options };
|
||||
this.sheetIndex = 1;
|
||||
this.datas = [];
|
||||
if (typeof selectors === 'string') {
|
||||
targetEl = document.querySelector(selectors);
|
||||
}
|
||||
this.bottombar = this.options.showBottomBar ? new Bottombar(() => {
|
||||
if (this.options.mode === 'read') return;
|
||||
const d = this.addSheet();
|
||||
this.sheet.resetData(d);
|
||||
}, (index) => {
|
||||
const d = this.datas[index];
|
||||
this.sheet.resetData(d);
|
||||
}, () => {
|
||||
this.deleteSheet();
|
||||
}, (index, value) => {
|
||||
this.datas[index].name = value;
|
||||
this.sheet.trigger('change');
|
||||
}) : null;
|
||||
this.data = this.addSheet();
|
||||
const rootEl = h('div', `${cssPrefix}`)
|
||||
.on('contextmenu', evt => evt.preventDefault());
|
||||
// create canvas element
|
||||
targetEl.appendChild(rootEl.el);
|
||||
this.sheet = new Sheet(rootEl, this.data);
|
||||
if (this.bottombar !== null) {
|
||||
rootEl.child(this.bottombar.el);
|
||||
}
|
||||
}
|
||||
|
||||
addSheet(name, active = true) {
|
||||
const n = name || `sheet${this.sheetIndex}`;
|
||||
const d = new DataProxy(n, this.options);
|
||||
d.change = (...args) => {
|
||||
this.sheet.trigger('change', ...args);
|
||||
};
|
||||
this.datas.push(d);
|
||||
// console.log('d:', n, d, this.datas);
|
||||
if (this.bottombar !== null) {
|
||||
this.bottombar.addItem(n, active, this.options);
|
||||
}
|
||||
this.sheetIndex += 1;
|
||||
return d;
|
||||
}
|
||||
|
||||
deleteSheet() {
|
||||
if (this.bottombar === null) return;
|
||||
|
||||
const [oldIndex, nindex] = this.bottombar.deleteItem();
|
||||
if (oldIndex >= 0) {
|
||||
this.datas.splice(oldIndex, 1);
|
||||
if (nindex >= 0) this.sheet.resetData(this.datas[nindex]);
|
||||
this.sheet.trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
loadData(data) {
|
||||
const ds = Array.isArray(data) ? data : [data];
|
||||
if (this.bottombar !== null) {
|
||||
this.bottombar.clear();
|
||||
}
|
||||
this.datas = [];
|
||||
if (ds.length > 0) {
|
||||
for (let i = 0; i < ds.length; i += 1) {
|
||||
const it = ds[i];
|
||||
const nd = this.addSheet(it.name, i === 0);
|
||||
nd.setData(it);
|
||||
if (i === 0) {
|
||||
this.sheet.resetData(nd);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
getData() {
|
||||
return this.datas.map(it => it.getData());
|
||||
}
|
||||
|
||||
cellText(ri, ci, text, sheetIndex = 0) {
|
||||
this.datas[sheetIndex].setCellText(ri, ci, text, 'finished');
|
||||
return this;
|
||||
}
|
||||
|
||||
cell(ri, ci, sheetIndex = 0) {
|
||||
return this.datas[sheetIndex].getCell(ri, ci);
|
||||
}
|
||||
|
||||
cellStyle(ri, ci, sheetIndex = 0) {
|
||||
return this.datas[sheetIndex].getCellStyle(ri, ci);
|
||||
}
|
||||
|
||||
reRender() {
|
||||
this.sheet.table.render();
|
||||
return this;
|
||||
}
|
||||
|
||||
on(eventName, func) {
|
||||
this.sheet.on(eventName, func);
|
||||
return this;
|
||||
}
|
||||
|
||||
validate() {
|
||||
const { validations } = this.data;
|
||||
return validations.errors.size <= 0;
|
||||
}
|
||||
|
||||
change(cb) {
|
||||
this.sheet.on('change', cb);
|
||||
return this;
|
||||
}
|
||||
|
||||
static locale(lang, message) {
|
||||
locale(lang, message);
|
||||
}
|
||||
}
|
||||
|
||||
export default Spreadsheet;
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,57 @@
|
||||
export default {
|
||||
toolbar: {
|
||||
undo: 'Rückgängig machen',
|
||||
redo: 'Wiederherstellen',
|
||||
paintformat: 'Format kopieren/einfügen',
|
||||
clearformat: 'Format löschen',
|
||||
format: 'Format',
|
||||
font: 'Schriftart',
|
||||
fontSize: 'Schriftgrad',
|
||||
fontBold: 'Fett',
|
||||
fontItalic: 'Kursiv',
|
||||
underline: 'Betonen',
|
||||
strike: 'Streichen',
|
||||
textColor: 'Text Farbe',
|
||||
fillColor: 'Füllung Farbe',
|
||||
border: 'Umrandung',
|
||||
merge: 'Zellen verbinden',
|
||||
align: 'Waagrechte Ausrichtung',
|
||||
valign: 'Vertikale uitlijning',
|
||||
textwrap: 'Textumbruch',
|
||||
freeze: 'Zelle sperren',
|
||||
formula: 'Funktionen',
|
||||
more: 'Mehr',
|
||||
},
|
||||
contextmenu: {
|
||||
copy: 'Kopieren',
|
||||
cut: 'Ausschneiden',
|
||||
paste: 'Einfügen',
|
||||
pasteValue: 'Nur Werte einfügen',
|
||||
pasteFormat: 'Nur Format einfügen',
|
||||
insertRow: 'Zeile einfügen',
|
||||
insertColumn: 'Spalte einfügen',
|
||||
deleteRow: 'Zeile löschen',
|
||||
deleteColumn: 'Spalte löschen',
|
||||
deleteCell: 'Zelle löschen',
|
||||
deleteCellText: 'Zellentext löschen',
|
||||
},
|
||||
format: {
|
||||
normal: 'Regulär',
|
||||
text: 'Text',
|
||||
number: 'Nummer',
|
||||
percent: 'Prozent',
|
||||
rmb: 'RMB',
|
||||
usd: 'USD',
|
||||
date: 'Datum',
|
||||
time: 'Termin',
|
||||
datetime: 'Datum Termin',
|
||||
duration: 'Dauer',
|
||||
},
|
||||
formula: {
|
||||
sum: 'Summe',
|
||||
average: 'Durchschnittliche',
|
||||
max: 'Max',
|
||||
min: 'Min',
|
||||
concat: 'Concat',
|
||||
},
|
||||
};
|
@ -0,0 +1,136 @@
|
||||
export default {
|
||||
toolbar: {
|
||||
undo: 'Undo',
|
||||
redo: 'Redo',
|
||||
print: 'Print',
|
||||
paintformat: 'Paint format',
|
||||
clearformat: 'Clear format',
|
||||
format: 'Format',
|
||||
fontName: 'Font',
|
||||
fontSize: 'Font size',
|
||||
fontBold: 'Font bold',
|
||||
fontItalic: 'Font italic',
|
||||
underline: 'Underline',
|
||||
strike: 'Strike',
|
||||
color: 'Text color',
|
||||
bgcolor: 'Fill color',
|
||||
border: 'Borders',
|
||||
merge: 'Merge cells',
|
||||
align: 'Horizontal align',
|
||||
valign: 'Vertical align',
|
||||
textwrap: 'Text wrapping',
|
||||
freeze: 'Freeze cell',
|
||||
autofilter: 'Filter',
|
||||
formula: 'Functions',
|
||||
more: 'More',
|
||||
},
|
||||
contextmenu: {
|
||||
copy: 'Copy',
|
||||
cut: 'Cut',
|
||||
paste: 'Paste',
|
||||
pasteValue: 'Paste values only',
|
||||
pasteFormat: 'Paste format only',
|
||||
hide: 'Hide',
|
||||
insertRow: 'Insert row',
|
||||
insertColumn: 'Insert column',
|
||||
deleteSheet: 'Delete',
|
||||
deleteRow: 'Delete row',
|
||||
deleteColumn: 'Delete column',
|
||||
deleteCell: 'Delete cell',
|
||||
deleteCellText: 'Delete cell text',
|
||||
validation: 'Data validations',
|
||||
cellprintable: 'Enable export',
|
||||
cellnonprintable: 'Disable export',
|
||||
celleditable: 'Enable editing',
|
||||
cellnoneditable: 'Disable editing',
|
||||
},
|
||||
print: {
|
||||
size: 'Paper size',
|
||||
orientation: 'Page orientation',
|
||||
orientations: ['Landscape', 'Portrait'],
|
||||
},
|
||||
format: {
|
||||
normal: 'Normal',
|
||||
text: 'Plain Text',
|
||||
number: 'Number',
|
||||
percent: 'Percent',
|
||||
rmb: 'RMB',
|
||||
usd: 'USD',
|
||||
eur: 'EUR',
|
||||
date: 'Date',
|
||||
time: 'Time',
|
||||
datetime: 'Date time',
|
||||
duration: 'Duration',
|
||||
},
|
||||
formula: {
|
||||
sum: 'Sum',
|
||||
average: 'Average',
|
||||
max: 'Max',
|
||||
min: 'Min',
|
||||
_if: 'IF',
|
||||
and: 'AND',
|
||||
or: 'OR',
|
||||
concat: 'Concat',
|
||||
},
|
||||
validation: {
|
||||
required: 'it must be required',
|
||||
notMatch: 'it not match its validation rule',
|
||||
between: 'it is between {} and {}',
|
||||
notBetween: 'it is not between {} and {}',
|
||||
notIn: 'it is not in list',
|
||||
equal: 'it equal to {}',
|
||||
notEqual: 'it not equal to {}',
|
||||
lessThan: 'it less than {}',
|
||||
lessThanEqual: 'it less than or equal to {}',
|
||||
greaterThan: 'it greater than {}',
|
||||
greaterThanEqual: 'it greater than or equal to {}',
|
||||
},
|
||||
error: {
|
||||
pasteForMergedCell: 'Unable to do this for merged cells',
|
||||
},
|
||||
calendar: {
|
||||
weeks: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
|
||||
months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||
},
|
||||
button: {
|
||||
next: 'Next',
|
||||
cancel: 'Cancel',
|
||||
remove: 'Remove',
|
||||
save: 'Save',
|
||||
ok: 'OK',
|
||||
},
|
||||
sort: {
|
||||
desc: 'Sort Z -> A',
|
||||
asc: 'Sort A -> Z',
|
||||
},
|
||||
filter: {
|
||||
empty: 'empty',
|
||||
},
|
||||
dataValidation: {
|
||||
mode: 'Mode',
|
||||
range: 'Cell Range',
|
||||
criteria: 'Criteria',
|
||||
modeType: {
|
||||
cell: 'Cell',
|
||||
column: 'Colun',
|
||||
row: 'Row',
|
||||
},
|
||||
type: {
|
||||
list: 'List',
|
||||
number: 'Number',
|
||||
date: 'Date',
|
||||
phone: 'Phone',
|
||||
email: 'Email',
|
||||
},
|
||||
operator: {
|
||||
be: 'between',
|
||||
nbe: 'not betwwen',
|
||||
lt: 'less than',
|
||||
lte: 'less than or equal to',
|
||||
gt: 'greater than',
|
||||
gte: 'greater than or equal to',
|
||||
eq: 'equal to',
|
||||
neq: 'not equal to',
|
||||
},
|
||||
},
|
||||
};
|
@ -0,0 +1,80 @@
|
||||
/* global window */
|
||||
import en from './en';
|
||||
|
||||
// Defines the fallback language as English
|
||||
let $languages = ['en'];
|
||||
const $messages = {
|
||||
en,
|
||||
};
|
||||
|
||||
function translate(key, messages) {
|
||||
if (messages) {
|
||||
// Return the translation from the first language in the languages array
|
||||
// that has a value for the provided key.
|
||||
for (const lang of $languages) {
|
||||
if (!messages[lang]) break;
|
||||
|
||||
let message = messages[lang];
|
||||
|
||||
// Splits the key at '.' except where escaped as '\.'
|
||||
const keys = key.match(/(?:\\.|[^.])+/g);
|
||||
|
||||
for (let i = 0; i < keys.length; i += 1) {
|
||||
const property = keys[i];
|
||||
const value = message[property];
|
||||
|
||||
// If value doesn't exist, try next language
|
||||
if (!value) break;
|
||||
|
||||
if (i === keys.length - 1) return value;
|
||||
|
||||
// Move down to the next level of the messages object
|
||||
message = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function t(key) {
|
||||
let v = translate(key, $messages);
|
||||
if (!v && window && window.x_spreadsheet && window.x_spreadsheet.$messages) {
|
||||
v = translate(key, window.x_spreadsheet.$messages);
|
||||
}
|
||||
return v || '';
|
||||
}
|
||||
|
||||
function tf(key) {
|
||||
return () => t(key);
|
||||
}
|
||||
|
||||
// If clearLangList is set to false, lang will be added to the front of the
|
||||
// languages array. The languages in the language array are searched in order
|
||||
// to find a translation. This allows the use of other languages as a fallback
|
||||
// if lang is missing some keys. The language array is preloaded with English.
|
||||
// To set the languages array to only include lang, set clearLangList to true.
|
||||
function locale(lang, message, clearLangList = false) {
|
||||
if (clearLangList) {
|
||||
$languages = [lang];
|
||||
} else {
|
||||
// Append to front of array.
|
||||
// Translation method will use the first language in the list that has a
|
||||
// matching key.
|
||||
$languages.unshift(lang);
|
||||
}
|
||||
|
||||
if (message) {
|
||||
$messages[lang] = message;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
t,
|
||||
};
|
||||
|
||||
export {
|
||||
locale,
|
||||
t,
|
||||
tf,
|
||||
};
|
@ -0,0 +1,57 @@
|
||||
export default {
|
||||
toolbar: {
|
||||
undo: 'Ongedaan maken',
|
||||
redo: 'Opnieuw uitvoeren',
|
||||
paintformat: 'Opmaak kopiëren/plakken',
|
||||
clearformat: 'Opmaak wissen',
|
||||
format: 'Opmaak',
|
||||
font: 'Lettertype',
|
||||
fontSize: 'Tekengrootte',
|
||||
fontBold: 'Vet',
|
||||
fontItalic: 'Cursief',
|
||||
underline: 'Onderstrepen',
|
||||
strike: 'Doorstrepen',
|
||||
textColor: 'Tekstkleur',
|
||||
fillColor: 'Opvulkleur',
|
||||
border: 'Randen',
|
||||
merge: 'Cellen samenvoegen',
|
||||
align: 'Horizontale uitlijning',
|
||||
valign: 'Verticale uitlijning',
|
||||
textwrap: 'Terugloop',
|
||||
freeze: 'Cel bevriezen',
|
||||
formula: 'Functies',
|
||||
more: 'Meer',
|
||||
},
|
||||
contextmenu: {
|
||||
copy: 'Kopiëren',
|
||||
cut: 'Knippen',
|
||||
paste: 'Plakken',
|
||||
pasteValue: 'Alleen waarden plakken',
|
||||
pasteFormat: 'Alleen opmaak plakken',
|
||||
insertRow: 'Rij invoegen',
|
||||
insertColumn: 'Kolom invoegen',
|
||||
deleteRow: 'Rij verwijderen',
|
||||
deleteColumn: 'Kolom verwijderen',
|
||||
deleteCell: 'Cel verwijderen',
|
||||
deleteCellText: 'Celtekst verwijderen',
|
||||
},
|
||||
format: {
|
||||
normal: 'Standaard',
|
||||
text: 'Tekst',
|
||||
number: 'Nummer',
|
||||
percent: 'Percentage',
|
||||
rmb: 'RMB',
|
||||
usd: 'USD',
|
||||
date: 'Datum',
|
||||
time: 'Tijdstip',
|
||||
datetime: 'Datum tijd',
|
||||
duration: 'Duratie',
|
||||
},
|
||||
formula: {
|
||||
sum: 'Som',
|
||||
average: 'Gemiddelde',
|
||||
max: 'Max',
|
||||
min: 'Min',
|
||||
concat: 'Concat',
|
||||
},
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user