@kwiz/fluentui 1.0.39 → 1.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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';
|