@brightspace-ui/labs 2.15.0 → 2.17.0
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/package.json +2 -1
- package/src/components/file-uploader/README.md +83 -0
- package/src/components/file-uploader/file-uploader.js +340 -0
- package/src/components/grade-result/README.md +85 -0
- package/src/components/grade-result/grade-result-icon-button.js +38 -0
- package/src/components/grade-result/grade-result-letter-score.js +91 -0
- package/src/components/grade-result/grade-result-numeric-score.js +108 -0
- package/src/components/grade-result/grade-result-presentational.js +253 -0
- package/src/components/grade-result/grade-result-student-grade-preview.js +139 -0
- package/src/controllers/grade-result/Grade.js +175 -0
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"./components/card-overlay.js": "./src/components/card-overlay/card-overlay.js",
|
|
13
13
|
"./components/community-button.js": "./src/components/community/community-button.js",
|
|
14
14
|
"./components/community-link.js": "./src/components/community/community-link.js",
|
|
15
|
+
"./components/file-uploader.js": "./src/components/file-uploader/file-uploader.js",
|
|
15
16
|
"./components/community-url-factory.js": "./src/components/community/community-url-factory.js",
|
|
16
17
|
"./components/navigation/navigation-band.js": "./src/components/navigation/navigation-band.js",
|
|
17
18
|
"./components/navigation/navigation-button-icon.js": "./src/components/navigation/navigation-button-icon.js",
|
|
@@ -82,5 +83,5 @@
|
|
|
82
83
|
"@lit/context": "^1.1.3",
|
|
83
84
|
"lit": "^3"
|
|
84
85
|
},
|
|
85
|
-
"version": "2.
|
|
86
|
+
"version": "2.17.0"
|
|
86
87
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# File Uploader
|
|
2
|
+
Lit component for uploading files with drag and drop capability. This component does not perform the actual uploading work, it simply provides visual cues and exposes an event when files have been uploaded.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<head>
|
|
9
|
+
<script type="module" src="@brightspace-ui/labs/components/file-uploader.js"></script>
|
|
10
|
+
</head>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Basic Usage with Accessible Label
|
|
14
|
+
|
|
15
|
+
It's important to always provide an accessible label which describes the purpose of the uploader using the `label` attribute. The label will be hidden visually but associated with the upload input for those using assistive technologies such as a screen reader.
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<d2l-labs-file-uploader label="profile picture"></d2l-labs-file-uploader>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Multi-file Uploads
|
|
22
|
+
|
|
23
|
+
To allow for multiple files to be uploaded, add the `multiple` attribute:
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<d2l-labs-file-uploader multiple ...></d2l-labs-file-uploader>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Localization
|
|
30
|
+
|
|
31
|
+
The file uploader will automatically render using the language found on the HTML element -- i.e. `<html lang="fr">`. If the language attribute is not present or isn't supported, the uploader will render in English.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Feedback Messages
|
|
35
|
+
|
|
36
|
+
If you encounter a scenario where you'd like to display feedback about the uploaded file(s) -- like a warning or an error -- use the `feedback` and `feedback-type` attributes.
|
|
37
|
+
|
|
38
|
+
The `feedback-type` defaults to "warning"(using `--d2l-color-feedback-warning`), but can also be set to "error"(using `--d2l-color-feedback-error`):
|
|
39
|
+
```html
|
|
40
|
+
<d2l-labs-file-uploader
|
|
41
|
+
feedback="Sorry, we cannot upload files larger than 1GB.">
|
|
42
|
+
</d2l-labs-file-uploader>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<d2l-labs-file-uploader
|
|
47
|
+
feedback="An error occurred occurred processing the upload."
|
|
48
|
+
feedback-type="error"></d2l-labs-file-uploader>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Feedback Changed Event
|
|
52
|
+
|
|
53
|
+
To listen for when feedback changes within the uploader, register for the `feedback-changed` event.
|
|
54
|
+
|
|
55
|
+
Vanilla JavaScript:
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<d2l-labs-file-uploader id="my-uploader" ...></d2l-labs-file-uploader>
|
|
59
|
+
<script>
|
|
60
|
+
document.getElementById('my-uploader')
|
|
61
|
+
.addEventListener('feedback-changed', function(evt) {
|
|
62
|
+
var feedbackMessage = evt.detail.value;
|
|
63
|
+
console.log(feedbackMessage);
|
|
64
|
+
});
|
|
65
|
+
</script>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Handling Uploaded Files
|
|
69
|
+
|
|
70
|
+
When the user uploads one or more files, a `d2l-file-uploader-files-added` event is fired. To listen for this event, wire up an event listener to the `<d2l-labs-file-uploader>` element. The listener will be passed an event with an array of [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects from the [File API](https://developer.mozilla.org/en/docs/Web/API/File).
|
|
71
|
+
|
|
72
|
+
Vanilla JavaScript:
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<d2l-labs-file-uploader id="my-uploader" ...></d2l-labs-file-uploader>
|
|
76
|
+
<script>
|
|
77
|
+
document.getElementById('my-uploader')
|
|
78
|
+
.addEventListener('d2l-file-uploader-files-added', function(evt) {
|
|
79
|
+
var files = evt.detail.files;
|
|
80
|
+
console.log(files);
|
|
81
|
+
});
|
|
82
|
+
</script>
|
|
83
|
+
```
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import '@brightspace-ui/core/components/colors/colors.js';
|
|
2
|
+
import '@brightspace-ui/core/components/offscreen/offscreen.js';
|
|
3
|
+
import { css, html, LitElement } from 'lit';
|
|
4
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
5
|
+
import { FocusMixin } from '@brightspace-ui/core/mixins/focus/focus-mixin.js';
|
|
6
|
+
import { LocalizeLabsElement } from '../localize-labs-element.js';
|
|
7
|
+
|
|
8
|
+
class FileUploader extends FocusMixin(LocalizeLabsElement(LitElement)) {
|
|
9
|
+
static get properties() {
|
|
10
|
+
return {
|
|
11
|
+
/**
|
|
12
|
+
* When set, displays a feedback message to the user. Used in conjunction with `feedback-type`.
|
|
13
|
+
*/
|
|
14
|
+
feedback: { type: String, reflect: true },
|
|
15
|
+
/**
|
|
16
|
+
* The type of feedback to display. One of: "error", "warning"
|
|
17
|
+
*/
|
|
18
|
+
feedbackType: { type: String, attribute: 'feedback-type', reflect: true },
|
|
19
|
+
/**
|
|
20
|
+
* A descriptive label to associate with the file input. Should
|
|
21
|
+
* be unique enough to distinguish the input from others on the page.
|
|
22
|
+
*/
|
|
23
|
+
label: { type: String },
|
|
24
|
+
/**
|
|
25
|
+
* Whether multiple files can be uploaded.
|
|
26
|
+
*/
|
|
27
|
+
multiple: { type: Boolean, reflect: true },
|
|
28
|
+
/**
|
|
29
|
+
* Collection of uploaded files, as [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects.
|
|
30
|
+
*/
|
|
31
|
+
_files: { type: Object },
|
|
32
|
+
/**
|
|
33
|
+
* Whether a file is currently being dragged over the document.
|
|
34
|
+
*/
|
|
35
|
+
_fileDragOver: { type: Boolean, attribute: '_file-drag-over', reflect: true },
|
|
36
|
+
_inputFocus: { type: Boolean }
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static get styles() {
|
|
41
|
+
return css`
|
|
42
|
+
:host {
|
|
43
|
+
box-sizing: border-box;
|
|
44
|
+
display: block;
|
|
45
|
+
max-width: 27rem;
|
|
46
|
+
}
|
|
47
|
+
:host([feedback-type="warning"]) {
|
|
48
|
+
--d2l-file-uploader-feedback-color: var(--d2l-color-feedback-warning);
|
|
49
|
+
}
|
|
50
|
+
:host([feedback-type="error"]) {
|
|
51
|
+
--d2l-file-uploader-feedback-color: var(--d2l-color-feedback-error);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.d2l-file-uploader-drop-zone {
|
|
55
|
+
border: 2px dashed var(--d2l-color-ferrite);
|
|
56
|
+
border-radius: 0.3rem;
|
|
57
|
+
padding: 1.5rem;
|
|
58
|
+
text-align: center;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
:host([_file-drag-over]) .d2l-file-uploader-drop-zone {
|
|
62
|
+
background-color: var(--d2l-color-celestine-plus-2);
|
|
63
|
+
border-color: var(--d2l-color-celestine);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.d2l-file-uploader-icon {
|
|
67
|
+
padding-bottom: 0.5rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
:host([_file-drag-over]) svg path {
|
|
71
|
+
fill: var(--d2l-color-celestine);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.d2l-file-uploader-input {
|
|
75
|
+
display: inline-block;
|
|
76
|
+
height: 100%;
|
|
77
|
+
left: 0;
|
|
78
|
+
opacity: 0;
|
|
79
|
+
position: absolute;
|
|
80
|
+
top: 0;
|
|
81
|
+
width: 100%;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.d2l-file-uploader-browse-label {
|
|
85
|
+
background: none;
|
|
86
|
+
border: none;
|
|
87
|
+
color: var(--d2l-color-celestine);
|
|
88
|
+
overflow: hidden;
|
|
89
|
+
padding-right: 0;
|
|
90
|
+
position: relative;
|
|
91
|
+
}
|
|
92
|
+
.d2l-file-uploader-browse-label:hover,
|
|
93
|
+
.d2l-file-uploader-browse-label-focus {
|
|
94
|
+
color: var(--d2l-color-celestine-minus-1);
|
|
95
|
+
text-decoration: underline;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.d2l-file-uploader-browse-files {
|
|
99
|
+
display: none;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.d2l-file-uploader-feedback {
|
|
103
|
+
-webkit-animation-duration: 0.7s;
|
|
104
|
+
animation-duration: 0.7s;
|
|
105
|
+
-webkit-animation-name: feedbackIn;
|
|
106
|
+
animation-name: feedbackIn;
|
|
107
|
+
border: 1px solid var(--d2l-color-mica);
|
|
108
|
+
border-left-color: var(--d2l-file-uploader-feedback-color);
|
|
109
|
+
border-left-width: 0.5rem;
|
|
110
|
+
border-radius: 0.3rem;
|
|
111
|
+
display: none;
|
|
112
|
+
margin-bottom: 1.5rem;
|
|
113
|
+
padding: 0.7rem 1rem 0.7rem;
|
|
114
|
+
}
|
|
115
|
+
:host(:dir(rtl)) > .d2l-file-uploader-feedback {
|
|
116
|
+
border-left-color: var(--d2l-color-mica);
|
|
117
|
+
border-left-width: 1px;
|
|
118
|
+
border-right-color: var(--d2l-file-uploader-feedback-color);
|
|
119
|
+
border-right-width: 0.5rem;
|
|
120
|
+
}
|
|
121
|
+
:host([feedback]) > .d2l-file-uploader-feedback {
|
|
122
|
+
display: block;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@keyframes feedbackIn {
|
|
126
|
+
0% {
|
|
127
|
+
max-height: 0.1rem;
|
|
128
|
+
opacity: 0;
|
|
129
|
+
}
|
|
130
|
+
20% {
|
|
131
|
+
opacity: 0;
|
|
132
|
+
}
|
|
133
|
+
80% {
|
|
134
|
+
max-height: 3rem;
|
|
135
|
+
}
|
|
136
|
+
100% {
|
|
137
|
+
opacity: 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@media (max-width: 992px) {
|
|
142
|
+
|
|
143
|
+
.d2l-file-uploader-input-container {
|
|
144
|
+
display: block;
|
|
145
|
+
margin-top: 0.8rem;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.d2l-file-uploader-browse-files {
|
|
149
|
+
display: inline;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.d2l-file-uploader-browse {
|
|
153
|
+
display: none;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.d2l-file-uploader-browse-label {
|
|
157
|
+
background-color: var(--d2l-color-celestine);
|
|
158
|
+
border-color: var(--d2l-color-celestine-minus-1);
|
|
159
|
+
border-radius: 0.3rem;
|
|
160
|
+
border-style: none;
|
|
161
|
+
color: #ffffff;
|
|
162
|
+
display: inline-block;
|
|
163
|
+
font-size: 0.7rem;
|
|
164
|
+
padding: 0.35rem 1.5rem;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.d2l-file-uploader-browse-label:hover,
|
|
168
|
+
.d2l-file-uploader-browse-label-focus {
|
|
169
|
+
background-color: var(--d2l-color-celestine-minus-1);
|
|
170
|
+
color: #ffffff;
|
|
171
|
+
text-decoration: none;
|
|
172
|
+
}
|
|
173
|
+
.d2l-file-uploader-browse-label-focus {
|
|
174
|
+
box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px var(--d2l-color-celestine);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
}
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
constructor() {
|
|
182
|
+
super();
|
|
183
|
+
this.feedbackType = 'warning';
|
|
184
|
+
this.multiple = false;
|
|
185
|
+
this._fileDragOver = false;
|
|
186
|
+
this.__onDragOver = this.__onDragOver.bind(this);
|
|
187
|
+
this.__onDragLeave = this.__onDragLeave.bind(this);
|
|
188
|
+
this.__onDragEnd = this.__onDragEnd.bind(this);
|
|
189
|
+
this.__onDrop = this.__onDrop.bind(this);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
static get focusElementSelector() {
|
|
193
|
+
return '.d2l-focusable';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
connectedCallback() {
|
|
197
|
+
super.connectedCallback();
|
|
198
|
+
document.addEventListener('dragover', this.__onDragOver);
|
|
199
|
+
document.addEventListener('dragleave', this.__onDragLeave);
|
|
200
|
+
document.addEventListener('dragend', this.__onDragEnd);
|
|
201
|
+
document.addEventListener('drop', this.__onDrop);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
disconnectedCallback() {
|
|
205
|
+
super.disconnectedCallback();
|
|
206
|
+
document.removeEventListener('dragover', this.__onDragOver);
|
|
207
|
+
document.removeEventListener('dragleave', this.__onDragLeave);
|
|
208
|
+
document.removeEventListener('dragend', this.__onDragEnd);
|
|
209
|
+
document.removeEventListener('drop', this.__onDrop);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
render() {
|
|
213
|
+
const labelClasses = {
|
|
214
|
+
'd2l-file-uploader-browse-label': true,
|
|
215
|
+
'd2l-file-uploader-browse-label-focus': this._inputFocus,
|
|
216
|
+
};
|
|
217
|
+
return html`
|
|
218
|
+
<div class="d2l-file-uploader-feedback" role="alert">${this.feedback}</div>
|
|
219
|
+
<div class="d2l-file-uploader-drop-zone">
|
|
220
|
+
<svg width="78" height="78" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 78 78" class="d2l-file-uploader-icon">
|
|
221
|
+
<path fill="#565a5c" d="M54.76 36A3.992 3.992 0 0 1 51 38.64h-9v36a3 3 0 0 1-6 0v-36h-9a4 4 0 0 1-2.56-7.07l12-10a3.988 3.988 0 0 1 5.12 0l12 10a4.007 4.007 0 0 1 1.2 4.43zM46 53.55a1.333 1.333 0 0 0 .49.09M46.5 50.64a1.386 1.386 0 0 0-.5.09"></path>
|
|
222
|
+
<path fill="#565a5c" d="M78 36.14a17.52 17.52 0 0 1-17.5 17.5c-.17 0-1.33 0-1.5-.02l-12.51.02a1.333 1.333 0 0 1-.49-.09 1.494 1.494 0 0 1 0-2.82 1.386 1.386 0 0 1 .5-.09h14a14.5 14.5 0 0 0 1.58-28.92c-.52-.05-1.05-.08-1.58-.08h-.35a1.49 1.49 0 0 1-1-.4 2.258 2.258 0 0 1-.542-1.075c-.138-.462-.306-.916-.478-1.365a20.484 20.484 0 0 0-38.26 0q-.21.54-.39 1.08a3.353 3.353 0 0 1-.53 1.26 1.542 1.542 0 0 1-1.12.5h-.33c-.53 0-1.06.03-1.58.08a14.5 14.5 0 0 0 1.58 28.92h13.99a1.5 1.5 0 0 1 .01 3s-13.5 0-13.5-.02a4.176 4.176 0 0 1-.5.02 17.5 17.5 0 0 1-.77-34.98 23.49 23.49 0 0 1 44.54 0A17.52 17.52 0 0 1 78 36.14z"></path>
|
|
223
|
+
</svg>
|
|
224
|
+
<div>
|
|
225
|
+
<span>${this.localize('components:fileUploader:file_upload_text')} </span>
|
|
226
|
+
<span class="d2l-file-uploader-input-container">
|
|
227
|
+
<d2l-offscreen id="d2l-file-uploader-offscreen">${this.label}</d2l-offscreen>
|
|
228
|
+
<label class="${classMap(labelClasses)}">
|
|
229
|
+
<input class="d2l-file-uploader-input d2l-focusable" type="file" aria-describedby="d2l-file-uploader-offscreen" ?multiple=${this.multiple} @change=${this._fileSelectHandler} @focus=${this.__onInputFocus} @blur=${this.__onInputBlur}>
|
|
230
|
+
<span class="d2l-file-uploader-browse">${this.localize('components:fileUploader:browse')}</span>
|
|
231
|
+
<span class="d2l-file-uploader-browse-files">${this.localize('components:fileUploader:browse_files')}</span>
|
|
232
|
+
</label>
|
|
233
|
+
</span>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
willUpdate(changedProperties) {
|
|
241
|
+
super.willUpdate(changedProperties);
|
|
242
|
+
if (changedProperties.has('feedback')) {
|
|
243
|
+
this.dispatchEvent(new CustomEvent('feedback-changed',
|
|
244
|
+
{ bubbles: true, composed: true, detail: { value: this.feedback } }
|
|
245
|
+
));
|
|
246
|
+
}
|
|
247
|
+
if (changedProperties.has('_files')) {
|
|
248
|
+
this._fileChangeHandler(this._files);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
__onDragEnd(event) {
|
|
254
|
+
this._fileDragOver = false;
|
|
255
|
+
const dataTransfer = event.dataTransfer;
|
|
256
|
+
if (dataTransfer.items) {
|
|
257
|
+
// Use DataTransferItemList interface to remove the drag data
|
|
258
|
+
for (let i = 0; i < dataTransfer.items.length; i++) {
|
|
259
|
+
dataTransfer.items.remove(i);
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
// Use DataTransfer interface to remove the drag data
|
|
263
|
+
event.dataTransfer.clearData();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
__onDragLeave(event) {
|
|
268
|
+
event.preventDefault();
|
|
269
|
+
this._fileDragOver = false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
__onDragOver(event) {
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
this._fileDragOver = true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
__onDrop(event) {
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
this._fileDragOver = false;
|
|
280
|
+
const files = [];
|
|
281
|
+
const dataTransfer = event.dataTransfer;
|
|
282
|
+
if (dataTransfer.items) {
|
|
283
|
+
if (!this.multiple && dataTransfer.items.length > 1) {
|
|
284
|
+
this.feedback = this.localize('components:fileUploader:choose_one_file_to_upload');
|
|
285
|
+
this.feedbackType = 'warning';
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
this.feedback = null;
|
|
289
|
+
|
|
290
|
+
// Use DataTransferItemList interface to access the file(s)
|
|
291
|
+
let count = 0;
|
|
292
|
+
for (let i = 0; i < dataTransfer.items.length; i++) {
|
|
293
|
+
if (dataTransfer.items[ i ].kind === 'file') {
|
|
294
|
+
files[count] = dataTransfer.items[i].getAsFile();
|
|
295
|
+
count++;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} else {
|
|
299
|
+
if (!this.multiple && dataTransfer.files.length > 1) {
|
|
300
|
+
this.feedback = this.localize('components:fileUploader:choose_one_file_to_upload');
|
|
301
|
+
this.feedbackType = 'warning';
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
this.feedback = null;
|
|
305
|
+
|
|
306
|
+
// Use DataTransfer interface to access the file(s)
|
|
307
|
+
for (let j = 0; j < dataTransfer.files.length; j++) {
|
|
308
|
+
files[j] = dataTransfer.files[j];
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
this._files = files;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
__onInputBlur() {
|
|
315
|
+
this._inputFocus = false;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
__onInputFocus() {
|
|
319
|
+
this._inputFocus = true;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
_fileChangeHandler(files) {
|
|
323
|
+
const evt = new CustomEvent(
|
|
324
|
+
'd2l-file-uploader-files-added',
|
|
325
|
+
{ detail: { files: files } }
|
|
326
|
+
);
|
|
327
|
+
this.dispatchEvent(evt);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
_fileSelectHandler(event) {
|
|
331
|
+
const files = [];
|
|
332
|
+
for (let i = 0; i < event.target.files.length; i++) {
|
|
333
|
+
files[i] = event.target.files[i];
|
|
334
|
+
}
|
|
335
|
+
this._files = files;
|
|
336
|
+
event.target.value = '';
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
customElements.define('d2l-labs-file-uploader', FileUploader);
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @d2l/labs-grade-result
|
|
2
|
+
|
|
3
|
+
A web component used for rendering grades in Brightspace
|
|
4
|
+
|
|
5
|
+
## Properties
|
|
6
|
+
|
|
7
|
+
#### d2l-labs-d2l-grade-result
|
|
8
|
+
|
|
9
|
+
| Property | Type | Default | Description |
|
|
10
|
+
| ----------------------------------| --------- | ------- | ------------------------------------------------------------ |
|
|
11
|
+
| `href` | `string` | `''` | The Hypermedia route to power the component. This component runs off of the /grade route or an activity. |
|
|
12
|
+
| `token` | `string` | `''` | For authentication |
|
|
13
|
+
| `disableAutoSave` | `boolean` | `false` | Prevent the component from automatically saving the grade to the API when the grade is changed. |
|
|
14
|
+
| `_hideTitle` | `boolean` | `false` | This property will hide the "Overall Grade" title above the component. |
|
|
15
|
+
| `customManualOverrideText` | `string` | `undefined` | This properly will substitute the stock text on the "Manual Override" button. |
|
|
16
|
+
| `customManualOverrideClearText` | `string` | `undefined` | This properly will substitute the stock text on the "Clear Manual Override" button. |
|
|
17
|
+
|
|
18
|
+
##### Public Methods
|
|
19
|
+
|
|
20
|
+
| Method | Description |
|
|
21
|
+
| ------------------------------ | ------------------------------------------------------------ |
|
|
22
|
+
| `saveGrade(): void` | This is the method used to manually save the grade to the server when `disableAutoSave = true`. This method will emit `@d2l-grade-result-grade-saved-success` or `@d2l-grade-result-grade-saved-error`. |
|
|
23
|
+
| `hasUnsavedChanges(): boolean` | Determines whether the grade has been changed by the user and has not been saved to the server yet. |
|
|
24
|
+
|
|
25
|
+
If you are only interested in rendering the presentational layer of the component, you can simply use the `d2l-grade-result-presentational` component.
|
|
26
|
+
|
|
27
|
+
#### d2l-labs-d2l-grade-result-presentational
|
|
28
|
+
|
|
29
|
+
| Property | GradeType | Type | Default | Description |
|
|
30
|
+
| ----------------------------------| -------------- | --------------------------- | ----------- | ------------------------------------------------------------ |
|
|
31
|
+
| `gradeType` | All | `string ('Numeric' or 'LetterGrade')` | `'Numeric'` | Specifies the type of grade that the component is meant to render. |
|
|
32
|
+
| `labelText` | All | `string` | `''` | The text that appears above the component. |
|
|
33
|
+
| `scoreNumerator` | Numeric | `number` | `0` | The numerator of the numeric score that is given. |
|
|
34
|
+
| `scoreDenominator` | Numeric | `number` | `0` | The denominator of the numeric score that is given. |
|
|
35
|
+
| `selectedLetterGrade` | LetterGrade | `string` | `''` | The current selected letter grade of the options given. |
|
|
36
|
+
| `letterGradeOptions` | LetterGrade | `Object` | `null` | A dictionary where the key is a unique id and the value is an object containing the LetterGrade text and the PercentStart. |
|
|
37
|
+
| `includeGradeButton` | All | `boolean` | `false` | Determines whether the grades icon button is rendered. |
|
|
38
|
+
| `includeReportsButton` | All | `boolean` | `false` | Determines whether the reports icon button is rendered. |
|
|
39
|
+
| `gradeButtonTooltip` | All | `string` | `''` | The text that is inside of the tooltip when hovering over the grades button. |
|
|
40
|
+
| `reportsButtonTooltip` | All | `string` | `''` | The text that is inside of the tooltip when hovering over the reports button. |
|
|
41
|
+
| `readOnly` | All | `boolean` | `false` | Set to `true` if the user does not have permissions to edit the grade. |
|
|
42
|
+
| `isManualOverrideActive` | All | `boolean` | `false` | Set to `true` if the user is currently manually overriding the grade. This will display the button to 'Clear Manual Override'. |
|
|
43
|
+
| `hideTitle` | All | `boolean` | `false` | This property will hide the "Overall Grade" title above the component. |
|
|
44
|
+
| `customManualOverrideClearText` | All | `string` | `undefined` | This property will substitute the stock text on the "Clear Manual Override" button. |
|
|
45
|
+
| `subtitleText` | All | `string` | `undefined` | This property will show the given text under the title. |
|
|
46
|
+
| `required` | Numeric | `Boolean` | `false` | Set to `true` if an undefined/blank grade is not considered valid |
|
|
47
|
+
| `inputLabelText` | Numeric | `string` | `''` | This property sets the label that will be used inside the aria-label and validation error tool-tips |
|
|
48
|
+
| `allowNegativeScore` | Numeric | `boolean` | `'false'` | Set to `true` if negative scores can be entered |
|
|
49
|
+
| `showFlooredScoreWarning` | Numeric | `boolean` | `'false'` | Set to `true` if displaying a negative grade that has been floored at 0 |
|
|
50
|
+
|
|
51
|
+
## Events
|
|
52
|
+
|
|
53
|
+
#### d2l-labs-d2l-grade-result
|
|
54
|
+
|
|
55
|
+
| Event | Description |
|
|
56
|
+
| ----------------------------------------------- | ------------------------------------------------------------ |
|
|
57
|
+
| `@d2l-grade-result-initialized-success` | This event is fired when the component is successfully initialized and a grade is loaded from the API. |
|
|
58
|
+
| `@d2l-grade-result-initialized-error` | This event is fired when there is an error initializing the component. This is usually caused by an invalid `href` or `token`. |
|
|
59
|
+
| `@d2l-grade-result-grade-updated-success` | This event is fired when the grade is successfully updated on the frontend. |
|
|
60
|
+
| `@d2l-grade-result-grade-updated-error` | This event is fired when there is an error updating the grade on the frontend. |
|
|
61
|
+
| `@d2l-grade-result-grade-saved-success` | This event is fired when the grade is successfully saved to the server. |
|
|
62
|
+
| `@d2l-grade-result-grade-saved-error` | This event is fired when there is an error while saving the grade to the server. |
|
|
63
|
+
| `@d2l-grade-result-grade-button-click` | This event is fired when the grades button is clicked. |
|
|
64
|
+
| `@d2l-grade-result-reports-button-click` | This event is fired when the reports button is clicked. |
|
|
65
|
+
| `@d2l-grade-result-manual-override-clear-click` | This event is fired when the manual override clear is clicked. |
|
|
66
|
+
|
|
67
|
+
#### d2l-labs-d2l-grade-result-presentational
|
|
68
|
+
|
|
69
|
+
| Event | Description |
|
|
70
|
+
| ----------------------------------------------- | ------------------------------------------------------------ |
|
|
71
|
+
| `@d2l-grade-result-grade-button-click` | This event is fired when the grades button is clicked. |
|
|
72
|
+
| `@d2l-grade-result-reports-button-click` | This event is fired when the reports button is clicked. |
|
|
73
|
+
| `@d2l-grade-result-grade-change` | This event is fired on the change of the grade for a `gradeType="Numeric"` grade. |
|
|
74
|
+
| `@d2l-grade-result-letter-score-selected` | This event is fired on the change of the grade for a `gradeType="LetterGrade"` grade. |
|
|
75
|
+
| `@d2l-grade-result-manual-override-clear-click` | This event is fired when the manual override clear is clicked. |
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<script type="module">
|
|
82
|
+
import '@d2l/labs-grade-result/d2l-grade-result.js';
|
|
83
|
+
</script>
|
|
84
|
+
<d2l-labs-d2l-grade-result href="href" token="token" disableAutoSave _hideTitle>My element</d2l-labs-d2l-grade-result>
|
|
85
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import '@brightspace-ui/core/components/button/button-icon.js';
|
|
2
|
+
import { html, LitElement } from 'lit';
|
|
3
|
+
import { getUniqueId } from '@brightspace-ui/core/helpers/uniqueId.js';
|
|
4
|
+
|
|
5
|
+
export class D2LGradeResultIconButton extends LitElement {
|
|
6
|
+
static get properties() {
|
|
7
|
+
return {
|
|
8
|
+
text: { type: String },
|
|
9
|
+
icon: { type: String },
|
|
10
|
+
_id: { type: String },
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this._id = getUniqueId();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
render() {
|
|
20
|
+
return html`
|
|
21
|
+
<d2l-button-icon
|
|
22
|
+
id="d2l-grade-result-icon-button-${this._id}"
|
|
23
|
+
icon="${this.icon}"
|
|
24
|
+
@click="${this._onClick}"
|
|
25
|
+
text="${this.text}"
|
|
26
|
+
></d2l-button-icon>
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_onClick() {
|
|
31
|
+
this.dispatchEvent(new CustomEvent('d2l-grade-result-icon-button-click', {
|
|
32
|
+
bubbles: true,
|
|
33
|
+
composed: true,
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
}
|
|
38
|
+
customElements.define('d2l-grade-result-icon-button', D2LGradeResultIconButton);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { css, html, LitElement } from 'lit';
|
|
2
|
+
import { bodyStandardStyles } from '@brightspace-ui/core/components/typography/styles.js';
|
|
3
|
+
import { LocalizeLabsElement } from '../localize-labs-element.js';
|
|
4
|
+
import { selectStyles } from '@brightspace-ui/core/components/inputs/input-select-styles.js';
|
|
5
|
+
|
|
6
|
+
export class D2LGradeResultLetterScore extends LocalizeLabsElement(LitElement) {
|
|
7
|
+
|
|
8
|
+
static get properties() {
|
|
9
|
+
return {
|
|
10
|
+
availableOptions: { type: Object },
|
|
11
|
+
label: { type: String },
|
|
12
|
+
selectedOption: { type: String },
|
|
13
|
+
readOnly: { type: Boolean }
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static get styles() {
|
|
18
|
+
return [selectStyles, bodyStandardStyles, css`
|
|
19
|
+
.d2l-grade-result-letter-score-container {
|
|
20
|
+
width: 8rem;
|
|
21
|
+
}
|
|
22
|
+
.d2l-grade-result-letter-score-select {
|
|
23
|
+
width: 100%;
|
|
24
|
+
}
|
|
25
|
+
.d2l-grade-result-letter-score-score-read-only {
|
|
26
|
+
height: calc(2rem + 2px);
|
|
27
|
+
line-height: calc(2rem + 2px);
|
|
28
|
+
}
|
|
29
|
+
`];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
super();
|
|
34
|
+
this.availableOptions = null;
|
|
35
|
+
this.selectedOption = '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
render() {
|
|
39
|
+
if (!this.readOnly) {
|
|
40
|
+
return html`
|
|
41
|
+
<div class="d2l-grade-result-letter-score-container">
|
|
42
|
+
<select
|
|
43
|
+
id="d2l-grade"
|
|
44
|
+
aria-label=${this.label ? this.label : this.localize('components:gradeResult:gradeScoreLabel')}
|
|
45
|
+
class="d2l-input-select d2l-grade-result-letter-score-select"
|
|
46
|
+
@change=${this._onOptionSelected}
|
|
47
|
+
.value=${this.selectedOption}>
|
|
48
|
+
${this._renderOptions()}
|
|
49
|
+
</select>
|
|
50
|
+
</div>
|
|
51
|
+
`;
|
|
52
|
+
} else {
|
|
53
|
+
return html`
|
|
54
|
+
<div class="d2l-grade-result-letter-score-score-read-only">
|
|
55
|
+
<span id="d2l-grade" class="d2l-body-standard">${this._selectedOptionText()}</span>
|
|
56
|
+
</div>
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_onOptionSelected(e) {
|
|
62
|
+
this.dispatchEvent(new CustomEvent('d2l-grade-result-letter-score-selected', {
|
|
63
|
+
composed: true,
|
|
64
|
+
bubbles: true,
|
|
65
|
+
detail: {
|
|
66
|
+
value: e.target.value
|
|
67
|
+
}
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_renderOptions() {
|
|
72
|
+
const itemTemplate = [];
|
|
73
|
+
for (const [id, option] of Object.entries(this.availableOptions)) {
|
|
74
|
+
if (this.selectedOption === id) {
|
|
75
|
+
itemTemplate.push(html`<option selected value=${id}>${option.LetterGrade}</option>`);
|
|
76
|
+
} else {
|
|
77
|
+
itemTemplate.push(html`<option value=${id}>${option.LetterGrade}</option>`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return itemTemplate;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_selectedOptionText() {
|
|
84
|
+
if (this.availableOptions[this.selectedOption]) {
|
|
85
|
+
return this.availableOptions[this.selectedOption].LetterGrade;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
customElements.define('d2l-grade-result-letter-score', D2LGradeResultLetterScore);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import '@brightspace-ui/core/components/inputs/input-number.js';
|
|
2
|
+
import '@brightspace-ui/core/components/offscreen/offscreen.js';
|
|
3
|
+
import { bodyCompactStyles, bodyStandardStyles } from '@brightspace-ui/core/components/typography/styles.js';
|
|
4
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
5
|
+
import { LocalizeLabsElement } from '../localize-labs-element.js';
|
|
6
|
+
|
|
7
|
+
const numberConverter = {
|
|
8
|
+
fromAttribute: (attr) => { return !attr ? undefined : Number(attr); },
|
|
9
|
+
toAttribute: (prop) => { return String(prop); }
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const EXTRA_SPACE = 2.5;
|
|
13
|
+
const MIN_WIDTH = 5.5;
|
|
14
|
+
const MIN_NEGATIVE_GRADE = -9999999999;
|
|
15
|
+
const MIN_POSITIVE_GRADE = 0;
|
|
16
|
+
|
|
17
|
+
export class D2LGradeResultNumericScore extends LocalizeLabsElement(LitElement) {
|
|
18
|
+
static get properties() {
|
|
19
|
+
return {
|
|
20
|
+
label: { type: String },
|
|
21
|
+
scoreNumerator: { type: Number, converter: numberConverter },
|
|
22
|
+
scoreDenominator: { type: Number },
|
|
23
|
+
readOnly: { type: Boolean },
|
|
24
|
+
required: { type: Boolean },
|
|
25
|
+
allowNegativeScore: { type: Boolean },
|
|
26
|
+
showFlooredScoreWarning: { type: Boolean },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static get styles() {
|
|
31
|
+
return [bodyCompactStyles, bodyStandardStyles, css`
|
|
32
|
+
.d2l-grade-result-numeric-score-container {
|
|
33
|
+
align-items: center;
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: row;
|
|
36
|
+
}
|
|
37
|
+
.d2l-grade-result-numeric-score-score-read-only {
|
|
38
|
+
height: calc(2rem + 2px);
|
|
39
|
+
line-height: calc(2rem + 2px);
|
|
40
|
+
max-width: 5.25rem;
|
|
41
|
+
}
|
|
42
|
+
.d2l-grade-result-numeric-score-hint {
|
|
43
|
+
margin: 0 0.3rem;
|
|
44
|
+
}
|
|
45
|
+
`];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
render() {
|
|
49
|
+
const roundedNumerator = isNaN(this.scoreNumerator) ? '' : Math.round((this.scoreNumerator + Number.EPSILON) * 100) / 100;
|
|
50
|
+
|
|
51
|
+
const denominatorLength = isNaN(this.scoreDenominator) ? 0 : this.scoreDenominator.toString().length;
|
|
52
|
+
const numeratorLength = roundedNumerator.toString().length;
|
|
53
|
+
const dynamicWidth = numeratorLength <= denominatorLength ? denominatorLength + EXTRA_SPACE : (numeratorLength * 0.5) + (denominatorLength * 0.5) + EXTRA_SPACE;
|
|
54
|
+
|
|
55
|
+
return html`
|
|
56
|
+
<div class="d2l-grade-result-numeric-score-container">
|
|
57
|
+
${!this.readOnly ? html`
|
|
58
|
+
<div class="d2l-grade-result-numeric-score-score">
|
|
59
|
+
<d2l-form>
|
|
60
|
+
<d2l-input-number
|
|
61
|
+
?required=${this.required}
|
|
62
|
+
id="d2l-grade"
|
|
63
|
+
label=${this.label ? this.label : this.localize('components:gradeResult:gradeScoreLabel')}
|
|
64
|
+
label-hidden
|
|
65
|
+
value="${this.scoreNumerator}"
|
|
66
|
+
input-width="${dynamicWidth > MIN_WIDTH ? dynamicWidth : MIN_WIDTH}rem"
|
|
67
|
+
min="${this.allowNegativeScore ? MIN_NEGATIVE_GRADE : MIN_POSITIVE_GRADE}"
|
|
68
|
+
max="9999999999"
|
|
69
|
+
max-fraction-digits="2"
|
|
70
|
+
unit="/ ${this.scoreDenominator}"
|
|
71
|
+
unit-label=${this.localize('components:gradeResult:outOfDenominator', { denominator: this.scoreDenominator })}
|
|
72
|
+
value-align="end"
|
|
73
|
+
@change=${this._onGradeChange}
|
|
74
|
+
></d2l-input-number>
|
|
75
|
+
</d2l-form>
|
|
76
|
+
</div>
|
|
77
|
+
` : html`
|
|
78
|
+
<div
|
|
79
|
+
aria-hidden="true"
|
|
80
|
+
class="d2l-body-standard d2l-grade-result-numeric-score-score-read-only"
|
|
81
|
+
id="d2l-grade">
|
|
82
|
+
${roundedNumerator} / ${this.scoreDenominator}
|
|
83
|
+
</div>
|
|
84
|
+
<d2l-offscreen>${this.localize('components:gradeResult:numeratorOutOfDenominator', { numerator: roundedNumerator, denominator: this.scoreDenominator })}</d2l-offscreen>
|
|
85
|
+
`}
|
|
86
|
+
${this.showFlooredScoreWarning ? html`
|
|
87
|
+
<div class="d2l-grade-result-numeric-score-hint d2l-body-compact">
|
|
88
|
+
${this.localize('components:gradeResult:cannotBeNegative')}
|
|
89
|
+
</div>
|
|
90
|
+
` : nothing}
|
|
91
|
+
</div>
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
_onGradeChange(e) {
|
|
96
|
+
const newScore = e.target.value;
|
|
97
|
+
this.dispatchEvent(new CustomEvent('d2l-grade-result-grade-change', {
|
|
98
|
+
bubbles: true,
|
|
99
|
+
composed: true,
|
|
100
|
+
detail: {
|
|
101
|
+
value: newScore
|
|
102
|
+
}
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
customElements.define('d2l-grade-result-numeric-score', D2LGradeResultNumericScore);
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import './grade-result-icon-button.js';
|
|
2
|
+
import './grade-result-numeric-score.js';
|
|
3
|
+
import './grade-result-letter-score.js';
|
|
4
|
+
import './grade-result-student-grade-preview.js';
|
|
5
|
+
import '@brightspace-ui/core/components/button/button-subtle.js';
|
|
6
|
+
import { bodySmallStyles, labelStyles } from '@brightspace-ui/core/components/typography/styles.js';
|
|
7
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
8
|
+
import { GradeType } from '../../controllers/grade-result/Grade.js';
|
|
9
|
+
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
10
|
+
import { LocalizeLabsElement } from '../localize-labs-element.js';
|
|
11
|
+
|
|
12
|
+
const numberConverter = {
|
|
13
|
+
fromAttribute: (attr) => { return !attr ? undefined : Number(attr); },
|
|
14
|
+
toAttribute: (prop) => { return String(prop); }
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class D2LGradeResultPresentational extends LocalizeLabsElement(LitElement) {
|
|
18
|
+
static get properties() {
|
|
19
|
+
return {
|
|
20
|
+
allowNegativeScore: { type: Boolean },
|
|
21
|
+
customManualOverrideClearText: { type: String },
|
|
22
|
+
displayStudentGradePreview: { type: Boolean, attribute: 'display-student-grade-preview' },
|
|
23
|
+
gradeButtonTooltip: { type: String },
|
|
24
|
+
gradeType: { type: String },
|
|
25
|
+
hideTitle: { type: Boolean },
|
|
26
|
+
includeGradeButton: { type: Boolean },
|
|
27
|
+
includeReportsButton: { type: Boolean },
|
|
28
|
+
inputLabelText: { type: String },
|
|
29
|
+
isManualOverrideActive: { type: Boolean },
|
|
30
|
+
labelHeadingLevel: { type: Number },
|
|
31
|
+
labelText: { type: String },
|
|
32
|
+
letterGradeOptions: { type: Object },
|
|
33
|
+
readOnly: { type: Boolean },
|
|
34
|
+
reportsButtonTooltip: { type: String },
|
|
35
|
+
required: { type: Boolean },
|
|
36
|
+
scoreDenominator: { type: Number },
|
|
37
|
+
scoreNumerator: { type: Number, converter: numberConverter },
|
|
38
|
+
selectedLetterGrade: { type: String },
|
|
39
|
+
showFlooredScoreWarning: { type: Boolean },
|
|
40
|
+
subtitleText: { type: String },
|
|
41
|
+
studentGradePreview: { type: Object, attribute: 'student-grade-preview' },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static get styles() {
|
|
46
|
+
return [ bodySmallStyles, labelStyles, css`
|
|
47
|
+
.d2l-grade-result-presentational-container {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-wrap: wrap;
|
|
50
|
+
gap: 0 0.9rem;
|
|
51
|
+
}
|
|
52
|
+
.d2l-grade-result-presentational-score-container {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-wrap: wrap;
|
|
55
|
+
gap: 0.3rem;
|
|
56
|
+
}
|
|
57
|
+
.d2l-grade-result-manual-override-clear {
|
|
58
|
+
margin-top: 0.3rem;
|
|
59
|
+
}
|
|
60
|
+
.d2l-label-text {
|
|
61
|
+
line-height: 1.6rem;
|
|
62
|
+
margin-bottom: 0.4rem;
|
|
63
|
+
}
|
|
64
|
+
.d2l-grade-result-presentational-subtitle {
|
|
65
|
+
font-weight: bold;
|
|
66
|
+
margin-top: -4px;
|
|
67
|
+
}
|
|
68
|
+
`];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
constructor() {
|
|
72
|
+
super();
|
|
73
|
+
this.allowNegativeScore = false;
|
|
74
|
+
this.customManualOverrideClearText = undefined;
|
|
75
|
+
this.hideTitle = false;
|
|
76
|
+
this.includeGradeButton = false;
|
|
77
|
+
this.includeReportsButton = false;
|
|
78
|
+
this.isManualOverrideActive = false;
|
|
79
|
+
this.labelHeadingLevel = undefined;
|
|
80
|
+
this.readOnly = false;
|
|
81
|
+
this.selectedLetterGrade = '';
|
|
82
|
+
this.showFlooredScoreWarning = false;
|
|
83
|
+
this.subtitleText = undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
render() {
|
|
87
|
+
return html`
|
|
88
|
+
<div class="d2l-grade-result-presentational-container">
|
|
89
|
+
<div>
|
|
90
|
+
${this._renderScoreLabel()}
|
|
91
|
+
${this._renderScoreSubtitle()}
|
|
92
|
+
<div class="d2l-grade-result-presentational-score-container">
|
|
93
|
+
${this._renderScoreComponent()}
|
|
94
|
+
${this._renderGradeIconButton()}
|
|
95
|
+
${this._renderGradeReportIconButton()}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
${this._renderStudentGradePreview()}
|
|
99
|
+
</div>
|
|
100
|
+
${this._renderManualOverrideButtonComponent()}
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_isReadOnly() {
|
|
105
|
+
return Boolean(this.readOnly);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
_onGradeButtonClick() {
|
|
109
|
+
this.dispatchEvent(new CustomEvent('d2l-grade-result-grade-button-click', {
|
|
110
|
+
bubbles: true,
|
|
111
|
+
composed: true,
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_onManualOverrideClearClick() {
|
|
116
|
+
this.dispatchEvent(new CustomEvent('d2l-grade-result-manual-override-clear-click', {
|
|
117
|
+
bubbles: true,
|
|
118
|
+
composed: true
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_onReportsButtonClick() {
|
|
123
|
+
this.dispatchEvent(new CustomEvent('d2l-grade-result-reports-button-click', {
|
|
124
|
+
bubbles: true,
|
|
125
|
+
composed: true,
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
_renderGradeIconButton() {
|
|
130
|
+
if (!this.includeGradeButton) {
|
|
131
|
+
return nothing;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return html`
|
|
135
|
+
<d2l-grade-result-icon-button
|
|
136
|
+
icon="tier1:grade"
|
|
137
|
+
text=${this.gradeButtonTooltip}
|
|
138
|
+
@d2l-grade-result-icon-button-click=${this._onGradeButtonClick}
|
|
139
|
+
></d2l-grade-result-icon-button>
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
_renderGradeReportIconButton() {
|
|
144
|
+
if (!this.includeReportsButton) {
|
|
145
|
+
return nothing;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return html`
|
|
149
|
+
<d2l-grade-result-icon-button
|
|
150
|
+
icon="tier1:reports"
|
|
151
|
+
text=${this.reportsButtonTooltip}
|
|
152
|
+
@d2l-grade-result-icon-button-click=${this._onReportsButtonClick}
|
|
153
|
+
></d2l-grade-result-icon-button>
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_renderLetterScoreComponent() {
|
|
158
|
+
return html`
|
|
159
|
+
<d2l-grade-result-letter-score
|
|
160
|
+
.availableOptions=${this.letterGradeOptions}
|
|
161
|
+
.label=${this.inputLabelText}
|
|
162
|
+
.readOnly=${this._isReadOnly()}
|
|
163
|
+
.selectedOption=${this.selectedLetterGrade}
|
|
164
|
+
></d2l-grade-result-letter-score>
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_renderManualOverrideButtonComponent() {
|
|
169
|
+
if (!this.isManualOverrideActive) {
|
|
170
|
+
return nothing;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const text = this.customManualOverrideClearText ? this.customManualOverrideClearText : this.localize('components:gradeResult:clearManualOverride');
|
|
174
|
+
|
|
175
|
+
return html`
|
|
176
|
+
<d2l-button-subtle
|
|
177
|
+
class="d2l-grade-result-manual-override-clear"
|
|
178
|
+
icon="tier1:close-default"
|
|
179
|
+
text=${text}
|
|
180
|
+
@click=${this._onManualOverrideClearClick}
|
|
181
|
+
></d2l-button-subtle>
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
_renderNumericScoreComponent() {
|
|
186
|
+
return html`
|
|
187
|
+
<d2l-grade-result-numeric-score
|
|
188
|
+
?allowNegativeScore=${this.allowNegativeScore}
|
|
189
|
+
.label=${this.inputLabelText}
|
|
190
|
+
.readOnly=${this._isReadOnly()}
|
|
191
|
+
?required=${this.required}
|
|
192
|
+
.scoreDenominator=${this.scoreDenominator}
|
|
193
|
+
.scoreNumerator=${this.scoreNumerator}
|
|
194
|
+
?showFlooredScoreWarning=${this.showFlooredScoreWarning}
|
|
195
|
+
></d2l-grade-result-numeric-score>
|
|
196
|
+
`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
_renderScoreComponent() {
|
|
200
|
+
if (this.gradeType === GradeType.Number) {
|
|
201
|
+
return this._renderNumericScoreComponent();
|
|
202
|
+
} else if (this.gradeType === GradeType.Letter) {
|
|
203
|
+
return this._renderLetterScoreComponent();
|
|
204
|
+
} else {
|
|
205
|
+
throw new Error('INVALID GRADE TYPE PROVIDED');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
_renderScoreLabel() {
|
|
210
|
+
if (this.hideTitle || !this.labelText) {
|
|
211
|
+
return nothing;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return html`
|
|
215
|
+
<label
|
|
216
|
+
aria-level=${ifDefined(this.labelHeadingLevel)}
|
|
217
|
+
class="d2l-label-text"
|
|
218
|
+
for="d2l-grade"
|
|
219
|
+
role=${this.labelHeadingLevel ? 'heading' : ''}>
|
|
220
|
+
${this.labelText}
|
|
221
|
+
</label>
|
|
222
|
+
`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_renderScoreSubtitle() {
|
|
226
|
+
if (!this.subtitleText) {
|
|
227
|
+
return nothing;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return html`
|
|
231
|
+
<div class="d2l-grade-result-presentational-subtitle d2l-body-small">
|
|
232
|
+
${this.subtitleText}
|
|
233
|
+
</div>
|
|
234
|
+
`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
_renderStudentGradePreview() {
|
|
238
|
+
if (!this.studentGradePreview) {
|
|
239
|
+
return nothing;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return html`
|
|
243
|
+
<d2l-grade-result-student-grade-preview
|
|
244
|
+
?hidden=${!this.displayStudentGradePreview}
|
|
245
|
+
out-of=${this.scoreDenominator}
|
|
246
|
+
.studentGradePreview=${this.studentGradePreview}
|
|
247
|
+
></d2l-grade-result-student-grade-preview>
|
|
248
|
+
`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
customElements.define('d2l-labs-d2l-grade-result-presentational', D2LGradeResultPresentational);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import '@brightspace-ui/core/components/offscreen/offscreen.js';
|
|
2
|
+
import { bodyCompactStyles, bodySmallStyles, labelStyles } from '@brightspace-ui/core/components/typography/styles.js';
|
|
3
|
+
import { css, html, LitElement, nothing } from 'lit';
|
|
4
|
+
import { formatNumber } from '@brightspace-ui/intl/lib/number.js';
|
|
5
|
+
import { LocalizeLabsElement } from '../localize-labs-element.js';
|
|
6
|
+
|
|
7
|
+
const previewOptions = {
|
|
8
|
+
colour: 'colour',
|
|
9
|
+
score: 'score',
|
|
10
|
+
symbol: 'symbol'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class D2LGradeResultStudentGradePreview extends LocalizeLabsElement(LitElement) {
|
|
14
|
+
|
|
15
|
+
static get properties() {
|
|
16
|
+
return {
|
|
17
|
+
hideLabel: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
attribute: 'hide-label'
|
|
20
|
+
},
|
|
21
|
+
outOf: {
|
|
22
|
+
type: Number,
|
|
23
|
+
attribute: 'out-of'
|
|
24
|
+
},
|
|
25
|
+
studentGradePreview: {
|
|
26
|
+
type: Object,
|
|
27
|
+
attribute: 'student-grade-preview'
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static get styles() {
|
|
33
|
+
return [bodySmallStyles, bodyCompactStyles, labelStyles, css`
|
|
34
|
+
:host {
|
|
35
|
+
display: inline-block;
|
|
36
|
+
}
|
|
37
|
+
:host([hidden]) {
|
|
38
|
+
display: none;
|
|
39
|
+
}
|
|
40
|
+
.d2l-grade-result-student-grade-preview-container {
|
|
41
|
+
align-items: center;
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: row;
|
|
44
|
+
gap: 0.5rem;
|
|
45
|
+
min-height: calc(2rem + 2px);
|
|
46
|
+
}
|
|
47
|
+
.d2l-grade-result-student-grade-preview-colour {
|
|
48
|
+
border-radius: 6px;
|
|
49
|
+
height: 0.9rem;
|
|
50
|
+
width: 0.9rem;
|
|
51
|
+
}
|
|
52
|
+
.d2l-label-text {
|
|
53
|
+
line-height: 1.6rem;
|
|
54
|
+
margin-bottom: 0.4rem;
|
|
55
|
+
}
|
|
56
|
+
`];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
constructor() {
|
|
60
|
+
super();
|
|
61
|
+
this.hideLabel = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
render() {
|
|
65
|
+
if (!this.studentGradePreview) {
|
|
66
|
+
return nothing;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let label = nothing;
|
|
70
|
+
if (!this.hideLabel) {
|
|
71
|
+
label = html`
|
|
72
|
+
<label class="d2l-label-text d2l-skeletize" for="d2l-grade-result-student-grade-preview">
|
|
73
|
+
${this.localize('components:gradeResult:studentGradePreviewLabel')}
|
|
74
|
+
</label>
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const shouldDisplayAny = Object.values(previewOptions).some(option => this._shouldDisplay(option));
|
|
79
|
+
if (!shouldDisplayAny) {
|
|
80
|
+
return html`
|
|
81
|
+
${label}
|
|
82
|
+
<div class="d2l-body-small d2l-grade-result-student-grade-preview-container" id="d2l-grade-result-student-grade-preview">
|
|
83
|
+
${this.localize('components:gradeResult:studentGradePreviewNotShown')}
|
|
84
|
+
</div>
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return html`
|
|
89
|
+
${label}
|
|
90
|
+
<div id="d2l-grade-result-student-grade-preview" class="d2l-grade-result-student-grade-preview-container">
|
|
91
|
+
${this._renderColour()}
|
|
92
|
+
${this._renderScoreAndSymbol()}
|
|
93
|
+
</div>
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_renderColour() {
|
|
98
|
+
if (!this._shouldDisplay(previewOptions.colour) || !this.studentGradePreview.colour) {
|
|
99
|
+
return nothing;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return html`
|
|
103
|
+
<div class="d2l-grade-result-student-grade-preview-colour"
|
|
104
|
+
style="background-color: ${this.studentGradePreview.colour};">
|
|
105
|
+
</div>
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_renderScoreAndSymbol() {
|
|
110
|
+
if (!this._shouldDisplay(previewOptions.score) && !this._shouldDisplay(previewOptions.symbol)) {
|
|
111
|
+
return nothing;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const score = this._shouldDisplay(previewOptions.score)
|
|
115
|
+
? `${this.studentGradePreview?.score && typeof this.studentGradePreview?.score === 'number' ? formatNumber(this.studentGradePreview?.score) : ''} / ${this.outOf && typeof this.outOf === 'number' ? formatNumber(this.outOf) : 0}`
|
|
116
|
+
: '';
|
|
117
|
+
const accessibleScore = this._shouldDisplay(previewOptions.score) ? this.localize('components:gradeResult:numeratorOutOfDenominator', { numerator: this.studentGradePreview?.score, denominator: this.outOf }) : '';
|
|
118
|
+
|
|
119
|
+
const symbol = this._shouldDisplay(previewOptions.symbol) ? this.studentGradePreview?.symbol : '';
|
|
120
|
+
|
|
121
|
+
const separator = score && symbol ? ' - ' : '';
|
|
122
|
+
|
|
123
|
+
return html`
|
|
124
|
+
<div aria-hidden="true" class="d2l-body-compact">
|
|
125
|
+
${`${score}${separator}${symbol}`}
|
|
126
|
+
</div>
|
|
127
|
+
<d2l-offscreen>
|
|
128
|
+
${`${accessibleScore}${separator}${symbol}`}
|
|
129
|
+
</d2l-offscreen>
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
_shouldDisplay(property) {
|
|
134
|
+
return Object.prototype.hasOwnProperty.call(this.studentGradePreview, property);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
customElements.define('d2l-grade-result-student-grade-preview', D2LGradeResultStudentGradePreview);
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
|
|
2
|
+
export const GradeType = {
|
|
3
|
+
Letter: 'LetterGrade',
|
|
4
|
+
Number: 'Numeric'
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const GradeErrors = {
|
|
8
|
+
GET_LETTER_GRADE_FROM_NUMERIC_SCORE: 'Grade must be of type LetterGrade to get the letter grade',
|
|
9
|
+
GET_ASSIGNED_VALUE_FROM_NUMERIC_SCORE: 'Grade must be of type LetterGrade to get the assigned value',
|
|
10
|
+
INVALID_SCORE_TYPE: 'Invalid scoreType provided',
|
|
11
|
+
INVALID_SCORE: 'Invalid score provided',
|
|
12
|
+
INVALID_OUT_OF: 'Invalid outOf provided',
|
|
13
|
+
INVALID_LETTER_GRADE: 'Invalid letterGrade provided',
|
|
14
|
+
INVALID_LETTER_GRADE_ID: 'Invalid letterGradeId provided',
|
|
15
|
+
INVALID_LETTER_GRADE_OPTIONS: 'Invalid letterGradeOptions provided',
|
|
16
|
+
LETTER_GRADE_NOT_IN_OPTIONS: 'letterGrade must be one of the letterGradeOptions provided',
|
|
17
|
+
LETTER_GRADE_ID_NO_ASSIGNED_VALUE: 'LetterGradeId does not have a corresponding AssignedValue',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class Grade {
|
|
21
|
+
|
|
22
|
+
constructor(scoreType, score, outOf, letterGrade, letterGradeOptions, entity, calculatedScore = null, aggregatedScore = null, display = null) {
|
|
23
|
+
this.entity = entity;
|
|
24
|
+
this.isManuallyOverridden = false;
|
|
25
|
+
this.calculatedScore = calculatedScore;
|
|
26
|
+
this.aggregatedScore = aggregatedScore;
|
|
27
|
+
this.display = display;
|
|
28
|
+
this.outOf = outOf;
|
|
29
|
+
this.scoreType = this._parseScoreType(scoreType);
|
|
30
|
+
if (this.isNumberGrade()) {
|
|
31
|
+
this._parseNumberGrade(score);
|
|
32
|
+
} else {
|
|
33
|
+
const letterGradeId = this._getLetterGradeIdFromLetterGrade(letterGrade, letterGradeOptions);
|
|
34
|
+
this._parseLetterGrade(letterGradeId, letterGradeOptions);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getDisplay() {
|
|
39
|
+
return this.display;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getEntity() {
|
|
43
|
+
return this.entity;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getLetterGrade() {
|
|
47
|
+
if (this.isNumberGrade()) {
|
|
48
|
+
throw new Error(GradeErrors.GET_LETTER_GRADE_FROM_NUMERIC_SCORE);
|
|
49
|
+
}
|
|
50
|
+
return this.letterGrade;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getLetterGradeAssignedValue() {
|
|
54
|
+
if (this.isNumberGrade()) {
|
|
55
|
+
throw new Error(GradeErrors.GET_LETTER_GRADE_FROM_NUMERIC_SCORE);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const letterGradeOption = this.letterGradeOptions[this.letterGradeId];
|
|
59
|
+
|
|
60
|
+
if (!letterGradeOption || (typeof letterGradeOption.AssignedValue !== 'number' && letterGradeOption.AssignedValue !== null)) {
|
|
61
|
+
throw new Error(GradeErrors.LETTER_GRADE_ID_NO_ASSIGNED_VALUE);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return letterGradeOption.AssignedValue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getLetterGradeOptions() {
|
|
68
|
+
return this.letterGradeOptions;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getOutOf() {
|
|
72
|
+
return this.outOf;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
getScore() {
|
|
76
|
+
return this.isNumberGrade() ? this.score : this.letterGradeId;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getScoreType() {
|
|
80
|
+
return this.scoreType;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
isLetterGrade() {
|
|
84
|
+
return this.scoreType === GradeType.Letter;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
isNumberGrade() {
|
|
88
|
+
return this.scoreType === GradeType.Number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setScore(score) {
|
|
92
|
+
if (this.isNumberGrade()) {
|
|
93
|
+
this._parseNumberGrade(score, this.outOf);
|
|
94
|
+
} else {
|
|
95
|
+
this._parseLetterGrade(score, this.letterGradeOptions);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_getLetterGradeIdFromLetterGrade(letterGrade, letterGradeOptions) {
|
|
100
|
+
if ((!letterGrade || typeof letterGrade !== 'string') && letterGrade !== null && letterGrade !== '') {
|
|
101
|
+
throw new Error(GradeErrors.INVALID_LETTER_GRADE);
|
|
102
|
+
}
|
|
103
|
+
if (!letterGradeOptions || typeof letterGradeOptions !== 'object' || Object.keys(letterGradeOptions).length === 0) {
|
|
104
|
+
throw new Error(GradeErrors.INVALID_LETTER_GRADE_OPTIONS);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let letterGradeId;
|
|
108
|
+
|
|
109
|
+
// this is the "None" case which has the id 0
|
|
110
|
+
if (letterGrade === '' || letterGrade === null) {
|
|
111
|
+
letterGradeId = '0';
|
|
112
|
+
} else {
|
|
113
|
+
letterGradeId = Object.keys(letterGradeOptions).find(key =>
|
|
114
|
+
letterGradeOptions[key].LetterGrade === letterGrade
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (letterGradeId === undefined) {
|
|
119
|
+
throw new Error(GradeErrors.LETTER_GRADE_NOT_IN_OPTIONS);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return letterGradeId;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_parseLetterGrade(letterGradeId, letterGradeOptions) {
|
|
126
|
+
if (!letterGradeId && letterGradeId !== 0) {
|
|
127
|
+
throw new Error(GradeErrors.INVALID_LETTER_GRADE_ID);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!letterGradeOptions || Object.keys(letterGradeOptions).length === 0) {
|
|
131
|
+
throw new Error(GradeErrors.INVALID_LETTER_GRADE_OPTIONS);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.score = null;
|
|
135
|
+
this.letterGradeId = letterGradeId;
|
|
136
|
+
this.letterGrade = letterGradeOptions[letterGradeId].LetterGrade;
|
|
137
|
+
this.letterGradeOptions = letterGradeOptions;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_parseNumberGrade(score) {
|
|
141
|
+
if (score === undefined) {
|
|
142
|
+
score = '';
|
|
143
|
+
} else if (isNaN(score)) {
|
|
144
|
+
throw new Error(GradeErrors.INVALID_SCORE);
|
|
145
|
+
} else if (typeof score === 'string') {
|
|
146
|
+
score = Number(score);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (this.calculatedScore !== null) {
|
|
150
|
+
this.isManuallyOverridden = score !== this.calculatedScore;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.score = score;
|
|
154
|
+
this.letterGradeId = null;
|
|
155
|
+
this.letterGrade = null;
|
|
156
|
+
this.letterGradeOptions = null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_parseScoreType(scoreType) {
|
|
160
|
+
const invalidScoreError = new Error(GradeErrors.INVALID_SCORE_TYPE);
|
|
161
|
+
|
|
162
|
+
if (!scoreType || typeof scoreType !== 'string') {
|
|
163
|
+
throw invalidScoreError;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (scoreType.toLowerCase() === GradeType.Number.toLowerCase()) {
|
|
167
|
+
return GradeType.Number;
|
|
168
|
+
} else if (scoreType.toLowerCase() === GradeType.Letter.toLowerCase()) {
|
|
169
|
+
return GradeType.Letter;
|
|
170
|
+
} else {
|
|
171
|
+
throw invalidScoreError;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
}
|