@kwiz/fluentui 1.0.39 → 1.0.41
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/controls/ColorPickerDialog.d.ts +13 -0
- package/dist/controls/ColorPickerDialog.js +34 -0
- package/dist/controls/ColorPickerDialog.js.map +1 -0
- package/dist/controls/canvas/CustomEventTargetBase.d.ts +7 -0
- package/dist/controls/canvas/CustomEventTargetBase.js +22 -0
- package/dist/controls/canvas/CustomEventTargetBase.js.map +1 -0
- package/dist/controls/canvas/DrawPad.d.ts +15 -0
- package/dist/controls/canvas/DrawPad.js +151 -0
- package/dist/controls/canvas/DrawPad.js.map +1 -0
- package/dist/controls/canvas/DrawPadManager.d.ts +84 -0
- package/dist/controls/canvas/DrawPadManager.js +478 -0
- package/dist/controls/canvas/DrawPadManager.js.map +1 -0
- package/dist/controls/canvas/bezier.d.ts +17 -0
- package/dist/controls/canvas/bezier.js +65 -0
- package/dist/controls/canvas/bezier.js.map +1 -0
- package/dist/controls/canvas/point.d.ts +16 -0
- package/dist/controls/canvas/point.js +26 -0
- package/dist/controls/canvas/point.js.map +1 -0
- package/dist/controls/file-upload.d.ts +8 -3
- package/dist/controls/file-upload.js +110 -28
- package/dist/controls/file-upload.js.map +1 -1
- package/dist/controls/kwizoverflow.js +3 -1
- package/dist/controls/kwizoverflow.js.map +1 -1
- package/dist/helpers/drag-drop/exports.d.ts +8 -0
- package/dist/helpers/drag-drop/exports.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/controls/ColorPickerDialog.tsx +76 -0
- package/src/controls/canvas/CustomEventTargetBase.ts +33 -0
- package/src/controls/canvas/DrawPad.tsx +195 -0
- package/src/controls/canvas/DrawPadManager.ts +668 -0
- package/src/controls/canvas/bezier.ts +110 -0
- package/src/controls/canvas/point.ts +45 -0
- package/src/controls/file-upload.tsx +117 -36
- package/src/controls/kwizoverflow.tsx +5 -2
- package/src/helpers/drag-drop/exports.ts +11 -1
- package/src/index.ts +2 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
import { BasicPoint, Point } from './point';
|
2
|
+
|
3
|
+
export class Bezier {
|
4
|
+
public static fromPoints(
|
5
|
+
points: Point[],
|
6
|
+
widths: { start: number; end: number; },
|
7
|
+
): Bezier {
|
8
|
+
const c2 = this.calculateControlPoints(points[0], points[1], points[2]).c2;
|
9
|
+
const c3 = this.calculateControlPoints(points[1], points[2], points[3]).c1;
|
10
|
+
|
11
|
+
return new Bezier(points[1], c2, c3, points[2], widths.start, widths.end);
|
12
|
+
}
|
13
|
+
|
14
|
+
private static calculateControlPoints(
|
15
|
+
s1: BasicPoint,
|
16
|
+
s2: BasicPoint,
|
17
|
+
s3: BasicPoint,
|
18
|
+
): {
|
19
|
+
c1: BasicPoint;
|
20
|
+
c2: BasicPoint;
|
21
|
+
} {
|
22
|
+
const dx1 = s1.x - s2.x;
|
23
|
+
const dy1 = s1.y - s2.y;
|
24
|
+
const dx2 = s2.x - s3.x;
|
25
|
+
const dy2 = s2.y - s3.y;
|
26
|
+
|
27
|
+
const m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
|
28
|
+
const m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
|
29
|
+
|
30
|
+
const l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
|
31
|
+
const l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
32
|
+
|
33
|
+
const dxm = m1.x - m2.x;
|
34
|
+
const dym = m1.y - m2.y;
|
35
|
+
|
36
|
+
const k = (l1 + l2) === 0 ? l2 : l2 / (l1 + l2);
|
37
|
+
|
38
|
+
const cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
|
39
|
+
|
40
|
+
const tx = s2.x - cm.x;
|
41
|
+
const ty = s2.y - cm.y;
|
42
|
+
|
43
|
+
return {
|
44
|
+
c1: new Point(m1.x + tx, m1.y + ty),
|
45
|
+
c2: new Point(m2.x + tx, m2.y + ty),
|
46
|
+
};
|
47
|
+
}
|
48
|
+
|
49
|
+
public constructor(
|
50
|
+
public startPoint: Point,
|
51
|
+
public control2: BasicPoint,
|
52
|
+
public control1: BasicPoint,
|
53
|
+
public endPoint: Point,
|
54
|
+
public startWidth: number,
|
55
|
+
public endWidth: number,
|
56
|
+
) { }
|
57
|
+
|
58
|
+
// Returns approximated length. Code taken from https://www.lemoda.net/maths/bezier-length/index.html.
|
59
|
+
public length(): number {
|
60
|
+
const steps = 10;
|
61
|
+
let length = 0;
|
62
|
+
let px;
|
63
|
+
let py;
|
64
|
+
|
65
|
+
for (let i = 0; i <= steps; i += 1) {
|
66
|
+
const t = i / steps;
|
67
|
+
const cx = this.point(
|
68
|
+
t,
|
69
|
+
this.startPoint.x,
|
70
|
+
this.control1.x,
|
71
|
+
this.control2.x,
|
72
|
+
this.endPoint.x,
|
73
|
+
);
|
74
|
+
const cy = this.point(
|
75
|
+
t,
|
76
|
+
this.startPoint.y,
|
77
|
+
this.control1.y,
|
78
|
+
this.control2.y,
|
79
|
+
this.endPoint.y,
|
80
|
+
);
|
81
|
+
|
82
|
+
if (i > 0) {
|
83
|
+
const xdiff = cx - (px as number);
|
84
|
+
const ydiff = cy - (py as number);
|
85
|
+
|
86
|
+
length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
|
87
|
+
}
|
88
|
+
|
89
|
+
px = cx;
|
90
|
+
py = cy;
|
91
|
+
}
|
92
|
+
|
93
|
+
return length;
|
94
|
+
}
|
95
|
+
|
96
|
+
// Calculate parametric value of x or y given t and the four point coordinates of a cubic bezier curve.
|
97
|
+
private point(
|
98
|
+
t: number,
|
99
|
+
start: number,
|
100
|
+
c1: number,
|
101
|
+
c2: number,
|
102
|
+
end: number,
|
103
|
+
): number {
|
104
|
+
// prettier-ignore
|
105
|
+
return (start * (1.0 - t) * (1.0 - t) * (1.0 - t))
|
106
|
+
+ (3.0 * c1 * (1.0 - t) * (1.0 - t) * t)
|
107
|
+
+ (3.0 * c2 * (1.0 - t) * t * t)
|
108
|
+
+ (end * t * t * t);
|
109
|
+
}
|
110
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
// Interface for point data structure used e.g. in SignaturePad#fromData method
|
2
|
+
export interface BasicPoint {
|
3
|
+
x: number;
|
4
|
+
y: number;
|
5
|
+
pressure: number;
|
6
|
+
time: number;
|
7
|
+
}
|
8
|
+
|
9
|
+
export class Point implements BasicPoint {
|
10
|
+
public x: number;
|
11
|
+
public y: number;
|
12
|
+
public pressure: number;
|
13
|
+
public time: number;
|
14
|
+
|
15
|
+
public constructor(x: number, y: number, pressure?: number, time?: number) {
|
16
|
+
if (isNaN(x) || isNaN(y)) {
|
17
|
+
throw new Error(`Point is invalid: (${x}, ${y})`);
|
18
|
+
}
|
19
|
+
this.x = +x;
|
20
|
+
this.y = +y;
|
21
|
+
this.pressure = pressure || 0;
|
22
|
+
this.time = time || Date.now();
|
23
|
+
}
|
24
|
+
|
25
|
+
public distanceTo(start: BasicPoint): number {
|
26
|
+
return Math.sqrt(
|
27
|
+
Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2),
|
28
|
+
);
|
29
|
+
}
|
30
|
+
|
31
|
+
public equals(other: BasicPoint): boolean {
|
32
|
+
return (
|
33
|
+
this.x === other.x &&
|
34
|
+
this.y === other.y &&
|
35
|
+
this.pressure === other.pressure &&
|
36
|
+
this.time === other.time
|
37
|
+
);
|
38
|
+
}
|
39
|
+
|
40
|
+
public velocityFrom(start: BasicPoint): number {
|
41
|
+
return this.time !== start.time
|
42
|
+
? this.distanceTo(start) / (this.time - start.time)
|
43
|
+
: 0;
|
44
|
+
}
|
45
|
+
}
|
@@ -1,8 +1,19 @@
|
|
1
|
-
import { ButtonProps } from "@fluentui/react-components";
|
2
|
-
import {
|
1
|
+
import { ButtonProps, makeStyles, shorthands, tokens } from "@fluentui/react-components";
|
2
|
+
import { ArrowUploadRegular } from "@fluentui/react-icons";
|
3
|
+
import { isFunction, isNotEmptyArray, isNotEmptyString, isNullOrEmptyString, lastOrNull } from '@kwiz/common';
|
3
4
|
import * as React from "react";
|
5
|
+
import { useDragDropContext } from "../helpers/drag-drop/drag-drop-context";
|
6
|
+
import { dropFiles } from "../helpers/drag-drop/exports";
|
7
|
+
import { useEffectOnlyOnMount } from "../helpers/hooks";
|
4
8
|
import { ButtonEX, CompoundButtonEXSecondary } from "./button";
|
5
9
|
|
10
|
+
const useStyles = makeStyles({
|
11
|
+
addRowIsOver: {
|
12
|
+
...shorthands.borderColor(tokens.colorBrandBackground)
|
13
|
+
}
|
14
|
+
});
|
15
|
+
|
16
|
+
type base64Result = { base64: string, filename: string };
|
6
17
|
interface iProps {
|
7
18
|
showTitleWithIcon?: boolean;
|
8
19
|
title?: string;
|
@@ -11,58 +22,128 @@ interface iProps {
|
|
11
22
|
limitFileTypes?: string[];
|
12
23
|
allowMultiple?: boolean;
|
13
24
|
icon?: JSX.Element;
|
14
|
-
onChange?: (newFile: File |
|
15
|
-
|
16
|
-
asBase64?: (base64: string) => void;
|
25
|
+
onChange?: (newFile: File | File[], errors: string[]) => void;
|
26
|
+
asBase64?: (files: base64Result[], errors: string[]) => void;
|
17
27
|
buttonProps?: ButtonProps;
|
18
28
|
disabled?: boolean;
|
29
|
+
/** limit file size in MB, for the asBase64 */
|
30
|
+
fileSizeLimit?: number;
|
19
31
|
}
|
20
32
|
|
21
33
|
export const FileUpload = React.forwardRef<HTMLButtonElement, (iProps)>((props, ref) => {
|
34
|
+
const classes = useStyles();
|
22
35
|
const hiddenFileInput = React.useRef(null);
|
23
36
|
const isMulti = props.allowMultiple === true;
|
37
|
+
const icon = props.icon || <ArrowUploadRegular />;
|
38
|
+
const title = isNotEmptyString(props.title) ? props.title : `Drop or select ${isMulti ? 'files' : 'file'}`;
|
39
|
+
|
40
|
+
const onGotFiles = React.useCallback(async (rawFiles: FileList) => {
|
41
|
+
let errors: string[] = [];
|
42
|
+
let acceptedFiles: File[] = [];
|
43
|
+
if (rawFiles && rawFiles.length > 0) {
|
44
|
+
//filter by types and size
|
45
|
+
for (let i = 0; i < (isMulti ? rawFiles.length : 1); i++) {
|
46
|
+
const currentFile = rawFiles[i];
|
47
|
+
let hadError = false;
|
48
|
+
if (props.fileSizeLimit > 0) {
|
49
|
+
const megabytes = currentFile.size / (1024 * 1024);
|
50
|
+
if (megabytes > props.fileSizeLimit) {
|
51
|
+
errors.push(`File ${currentFile.name} is over the size limit`);
|
52
|
+
hadError = true;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
if (!hadError) {
|
56
|
+
if (isNotEmptyArray(props.limitFileTypes)) {
|
57
|
+
let fileType = lastOrNull(currentFile.name.split('.')).toLowerCase();
|
58
|
+
if (props.limitFileTypes.indexOf(fileType) < 0) {
|
59
|
+
errors.push(`File ${currentFile.name} is not allowed`);
|
60
|
+
hadError = true;
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
if (!hadError) acceptedFiles.push(currentFile);
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
if (isMulti) {
|
69
|
+
if (isFunction(props.onChange)) {
|
70
|
+
props.onChange(acceptedFiles, errors);
|
71
|
+
}
|
72
|
+
}
|
73
|
+
else {
|
74
|
+
const fileUploaded = acceptedFiles[0];
|
75
|
+
if (isFunction(props.onChange)) {
|
76
|
+
props.onChange(fileUploaded, errors);
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
if (isFunction(props.asBase64)) {
|
81
|
+
const filesAs64: base64Result[] = [];
|
82
|
+
for (let i = 0; i < (isMulti ? acceptedFiles.length : 1); i++) {
|
83
|
+
const currentFile = acceptedFiles[i];
|
84
|
+
let hadError = false;
|
85
|
+
if (props.fileSizeLimit > 0) {
|
86
|
+
const megabytes = currentFile.size / (1024 * 1024);
|
87
|
+
if (megabytes > props.fileSizeLimit) {
|
88
|
+
errors.push(`File ${currentFile.name} is over the size limit`);
|
89
|
+
hadError = true;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
if (!hadError) {
|
93
|
+
let as64 = await getFileAsBase64(acceptedFiles[i]);
|
94
|
+
if (as64) filesAs64.push(as64);
|
95
|
+
else errors.push(`Could not read file ${acceptedFiles[i].name}`);
|
96
|
+
}
|
97
|
+
}
|
98
|
+
props.asBase64(filesAs64, errors);
|
99
|
+
}
|
100
|
+
}, useEffectOnlyOnMount);
|
101
|
+
|
102
|
+
const dropContext = useDragDropContext<never, dropFiles>({
|
103
|
+
dropInfo: {
|
104
|
+
acceptTypes: ["__NATIVE_FILE__"],
|
105
|
+
onItemDrop: item => {
|
106
|
+
onGotFiles(item.files);
|
107
|
+
}
|
108
|
+
}
|
109
|
+
});
|
110
|
+
|
24
111
|
return <>
|
25
112
|
{isNullOrEmptyString(props.secondaryContent)
|
26
|
-
? <ButtonEX ref={ref} {...(props.buttonProps || {})} icon={
|
113
|
+
? <ButtonEX ref={ref || dropContext.dragDropRef} {...(props.buttonProps || {})} icon={icon} showTitleWithIcon={props.showTitleWithIcon} onClick={() => {
|
27
114
|
hiddenFileInput.current.value = "";
|
28
115
|
hiddenFileInput.current.click();
|
29
|
-
}}
|
30
|
-
disabled={props.disabled}
|
116
|
+
}}
|
117
|
+
title={title} disabled={props.disabled}
|
118
|
+
className={dropContext.drop.isOver && classes.addRowIsOver}
|
31
119
|
/>
|
32
|
-
: <CompoundButtonEXSecondary ref={ref} {...(props.buttonProps || {})} icon={
|
120
|
+
: <CompoundButtonEXSecondary ref={ref || dropContext.dragDropRef} {...(props.buttonProps || {})} icon={icon}
|
33
121
|
secondaryContent={props.secondaryContent}
|
34
122
|
onClick={() => {
|
35
123
|
hiddenFileInput.current.value = "";
|
36
124
|
hiddenFileInput.current.click();
|
37
|
-
}}
|
38
|
-
disabled={props.disabled}
|
125
|
+
}}
|
126
|
+
title={title} disabled={props.disabled}
|
127
|
+
className={dropContext.drop.isOver && classes.addRowIsOver}
|
39
128
|
/>}
|
40
129
|
<input type="file" ref={hiddenFileInput} style={{ display: "none" }} multiple={isMulti}
|
41
130
|
accept={isNotEmptyArray(props.limitFileTypes) ? props.limitFileTypes.map(ft => `.${ft}`).join() : undefined}
|
42
|
-
onChange={(e) =>
|
43
|
-
if (e.target.files && e.target.files.length > 0) {
|
44
|
-
if (isMulti) {
|
45
|
-
if (isFunction(props.onChange)) {
|
46
|
-
props.onChange(e.target.files);
|
47
|
-
}
|
48
|
-
}
|
49
|
-
else {
|
50
|
-
const fileUploaded = e.target.files && e.target.files[0];
|
51
|
-
if (isFunction(props.onChange)) {
|
52
|
-
props.onChange(fileUploaded);
|
53
|
-
}
|
54
|
-
if (isFunction(props.asBase64) && fileUploaded) {
|
55
|
-
const reader = new FileReader();
|
56
|
-
reader.onloadend = () => {
|
57
|
-
console.log(reader.result);
|
58
|
-
if (!isNullOrEmptyString(reader.result))
|
59
|
-
props.asBase64(reader.result as string);
|
60
|
-
};
|
61
|
-
reader.readAsDataURL(fileUploaded);
|
62
|
-
}
|
63
|
-
}
|
64
|
-
}
|
65
|
-
}}
|
131
|
+
onChange={async (e) => onGotFiles(e.target.files)}
|
66
132
|
/>
|
67
133
|
</>;
|
68
|
-
});
|
134
|
+
});
|
135
|
+
|
136
|
+
async function getFileAsBase64(file: File): Promise<base64Result> {
|
137
|
+
return new Promise<base64Result>(resolve => {
|
138
|
+
const reader = new FileReader();
|
139
|
+
reader.onloadend = () => {
|
140
|
+
if (!isNullOrEmptyString(reader.result))
|
141
|
+
resolve({ filename: file.name, base64: reader.result as string });
|
142
|
+
else {
|
143
|
+
console.warn("Empty file selected");
|
144
|
+
resolve(null);
|
145
|
+
}
|
146
|
+
};
|
147
|
+
reader.readAsDataURL(file);
|
148
|
+
});
|
149
|
+
}
|
@@ -4,6 +4,7 @@ import {
|
|
4
4
|
} from "@fluentui/react-components";
|
5
5
|
import { MoreHorizontalFilled } from "@fluentui/react-icons";
|
6
6
|
import { isNumber } from '@kwiz/common';
|
7
|
+
import { useKWIZFluentContext } from "../helpers/context";
|
7
8
|
|
8
9
|
interface IProps<ItemType> {
|
9
10
|
/** you cannot have a menu with trigger in overflow items. put those in groupWrapper controls before/after rendering children. */
|
@@ -18,6 +19,8 @@ interface IProps<ItemType> {
|
|
18
19
|
className?: string;
|
19
20
|
}
|
20
21
|
const OverflowMenu = <ItemType,>(props: IProps<ItemType>) => {
|
22
|
+
const ctx = useKWIZFluentContext();
|
23
|
+
|
21
24
|
const { ref, isOverflowing, overflowCount } =
|
22
25
|
useOverflowMenu<HTMLButtonElement>();
|
23
26
|
|
@@ -25,12 +28,12 @@ const OverflowMenu = <ItemType,>(props: IProps<ItemType>) => {
|
|
25
28
|
return null;
|
26
29
|
}
|
27
30
|
|
28
|
-
let menu = <Menu>
|
31
|
+
let menu = <Menu mountNode={ctx.mountNode}>
|
29
32
|
<MenuTrigger disableButtonEnhancement>
|
30
33
|
{props.menuTrigger
|
31
34
|
? props.menuTrigger(props.menuRef || ref, overflowCount)
|
32
35
|
: <MenuButton
|
33
|
-
icon={<MoreHorizontalFilled/>}
|
36
|
+
icon={<MoreHorizontalFilled />}
|
34
37
|
ref={props.menuRef || ref}
|
35
38
|
aria-label="More items"
|
36
39
|
appearance="subtle"
|
@@ -1,4 +1,14 @@
|
|
1
|
+
import { NativeTypes } from 'react-dnd-html5-backend';
|
2
|
+
import { iDraggedItemType } from './use-draggable';
|
3
|
+
import { iDroppableProps } from './use-droppable';
|
4
|
+
|
1
5
|
export { DragDropContainer } from './drag-drop-container';
|
2
6
|
export { DragDropContextProvider, useDragDropContext } from "./drag-drop-context";
|
3
7
|
export type { iDraggedItemType } from "./use-draggable";
|
4
|
-
export type { iDroppableProps } from "./use-droppable";
|
8
|
+
export type { iDroppableProps } from "./use-droppable";
|
9
|
+
|
10
|
+
type fileNativeType = typeof NativeTypes.FILE;
|
11
|
+
interface dragFiles extends iDraggedItemType<fileNativeType> {
|
12
|
+
files: FileList;
|
13
|
+
}
|
14
|
+
export type dropFiles = iDroppableProps<fileNativeType, dragFiles>;
|
package/src/index.ts
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
export * from './controls/accordion';
|
2
2
|
export * from './controls/button';
|
3
|
+
export * from './controls/canvas/DrawPad';
|
3
4
|
export * from './controls/centered';
|
5
|
+
export * from './controls/ColorPickerDialog';
|
4
6
|
export * from './controls/date';
|
5
7
|
export * from './controls/divider';
|
6
8
|
export * from './controls/dropdown';
|