Skip to content

Commit

Permalink
feat(upload-interaction): add QtiUploadInteraction component with fil…
Browse files Browse the repository at this point in the history
…e upload functionality
  • Loading branch information
Patrick de Klein committed Dec 20, 2024
1 parent f08b4a2 commit 0371738
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/lib/qti-components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export * from './qti-interaction/qti-inline-choice-interaction/qti-inline-choice
/* start choiceinteraction */
export * from './qti-interaction/qti-choice-interaction/qti-choice-interaction';
/* end choiceinteraction */
/* start uploadinteraction */
export * from './qti-interaction/qti-upload-interaction/qti-upload-interaction';
/* end uploadinteraction */
export * from './qti-outcome-processing/qti-outcome-processing';
export * from './qti-response-processing';
/* start custom interactions */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { html } from 'lit';
import { getWcStorybookHelpers } from 'wc-storybook-helpers';
import type { Meta, StoryObj } from '@storybook/web-components';
import { QtiUploadInteraction } from './qti-upload-interaction';

const { events, args, argTypes, template } = getWcStorybookHelpers('qti-upload-interaction');

type Story = StoryObj<QtiUploadInteraction & typeof args>;

const meta: Meta<QtiUploadInteraction> = {
component: 'qti-upload-interaction',
args,
argTypes,
parameters: {
actions: {
handles: events
}
},
tags: ['autodocs']
};
export default meta;

export const Default = {
render: args => html`
<qti-upload-interaction
response-identifier="RESPONSE"
@qti-interaction-response="${e => {
console.log('File uploaded:', e.detail);
}}"
?disabled=${args.disabled}
?readonly=${args.readonly}
>
<qti-prompt>
<p>
Build a spreadsheet to simulate 50 cartons of chocolates when each carton contains 10 chocolates, and when
one-seventh of the chocolates have nut centres. Your spreadsheet should include 50 rows representing the 50
cartons, each row containing 10 columns to represent the chocolates.
</p>
</qti-prompt>
</qti-upload-interaction>
`,

args: {
disabled: false,
readonly: false
},
parameters: {
chromatic: { disableSnapshot: true }
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { css, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { Interaction } from '../internal/interaction/interaction';

@customElement('qti-upload-interaction')
export class QtiUploadInteraction extends Interaction {
private _file: File | null = null;
private _base64: string | null = null;

reset() {
this._file = null;
this._base64 = null;
this.saveResponse(null);
}

validate(): boolean {
return this._base64 !== null; // Ensure the Base64 string is set
}

get value(): string | string[] | null {
return this._base64; // Return the Base64 string
}

set value(base64: string | null) {
if (typeof base64 === 'string') {
this._base64 = base64;
this.saveResponse(base64); // Save Base64 string as the response
} else if (base64 === null) {
this.reset();
} else {
throw new Error('Value must be a Base64-encoded string or null');
}
}

static override get properties() {
return {
...Interaction.properties
};
}

static override styles = [
css`
:host {
display: block;
margin: 1em 0;
}
input[type='file'] {
display: block;
margin-top: 0.5em;
}
`
];

override render() {
return html`
<div>
<slot name="prompt"></slot>
<input type="file" @change="${this._onFileChange}" ?disabled="${this.disabled}" ?readonly="${this.readonly}" />
</div>
`;
}

private async _onFileChange(event: Event) {
const input = event.target as HTMLInputElement;
if (input.files && input.files.length > 0) {
this._file = input.files[0];
this._base64 = await this._convertToBase64(this._file);
this.saveResponse(this._base64); // Save the Base64 string
this.dispatchEvent(
new CustomEvent('qti-interaction-response', {
detail: { response: this._base64 }
})
);
}
}

private _convertToBase64(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result as string);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file); // Converts to Base64
});
}
}

declare global {
interface HTMLElementTagNameMap {
'qti-upload-interaction': QtiUploadInteraction;
}
}

0 comments on commit 0371738

Please sign in to comment.