@pie-element/drawing-response 12.1.2-next.2 → 12.1.2
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/CHANGELOG.json +547 -0
- package/CHANGELOG.md +1966 -0
- package/LICENSE.md +5 -0
- package/README.md +1 -0
- package/configure/CHANGELOG.json +367 -0
- package/configure/CHANGELOG.md +1766 -0
- package/configure/lib/button.js +37 -0
- package/configure/lib/button.js.map +1 -0
- package/configure/lib/defaults.js +116 -0
- package/configure/lib/defaults.js.map +1 -0
- package/configure/lib/image-container.js +307 -0
- package/configure/lib/image-container.js.map +1 -0
- package/configure/lib/index.js +147 -0
- package/configure/lib/index.js.map +1 -0
- package/configure/lib/root.js +219 -0
- package/configure/lib/root.js.map +1 -0
- package/configure/package.json +24 -0
- package/configure/src/__tests__/image-container.test.jsx +231 -0
- package/configure/src/__tests__/index.test.js +72 -0
- package/configure/src/__tests__/root.test.jsx +110 -0
- package/configure/src/button.jsx +33 -0
- package/configure/src/defaults.js +80 -0
- package/configure/src/image-container.jsx +316 -0
- package/configure/src/index.js +167 -0
- package/configure/src/root.jsx +198 -0
- package/controller/CHANGELOG.json +52 -0
- package/controller/CHANGELOG.md +261 -0
- package/controller/lib/defaults.js +22 -0
- package/controller/lib/defaults.js.map +1 -0
- package/controller/lib/index.js +84 -0
- package/controller/lib/index.js.map +1 -0
- package/controller/package.json +16 -0
- package/controller/src/__tests__/index.test.js +172 -0
- package/controller/src/defaults.js +12 -0
- package/controller/src/index.js +71 -0
- package/docs/config-schema.json +1766 -0
- package/docs/config-schema.json.md +1313 -0
- package/docs/demo/config.js +8 -0
- package/docs/demo/generate.js +14 -0
- package/docs/demo/index.html +1 -0
- package/docs/demo/session.js +37 -0
- package/docs/pie-schema.json +823 -0
- package/docs/pie-schema.json.md +606 -0
- package/lib/drawing-response/button.js +49 -0
- package/lib/drawing-response/button.js.map +1 -0
- package/lib/drawing-response/constants.js +43 -0
- package/lib/drawing-response/constants.js.map +1 -0
- package/lib/drawing-response/container.js +327 -0
- package/lib/drawing-response/container.js.map +1 -0
- package/lib/drawing-response/drawable-circle.js +80 -0
- package/lib/drawing-response/drawable-circle.js.map +1 -0
- package/lib/drawing-response/drawable-eraser.js +64 -0
- package/lib/drawing-response/drawable-eraser.js.map +1 -0
- package/lib/drawing-response/drawable-free-path.js +71 -0
- package/lib/drawing-response/drawable-free-path.js.map +1 -0
- package/lib/drawing-response/drawable-helper.js +34 -0
- package/lib/drawing-response/drawable-helper.js.map +1 -0
- package/lib/drawing-response/drawable-image.js +41 -0
- package/lib/drawing-response/drawable-image.js.map +1 -0
- package/lib/drawing-response/drawable-line.js +75 -0
- package/lib/drawing-response/drawable-line.js.map +1 -0
- package/lib/drawing-response/drawable-main.js +345 -0
- package/lib/drawing-response/drawable-main.js.map +1 -0
- package/lib/drawing-response/drawable-palette.js +140 -0
- package/lib/drawing-response/drawable-palette.js.map +1 -0
- package/lib/drawing-response/drawable-rectangle.js +80 -0
- package/lib/drawing-response/drawable-rectangle.js.map +1 -0
- package/lib/drawing-response/drawable-text.js +295 -0
- package/lib/drawing-response/drawable-text.js.map +1 -0
- package/lib/drawing-response/drawable-transformer.js +52 -0
- package/lib/drawing-response/drawable-transformer.js.map +1 -0
- package/lib/drawing-response/factory.js +22 -0
- package/lib/drawing-response/factory.js.map +1 -0
- package/lib/drawing-response/icon.js +55 -0
- package/lib/drawing-response/icon.js.map +1 -0
- package/lib/drawing-response/index.js +91 -0
- package/lib/drawing-response/index.js.map +1 -0
- package/lib/index.js +68 -0
- package/lib/index.js.map +1 -0
- package/package.json +19 -82
- package/src/__tests__/drawing-index-test.jsx +103 -0
- package/src/drawing-response/__tests__/container.test.jsx +82 -0
- package/src/drawing-response/__tests__/drawable-helper.test.jsx +24 -0
- package/src/drawing-response/__tests__/drawing-main.test.jsx +855 -0
- package/src/drawing-response/__tests__/factory.test.js +13 -0
- package/src/drawing-response/button.jsx +45 -0
- package/src/drawing-response/constants.js +44 -0
- package/src/drawing-response/container.jsx +301 -0
- package/src/drawing-response/drawable-circle.jsx +74 -0
- package/src/drawing-response/drawable-eraser.jsx +53 -0
- package/src/drawing-response/drawable-free-path.jsx +59 -0
- package/src/drawing-response/drawable-helper.jsx +21 -0
- package/src/drawing-response/drawable-image.jsx +36 -0
- package/src/drawing-response/drawable-line.jsx +62 -0
- package/src/drawing-response/drawable-main.jsx +333 -0
- package/src/drawing-response/drawable-palette.jsx +99 -0
- package/src/drawing-response/drawable-rectangle.jsx +69 -0
- package/src/drawing-response/drawable-text.jsx +318 -0
- package/src/drawing-response/drawable-transformer.jsx +52 -0
- package/src/drawing-response/factory.js +17 -0
- package/src/drawing-response/icon.jsx +52 -0
- package/src/drawing-response/index.jsx +92 -0
- package/src/index.js +72 -0
- package/configure.js +0 -2
- package/controller.js +0 -1
- package/dist/author/button.d.ts +0 -29
- package/dist/author/button.js +0 -25
- package/dist/author/defaults.d.ts +0 -118
- package/dist/author/defaults.js +0 -86
- package/dist/author/image-container.d.ts +0 -42
- package/dist/author/image-container.js +0 -183
- package/dist/author/index.d.ts +0 -37
- package/dist/author/index.js +0 -79
- package/dist/author/root.d.ts +0 -17
- package/dist/author/root.js +0 -142
- package/dist/browser/MenuItem-N0xdzWSs.js +0 -10483
- package/dist/browser/MenuItem-N0xdzWSs.js.map +0 -1
- package/dist/browser/author/index.js +0 -37837
- package/dist/browser/author/index.js.map +0 -1
- package/dist/browser/browser-kkT1XVKw.js +0 -219
- package/dist/browser/browser-kkT1XVKw.js.map +0 -1
- package/dist/browser/controller/index.js +0 -61
- package/dist/browser/controller/index.js.map +0 -1
- package/dist/browser/delivery/index.js +0 -13560
- package/dist/browser/delivery/index.js.map +0 -1
- package/dist/browser/drawing-response.css +0 -2
- package/dist/controller/defaults.d.ts +0 -24
- package/dist/controller/defaults.js +0 -18
- package/dist/controller/index.d.ts +0 -13
- package/dist/controller/index.js +0 -46
- package/dist/delivery/drawing-response/button.d.ts +0 -31
- package/dist/delivery/drawing-response/button.js +0 -36
- package/dist/delivery/drawing-response/constants.d.ts +0 -16
- package/dist/delivery/drawing-response/constants.js +0 -40
- package/dist/delivery/drawing-response/container.d.ts +0 -32
- package/dist/delivery/drawing-response/container.js +0 -212
- package/dist/delivery/drawing-response/drawable-circle.d.ts +0 -18
- package/dist/delivery/drawing-response/drawable-circle.js +0 -51
- package/dist/delivery/drawing-response/drawable-eraser.d.ts +0 -17
- package/dist/delivery/drawing-response/drawable-eraser.js +0 -52
- package/dist/delivery/drawing-response/drawable-free-path.d.ts +0 -18
- package/dist/delivery/drawing-response/drawable-free-path.js +0 -52
- package/dist/delivery/drawing-response/drawable-helper.d.ts +0 -17
- package/dist/delivery/drawing-response/drawable-helper.js +0 -12
- package/dist/delivery/drawing-response/drawable-image.d.ts +0 -24
- package/dist/delivery/drawing-response/drawable-image.js +0 -28
- package/dist/delivery/drawing-response/drawable-line.d.ts +0 -18
- package/dist/delivery/drawing-response/drawable-line.js +0 -53
- package/dist/delivery/drawing-response/drawable-main.d.ts +0 -25
- package/dist/delivery/drawing-response/drawable-main.js +0 -210
- package/dist/delivery/drawing-response/drawable-palette.d.ts +0 -14
- package/dist/delivery/drawing-response/drawable-palette.js +0 -89
- package/dist/delivery/drawing-response/drawable-rectangle.d.ts +0 -18
- package/dist/delivery/drawing-response/drawable-rectangle.js +0 -49
- package/dist/delivery/drawing-response/drawable-text.d.ts +0 -30
- package/dist/delivery/drawing-response/drawable-text.js +0 -189
- package/dist/delivery/drawing-response/drawable-transformer.d.ts +0 -16
- package/dist/delivery/drawing-response/drawable-transformer.js +0 -35
- package/dist/delivery/drawing-response/factory.d.ts +0 -11
- package/dist/delivery/drawing-response/factory.js +0 -19
- package/dist/delivery/drawing-response/icon.d.ts +0 -19
- package/dist/delivery/drawing-response/icon.js +0 -52
- package/dist/delivery/drawing-response/index.d.ts +0 -18
- package/dist/delivery/drawing-response/index.js +0 -70
- package/dist/delivery/index.d.ts +0 -19
- package/dist/delivery/index.js +0 -47
- package/dist/index.d.ts +0 -1
- package/dist/index.iife.d.ts +0 -8
- package/dist/index.iife.js +0 -165
- package/dist/index.js +0 -2
- package/dist/runtime-support.d.ts +0 -12
- package/dist/runtime-support.js +0 -12
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
|
4
|
+
|
|
5
|
+
import { Root } from '../root';
|
|
6
|
+
import defaults from '../defaults';
|
|
7
|
+
|
|
8
|
+
jest.mock('@pie-lib/config-ui', () => ({
|
|
9
|
+
settings: {
|
|
10
|
+
Panel: (props) => <div {...props} />,
|
|
11
|
+
toggle: jest.fn(),
|
|
12
|
+
radio: jest.fn(),
|
|
13
|
+
dropdown: jest.fn(),
|
|
14
|
+
},
|
|
15
|
+
layout: {
|
|
16
|
+
ConfigLayout: (props) => <div>{props.children}</div>,
|
|
17
|
+
},
|
|
18
|
+
InputContainer: (props) => <div {...props}>{props.children}</div>,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
jest.mock('@pie-lib/editable-html-tip-tap', () => (props) => <div {...props} />);
|
|
22
|
+
|
|
23
|
+
jest.mock('../image-container', () => (props) => <div data-testid="image-container" {...props} />);
|
|
24
|
+
|
|
25
|
+
const theme = createTheme();
|
|
26
|
+
|
|
27
|
+
const model = {
|
|
28
|
+
prompt: 'Test Prompt',
|
|
29
|
+
promptEnabled: true,
|
|
30
|
+
imageUrl: '',
|
|
31
|
+
imageDimensions: {
|
|
32
|
+
height: 100,
|
|
33
|
+
width: 100,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
describe('Root', () => {
|
|
38
|
+
const onConfigurationChanged = jest.fn();
|
|
39
|
+
const onModelChanged = jest.fn();
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
jest.clearAllMocks();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const renderRoot = (props = {}) => {
|
|
46
|
+
const defaultProps = {
|
|
47
|
+
model,
|
|
48
|
+
configuration: defaults.configuration,
|
|
49
|
+
onConfigurationChanged,
|
|
50
|
+
onModelChanged,
|
|
51
|
+
...props,
|
|
52
|
+
};
|
|
53
|
+
return render(
|
|
54
|
+
<ThemeProvider theme={theme}>
|
|
55
|
+
<Root {...defaultProps} />
|
|
56
|
+
</ThemeProvider>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
describe('logic', () => {
|
|
61
|
+
it('onPromptChanged calls onModelChanged', () => {
|
|
62
|
+
const { container } = renderRoot();
|
|
63
|
+
// Get the Root component instance by finding the component ref
|
|
64
|
+
const rootInstance = container.querySelector('[data-testid]')?.parentElement?.__reactFiber$?.return?.stateNode;
|
|
65
|
+
|
|
66
|
+
// Since we can't easily access instance methods in RTL, we'll verify the behavior
|
|
67
|
+
// by checking that the handler is correctly bound
|
|
68
|
+
expect(onModelChanged).not.toHaveBeenCalled();
|
|
69
|
+
|
|
70
|
+
// Create a new Root instance to test the method directly
|
|
71
|
+
const testInstance = new Root({ model, onModelChanged, configuration: defaults.configuration, onConfigurationChanged });
|
|
72
|
+
testInstance.onPromptChanged('New Prompt');
|
|
73
|
+
|
|
74
|
+
expect(onModelChanged).toHaveBeenCalledWith(expect.objectContaining({ prompt: 'New Prompt' }));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('onTeacherInstructionsChanged calls onModelChanged', () => {
|
|
78
|
+
const testInstance = new Root({ model, onModelChanged, configuration: defaults.configuration, onConfigurationChanged });
|
|
79
|
+
testInstance.onTeacherInstructionsChanged('New Teacher Instructions');
|
|
80
|
+
|
|
81
|
+
expect(onModelChanged).toHaveBeenCalledWith(
|
|
82
|
+
expect.objectContaining({ teacherInstructions: 'New Teacher Instructions' }),
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('onUpdateImageDimension calls onModelChanged', () => {
|
|
87
|
+
const testInstance = new Root({ model, onModelChanged, configuration: defaults.configuration, onConfigurationChanged });
|
|
88
|
+
testInstance.onUpdateImageDimension({
|
|
89
|
+
height: 200,
|
|
90
|
+
width: 200,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(onModelChanged).toHaveBeenCalledWith(
|
|
94
|
+
expect.objectContaining({
|
|
95
|
+
imageDimensions: {
|
|
96
|
+
height: 200,
|
|
97
|
+
width: 200,
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('onImageUpload calls onModelChanged', () => {
|
|
104
|
+
const testInstance = new Root({ model, onModelChanged, configuration: defaults.configuration, onConfigurationChanged });
|
|
105
|
+
testInstance.onImageUpload('url');
|
|
106
|
+
|
|
107
|
+
expect(onModelChanged).toHaveBeenCalledWith(expect.objectContaining({ imageUrl: 'url' }));
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { styled } from '@mui/material/styles';
|
|
4
|
+
import Button from '@mui/material/Button';
|
|
5
|
+
|
|
6
|
+
const StyledButton = styled(Button)({
|
|
7
|
+
marginLeft: 8,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const CustomButton = ({ label, onClick, disabled }) => (
|
|
11
|
+
<StyledButton
|
|
12
|
+
onClick={onClick}
|
|
13
|
+
disabled={disabled}
|
|
14
|
+
size="small"
|
|
15
|
+
variant="contained">
|
|
16
|
+
{label}
|
|
17
|
+
</StyledButton>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
CustomButton.propTypes = {
|
|
21
|
+
disabled: PropTypes.bool,
|
|
22
|
+
label: PropTypes.string,
|
|
23
|
+
onClick: PropTypes.func,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
CustomButton.defaultProps = {
|
|
27
|
+
className: '',
|
|
28
|
+
disabled: false,
|
|
29
|
+
label: 'Add',
|
|
30
|
+
onClick: () => { },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default CustomButton;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
model: {
|
|
3
|
+
backgroundImageEnabled: true,
|
|
4
|
+
imageDimensions: { height: 0, width: 0 },
|
|
5
|
+
imageUrl: '',
|
|
6
|
+
prompt: '',
|
|
7
|
+
promptEnabled: true,
|
|
8
|
+
spellCheckEnabled: true,
|
|
9
|
+
studentInstructionsEnabled: true,
|
|
10
|
+
teacherInstructions: '',
|
|
11
|
+
teacherInstructionsEnabled: true,
|
|
12
|
+
toolbarEditorPosition: 'bottom',
|
|
13
|
+
},
|
|
14
|
+
configuration: {
|
|
15
|
+
baseInputConfiguration: {
|
|
16
|
+
h3: { disabled: true },
|
|
17
|
+
audio: { disabled: false },
|
|
18
|
+
video: { disabled: false },
|
|
19
|
+
image: { disabled: false },
|
|
20
|
+
textAlign: { disabled: true },
|
|
21
|
+
showParagraphs: { disabled: false },
|
|
22
|
+
separateParagraphs: { disabled: true },
|
|
23
|
+
},
|
|
24
|
+
spellCheck: {
|
|
25
|
+
label: 'Spellcheck',
|
|
26
|
+
settings: false,
|
|
27
|
+
enabled: true,
|
|
28
|
+
},
|
|
29
|
+
backgroundImage: {
|
|
30
|
+
settings: true,
|
|
31
|
+
label: 'Background Image',
|
|
32
|
+
},
|
|
33
|
+
prompt: {
|
|
34
|
+
settings: true,
|
|
35
|
+
label: 'Prompt',
|
|
36
|
+
inputConfiguration: {
|
|
37
|
+
audio: { disabled: false },
|
|
38
|
+
video: { disabled: false },
|
|
39
|
+
image: { disabled: false },
|
|
40
|
+
},
|
|
41
|
+
required: false,
|
|
42
|
+
},
|
|
43
|
+
settingsPanelDisabled: false,
|
|
44
|
+
teacherInstructions: {
|
|
45
|
+
settings: true,
|
|
46
|
+
label: 'Teacher Instructions',
|
|
47
|
+
inputConfiguration: {
|
|
48
|
+
audio: { disabled: false },
|
|
49
|
+
video: { disabled: false },
|
|
50
|
+
image: { disabled: false },
|
|
51
|
+
},
|
|
52
|
+
required: false,
|
|
53
|
+
},
|
|
54
|
+
maxImageWidth: {
|
|
55
|
+
teacherInstructions: 300,
|
|
56
|
+
prompt: 300,
|
|
57
|
+
},
|
|
58
|
+
maxImageHeight: {
|
|
59
|
+
teacherInstructions: 300,
|
|
60
|
+
prompt: 300,
|
|
61
|
+
},
|
|
62
|
+
withRubric: {
|
|
63
|
+
settings: false,
|
|
64
|
+
label: 'Add Rubric',
|
|
65
|
+
},
|
|
66
|
+
mathMlOptions: {
|
|
67
|
+
mmlOutput: false,
|
|
68
|
+
mmlEditing: false,
|
|
69
|
+
},
|
|
70
|
+
language: {
|
|
71
|
+
settings: false,
|
|
72
|
+
label: 'Specify Language',
|
|
73
|
+
enabled: false,
|
|
74
|
+
},
|
|
75
|
+
languageChoices: {
|
|
76
|
+
label: 'Language Choices',
|
|
77
|
+
options: [],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
};
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import React, { Component } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { styled } from '@mui/material/styles';
|
|
4
|
+
|
|
5
|
+
import Button from './button';
|
|
6
|
+
|
|
7
|
+
const isImage = (file) => {
|
|
8
|
+
const imageType = /image.*/;
|
|
9
|
+
return file.type.match(imageType);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const BaseContainer = styled('div')(({ theme }) => ({
|
|
13
|
+
marginTop: theme.spacing(1),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
const Box = styled('div')(({ active }) => ({
|
|
17
|
+
border: active ? '1px solid #0032C2' : '1px solid #E0E1E6',
|
|
18
|
+
borderRadius: '5px',
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const CenteredDiv = styled('div')({
|
|
22
|
+
alignItems: 'center',
|
|
23
|
+
display: 'flex',
|
|
24
|
+
flexDirection: 'column',
|
|
25
|
+
justifyContent: 'center',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const DrawableHeight = styled('div')({
|
|
29
|
+
minHeight: 350,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const Image = styled('img')({
|
|
33
|
+
alignItems: 'center',
|
|
34
|
+
display: 'flex',
|
|
35
|
+
justifyContent: 'center',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const StyledImageContainer = styled('div')({
|
|
39
|
+
position: 'relative',
|
|
40
|
+
width: 'fit-content',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const ResizeHandle = styled('div')({
|
|
44
|
+
borderBottom: '1px solid #727272',
|
|
45
|
+
borderRight: '1px solid #727272',
|
|
46
|
+
bottom: '-10px',
|
|
47
|
+
cursor: 'se-resize',
|
|
48
|
+
height: '10px',
|
|
49
|
+
position: 'absolute',
|
|
50
|
+
right: '-10px',
|
|
51
|
+
width: '10px',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const HiddenInput = styled('input')({
|
|
55
|
+
display: 'none',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const Toolbar = styled('div')({
|
|
59
|
+
backgroundColor: '#ECEDF1',
|
|
60
|
+
borderBottom: '1px solid #E0E1E6',
|
|
61
|
+
borderTopLeftRadius: '5px',
|
|
62
|
+
borderTopRightRadius: '5px',
|
|
63
|
+
display: 'flex',
|
|
64
|
+
padding: '12px 8px',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export class ImageContainer extends Component {
|
|
68
|
+
|
|
69
|
+
static propTypes = {
|
|
70
|
+
imageDimensions: PropTypes.object,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
static defaultProps = {};
|
|
74
|
+
|
|
75
|
+
constructor(props) {
|
|
76
|
+
super(props);
|
|
77
|
+
this.state = {
|
|
78
|
+
maxImageWidth: '100%',
|
|
79
|
+
maxImageHeight: 'auto',
|
|
80
|
+
dragEnabled: true,
|
|
81
|
+
dropzoneActive: false,
|
|
82
|
+
};
|
|
83
|
+
this.fakeImageHandler = {
|
|
84
|
+
cancel: () => {},
|
|
85
|
+
done: (a, url) => this.props.onImageUpload(url),
|
|
86
|
+
fileChosen: () => {},
|
|
87
|
+
progress: () => {},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
componentDidMount() {
|
|
92
|
+
if (this.imageSection) {
|
|
93
|
+
const positionInfo = this.imageSection.getBoundingClientRect();
|
|
94
|
+
const { height, width } = positionInfo;
|
|
95
|
+
this.setState({
|
|
96
|
+
maxImageWidth: width,
|
|
97
|
+
maxImageHeight: height,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
handleFileRead = (file) => {
|
|
103
|
+
const { onImageUpload, insertImage } = this.props;
|
|
104
|
+
const reader = new FileReader();
|
|
105
|
+
|
|
106
|
+
reader.onloadend = () => onImageUpload(reader.result);
|
|
107
|
+
reader.readAsDataURL(file);
|
|
108
|
+
|
|
109
|
+
if (insertImage) {
|
|
110
|
+
insertImage({
|
|
111
|
+
...this.fakeImageHandler,
|
|
112
|
+
getChosenFile: () => file,
|
|
113
|
+
isPasted: true,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
handleUploadImage = (e) => {
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
const file = e.target.files[0];
|
|
121
|
+
this.handleFileRead(file);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
makeDropzoneActive = () => this.setState({ dropzoneActive: true });
|
|
125
|
+
|
|
126
|
+
makeDropzoneInactive = () => this.setState({ dropzoneActive: false });
|
|
127
|
+
|
|
128
|
+
handleOnDrop = (e) => {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
const { items, files } = e.dataTransfer;
|
|
131
|
+
|
|
132
|
+
if (items && items[0].kind === 'file') {
|
|
133
|
+
const file = items[0].getAsFile();
|
|
134
|
+
if (isImage(file)) {
|
|
135
|
+
this.handleFileRead(file);
|
|
136
|
+
}
|
|
137
|
+
} else if (isImage(files[0])) {
|
|
138
|
+
this.handleFileRead(files[0]);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.makeDropzoneInactive();
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
handleOnDragOver = (e) => {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
const { dropzoneActive } = this.state;
|
|
147
|
+
|
|
148
|
+
if (!dropzoneActive) {
|
|
149
|
+
this.makeDropzoneActive();
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
handleOnDragExit = (e) => {
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
this.makeDropzoneInactive();
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
handleEnableDrag = () => this.setState({ dragEnabled: true });
|
|
159
|
+
handleDisableDrag = () => this.setState({ dragEnabled: false });
|
|
160
|
+
handleInputClick = () => {
|
|
161
|
+
const { insertImage } = this.props;
|
|
162
|
+
|
|
163
|
+
if (insertImage) {
|
|
164
|
+
insertImage(this.fakeImageHandler);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
handleOnImageLoad = ({ target: { offsetHeight, offsetWidth, naturalHeight, naturalWidth } }) => {
|
|
169
|
+
const { onUpdateImageDimension, imageDimensions } = this.props;
|
|
170
|
+
const resizeHandle = this.resize;
|
|
171
|
+
|
|
172
|
+
const dimensions = {
|
|
173
|
+
height: (imageDimensions && imageDimensions.height) || offsetHeight || naturalHeight,
|
|
174
|
+
width: (imageDimensions && imageDimensions.width) || offsetWidth || naturalWidth,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// check if aspect ratio is not respected on replacing image
|
|
178
|
+
const imageAspectRatio = naturalWidth / naturalHeight;
|
|
179
|
+
|
|
180
|
+
if (dimensions.width !== dimensions.height * imageAspectRatio) {
|
|
181
|
+
dimensions.width = dimensions.height * imageAspectRatio;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.setState({ dimensions });
|
|
185
|
+
onUpdateImageDimension(dimensions);
|
|
186
|
+
|
|
187
|
+
resizeHandle.addEventListener('mousedown', this.initialiseResize, false);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
initialiseResize = () => {
|
|
191
|
+
window.addEventListener('mousemove', this.startResizing, false);
|
|
192
|
+
window.addEventListener('mouseup', this.stopResizing, false);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
stopResizing = () => {
|
|
196
|
+
const { onUpdateImageDimension } = this.props;
|
|
197
|
+
const { dimensions } = this.state;
|
|
198
|
+
|
|
199
|
+
this.handleEnableDrag();
|
|
200
|
+
onUpdateImageDimension(dimensions);
|
|
201
|
+
|
|
202
|
+
window.removeEventListener('mousemove', this.startResizing, false);
|
|
203
|
+
window.removeEventListener('mouseup', this.stopResizing, false);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
startResizing = (e) => {
|
|
207
|
+
const box = this.image;
|
|
208
|
+
const { maxImageWidth, maxImageHeight, dimensions } = this.state;
|
|
209
|
+
|
|
210
|
+
const bounds = e.target.getBoundingClientRect();
|
|
211
|
+
const x = e.clientX - bounds.left;
|
|
212
|
+
const y = e.clientY - bounds.top;
|
|
213
|
+
|
|
214
|
+
const imageAspectRatio = dimensions.width / dimensions.height;
|
|
215
|
+
const fitsContainer = x <= maxImageWidth + 5 && x / imageAspectRatio <= maxImageHeight + 5;
|
|
216
|
+
const hasMinimumWidth = x > 150 && y > 150;
|
|
217
|
+
|
|
218
|
+
if (fitsContainer && hasMinimumWidth && box) {
|
|
219
|
+
box.style.width = `${x}px`;
|
|
220
|
+
box.style.height = `${x / imageAspectRatio}px`;
|
|
221
|
+
|
|
222
|
+
this.setState({
|
|
223
|
+
dimensions: {
|
|
224
|
+
width: x,
|
|
225
|
+
height: x / imageAspectRatio,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this.handleDisableDrag();
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
renderUploadControl(label) {
|
|
234
|
+
return (
|
|
235
|
+
<div>
|
|
236
|
+
<Button label={label} onClick={this.handleInputClick} />
|
|
237
|
+
<HiddenInput
|
|
238
|
+
accept="image/*"
|
|
239
|
+
onChange={this.handleUploadImage}
|
|
240
|
+
ref={(ref) => {
|
|
241
|
+
this.input = ref;
|
|
242
|
+
}}
|
|
243
|
+
type="file"
|
|
244
|
+
/>
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
render() {
|
|
250
|
+
const { imageUrl, imageDimensions } = this.props;
|
|
251
|
+
const { dropzoneActive, dragEnabled, maxImageHeight, maxImageWidth } = this.state;
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<BaseContainer>
|
|
255
|
+
<Box
|
|
256
|
+
active={dropzoneActive}
|
|
257
|
+
{...(dragEnabled
|
|
258
|
+
? {
|
|
259
|
+
onDragExit: this.handleOnDragExit,
|
|
260
|
+
onDragLeave: this.handleOnDragExit,
|
|
261
|
+
onDragOver: this.handleOnDragOver,
|
|
262
|
+
onDrop: this.handleOnDrop,
|
|
263
|
+
}
|
|
264
|
+
: {})}
|
|
265
|
+
>
|
|
266
|
+
<Toolbar>{this.renderUploadControl(imageUrl ? 'Replace Image' : 'Upload Image')}</Toolbar>
|
|
267
|
+
|
|
268
|
+
<DrawableHeight
|
|
269
|
+
ref={(ref) => {
|
|
270
|
+
this.imageSection = ref;
|
|
271
|
+
}}
|
|
272
|
+
>
|
|
273
|
+
{imageUrl ? (
|
|
274
|
+
<StyledImageContainer>
|
|
275
|
+
<Image
|
|
276
|
+
height="auto"
|
|
277
|
+
onLoad={this.handleOnImageLoad}
|
|
278
|
+
ref={(ref) => {
|
|
279
|
+
this.image = ref;
|
|
280
|
+
}}
|
|
281
|
+
src={imageUrl}
|
|
282
|
+
style={{
|
|
283
|
+
width: imageDimensions && imageDimensions.width ? imageDimensions.width : undefined,
|
|
284
|
+
maxWidth: maxImageWidth,
|
|
285
|
+
maxHeight: maxImageHeight,
|
|
286
|
+
}}
|
|
287
|
+
alt=""
|
|
288
|
+
/>
|
|
289
|
+
<ResizeHandle
|
|
290
|
+
ref={(ref) => {
|
|
291
|
+
this.resize = ref;
|
|
292
|
+
}}
|
|
293
|
+
/>
|
|
294
|
+
</StyledImageContainer>
|
|
295
|
+
) : (
|
|
296
|
+
<DrawableHeight as={CenteredDiv}>
|
|
297
|
+
<label>Drag and drop or upload image from computer</label>
|
|
298
|
+
<br />
|
|
299
|
+
{this.renderUploadControl('Upload Image')}
|
|
300
|
+
</DrawableHeight>
|
|
301
|
+
)}
|
|
302
|
+
</DrawableHeight>
|
|
303
|
+
</Box>
|
|
304
|
+
</BaseContainer>
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
ImageContainer.propTypes = {
|
|
310
|
+
imageUrl: PropTypes.string.isRequired,
|
|
311
|
+
onImageUpload: PropTypes.func.isRequired,
|
|
312
|
+
onUpdateImageDimension: PropTypes.func.isRequired,
|
|
313
|
+
insertImage: PropTypes.func,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
export default ImageContainer;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeleteImageEvent,
|
|
3
|
+
DeleteSoundEvent,
|
|
4
|
+
InsertImageEvent,
|
|
5
|
+
InsertSoundEvent,
|
|
6
|
+
ModelUpdatedEvent,
|
|
7
|
+
} from '@pie-framework/pie-configure-events';
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
import { createRoot } from 'react-dom/client';
|
|
11
|
+
import debug from 'debug';
|
|
12
|
+
|
|
13
|
+
import Root from './root';
|
|
14
|
+
import sensibleDefaults from './defaults';
|
|
15
|
+
|
|
16
|
+
const log = debug('hotspot:configure');
|
|
17
|
+
|
|
18
|
+
export default class DrawableResponseConfigure extends HTMLElement {
|
|
19
|
+
static createDefaultModel = (model = {}, config) => {
|
|
20
|
+
const defaultModel = {
|
|
21
|
+
...sensibleDefaults.model,
|
|
22
|
+
...model,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// if configuration.withRubric.forceEnabled is true, then we update the model
|
|
26
|
+
// without triggering the Model Updated event (for more details, check documentation)
|
|
27
|
+
if (config?.withRubric?.forceEnabled && !defaultModel.rubricEnabled) {
|
|
28
|
+
defaultModel.rubricEnabled = true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return defaultModel;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
super();
|
|
36
|
+
this._root = null;
|
|
37
|
+
this._configuration = sensibleDefaults.configuration;
|
|
38
|
+
|
|
39
|
+
// if configuration.withRubric.forceEnabled is true, then we
|
|
40
|
+
// update the configuration (we do not want to display the toggle in the Settings Panel)
|
|
41
|
+
if (this._configuration.withRubric?.forceEnabled) {
|
|
42
|
+
this._configuration.withRubric.settings = false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this._model = DrawableResponseConfigure.createDefaultModel({}, this._configuration);
|
|
46
|
+
this.onModelChanged = this.onModelChanged.bind(this);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set model(s) {
|
|
50
|
+
this._model = DrawableResponseConfigure.createDefaultModel(s, this._configuration);
|
|
51
|
+
this._render();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
set configuration(c) {
|
|
55
|
+
const newConfiguration = {
|
|
56
|
+
...sensibleDefaults.configuration,
|
|
57
|
+
...c,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
this._configuration = newConfiguration;
|
|
61
|
+
|
|
62
|
+
const { withRubric = {} } = c || {};
|
|
63
|
+
|
|
64
|
+
// if configuration.withRubric.forceEnabled is true, then we update the model
|
|
65
|
+
// without triggering the Model Updated event (for more details, check documentation)
|
|
66
|
+
// and also update the configuration (we do not want to display the toggle in the Settings Panel)
|
|
67
|
+
if (withRubric?.forceEnabled) {
|
|
68
|
+
this._configuration.withRubric.settings = false;
|
|
69
|
+
|
|
70
|
+
if (!this._model.rubricEnabled) {
|
|
71
|
+
this._model.rubricEnabled = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// if language:enabled is true, then the corresponding default item model should include a language value;
|
|
76
|
+
// if it is false, then the language field should be omitted from the item model.
|
|
77
|
+
// if a default item model includes a language value (e.g., en_US) and the corresponding authoring view settings have language:settings = true,
|
|
78
|
+
// then (a) language:enabled should also be true, and (b) that default language value should be represented in languageChoices[] (as a key).
|
|
79
|
+
if (newConfiguration?.language?.enabled) {
|
|
80
|
+
if (newConfiguration?.languageChoices?.options?.length) {
|
|
81
|
+
this._model.language = newConfiguration?.languageChoices.options[0].value;
|
|
82
|
+
}
|
|
83
|
+
} else if (newConfiguration.language.settings && this._model.language) {
|
|
84
|
+
this._configuration.language.enabled = true;
|
|
85
|
+
|
|
86
|
+
if (!this._configuration.languageChoices.options || !this._configuration.languageChoices.options.length) {
|
|
87
|
+
this._configuration.languageChoices.options = [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// check if the language is already included in the languageChoices.options array
|
|
91
|
+
// and if not, then add it.
|
|
92
|
+
if (!this._configuration.languageChoices.options.find(option => option.value === this._model.language)) {
|
|
93
|
+
this._configuration.languageChoices.options.push({
|
|
94
|
+
value: this._model.language,
|
|
95
|
+
label: this._model.language,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
delete this._model.language;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
this._render();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
dispatchModelUpdated(reset) {
|
|
106
|
+
const resetValue = !!reset;
|
|
107
|
+
|
|
108
|
+
this.dispatchEvent(new ModelUpdatedEvent(this._model, resetValue));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
onModelChanged = (m, reset) => {
|
|
112
|
+
this._model = m;
|
|
113
|
+
this.dispatchModelUpdated(reset);
|
|
114
|
+
this._render();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
onConfigurationChanged = (c) => {
|
|
118
|
+
this._configuration = c;
|
|
119
|
+
|
|
120
|
+
this._render();
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
insertImage = (handler) => {
|
|
124
|
+
this.dispatchEvent(new InsertImageEvent(handler));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
onDeleteImage = (src, done) => {
|
|
128
|
+
this.dispatchEvent(new DeleteImageEvent(src, done));
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
insertSound(handler) {
|
|
132
|
+
this.dispatchEvent(new InsertSoundEvent(handler));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
onDeleteSound(src, done) {
|
|
136
|
+
this.dispatchEvent(new DeleteSoundEvent(src, done));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
_render() {
|
|
140
|
+
log('_render');
|
|
141
|
+
|
|
142
|
+
let element = React.createElement(Root, {
|
|
143
|
+
model: this._model,
|
|
144
|
+
configuration: this._configuration,
|
|
145
|
+
onModelChanged: this.onModelChanged,
|
|
146
|
+
onConfigurationChanged: this.onConfigurationChanged,
|
|
147
|
+
imageSupport: {
|
|
148
|
+
add: this.insertImage,
|
|
149
|
+
delete: this.onDeleteImage,
|
|
150
|
+
},
|
|
151
|
+
uploadSoundSupport: {
|
|
152
|
+
add: this.insertSound.bind(this),
|
|
153
|
+
delete: this.onDeleteSound.bind(this),
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
if (!this._root) {
|
|
157
|
+
this._root = createRoot(this);
|
|
158
|
+
}
|
|
159
|
+
this._root.render(element);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
disconnectedCallback() {
|
|
163
|
+
if (this._root) {
|
|
164
|
+
this._root.unmount();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|