Skip to content

Commit

Permalink
Feature: Add file control.
Browse files Browse the repository at this point in the history
  • Loading branch information
kitschpatrol committed Oct 24, 2024
1 parent 3b1a809 commit 8691bb5
Show file tree
Hide file tree
Showing 11 changed files with 331 additions and 22 deletions.
6 changes: 6 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ module.exports = {
'unicorn/prefer-export-from': 'off'
}
},
{
files: ['FileExample.svelte'],
rules: {
'unicorn/prefer-top-level-await': 'off'
}
},
{
files: ['HomeDemo.svelte', 'TweakpaneDemo.svelte'],
rules: {
Expand Down
1 change: 1 addition & 0 deletions docs/src/content/docs/docs/acknowledgments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ _Svelte Tweakpane UI_ incorporates the following Tweakpane plugins:

- [CameraKit](https://github.com/tweakpane/plugin-camerakit) by [Hiroki Kokubun](https://cocopon.me)
- [Essentials](https://github.com/tweakpane/plugin-essentials) by [Hiroki Kokubun](https://cocopon.me)
- [File Import](https://github.com/LuchoTurtle/tweakpane-plugin-file-import) by [LuchoTurtle](https://github.com/LuchoTurtle)
- [Image](https://github.com/metehus/tweakpane-image-plugin) by [Florian Morel](http://ayamflow.fr), [Matheus Dias](https://www.linkedin.com/in/matheusdbs/), and [Palash Bansal](https://github.com/repalash)
- [Inputs](https://github.com/tallneil/tweakpane-plugin-inputs) by [Neil Shankar](https://tallneil.io/)
- [Profiler](https://github.com/0b5vr/tweakpane-plugin-profiler) by [0b5vr](https://0b5vr.com)
Expand Down
22 changes: 11 additions & 11 deletions docs/src/content/docs/docs/plugins.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,17 @@ The result should be the same:

The following plugins are included with _Svelte Tweakpane UI_, and are ready to use out of the box:

| Plugin | Components | Optimized Fork |
| --------------------------------------------------------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [CameraKit](https://github.com/tweakpane/plugin-camerakit) | `<Ring>`, `<Wheel>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-camerakit) |
| [Essentials](https://github.com/tweakpane/plugin-essentials) | `<ButtonGrid>`, `<RadioGrid>`, `<CubicBezier>`, `<FpsGraph>`, `<IntervalSlider>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-essentials) |
| [Image](https://github.com/metehus/tweakpane-image-plugin) | `<Image>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-image) |
| [Inputs](https://github.com/tallneil/tweakpane-plugin-inputs) | `<Stepper>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-inputs) |
| [Profiler](https://github.com/0b5vr/tweakpane-plugin-profiler) | `<Profiler>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-profiler) |
| [Rotation](https://github.com/0b5vr/tweakpane-plugin-rotation) | `<RotationQuaternion>`, `<RotationEuler>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-rotation) |
| [Textarea](https://github.com/panGenerator/tweakpane-textarea-plugin) | `<Textarea>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-textarea) |
| [Waveform](https://github.com/shoedler/tweakpane-plugin-waveform) | `<WaveformMonitor>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-waveform) |
| Plugin | Components | Optimized Fork |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [CameraKit](https://github.com/tweakpane/plugin-camerakit) | `<Ring>`, `<Wheel>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-camerakit) |
| [Essentials](https://github.com/tweakpane/plugin-essentials) | `<ButtonGrid>`, `<RadioGrid>`, `<CubicBezier>`, `<FpsGraph>`, `<IntervalSlider>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-essentials) |
| [File Import](https://github.com/LuchoTurtle/tweakpane-plugin-file-import) | `<File>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-file-import) |
| [Image](https://github.com/metehus/tweakpane-image-plugin) | `<Image>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-image) |
| [Inputs](https://github.com/tallneil/tweakpane-plugin-inputs) | `<Stepper>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-inputs) |
| [Profiler](https://github.com/0b5vr/tweakpane-plugin-profiler) | `<Profiler>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-profiler) |
| [Rotation](https://github.com/0b5vr/tweakpane-plugin-rotation) | `<RotationQuaternion>`, `<RotationEuler>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-rotation) |
| [Textarea](https://github.com/panGenerator/tweakpane-textarea-plugin) | `<Textarea>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-textarea) |
| [Waveform](https://github.com/shoedler/tweakpane-plugin-waveform) | `<WaveformMonitor>` | [GitHub](https://github.com/kitschpatrol/tweakpane-plugin-waveform) |

\*_Tweakpane version 4 is a relatively recent release, and it introduced a number of breaking changes for plugin developers. I've ported the asterisked plugins above from Tweakpane 3 to Tweakpane 4, and and submitted PRs to the project owners. As soon as the PRs are merged, I will update the dependencies in Svelte Tweakpane UI to point to the source instead of my fork._

Expand Down Expand Up @@ -123,7 +124,6 @@ If you'd like to see additional Tweakpane plugins either integrated with _Svelte
- [tweakpane-plugin-search-list](https://github.com/hirohe/tweakpane-plugin-search-list) <InlineImage alt="Tweakpane Logo" src="/svelte-tweakpane-ui/tweakpane-logo.svg" />v3
- [midi-control](https://github.com/mollerse/midi-control) <InlineImage alt="Tweakpane Logo" src="/svelte-tweakpane-ui/tweakpane-logo.svg" />v3
- [tweakpane-plugin-media](https://github.com/leochocolat/tweakpane-plugin-media) <InlineImage alt="Tweakpane Logo" src="/svelte-tweakpane-ui/tweakpane-logo.svg" />v3
- [tweakpane-plugin-file-import](https://github.com/LuchoTurtle/tweakpane-plugin-file-import) <InlineImage alt="Tweakpane Logo" src="/svelte-tweakpane-ui/tweakpane-logo.svg" />v3
- [tweakpane-plugin-audio-player](https://github.com/brunoimbrizi/tweakpane-plugin-audio-player) <InlineImage alt="Tweakpane Logo" src="/svelte-tweakpane-ui/tweakpane-logo.svg" />v3
- [tweakpane-plugin-grouplist](https://github.com/pierogis/tweakpane-plugin-grouplist) <InlineImage alt="Tweakpane Logo" src="/svelte-tweakpane-ui/tweakpane-logo.svg" />v3
- [tweakpane-media](https://github.com/vnvyvu/tweakpane-media) <InlineImage alt="Tweakpane Logo" src="/svelte-tweakpane-ui/tweakpane-logo.svg" />v3
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
"types": "./dist/control/CubicBezier.svelte.d.ts",
"svelte": "./dist/control/CubicBezier.svelte"
},
"./File.svelte": {
"types": "./dist/control/File.svelte.d.ts",
"svelte": "./dist/control/File.svelte"
},
"./Image.svelte": {
"types": "./dist/control/Image.svelte.d.ts",
"svelte": "./dist/control/Image.svelte"
Expand Down Expand Up @@ -237,6 +241,7 @@
"dependencies": {
"@kitschpatrol/tweakpane-plugin-camerakit": "0.3.1-beta.2",
"@kitschpatrol/tweakpane-plugin-essentials": "0.2.2-beta.2",
"@kitschpatrol/tweakpane-plugin-file-import": "1.1.2-beta.1",
"@kitschpatrol/tweakpane-plugin-image": "2.0.1-beta.4",
"@kitschpatrol/tweakpane-plugin-inputs": "1.0.4-beta.3",
"@kitschpatrol/tweakpane-plugin-profiler": "0.4.2-beta.2",
Expand Down
14 changes: 14 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

## Overview

🎛️ **_Svelte Tweakpane UI_** wraps user-interface elements from the excellent [Tweakpane](https://tweakpane.github.io/docs/) library in a collection of <!-- component-count -->32<!-- /component-count --> idiomatic, reactive, type-safe, carefully-crafted, and obsessively-documented [Svelte](https://svelte.dev) components.
🎛️ **_Svelte Tweakpane UI_** wraps user-interface elements from the excellent [Tweakpane](https://tweakpane.github.io/docs/) library in a collection of <!-- component-count -->33<!-- /component-count --> idiomatic, reactive, type-safe, carefully-crafted, and obsessively-documented [Svelte](https://svelte.dev) components.

The library makes it easy to quickly and declaratively add knobs and dials to your projects using components that feel like they were made for Svelte. It also augments Tweakpane with a few [extra features](https://kitschpatrol.com/svelte-tweakpane-ui/docs/features) for your convenience and enjoyment.

Expand Down Expand Up @@ -102,6 +102,8 @@ npm install svelte-tweakpane-ui
A color picker.
- **[CubicBezier](https://kitschpatrol.com/svelte-tweakpane-ui/docs/components/cubicbezier)**\
A control for editing a bezier curve. Ideal for tweaking animation easing values.
- **[File](https://kitschpatrol.com/svelte-tweakpane-ui/docs/components/file)**\
A file input control.
- **[Image](https://kitschpatrol.com/svelte-tweakpane-ui/docs/components/image)**\
An image input control.
- **[IntervalSlider](https://kitschpatrol.com/svelte-tweakpane-ui/docs/components/intervalslider)**\
Expand Down
39 changes: 39 additions & 0 deletions src/examples/tests/TestFile.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script lang="ts">
import { File, type FileValue } from '$lib';
let file: FileValue;
let binding1InternalEventCount = 0;
let binding1ExternalEventCount = 0;
let binding2InternalEventCount = 0;
let binding2ExternalEventCount = 0;
</script>

<File
bind:value={file}
on:change={(event) => {
if (event.detail.origin === 'internal') {
binding1InternalEventCount++;
} else {
binding1ExternalEventCount++;
}
}}
label="File 1"
/>
<File
bind:value={file}
on:change={(event) => {
if (event.detail.origin === 'internal') {
binding2InternalEventCount++;
} else {
binding2ExternalEventCount++;
}
}}
label="File 2"
/>

<pre>Value: <span>{file}</span></pre>
<pre>Binding 1 Internal: <span>{binding1InternalEventCount}</span></pre>
<pre>Binding 1 External: <span>{binding1ExternalEventCount}</span></pre>
<pre>Binding 2 Internal: <span>{binding2InternalEventCount}</span></pre>
<pre>Binding 2 External: <span>{binding2ExternalEventCount}</span></pre>
200 changes: 200 additions & 0 deletions src/lib/control/File.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
<script context="module" lang="ts">
import type { ValueChangeEvent } from '$lib/utils.js';
export type FileValue = File | undefined;
export type FileChangeEvent = ValueChangeEvent<FileValue>;
</script>

<script lang="ts">
import type { PluginInputParams } from '@kitschpatrol/tweakpane-plugin-file-import/dist/types/plugin.d.ts';
import type { ComponentProps } from 'svelte';
import ClsPad from '$lib/internal/ClsPad.svelte';
import GenericInput from '$lib/internal/GenericInput.svelte';
import { fillWith } from '$lib/utils';
import * as pluginModule from '@kitschpatrol/tweakpane-plugin-file-import';
import { BROWSER } from 'esm-env';
import { shallowEqual } from 'fast-equals';
type FileValueInternal = File | null | string;
type $$Props = {
/**
* File data, or `undefined` to clear the file input.
* @default `undefined`
* @bindable
*/
value?: FileValue;
/**
* Array of valid file extensions.
* @default Any file extension
*/
extensions?: string[] | undefined;
/**
* String shown when the user tries to upload an invalid filetype.
* @default `'Unaccepted file type.'`
*/
invalidExtensionMessage?: string | undefined;
/**
* Height of the file input drop zone, in rows.
* @default `3`
*/
rows?: number | undefined;
} & Omit<ComponentProps<GenericInput<FileValueInternal>>, 'plugin' | 'ref' | 'value'>;
// Unique
export let value: $$Props['value'] = undefined;
export let rows: $$Props['rows'] = undefined;
export let invalidExtensionMessage: $$Props['invalidExtensionMessage'] = undefined;
export let extensions: $$Props['extensions'] = undefined;
// Inheriting here with ComponentEvents makes a documentation mess
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type $$Events = {
/**
* Fires when `value` changes.
*
* _This event is provided for advanced use cases. It's usually preferred to bind to the `value` prop instead._
*
* The `event.details` payload includes a copy of the value and an `origin` field to distinguish between user-interactive changes (`internal`)
* and changes resulting from programmatic manipulation of the `value` (`external`).
*
* @extends ValueChangeEvent
* @event
* */
change: FileChangeEvent;
};
let internalValue: FileValueInternal;
function updateInternalValueFromValue() {
// Manual difference checks required to prevent Svelte 5 infinite update loops
const newInternalValue: FileValueInternal = value ?? '';
if (!shallowEqual(internalValue, newInternalValue)) {
// TODO copy?
internalValue = newInternalValue;
}
}
function updateValueFromInternalValue() {
// Manual difference checks required to prevent Svelte 5 infinite update loops
if (internalValue instanceof File) {
if (!shallowEqual(value, internalValue)) {
// TODO copy?
value = internalValue;
}
} else if (value !== undefined) {
value = undefined;
}
}
let options: PluginInputParams;
$: options = {
extensions,
filetypes: extensions,
invalidFiletypeMessage: invalidExtensionMessage,
lineCount: rows,
view: 'file-input'
};
$: value, updateInternalValueFromValue();
$: internalValue, updateValueFromInternalValue();
</script>

<!--
@component
A file input control.
_Important: This component has some rough edges, and should be considered experimental._
Integrates the [File Input](https://github.com/LuchoTurtle/tweakpane-plugin-file-import/blob/main/src/plugin.ts) control from [LuchoTurtle's](https://github.com/LuchoTurtle) [tweakpane-plugin-file-import](https://github.com/LuchoTurtle/tweakpane-plugin-file-import) plugin. Some of the control's parameter names have been changed for consistency with the `<Image>` CompositionEvent.
Use the `<Image>` control instead if you're working with images and want to see a thumbnail preview of the image.
There is currently a known bug where change events' `origin` values are sometimes incorrect. (This issue is limited to this component.)
Usage outside of a `<Pane>` component will implicitly wrap the image control in `<Pane position="inline">`.
Note that _Svelte Tweakpane UI_ embeds a functionally identical [fork](https://github.com/kitschpatrol/tweakpane-plugin-file-import) of the plugin with build optimizations.
@emits {FileChangeEvent} change - When `value` changes. (This event is provided for advanced use cases. Prefer binding to `value`.)
@example
```svelte
<script lang="ts">
import { File, type FileValue } from '$lib';
let file: FileValue;
async function getFileBase64(file: FileValue): Promise<string> {
if (file === undefined) return 'Your bytes here...';
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener('load', () => {
const { result } = reader;
if (result && typeof result === 'string') resolve(result);
else reject(new Error('Empty result'));
});
reader.addEventListener('error', reject);
reader.readAsDataURL(file);
});
}
function truncate(text: string, length: number) {
return text.length > length ? text.slice(0, length - 1) + '...' : text;
}
</script>
<File bind:value={file} label="File" />
<div class="demo">
<p>
{#await getFileBase64(file)}
Loading...
{:then value}
{truncate(value, 512)}
{/await}
</p>
</div>
<style>
.demo {
width: 100%;
background: linear-gradient(45deg, orange, magenta);
}
.demo > p {
margin: 0;
padding: 0.5rem;
font-family: monospace;
line-height: 1.2;
color: white;
word-break: break-all;
white-space: pre-wrap;
}
</style>
```
@sourceLink
[File.svelte](https://github.com/kitschpatrol/svelte-tweakpane-ui/blob/main/src/lib/control/File.svelte)
-->

<GenericInput
bind:value={internalValue}
on:change
{options}
plugin={pluginModule}
{...$$restProps}
/>
{#if !BROWSER}
{#if rows}
<ClsPad keysAdd={fillWith('containerUnitSize', rows)} theme={$$props.theme} />
{:else}
<ClsPad keysAdd={fillWith('containerUnitSize', 3)} theme={$$props.theme} />
{/if}
{/if}
2 changes: 2 additions & 0 deletions src/lib/control/Image.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ incorporating work by [Florian Morel](http://ayamflow.fr), [Matheus
Dias](https://www.linkedin.com/in/matheusdbs/), [Palash Bansal](https://github.com/repalash), and
others.
Use the `<File>` control instead if you're working with other file types, or don't wish to display a thumbnail preview of an uploaded image.
There is currently a known bug where change events' `origin` values are sometimes incorrect. (This issue is limited to this component.)
Usage outside of a `<Pane>` component will implicitly wrap the image control in `<Pane
Expand Down
Loading

0 comments on commit 8691bb5

Please sign in to comment.