Skip to content

Commit

Permalink
refactor(demo upload): extract options form
Browse files Browse the repository at this point in the history
  • Loading branch information
gilest committed Nov 18, 2023
1 parent 5630184 commit 02d65ef
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 101 deletions.
116 changes: 15 additions & 101 deletions website/app/components/demo-upload.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,76 +7,39 @@ import { FileState } from 'ember-file-upload';
import FileDropzone from 'ember-file-upload/components/file-dropzone';
import fileQueue from 'ember-file-upload/helpers/file-queue';
import { onloadstart, onprogress, onloadend } from 'ember-file-upload/internal';
import { on } from '@ember/modifier';
import { htmlSafe } from '@ember/template';
import OptionsForm, { UPLOAD_TYPES, DEFAULT_OPTIONS } from './options-form';

const UPLOAD_TYPES = {
simulated: 'Simulated 🧑‍🔬',
http: 'HTTP 📡',
};

// Values in kilobits per second (kbps)
const UPLOAD_RATES = {
'Disconnected - 0 Mbps': 0,
'Very slow - 0.1 Mbps': 100,
'Slow 3G - 0.4 Mbps': 400,
'Fast 3G - 0.675 Mbps': 675,
'ADSL - 1.5 Mbps': 1_500,
'4G/LTE - 50 Mbps': 50_000,
'Fast Fibre - 100 Mbps': 100_000,
};
const DEFAULT_RATE = UPLOAD_RATES['Fast 3G - 0.675 Mbps'];
const DEFAULT_URL = 'https://api.bytescale.com/v1/files/basic';
const DEFAULT_HEADERS = {
Authorization: 'Basic YXBpa2V5OmZyZWU',
'Content-Type': 'application/x-www-form-urlencoded',
};
const DEFAULT_METHOD = 'POST';

const STEP_INTERVAL = 100; // Progress events every 100ms
const STEPS_PER_SECOND = 1_000 / STEP_INTERVAL;
// Simulated progress events every 100ms
const SIMULATED_TICK_INTERVAL = 100;
const SIMULATED_TICKS_PER_SECOND = 1_000 / SIMULATED_TICK_INTERVAL;
const BYTES_PER_KILOBYTE = 1_024;
const BITS_PER_BYTE = 8;

const round = (number) => Math.round(number);
const localeNumber = (number) => number.toLocaleString('en-GB');
const eq = (a, b) => a === b;

export default class DemoUploadComponent extends Component {
@service fileQueue;

@tracked uploadOptions = DEFAULT_OPTIONS;
@tracked files = [];

@tracked uploadOptions = {
type: UPLOAD_TYPES.simulated,
rate: DEFAULT_RATE,
url: DEFAULT_URL,
method: DEFAULT_METHOD,
headers: DEFAULT_HEADERS,
};

get queue() {
return this.fileQueue.find('demo');
}

get bytesPerStep() {
get simulatedBytesPerStep() {
const kilobytesPerSecond =
// Convert to kilobytes
(this.uploadOptions.rate / BITS_PER_BYTE) *
// and then to bytes
BYTES_PER_KILOBYTE;
return kilobytesPerSecond / STEPS_PER_SECOND;
return kilobytesPerSecond / SIMULATED_TICKS_PER_SECOND;
}

@action
setUploadOptions(event) {
const formData = new FormData(event.currentTarget);
const entries = Object.fromEntries(formData.entries());
this.uploadOptions = {
...entries,
rate: parseInt(entries.rate, 10),
headers: JSON.parse(entries.headers),
};
setUploadOptions(options) {
this.uploadOptions = options;
}

@action
Expand All @@ -86,7 +49,6 @@ export default class DemoUploadComponent extends Component {
switch (this.uploadOptions.type) {
case UPLOAD_TYPES.simulated:
file.queue = this.queue;
file.state = FileState.Queued;
this.simulateUpload.perform(file);
break;
case UPLOAD_TYPES.http:
Expand All @@ -109,13 +71,13 @@ export default class DemoUploadComponent extends Component {
);

while (file.progress < 100) {
yield timeout(STEP_INTERVAL);
yield timeout(SIMULATED_TICK_INTERVAL);

onprogress(
file,
new ProgressEvent('progress', {
lengthComputable: true,
loaded: file.loaded + this.bytesPerStep,
loaded: file.loaded + this.simulatedBytesPerStep,
total: file.size,
}),
);
Expand All @@ -142,59 +104,11 @@ export default class DemoUploadComponent extends Component {
});
}

toggleVisibility = (type) => {
return this.uploadOptions.type === type
? htmlSafe('')
: htmlSafe('display: none');
};

<template>
{{log this.uploadOptions}}
<form aria-label='Upload options' {{on 'change' this.setUploadOptions}}>
<fieldset>
<legend>Upload type:</legend>
<label>
{{#each-in UPLOAD_TYPES as |_key type|}}
<div>
<input
type='radio'
name='type'
id={{type}}
value={{type}}
checked={{eq this.uploadOptions.type type}}
/>
<label for={{type}}>{{type}}</label>
</div>
{{/each-in}}
</label>
</fieldset>

<label style={{this.toggleVisibility UPLOAD_TYPES.simulated}}>
Simulated speed:
<select name='rate'>
{{#each-in UPLOAD_RATES as |name rate|}}
<option value={{rate}} selected={{eq this.uploadOptions.rate rate}}>
{{name}}
</option>
{{/each-in}}
</select>
</label>

<label style={{this.toggleVisibility UPLOAD_TYPES.http}}>
URL:
<input type='text' name='url' value={{DEFAULT_URL}} />
</label>

<label style={{this.toggleVisibility UPLOAD_TYPES.http}}>
Method:
<input type='text' name='method' value={{DEFAULT_METHOD}} />
</label>

<label style={{this.toggleVisibility UPLOAD_TYPES.http}}>
Headers:
<textarea name='headers' rows='5' spellcheck='false'>{{JSON.stringify DEFAULT_HEADERS null 2}}</textarea>
</label>
</form>
<OptionsForm
@uploadOptions={{this.uploadOptions}}
@onUpdate={{this.setUploadOptions}}
/>

{{#let (fileQueue name='demo' onFileAdded=this.addToQueue) as |queue|}}
<FileDropzone
Expand Down
120 changes: 120 additions & 0 deletions website/app/components/options-form.gjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { on } from '@ember/modifier';
import { htmlSafe } from '@ember/template';

export const UPLOAD_TYPES = {
simulated: 'Simulated 🧑‍🔬',
http: 'HTTP 📡',
};

// Simulated
// Values in kilobits per second (kbps)
const RATES = {
'Disconnected - 0 Mbps': 0,
'Very slow - 0.1 Mbps': 100,
'Slow 3G - 0.4 Mbps': 400,
'Fast 3G - 0.675 Mbps': 675,
'ADSL - 1.5 Mbps': 1_500,
'4G/LTE - 50 Mbps': 50_000,
'Fast Fibre - 100 Mbps': 100_000,
};
const DEFAULT_RATE = RATES['Fast 3G - 0.675 Mbps'];

// HTTP
const DEFAULT_URL = 'https://api.bytescale.com/v1/files/basic';
const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'HEAD'];
const DEFAULT_METHOD = 'POST';
const DEFAULT_HEADERS = {
Authorization: 'Basic YXBpa2V5OmZyZWU',
'Content-Type': 'application/x-www-form-urlencoded',
};

export const DEFAULT_OPTIONS = {
type: UPLOAD_TYPES.simulated,
// Simulated
rate: DEFAULT_RATE,
// HTTP
url: DEFAULT_URL,
method: DEFAULT_METHOD,
headers: DEFAULT_HEADERS,
};

const eq = (a, b) => a === b;

export default class OptionsFormComponent extends Component {
@action
setOptions(event) {
const formData = new FormData(event.currentTarget);
const entries = Object.fromEntries(formData.entries());
const uploadOptions = {
...entries,
rate: parseInt(entries.rate, 10),
headers: JSON.parse(entries.headers),
};
this.args.onUpdate(uploadOptions);
}

// Keep forms in DOM to prevent data loss
toggleVisibility = (type) => {
return this.args.uploadOptions.type === type
? htmlSafe('')
: htmlSafe('display: none');
};

<template>
<form aria-label='Upload options' {{on 'change' this.setOptions}}>
<fieldset>
<legend>Upload type:</legend>
{{#each (Object.values UPLOAD_TYPES) as |type|}}
<div>
<input
type='radio'
name='type'
id={{type}}
value={{type}}
checked={{eq @uploadOptions.type type}}
/>
<label for={{type}}>{{type}}</label>
</div>
{{/each}}
</fieldset>

<label style={{this.toggleVisibility UPLOAD_TYPES.simulated}}>
Simulated speed:
<select name='rate'>
{{#each-in RATES as |name rate|}}
<option value={{rate}} selected={{eq @uploadOptions.rate rate}}>
{{name}}
</option>
{{/each-in}}
</select>
</label>

<label style={{this.toggleVisibility UPLOAD_TYPES.http}}>
URL:
<input type='text' name='url' value={{DEFAULT_URL}} />
</label>

<label style={{this.toggleVisibility UPLOAD_TYPES.http}}>
Method:
<select name='method'>
{{#each METHODS as |method|}}
<option value={{method}} selected={{eq @uploadOptions.method method}}>
{{method}}
</option>
{{/each}}
</select>
</label>

<label style={{this.toggleVisibility UPLOAD_TYPES.http}}>
Headers:
<textarea name='headers' rows='5' spellcheck='false'>{{JSON.stringify
DEFAULT_HEADERS
null
2
}}</textarea>
</label>
</form>
</template>
}

0 comments on commit 02d65ef

Please sign in to comment.