Svelte migration (WIP) (#448)

* first migration commit

* style comment

* first mvp working with calculator

* ali components

* carousel

* more changes

* changes

* add examples

* examples support

* more changes

* interpretation

* interpretation

* submission state

* first migration commit

* style comment

* first mvp working with calculator

* ali components

* carousel

* more changes

* changes

* add examples

* examples support

* more changes

* interpretation

* interpretation

* submission state

* base image cropper

* add image editor

* css tweaks

* remove dead code

* finalise sketch tools

* add webcam snapshot source

* tweak config

* tweak config

* tweak config

* tweaks

* reset egg files

* lockfile v2

* image tweaks

* record audio from mic

* add audio input components

* audio tweaks

* editable table

* more table tweaks

* sort columns

* add row/col to table

* add output table

* fix broken paths

* fix svelte build destination

* fix svelte build destination again

* fix gitignore

* fix css

* add themes

* add all themes

* snake core classnames

* actually fix themes this time

* merge changes

Co-authored-by: Ali Abid <aliabid94@gmail.com>
Co-authored-by: Ali Abid <aliabid@Alis-MacBook-Pro.local>
Co-authored-by: pngwn <hello@pngwn.io>
This commit is contained in:
aliabid94 2022-01-26 18:33:49 -08:00 committed by GitHub
parent 7f23f9b326
commit d6b1247e21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
181 changed files with 9721 additions and 44634 deletions

2
.gitignore vendored
View File

@ -12,6 +12,8 @@ build/
# JS build
gradio/templates/frontend/static
gradio/templates/frontend/build
gradio/templates/frontend/global.css
# Secrets
.env

View File

@ -86,9 +86,11 @@ iface = gr.Interface(
fn,
inputs=[
gr.inputs.Textbox(default="Lorem ipsum", label="Textbox"),
gr.inputs.Textbox(lines=3, placeholder="Type here..", label="Textbox 2"),
gr.inputs.Textbox(lines=3, placeholder="Type here..",
label="Textbox 2"),
gr.inputs.Number(label="Number", default=42),
gr.inputs.Slider(minimum=10, maximum=20, default=15, label="Slider: 10 - 20"),
gr.inputs.Slider(minimum=10, maximum=20, default=15,
label="Slider: 10 - 20"),
gr.inputs.Slider(maximum=20, step=0.04, label="Slider: step @ 0.04"),
gr.inputs.Checkbox(label="Checkbox"),
gr.inputs.CheckboxGroup(
@ -97,14 +99,17 @@ iface = gr.Interface(
gr.inputs.Radio(label="Radio", choices=CHOICES, default=CHOICES[2]),
gr.inputs.Dropdown(label="Dropdown", choices=CHOICES),
gr.inputs.Image(label="Image", optional=True),
gr.inputs.Image(label="Image w/ Cropper", tool="select", optional=True),
gr.inputs.Image(label="Image w/ Cropper",
tool="select", optional=True),
gr.inputs.Image(label="Sketchpad", source="canvas", optional=True),
gr.inputs.Image(label="Webcam", source="webcam", optional=True),
gr.inputs.Video(label="Video", optional=True),
gr.inputs.Audio(label="Audio", optional=True),
gr.inputs.Audio(label="Microphone", source="microphone", optional=True),
gr.inputs.Audio(label="Microphone",
source="microphone", optional=True),
gr.inputs.File(label="File", optional=True),
gr.inputs.Dataframe(label="Dataframe", headers=["Name", "Age", "Gender"]),
gr.inputs.Dataframe(label="Dataframe", headers=[
"Name", "Age", "Gender"]),
gr.inputs.Timeseries(x="time", y=["price", "value"], optional=True),
],
outputs=[
@ -113,7 +118,8 @@ iface = gr.Interface(
gr.outputs.Audio(label="Audio"),
gr.outputs.Image(label="Image"),
gr.outputs.Video(label="Video"),
gr.outputs.HighlightedText(label="HighlightedText"),
gr.outputs.HighlightedText(label="HighlightedText", color_map={
"punc": "pink", "test 0": "blue"}),
gr.outputs.HighlightedText(label="HighlightedText", show_legend=True),
gr.outputs.JSON(label="JSON"),
gr.outputs.HTML(label="HTML"),
@ -121,7 +127,8 @@ iface = gr.Interface(
gr.outputs.Dataframe(label="Dataframe"),
gr.outputs.Dataframe(label="Numpy", type="numpy"),
gr.outputs.Carousel("image", label="Carousel"),
gr.outputs.Timeseries(x="time", y=["price", "value"], label="Timeseries"),
gr.outputs.Timeseries(
x="time", y=["price", "value"], label="Timeseries"),
],
examples=[
[
@ -147,7 +154,7 @@ iface = gr.Interface(
]
]
* 3,
theme="huggingface",
theme="default",
title="Kitchen Sink",
description="Try out all the components!",
article="Learn more about [Gradio](http://gradio.app)",

BIN
demo/zip_two_files/tmp.zip Normal file

Binary file not shown.

View File

@ -1,23 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -1 +0,0 @@
src/vendor

23
frontend/.gitignore vendored
View File

@ -1,23 +1,4 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
/node_modules/
/public/build/
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -1,20 +1,109 @@
# Gradio Frontend
*Psst — looking for a more complete solution? Check out [SvelteKit](https://kit.svelte.dev), the official framework for building web applications of all sizes, with a beautiful development experience and flexible filesystem-based routing.*
This directory contains a React App that renders the frontend of the gradio framework.
*Looking for a shareable component template instead? You can [use SvelteKit for that as well](https://kit.svelte.dev/docs#packaging) or the older [sveltejs/component-template](https://github.com/sveltejs/component-template)*
## Development Setup
---
To make changes to the gradio frontend, you will need to have the following installed
- npm / node
- python3
- gradio
# svelte app
Once node is installed, make sure to run `npm install` in this directory to install the node packages.
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
Because this is only the frontend of the library, you must first launch a Gradio interface running on port 7860, which will be used as a backend. You can use any of the demo projects in the gradio/demo folder to serve this backend role, but make sure that the port is set to 7860. Then you launch the development frontend by running `npm run start`. Once this is run, the frontend development will launch on port 3000. It will connect with port 7860 to load the initial configuration and make API calls on submit.
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
As a Create-React-App, any changes you make in the code will automatically reflect in the development frontend.
```bash
npx degit sveltejs/template svelte-app
cd svelte-app
```
## Committing Changes
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
In production, the frontend is compiled and stored in the gradio/gradio/frontend directory. (This readme is in gradio/frontend, a different directory outside the python package). To compile, run `npm run build`. At the end of the process, you should see the message "Compiled successfully" - there may be an warning or error thrown about not being able to find bundle.css which you can ignore. To include the compiled js into your local python version of gradio, run `python3 setup.py install` and then launch a gradio interface. Your changes should be visible on port 7860.
## Get started
Install the dependencies...
```bash
cd svelte-app
npm install
```
...then start [Rollup](https://rollupjs.org):
```bash
npm run dev
```
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense.
## Building and running in production mode
To create an optimised version of the app:
```bash
npm run build
```
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
## Single-page app mode
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
```js
"start": "sirv public --single"
```
## Using TypeScript
This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with:
```bash
node scripts/setupTypeScript.js
```
Or remove the script via:
```bash
rm scripts/setupTypeScript.js
```
If you want to use `baseUrl` or `path` aliases within your `tsconfig`, you need to set up `@rollup/plugin-alias` to tell Rollup to resolve the aliases. For more info, see [this StackOverflow question](https://stackoverflow.com/questions/63427935/setup-tsconfig-path-in-svelte).
## Deploying to the web
### With [Vercel](https://vercel.com)
Install `vercel` if you haven't already:
```bash
npm install -g vercel
```
Then, from within your project folder:
```bash
cd public
vercel deploy --name my-project
```
### With [surge](https://surge.sh/)
Install `surge` if you haven't already:
```bash
npm install -g surge
```
Then, from within your project folder:
```bash
npm run build
surge public my-project.surge.sh
```

View File

@ -1,49 +0,0 @@
const path = require("path");
const webpack = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
webpack: {
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
}),
new MiniCssExtractPlugin({
filename: "static/bundle.css",
chunkFilename: "static/css/[name].chunk.css"
})
],
eslint: {
enable: true /* (default value) */,
mode: "extends" /* (default value) */ || "file"
},
configure: (webpackConfig, { env, paths }) => {
webpackConfig.optimization = {
splitChunks: {
cacheGroups: {
default: false
}
},
runtimeChunk: false
};
webpackConfig.entry = "./src/index";
webpackConfig.output = {
publicPath: "",
path: path.resolve(__dirname, "../gradio/templates/frontend"),
filename: "static/bundle.js",
chunkFilename: "static/js/[name].chunk.js"
};
paths.appBuild = webpackConfig.output.path;
return webpackConfig;
}
},
style: {
postcss: {
plugins: [
require("postcss-prefixwrap")(".gradio_wrapper"),
require("tailwindcss"),
require("autoprefixer")
]
}
}
};

41337
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,69 +1,40 @@
{
"name": "gradio",
"name": "svelte-app",
"version": "1.0.0",
"private": true,
"dependencies": {
"@craco/craco": "^6.2.0",
"@tailwindcss/typography": "^0.4.1",
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^12.8.3",
"@toast-ui/react-image-editor": "^3.14.2",
"chart.js": "^3.6.0",
"classnames": "^2.3.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"fabric": "^4.5.0",
"html2canvas-objectfit-fix": "^1.2.0",
"jspreadsheet-ce": "^4.7.3",
"mime-types": "^2.1.33",
"postcss-prefixwrap": "^1.26.0",
"prettier-eslint": "^13.0.0",
"prettier-eslint-cli": "^5.0.1",
"react": "^17.0.2",
"react-chartjs-2": "^3.3.0",
"react-cropper": "^2.1.8",
"react-dom": "^17.0.2",
"react-json-tree": "^0.15.0",
"react-scripts": "4.0.3",
"react-webcam": "^5.2.3",
"recorder-js": "^1.0.7",
"sass": "^1.32.8",
"source-map-explorer": "^2.5.2",
"web-vitals": "^1.1.1",
"webpack": "^4.44.2"
},
"scripts": {
"start": "cross-env REACT_APP_BACKEND_URL='http://localhost:7860/' craco start",
"format": "prettier-eslint --write '**/*.js*'",
"build": "REACT_APP_BACKEND_URL='' REACT_APP_VERSION=$(cat ../gradio/version.txt) GENERATE_SOURCEMAP=false craco build",
"build:win": "cross-env REACT_APP_BACKEND_URL='' GENERATE_SOURCEMAP=false craco build",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"prettier"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"build": "rollup -c",
"dev": "rollup -c -w",
"start": "sirv public --no-clear --port 3000"
},
"devDependencies": {
"autoprefixer": "^9.8.6",
"cross-env": "^7.0.3",
"eslint": "^7.32.0",
"mini-css-extract-plugin": "^0.11.3",
"postcss": "^7.0.36",
"prettier": "^2.3.2",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.1.2"
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"mime-types": "^2.1.34",
"postcss": "^8.4.5",
"postcss-nested": "^5.0.6",
"rollup": "^2.3.4",
"rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-livereload": "^2.0.0",
"rollup-plugin-svelte": "^7.0.0",
"rollup-plugin-terser": "^7.0.0",
"svelte": "^3.0.0"
},
"dependencies": {
"@rollup/plugin-replace": "^3.0.1",
"autoprefixer": "^9.8.8",
"cropperjs": "^1.5.12",
"lazy-brush": "^1.0.1",
"mime-types": "^2.1.34",
"node-sass": "^7.0.1",
"resize-observer-polyfill": "^1.5.1",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-postcss": "^4.0.2",
"sirv-cli": "^1.0.0",
"svelte-preprocess": "^4.10.1",
"svelte-range-slider-pips": "^2.0.1",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17",
"tui-image-editor": "^3.15.2"
}
}

BIN
frontend/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

377
frontend/public/global.css Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,13 @@
<!DOCTYPE html>
<html lang="en" style="height: 100%; margin: 0; padding: 0;">
<html lang="en" style="min-height: 100%; margin: 0; padding: 0;">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel='stylesheet' href='/build/bundle.css'>
<link rel='stylesheet' href='/build/themes.css'>
<link rel="stylesheet" href="./global.css">
<title>{{ config['title'] or 'Gradio' }}</title>
<meta property="og:url" content="https://gradio.app/" />
@ -16,11 +20,6 @@
<meta name="twitter:title" content="{{ config['title'] or '' }}">
<meta name="twitter:description" content="{{ config['description'] or '' }}">
<meta name="twitter:image" content="{{ config['thumbnail'] or '' }}">
{%if config['favicon_path']%}
<link rel="shortcut icon" href="{{ config['favicon_path']}}">
{%else%}
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
{%endif%}
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-156449732-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
@ -40,6 +39,8 @@
<body style="height: 100%; margin: 0; padding: 0;">
<div id="root" style="height: 100%"></div>
</body>
</html>
</body>
<script defer src='/build/bundle.js'></script>
</html>

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-camera"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path><circle cx="12" cy="13" r="4"></circle></svg>

After

Width:  |  Height:  |  Size: 349 B

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

117
frontend/rollup.config.js Normal file
View File

@ -0,0 +1,117 @@
import svelte from 'rollup-plugin-svelte';
import sveltePreprocess from "svelte-preprocess";
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import css from 'rollup-plugin-css-only';
import replace from '@rollup/plugin-replace';
import json from "@rollup/plugin-json";
import copy from 'rollup-plugin-copy';
import postcss from 'rollup-plugin-postcss';
const production = !process.env.ROLLUP_WATCH;
function serve() {
let server;
function toExit() {
if (server) server.kill(0);
}
return {
writeBundle() {
if (server) return;
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
process.on('SIGTERM', toExit);
process.on('exit', toExit);
}
};
}
export default {
input: 'src/main.js',
output: [{
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js'
}, {
sourcemap: true,
format: 'iife',
name: 'app',
file: '../gradio/templates/frontend/build/bundle.js'
}],
plugins: [
copy({
targets: [
{ src: 'public/*', dest: '../gradio/templates/frontend' },
{ src: 'public/static', dest: '../gradio/templates/frontend' }
]
}),
json(),
replace({
BUILD_MODE: production ? "prod" : "dev",
BACKEND_URL: production ? "" : "http://localhost:7860/"
}),
postcss({
extract: 'themes.css',
plugins: [
require("tailwindcss"),
require("postcss-nested"),
require("autoprefixer"),
]
}),
svelte({
preprocess: sveltePreprocess({
// sourceMap: true,
postcss: {
plugins: [
require("tailwindcss"),
require("postcss-nested"),
require("autoprefixer"),
],
},
}),
compilerOptions: {
// enable run-time checks when not in production
dev: !production
}
}),
// we'll extract any component CSS out into
// a separate file - better for performance
css({
output: 'bundle.css'
}),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};

View File

@ -0,0 +1,121 @@
// @ts-check
/** This script modifies the project to support TS code in .svelte files like:
<script lang="ts">
export let name: string;
</script>
As well as validating the code for CI.
*/
/** To work on this script:
rm -rf test-template template && git clone sveltejs/template test-template && node scripts/setupTypeScript.js test-template
*/
const fs = require("fs")
const path = require("path")
const { argv } = require("process")
const projectRoot = argv[2] || path.join(__dirname, "..")
// Add deps to pkg.json
const packageJSON = JSON.parse(fs.readFileSync(path.join(projectRoot, "package.json"), "utf8"))
packageJSON.devDependencies = Object.assign(packageJSON.devDependencies, {
"svelte-check": "^2.0.0",
"svelte-preprocess": "^4.0.0",
"@rollup/plugin-typescript": "^8.0.0",
"typescript": "^4.0.0",
"tslib": "^2.0.0",
"@tsconfig/svelte": "^2.0.0"
})
// Add script for checking
packageJSON.scripts = Object.assign(packageJSON.scripts, {
"check": "svelte-check --tsconfig ./tsconfig.json"
})
// Write the package JSON
fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify(packageJSON, null, " "))
// mv src/main.js to main.ts - note, we need to edit rollup.config.js for this too
const beforeMainJSPath = path.join(projectRoot, "src", "main.js")
const afterMainTSPath = path.join(projectRoot, "src", "main.ts")
fs.renameSync(beforeMainJSPath, afterMainTSPath)
// Switch the app.svelte file to use TS
const appSveltePath = path.join(projectRoot, "src", "App.svelte")
let appFile = fs.readFileSync(appSveltePath, "utf8")
appFile = appFile.replace("<script>", '<script lang="ts">')
appFile = appFile.replace("export let name;", 'export let name: string;')
fs.writeFileSync(appSveltePath, appFile)
// Edit rollup config
const rollupConfigPath = path.join(projectRoot, "rollup.config.js")
let rollupConfig = fs.readFileSync(rollupConfigPath, "utf8")
// Edit imports
rollupConfig = rollupConfig.replace(`'rollup-plugin-terser';`, `'rollup-plugin-terser';
import sveltePreprocess from 'svelte-preprocess';
import typescript from '@rollup/plugin-typescript';`)
// Replace name of entry point
rollupConfig = rollupConfig.replace(`'src/main.js'`, `'src/main.ts'`)
// Add preprocessor
rollupConfig = rollupConfig.replace(
'compilerOptions:',
'preprocess: sveltePreprocess({ sourceMap: !production }),\n\t\t\tcompilerOptions:'
);
// Add TypeScript
rollupConfig = rollupConfig.replace(
'commonjs(),',
'commonjs(),\n\t\ttypescript({\n\t\t\tsourceMap: !production,\n\t\t\tinlineSources: !production\n\t\t}),'
);
fs.writeFileSync(rollupConfigPath, rollupConfig)
// Add TSConfig
const tsconfig = `{
"extends": "@tsconfig/svelte/tsconfig.json",
"include": ["src/**/*"],
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
}`
const tsconfigPath = path.join(projectRoot, "tsconfig.json")
fs.writeFileSync(tsconfigPath, tsconfig)
// Add global.d.ts
const dtsPath = path.join(projectRoot, "src", "global.d.ts")
fs.writeFileSync(dtsPath, `/// <reference types="svelte" />`)
// Delete this script, but not during testing
if (!argv[2]) {
// Remove the script
fs.unlinkSync(path.join(__filename))
// Check for Mac's DS_store file, and if it's the only one left remove it
const remainingFiles = fs.readdirSync(path.join(__dirname))
if (remainingFiles.length === 1 && remainingFiles[0] === '.DS_store') {
fs.unlinkSync(path.join(__dirname, '.DS_store'))
}
// Check if the scripts folder is empty
if (fs.readdirSync(path.join(__dirname)).length === 0) {
// Remove the scripts folder
fs.rmdirSync(path.join(__dirname))
}
}
// Adds the extension recommendation
fs.mkdirSync(path.join(projectRoot, ".vscode"), { recursive: true })
fs.writeFileSync(path.join(projectRoot, ".vscode", "extensions.json"), `{
"recommendations": ["svelte.svelte-vscode"]
}
`)
console.log("Converted to TypeScript.")
if (fs.existsSync(path.join(projectRoot, "node_modules"))) {
console.log("\nYou will need to re-run your dependency manager to get started.")
}

52
frontend/src/App.svelte Normal file
View File

@ -0,0 +1,52 @@
<script>
import Interface from "./Interface.svelte";
export let title;
export let description;
export let theme;
export let dark;
export let input_components;
export let output_components;
export let examples;
export let fn;
export let root;
export let allow_flagging;
export let allow_interpretation;
</script>
<div
class="gradio-bg flex flex-col dark:bg-gray-600 {window.gradio_mode === 'app'
? 'h-full'
: 'h-auto'}"
{theme}
class:dark
>
<div
class="gradio-page container mx-auto flex flex-col box-border flex-grow text-gray-700 dark:text-gray-50"
>
<div class="content pt-4 px-4 mb-4">
{#if title}
<h1 class="title text-center p-4 text-4xl">{title}</h1>
{/if}
{#if description}
<p class="description pb-4">{description}</p>
{/if}
<Interface
{input_components}
{output_components}
{examples}
{theme}
{fn}
{root}
{allow_flagging}
{allow_interpretation}
/>
</div>
</div>
</div>
<style global lang="postcss">
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>

View File

@ -0,0 +1,101 @@
<script>
import { input_component_map } from "./components/directory.js";
export let examples,
examples_dir,
example_id,
setExampleId,
examples_per_page,
input_components,
theme;
let selected_examples = examples;
let gallery = input_components.length === 1;
</script>
<div class="examples" {theme}>
<h4 class="text-lg font-semibold my-2">Examples</h4>
<div
class="examples-holder mt-4 inline-block max-w-full"
class:gallery
class:overflow-x-auto={!gallery}
>
{#if gallery}
<div class="examples-gallery flex gap-2 flex-wrap">
{#each selected_examples as example_row, i}
<button
class="example cursor-pointer p-2 rounded bg-gray-50 dark:bg-gray-700 transition"
on:click={() => setExampleId(i)}
>
<svelte:component
this={input_component_map[input_components[0].name].example}
{theme}
value={example_row[0]}
{examples_dir}
/>
</button>
{/each}
</div>
{:else}
<table
class="examples-table table-auto p-2 bg-gray-50 dark:bg-gray-600 rounded max-w-full border-collapse"
>
<thead class="border-b-2 dark:border-gray-600">
<tr>
{#each input_components as input_component, i}
<th class="py-2 px-4" key={i}>
{input_component.label}
</th>
{/each}
</tr>
</thead>
<tbody>
{#each selected_examples as example_row, i}
<tr
class="cursor-pointer transition"
key={i}
class:selected={i === example_id}
on:click={() => setExampleId(i)}
>
{#each example_row as example_cell, j}
<td class="py-2 px-4">
<svelte:component
this={input_component_map[input_components[j].name].example}
{theme}
value={example_cell}
{examples_dir}
/>
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
{/if}
</div>
</div>
<style lang="postcss" global>
.examples[theme="default"] {
.examples-holder:not(.gallery) {
@apply shadow;
.examples-table {
@apply rounded dark:bg-gray-700;
thead {
@apply border-gray-300 dark:border-gray-600;
}
tbody tr:hover {
@apply bg-yellow-500 dark:bg-red-700 text-white;
}
}
}
.examples-holder .examples-gallery {
.example {
@apply shadow;
}
.example:hover {
@apply bg-yellow-500 text-white;
}
}
}
</style>

View File

@ -0,0 +1,319 @@
<script>
import {
input_component_map,
output_component_map,
} from "./components/directory.js";
import { deepCopy } from "./components/utils/helpers.js";
import ExampleSet from "./ExampleSet.svelte";
export let input_components;
export let output_components;
export let theme;
export let fn;
export let examples;
export let root;
export let allow_flagging;
export let allow_interpretation;
export let avg_durations;
let examples_dir = root + "file/";
let interpret_mode = false;
let submission_count = 0;
let state = "START";
let last_duration = null;
const default_inputs = input_components.map((component) =>
"default" in component ? component.default : null
);
const default_outputs = new Array(output_components.length).fill(null);
let input_values = deepCopy(default_inputs);
let output_values = deepCopy(default_outputs);
let interpretation_values = [];
let timer = null;
let timer_start = 0;
let timer_diff = 0;
let avg_duration = Array.isArray(avg_durations)
? this.props.avg_durations[0]
: null;
const setValues = (index, value) => {
input_values[index] = value;
};
const setExampleId = async (example_id) => {
input_components.forEach(async (input_component, i) => {
const process_example =
input_component_map[input_component.name].process_example;
if (process_example !== undefined) {
input_values[i] = await process_example(
examples[example_id][i],
examples_dir
);
} else {
input_values[i] = examples[example_id][i];
}
});
};
const startTimer = () => {
timer_start = Date.now();
timer_diff = 0;
timer = setInterval(() => {
timer_diff = (Date.now() - timer_start) / 1000;
}, 100);
};
const stopTimer = () => {
clearInterval(timer);
};
const submit = () => {
if (state === "PENDING") {
return;
}
state = "PENDING";
submission_count += 1;
let submission_count_at_click = submission_count;
startTimer();
fn("predict", { data: input_values })
.then((output) => {
if (
state !== "PENDING" ||
submission_count_at_click !== submission_count
) {
return;
}
stopTimer();
output_values = output["data"];
if ("durations" in output) {
last_duration = output["durations"][0];
}
if ("avg_duration" in output) {
avg_duration = output["avg_durations"][0];
}
state = "COMPLETE";
})
.catch((e) => {
if (
state !== "PENDING" ||
submission_count_at_click !== submission_count
) {
return;
}
stopTimer();
console.error(e);
state = "ERROR";
output_values = deepCopy(default_outputs);
});
};
const clear = () => {
input_values = deepCopy(default_inputs);
output_values = deepCopy(default_outputs);
interpret_mode = false;
state = "START";
stopTimer();
};
const flag = () => {
fn("flag", {
data: {
input_data: input_values,
output_data: output_values,
},
});
};
const interpret = () => {
if (interpret_mode) {
interpret_mode = false;
} else {
fn("interpret", {
data: input_values,
}).then((output) => {
interpret_mode = true;
interpretation_values = output.interpretation_scores;
});
}
};
</script>
<div class="gradio-interface" {theme}>
<div class="panels flex flex-wrap justify-center gap-4">
<div class="panel flex-1">
<div
class="component-set p-2 rounded flex flex-col flex-1 gap-2"
style="min-height: 36px"
>
{#each input_components as input_component, i}
<div class="component" key={i}>
<div class="panel-header mb-1.5">{input_component.label}</div>
<svelte:component
this={input_component_map[input_component.name][
interpret_mode ? "interpretation" : "component"
]}
{...input_component}
{theme}
value={input_values[i]}
interpretation={interpret_mode ? interpretation_values[i] : null}
setValue={setValues.bind(this, i)}
/>
</div>
{/each}
</div>
<div class="panel-buttons flex gap-4 my-4">
<button
class="panel-button bg-gray-50 dark:bg-gray-700 flex-1 p-3 rounded transition font-semibold focus:outline-none"
on:click={clear}
>
Clear
</button>
<button
class="panel-button submit bg-gray-50 dark:bg-gray-700 flex-1 p-3 rounded transition font-semibold focus:outline-none"
on:click={submit}
>
Submit
</button>
</div>
</div>
<div class="panel flex-1">
<div
class="component-set p-2 rounded flex flex-col flex-1 gap-2 relative"
style="min-height: 36px"
class:opacity-50={state === "PENDING"}
>
{#if state !== "START"}
<div class="state absolute right-2 flex items-center gap-2 text-xs">
{#if state === "PENDING"}
<div class="timer font-mono">{timer_diff.toFixed(1)}s</div>
<img
src="./static/img/logo.svg"
alt="Pending"
class="pending h-5 ml-2 inline-block"
/>
{:else if state === "ERROR"}
<img
src="./static/img/logo_error.svg"
alt="Error"
class="error h-5 ml-2 inline-block"
/>
{:else if state === "COMPLETE" && last_duration !== null}
<div class="duration font-mono">{last_duration.toFixed(1)}s</div>
{/if}
</div>
{/if}
{#each output_components as output_component, i}
{#if output_values[i] !== null}
<div class="component" key={i}>
<div class="panel-header mb-1.5">{output_component.label}</div>
<svelte:component
this={output_component_map[output_component.name].component}
{...output_component}
{theme}
value={output_values[i]}
/>
</div>
{/if}
{/each}
</div>
<div class="panel-buttons flex gap-4 my-4">
{#if allow_interpretation !== false}
<button
class="panel-button flag bg-gray-50 dark:bg-gray-700 flex-1 p-3 rounded transition font-semibold focus:outline-none"
on:click={interpret}
>
{#if interpret_mode}
Hide
{:else}
Interpret
{/if}
</button>
{/if}
{#if allow_flagging !== "never"}
<button
class="panel-button flag bg-gray-50 dark:bg-gray-700 flex-1 p-3 rounded transition font-semibold focus:outline-none"
on:click={flag}
>
Flag
</button>
{/if}
</div>
</div>
</div>
{#if examples}
<ExampleSet
{examples}
{input_components}
{theme}
{examples_dir}
{setExampleId}
/>
{/if}
</div>
<style lang="postcss" global>
.pending {
@keyframes ld-breath {
0% {
animation-timing-function: cubic-bezier(
0.9647,
0.2413,
-0.0705,
0.7911
);
transform: scale(0.9);
}
51% {
animation-timing-function: cubic-bezier(
0.9226,
0.2631,
-0.0308,
0.7628
);
transform: scale(1.2);
}
100% {
transform: scale(0.9);
}
}
animation: ld-breath 0.75s infinite linear;
}
.gradio-interface[theme="default"] {
.component-set {
@apply bg-gray-50 dark:bg-gray-700 dark:drop-shadow-xl shadow;
}
.component {
@apply mb-2;
}
.panel-header {
@apply uppercase text-xs;
}
.panel-button {
@apply hover:bg-gray-100 dark:hover:bg-gray-600 shadow;
}
.panel-button.disabled {
@apply text-gray-400 cursor-not-allowed;
}
.panel-button.submit {
@apply bg-yellow-500 hover:bg-yellow-400 dark:bg-red-700 dark:hover:bg-red-600 text-white;
}
.examples {
.examples-table-holder:not(.gallery) {
@apply shadow;
.examples-table {
@apply rounded dark:bg-gray-700;
thead {
@apply border-gray-300 dark:border-gray-600;
}
tbody tr:hover {
@apply bg-yellow-500 dark:bg-red-700 text-white;
}
}
}
.examples-table-holder.gallery .examples-table {
tbody td {
@apply shadow;
}
tbody td:hover {
@apply bg-yellow-500 text-white;
}
}
}
}
.gradio-interface[theme="huggingface"] {
}
</style>

54
frontend/src/api.js Normal file
View File

@ -0,0 +1,54 @@
function delay(n) {
return new Promise(function (resolve) {
setTimeout(resolve, n * 1000);
});
}
let postData = async (url, body) => {
const output = await fetch(url, {
method: "POST",
body: JSON.stringify(body),
headers: { "Content-Type": "application/json" }
});
return output;
};
export const fn = async (api_endpoint, action, data, queue, queue_callback) => {
if (queue && ["predict", "interpret"].includes(action)) {
data["action"] = action;
const output = await postData(api_endpoint + "queue/push/", data);
const output_json = await output.json();
let [hash, queue_position] = [
output_json["hash"],
output_json["queue_position"]
];
queue_callback(queue_position, /*is_initial=*/ true);
let status = "UNKNOWN";
while (status != "COMPLETE" && status != "FAILED") {
if (status != "UNKNOWN") {
await delay(1);
}
const status_response = await postData(api_endpoint + "queue/status/", {
hash: hash
});
var status_obj = await status_response.json();
status = status_obj["status"];
if (status === "QUEUED") {
queue_callback(status_obj["data"]);
} else if (status === "PENDING") {
queue_callback(null);
}
}
if (status == "FAILED") {
throw new Error(status);
} else {
return status_obj["data"];
}
} else {
const output = await postData(api_endpoint + action + "/", data);
if (output.status !== 200) {
throw new Error(output.statusText);
}
return await output.json();
}
};

View File

@ -1,229 +0,0 @@
import React from "react";
import { AudioInput, AudioInputExample } from "./components/input/audio";
import {
CheckboxGroupInput,
CheckboxGroupInputExample
} from "./components/input/checkbox_group";
import {
CheckboxInput,
CheckboxInputExample
} from "./components/input/checkbox";
import {
DataframeInput,
DataframeInputExample
} from "./components/input/dataframe";
import {
DropdownInput,
DropdownInputExample
} from "./components/input/dropdown";
import { FileInput, FileInputExample } from "./components/input/file";
import { ImageInput, ImageInputExample } from "./components/input/image";
import { NumberInput, NumberInputExample } from "./components/input/number";
import { RadioInput, RadioInputExample } from "./components/input/radio";
import { SliderInput, SliderInputExample } from "./components/input/slider";
import { TextboxInput, TextboxInputExample } from "./components/input/textbox";
import {
TimeseriesInput,
TimeseriesInputExample
} from "./components/input/timeseries";
import { VideoInput, VideoInputExample } from "./components/input/video";
import { AudioOutput, AudioOutputExample } from "./components/output/audio";
import {
CarouselOutput,
CarouselOutputExample
} from "./components/output/carousel";
import {
DataframeOutput,
DataframeOutputExample
} from "./components/output/dataframe";
import { FileOutput, FileOutputExample } from "./components/output/file";
import {
HighlightedTextOutput,
HighlightedTextOutputExample
} from "./components/output/highlighted_text";
import { HTMLOutput, HTMLOutputExample } from "./components/output/html";
import { ImageOutput, ImageOutputExample } from "./components/output/image";
import { JSONOutput, JSONOutputExample } from "./components/output/json";
import {
KeyValuesOutput,
KeyValuesOutputExample
} from "./components/output/key_values";
import { LabelOutput, LabelOutputExample } from "./components/output/label";
import {
TextboxOutput,
TextboxOutputExample
} from "./components/output/textbox";
import {
TimeseriesOutput,
TimeseriesOutputExample
} from "./components/output/timeseries";
import { VideoOutput, VideoOutputExample } from "./components/output/video";
let input_component_set = [
{
name: "audio",
component: AudioInput,
memoized_component: null,
example_component: AudioInputExample
},
{
name: "checkboxgroup",
component: CheckboxGroupInput,
memoized_component: null,
example_component: CheckboxGroupInputExample
},
{
name: "checkbox",
component: CheckboxInput,
memoized_component: null,
example_component: CheckboxInputExample
},
{
name: "dataframe",
component: DataframeInput,
memoized_component: null,
example_component: DataframeInputExample
},
{
name: "dropdown",
component: DropdownInput,
memoized_component: null,
example_component: DropdownInputExample
},
{
name: "file",
component: FileInput,
memoized_component: null,
example_component: FileInputExample
},
{
name: "image",
component: ImageInput,
memoized_component: null,
example_component: ImageInputExample
},
{
name: "number",
component: NumberInput,
memoized_component: null,
example_component: NumberInputExample
},
{
name: "radio",
component: RadioInput,
memoized_component: null,
example_component: RadioInputExample
},
{
name: "slider",
component: SliderInput,
memoized_component: null,
example_component: SliderInputExample
},
{
name: "textbox",
component: TextboxInput,
memoized_component: null,
example_component: TextboxInputExample
},
{
name: "timeseries",
component: TimeseriesInput,
memoized_component: null,
example_component: TimeseriesInputExample
},
{ name: "video", component: VideoInput, example_component: VideoInputExample }
];
let output_component_set = [
{
name: "audio",
component: AudioOutput,
memoized_component: null,
example_component: AudioOutputExample
},
{
name: "carousel",
component: CarouselOutput,
memoized_component: null,
example_component: CarouselOutputExample
},
{
name: "dataframe",
component: DataframeOutput,
memoized_component: null,
example_component: DataframeOutputExample
},
{
name: "file",
component: FileOutput,
memoized_component: null,
example_component: FileOutputExample
},
{
name: "highlightedtext",
component: HighlightedTextOutput,
memoized_component: null,
example_component: HighlightedTextOutputExample
},
{
name: "html",
component: HTMLOutput,
memoized_component: null,
example_component: HTMLOutputExample
},
{
name: "image",
component: ImageOutput,
memoized_component: null,
example_component: ImageOutputExample
},
{
name: "json",
component: JSONOutput,
memoized_component: null,
example_component: JSONOutputExample
},
{
name: "keyvalues",
component: KeyValuesOutput,
memoized_component: null,
example_component: KeyValuesOutputExample
},
{
name: "label",
component: LabelOutput,
memoized_component: null,
example_component: LabelOutputExample
},
{
name: "textbox",
component: TextboxOutput,
memoized_component: null,
example_component: TextboxOutputExample
},
{
name: "timeseries",
component: TimeseriesOutput,
memoized_component: null,
example_component: TimeseriesOutputExample
},
{
name: "video",
component: VideoOutput,
memoized_component: null,
example_component: VideoOutputExample
}
];
for (let component_set of [input_component_set, output_component_set]) {
for (let component_data of component_set) {
component_data.memoized_component = React.memo(
component_data.component,
component_data.component.memo
);
}
}
export { input_component_set, output_component_set };

View File

@ -0,0 +1,9 @@
<script>
export let value, setValue, theme;
export let choices;
</script>
<div class="dummy" {theme}>DUMMY</div>
<style lang="postcss">
</style>

View File

@ -1,10 +0,0 @@
import React from "react";
export default class BaseComponent extends React.Component {
static memo = (a, b) => {
return a.value === b.value && a.interpretation === b.interpretation;
};
static postprocess = (y) => {
return y;
};
}

View File

@ -1,47 +0,0 @@
import React from "react";
import BaseComponent from "./base_component";
export default class ComponentExample extends React.Component {
render() {
return <div>{this.props.value}</div>;
}
static async preprocess(x, examples_dir, component_config) {
return x;
}
}
export class FileComponentExample extends ComponentExample {
static async preprocess(x, examples_dir, component_config) {
return {
name: x,
data: examples_dir + "/" + x,
is_example: true
};
}
}
export class DataURLComponentExample extends ComponentExample {
static async preprocess(x, examples_dir, component_config) {
let file_url = examples_dir + "/" + x;
let response = await fetch(file_url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
let blob = await response.blob();
return new Promise((resolve, reject) => {
var reader = new FileReader();
reader.addEventListener(
"load",
function () {
resolve(reader.result);
},
false
);
reader.onerror = () => {
return reject(this);
};
reader.readAsDataURL(blob);
});
}
}

View File

@ -0,0 +1,57 @@
import InputAudio from "./input/Audio/config.js";
import InputCheckbox from "./input/Checkbox/config.js";
import InputCheckboxGroup from "./input/CheckboxGroup/config.js";
import InputDropdown from "./input/Dropdown/config.js";
import InputFile from "./input/File/config.js";
import InputImage from "./input/Image/config.js";
import InputNumber from "./input/Number/config.js";
import InputRadio from "./input/Radio/config.js";
import InputSlider from "./input/Slider/config.js";
import InputTextbox from "./input/Textbox/config.js";
import InputVideo from "./input/Video/config.js";
import InputDataFrame from "./input/DataFrame/config.js";
import OutputAudio from "./output/Audio/config.js";
import OutputCarousel from "./output/Carousel/config.js";
import OutputDataframe from "./output/Dataframe/config.js";
import OutputFile from "./output/File/config.js";
import OutputHighlightedText from "./output/HighlightedText/config.js";
import OutputHtml from "./output/Html/config.js";
import OutputImage from "./output/Image/config.js";
import OutputJson from "./output/Json/config.js";
import OutputLabel from "./output/Label/config.js";
import OutputTextbox from "./output/Textbox/config.js";
import OutputVideo from "./output/Video/config.js";
import Dummy from "./Dummy.svelte"
export const input_component_map = {
"audio": InputAudio,
"checkbox": InputCheckbox,
"checkboxgroup": InputCheckboxGroup,
"dataframe": InputDataFrame,
"dropdown": InputDropdown,
"file": InputFile,
"image": InputImage,
"number": InputNumber,
"radio": InputRadio,
"slider": InputSlider,
"textbox": InputTextbox,
"timeseries": {"component": Dummy, "example": Dummy},
"video": InputVideo,
}
export const output_component_map = {
"audio": OutputAudio,
"carousel": OutputCarousel,
"dataframe": OutputDataframe,
"file": OutputFile,
"highlightedtext": OutputHighlightedText,
"html": OutputHtml,
"image": OutputImage,
"json": OutputJson,
"label": OutputLabel,
"textbox": OutputTextbox,
"timeseries": {"component": Dummy, "example": Dummy},
"video": OutputVideo,
}

View File

@ -0,0 +1,162 @@
<script>
import { onDestroy } from "svelte";
import Upload from "../../utils/Upload.svelte";
import ModifyUpload from "../../utils/ModifyUpload.svelte";
import Range from "svelte-range-slider-pips";
export let value,
setValue,
theme,
name,
is_example = false;
export let source;
let recording = false;
let recorder;
let mode = "";
let audio_chunks = [];
let audio_blob;
let player;
let inited = false;
let crop_values = [0, 100];
function blob_to_data_url(blob) {
return new Promise((fulfill, reject) => {
let reader = new FileReader();
reader.onerror = reject;
reader.onload = (e) => fulfill(reader.result);
reader.readAsDataURL(blob);
});
}
async function prepare_audio() {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
recorder = new MediaRecorder(stream);
recorder.addEventListener("dataavailable", (event) => {
audio_chunks.push(event.data);
});
recorder.addEventListener("stop", async () => {
recording = false;
audio_blob = new Blob(audio_chunks, { type: "audio/wav" });
setValue({
data: await blob_to_data_url(audio_blob),
name,
is_example,
});
});
}
async function record() {
recording = true;
audio_chunks = [];
if (!inited) await prepare_audio();
recorder.start();
}
onDestroy(() => {
if (recorder) {
console.log(recorder);
recorder.stop();
}
});
const stop = () => {
recorder.stop();
};
function clear() {
setValue(null);
mode = "";
}
function loaded(node) {
function clamp_playback() {
const start_time = (crop_values[0] / 100) * node.duration;
const end_time = (crop_values[1] / 100) * node.duration;
if (node.currentTime < start_time) {
node.currentTime = start_time;
}
if (node.currentTime > end_time) {
node.currentTime = start_time;
node.pause();
}
}
node.addEventListener("timeupdate", clamp_playback);
return {
destroy: () => node.removeEventListener("timeupdate", clamp_playback),
};
}
</script>
<div class="input-audio">
{#if value === null}
{#if source === "microphone"}
{#if recording}
<button
class="p-2 rounded font-semibold bg-red-200 text-red-500 dark:bg-red-600 dark:text-red-100 shadow transition hover:shadow-md"
on:click={stop}
>
Stop Recording
</button>
{:else}
<button
class="p-2 rounded font-semibold bg-white dark:bg-gray-600 shadow transition hover:shadow-md bg-white dark:bg-gray-800"
on:click={record}
>
Record
</button>
{/if}
{:else if source === "upload"}
<Upload filetype="audio/*" load={setValue} {theme}>
Drop Audio Here
<br />- or -<br />
Click to Upload
</Upload>
{/if}
{:else}
<ModifyUpload
{clear}
edit={() => (mode = "edit")}
absolute={false}
{theme}
/>
<audio
use:loaded
class="w-full"
controls
bind:this={player}
preload="metadata"
src={value.data}
/>
{#if mode === "edit" && player?.duration}
<Range
bind:values={crop_values}
range
min={0}
max={100}
step={1}
on:change={({ detail: { values } }) =>
setValue({
data: value.data,
name,
is_example,
crop_min: values[0],
crop_max: values[1],
})}
/>
{/if}
{/if}
</div>
<style lang="postcss">
</style>

View File

@ -0,0 +1,5 @@
<script>
export let value;
</script>
<div class="input-audio-example">{value}</div>

View File

@ -0,0 +1,18 @@
<script>
import { getSaliencyColor } from "../../utils/helpers";
export let value, interpretation, theme;
</script>
<div class="input-audio" {theme}>
<audio class="w-full" controls>
<source src={value.data} />
</audio>
<div class="interpret_range flex">
{#each interpretation as interpret_value}
<div class="flex-1 h-4" style={"background-color: " + getSaliencyColor(interpret_value)} />
{/each}
</div>
</div>
<style lang="postcss">
</style>

View File

@ -0,0 +1,11 @@
import Component from "./Component.svelte";
import ExampleComponent from "./Example.svelte";
import Interpretation from "./Interpretation.svelte";
import { loadAsFile } from "../../utils/example_processors";
export default {
"component": Component,
"example": ExampleComponent,
"interpretation": Interpretation,
"process_example": loadAsFile
}

View File

@ -0,0 +1,54 @@
<script>
export let value, setValue, theme;
</script>
<div
class="input-checkbox inline-block"
{theme}
on:click={() => setValue(!value)}
>
<button class="checkbox-item py-2 px-3 rounded cursor-pointer" class:selected={value}>
<div class="checkbox w-4 h-4 bg-white flex items-center justify-center">
<svg class="check opacity-0 h-3 w-4" viewBox="-10 -10 20 20">
<line
x1="-7.5"
y1="0"
x2="-2.5"
y2="5"
stroke="white"
stroke-width="4"
stroke-linecap="round"
/>
<line
x1="-2.5"
y1="5"
x2="7.5"
y2="-7.5"
stroke="white"
stroke-width="4"
stroke-linecap="round"
/>
</svg>
</div>
</button>
</div>
<style lang="postcss">
.selected .check {
@apply opacity-100;
}
.input-checkbox[theme="default"] {
.checkbox-item {
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
}
.checkbox {
@apply bg-gray-100 dark:bg-gray-400 transition;
}
.checkbox-item.selected {
@apply bg-yellow-500 dark:bg-red-600 text-white;
}
.selected .checkbox {
@apply bg-yellow-600 dark:bg-red-700;
}
}
</style>

View File

@ -0,0 +1,5 @@
<script>
export let value;
</script>
<div class="input-checkbox-example">{value.toLocaleString()}</div>

View File

@ -0,0 +1,56 @@
<script>
import { getSaliencyColor } from "../../utils/helpers";
export let value, interpretation, theme;
</script>
<div class="input-checkbox inline-block" {theme}>
<button
class="checkbox-item py-2 px-3 rounded cursor-pointer flex gap-1"
class:selected={value}
>
<div
class="checkbox w-4 h-4 bg-white flex items-center justify-center border border-gray-400 box-border"
style={"background-color: " + getSaliencyColor(interpretation[0])}
/>
<div
class="checkbox w-4 h-4 bg-white flex items-center justify-center border border-gray-400 box-border"
style={"background-color: " + getSaliencyColor(interpretation[1])}
>
<svg class="check h-3 w-4" viewBox="-10 -10 20 20">
<line
x1="-7.5"
y1="0"
x2="-2.5"
y2="5"
stroke="black"
stroke-width="4"
stroke-linecap="round"
/>
<line
x1="-2.5"
y1="5"
x2="7.5"
y2="-7.5"
stroke="black"
stroke-width="4"
stroke-linecap="round"
/>
</svg>
</div>
</button>
</div>
<style lang="postcss">
.selected .check {
@apply opacity-100;
}
.input-checkbox[theme="default"] {
.checkbox-item {
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
}
.checkbox-item.selected {
@apply bg-yellow-500 dark:bg-red-600 text-white;
}
}
</style>

View File

@ -0,0 +1,9 @@
import Component from "./Component.svelte";
import ExampleComponent from "./Example.svelte";
import Interpretation from "./Interpretation.svelte";
export default {
"component": Component,
"example": ExampleComponent,
"interpretation": Interpretation,
}

View File

@ -0,0 +1,68 @@
<script>
export let value, setValue, theme;
export let choices;
const toggleChoice = (choice) => {
if (value.includes(choice)) {
value.splice(value.indexOf(choice), 1);
} else {
value.push(choice);
}
setValue(value);
};
</script>
<div class="input-checkbox-group flex flex-wrap gap-2" {theme}>
{#each choices as choice, i}
<button
class="checkbox-item py-2 px-3 font-semibold rounded cursor-pointer flex items-center gap-2"
class:selected={value.includes(choice)}
key={i}
on:click={() => toggleChoice(choice)}
>
<div class="checkbox w-4 h-4 bg-white flex items-center justify-center">
<svg class="check opacity-0 h-3 w-4" viewBox="-10 -10 20 20">
<line
x1="-7.5"
y1="0"
x2="-2.5"
y2="5"
stroke="white"
stroke-width="4"
stroke-linecap="round"
/>
<line
x1="-2.5"
y1="5"
x2="7.5"
y2="-7.5"
stroke="white"
stroke-width="4"
stroke-linecap="round"
/>
</svg>
</div>
{choice}
</button>
{/each}
</div>
<style lang="postcss">
.selected .check {
@apply opacity-100;
}
.input-checkbox-group[theme="default"] {
.checkbox-item {
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
}
.checkbox {
@apply bg-gray-100 dark:bg-gray-400 transition;
}
.checkbox-item.selected {
@apply bg-yellow-500 dark:bg-red-600 text-white;
}
.selected .checkbox {
@apply bg-yellow-600 dark:bg-red-700;
}
}
</style>

View File

@ -0,0 +1,5 @@
<script>
export let value;
</script>
<div class="input-checkboxgroup-example">{value.join(", ")}</div>

View File

@ -0,0 +1,67 @@
<script>
import { getSaliencyColor } from "../../utils/helpers";
export let value, interpretation, theme;
export let choices;
</script>
<div class="input-checkbox-group flex flex-wrap gap-2" {theme}>
{#each choices as choice, i}
<button
class="checkbox-item py-2 px-3 font-semibold rounded cursor-pointer flex items-center gap-1"
class:selected={value.includes(choice)}
key={i}
>
<div
class="checkbox w-4 h-4 bg-white flex items-center justify-center border border-gray-400 box-border"
style={"background-color: " + getSaliencyColor(interpretation[i][0])}
/>
<div
class="checkbox w-4 h-4 bg-white flex items-center justify-center border border-gray-400 box-border"
style={"background-color: " + getSaliencyColor(interpretation[i][1])}
>
<svg class="check h-3 w-4" viewBox="-10 -10 20 20">
<line
x1="-7.5"
y1="0"
x2="-2.5"
y2="5"
stroke="black"
stroke-width="4"
stroke-linecap="round"
/>
<line
x1="-2.5"
y1="5"
x2="7.5"
y2="-7.5"
stroke="black"
stroke-width="4"
stroke-linecap="round"
/>
</svg>
</div>
{choice}
</button>
{/each}
</div>
<style lang="postcss">
.selected .check {
@apply opacity-100;
}
.input-checkbox-group[theme="default"] {
.checkbox-item {
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
}
.checkbox {
@apply bg-gray-100 dark:bg-gray-400 transition;
}
.checkbox-item.selected {
@apply bg-yellow-500 dark:bg-red-600 text-white;
}
.selected .checkbox {
@apply bg-yellow-600 dark:bg-red-700;
}
}
</style>

View File

@ -0,0 +1,9 @@
import Component from "./Component.svelte";
import ExampleComponent from "./Example.svelte";
import Interpretation from "./Interpretation.svelte";
export default {
"component": Component,
"example": ExampleComponent,
"interpretation": Interpretation,
}

View File

@ -0,0 +1,362 @@
<script>
import { tick } from "svelte";
export let theme = "";
export let label = "Title";
export let headers = [];
export let values = [
["Frank", 32, "Male"],
["Beatrice", 99, "Female"],
["Simone", 999, "Male"],
];
export let setValue;
export let editable = true;
let id = 0;
let editing = false;
let selected = false;
let els = {};
function make_headers(_h) {
if (_h.length === 0) {
return values[0].map((_, i) => {
const _id = ++id;
els[_id] = { cell: null, input: null };
return { id: _id, value: i + 1 };
});
} else {
return _h.map((h) => {
const _id = ++id;
els[_id] = { cell: null, input: null };
return { id: _id, value: h };
});
}
}
let _headers = make_headers(headers);
let data = values.map((x) =>
x.map((n) => {
const _id = ++id;
els[id] = { input: null, cell: null };
return { value: n, id: _id };
})
) || [
Array(headers.length)
.fill(0)
.map((_) => {
const _id = ++id;
els[id] = { input: null, cell: null };
return { value: "", id: _id };
}),
];
$: setValue(data.map((r) => r.map(({ value }) => value)));
function get_sort_status(name, sort) {
if (!sort) return "none";
if (sort[0] === name) {
return sort[1];
}
}
async function start_edit(id) {
if (!editable) return;
editing = id;
await tick();
const { input } = els[id];
input.focus();
}
function handle_keydown(event, i, j, id) {
let is_data;
switch (event.key) {
case "ArrowRight":
if (editing) break;
event.preventDefault();
is_data = data[i][j + 1];
selected = is_data ? is_data.id : selected;
break;
case "ArrowLeft":
if (editing) break;
event.preventDefault();
is_data = data[i][j - 1];
selected = is_data ? is_data.id : selected;
break;
case "ArrowDown":
if (editing) break;
event.preventDefault();
is_data = data[i + 1];
selected = is_data ? is_data[j].id : selected;
break;
case "ArrowUp":
if (editing) break;
event.preventDefault();
is_data = data[i - 1];
selected = is_data ? is_data[j].id : selected;
break;
case "Escape":
if (!editable) break;
event.preventDefault();
editing = false;
break;
case "Enter":
if (!editable) break;
event.preventDefault();
if (editing === id) {
editing = false;
} else {
editing = id;
}
break;
default:
break;
}
}
async function handle_cell_click(id) {
editing = false;
selected = id;
}
async function set_focus(id, type) {
if (type === "edit" && typeof id == "number") {
await tick();
els[id].input.focus();
}
if (type === "edit" && typeof id == "boolean") {
let cell = els[selected]?.cell;
await tick();
cell?.focus();
}
if (type === "select" && typeof id == "number") {
const { cell } = els[id];
cell.setAttribute("tabindex", 0);
await tick();
els[id].cell.focus();
}
}
$: set_focus(editing, "edit");
$: set_focus(selected, "select");
let sort_direction;
let sort_by;
function sort(col, dir) {
if (dir === "asc") {
data = data.sort((a, b) => (a[col].value < b[col].value ? -1 : 1));
} else if (dir === "des") {
data = data.sort((a, b) => (a[col].value > b[col].value ? -1 : 1));
}
}
function handle_sort(col) {
if (typeof sort_by !== "number" || sort_by !== col) {
sort_direction = "asc";
sort_by = col;
} else {
if (sort_direction === "asc") {
sort_direction = "des";
} else if (sort_direction === "des") {
sort_direction = "asc";
}
}
sort(col, sort_direction);
}
let header_edit;
async function edit_header(_id, select) {
if (!editable) return;
header_edit = _id;
await tick();
els[_id].input.focus();
if (select) els[_id].input.select();
}
function end_header_edit(event) {
if (!editable) return;
switch (event.key) {
case "Escape":
event.preventDefault();
header_edit = false;
break;
case "Enter":
event.preventDefault();
header_edit = false;
}
}
function add_row() {
data.push(
headers.map(() => {
const _id = ++id;
els[_id] = { cell: null, input: null };
return { id: _id, value: "" };
})
);
data = data;
}
async function add_col() {
for (let i = 0; i < data.length; i++) {
const _id = ++id;
els[_id] = { cell: null, input: null };
data[i].push({ id: _id, value: "" });
}
const _id = ++id;
els[_id] = { cell: null, input: null };
_headers.push({ id: _id, value: `Header ${_headers.length + 1}` });
data = data;
_headers = _headers;
await tick();
edit_header(_id, true);
}
const double_click = (node, { click, dblclick }) => {
let timer;
function handler(event) {
if (timer) {
clearTimeout(timer);
timer = undefined;
dblclick(event);
} else {
timer = setTimeout(() => {
click(event);
timer = undefined;
}, 250);
}
}
node.addEventListener("click", handler);
return {
destroy: () => node.removeEventListener("click", handler),
};
};
</script>
<h4 id="title">{label}</h4>
<div class="shadow overflow-hidden border-gray-200 rounded-sm relative">
<table
id="grid"
role="grid"
aria-labelledby="title"
class="min-w-full divide-y divide-gray-200 "
>
<thead class="bg-gray-50">
<tr>
{#each _headers as { value, id }, i (id)}
<th
use:double_click={{
click: () => handle_sort(i),
dblclick: () => edit_header(id),
}}
aria-sort={get_sort_status(value, sort_by)}
class="relative after:absolute after:opacity-0 after:content-['▲'] after:ml-2 after:inset-y-0 after:h-[1.05rem] after:m-auto relative px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
class:sorted={sort_by === i}
class:des={sort_by === i && sort_direction === "des"}
>
{#if header_edit === id}
<input
class="bg-transparent inset-y-0 left-6 w-full outline-none absolute p-0 w-3/4 text-xs font-medium text-gray-500 uppercase tracking-wider"
tabindex="-1"
bind:value
bind:this={els[id].input}
on:keydown={end_header_edit}
on:blur={({ currentTarget }) =>
currentTarget.setAttribute("tabindex", -1)}
/>
{/if}
<span
tabindex="-1"
role="button"
class="min-h-full"
class:opacity-0={header_edit === id}>{value}</span
>
</th>
{/each}
</tr></thead
><tbody class="bg-white divide-y divide-gray-200">
{#each data as row, i (row)}
<tr>
{#each row as { value, id }, j (id)}
<td
tabindex="-1"
class="p-0 whitespace-nowrap display-block outline-none relative "
on:dblclick={() => start_edit(id)}
on:click={() => handle_cell_click(id)}
on:keydown={(e) => handle_keydown(e, i, j, id)}
bind:this={els[id].cell}
on:blur={({ currentTarget }) =>
currentTarget.setAttribute("tabindex", -1)}
>
<div
class:border-gray-600={selected === id}
class:border-transparent={selected !== id}
class="min-h-[3.3rem] px-5 py-3 border-[0.125rem]"
>
{#if editing === id}
<input
class="w-full outline-none absolute p-0 w-3/4"
tabindex="-1"
bind:value
bind:this={els[id].input}
on:blur={({ currentTarget }) =>
currentTarget.setAttribute("tabindex", -1)}
/>
{/if}
<span
class=" cursor-default w-full"
class:opacity-0={editing === id}
tabindex="-1"
role="button"
>
{value}
</span>
</div>
</td>
{/each}
</tr>
{/each}
</tbody>
</table>
</div>
{#if editable}
<div class="flex justify-end ">
<button
on:click={add_col}
class="hover:bg-gray-100 dark:hover:bg-gray-600 shadow py-1 px-3 rounded transition focus:outline-none m-2 mr-0"
>New Column</button
>
<button
on:click={add_row}
class="bg-yellow-500 hover:bg-yellow-400 dark:bg-red-700 dark:hover:bg-red-600 text-white shadow py-1 px-3 rounded transition focus:outline-none m-2 mr-0"
>New Row</button
>
</div>
{/if}
<style>
.sorted::after {
opacity: 1;
}
.des::after {
transform: rotate(180deg) translateY(1.5px);
}
</style>

View File

@ -0,0 +1,5 @@
import Component from "./Component.svelte";
export default {
"component": Component,
}

View File

@ -0,0 +1,46 @@
<script>
export let value, setValue, theme;
export let choices;
</script>
<div class="input-dropdown group inline-block relative" {theme}>
<button
class="selector py-2 px-3 font-semibold rounded inline-flex items-center"
>
{value}
<svg class="caret ml-2 fill-current h-4 w-4" viewBox="0 0 20 20">
<path
d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
/>
</svg>
</button>
<div
class="dropdown-menu-holder absolute hidden group-hover:block pt-1 z-10 bg-none"
>
<ul class="dropdown-menu max-h-80 overflow-y-auto">
{#each choices as choice, i}
<li
class="dropdown-item first:rounded-t transition last:rounded-b py-2 px-3 block whitespace-nowrap cursor-pointer"
on:click={() => setValue(choice)}
key={i}
>
{choice}
</li>
{/each}
</ul>
</div>
</div>
<style lang="postcss" global>
.input-dropdown[theme="default"] {
.selector {
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
}
.dropdown-menu {
@apply shadow;
}
.dropdown-item {
@apply bg-white dark:bg-gray-800 hover:bg-yellow-500 dark:hover:bg-red-600 hover:text-gray-50 hover:font-semibold;
}
}
</style>

View File

@ -0,0 +1,5 @@
<script>
export let value;
</script>
<div class="input-dropdown-example">{value}</div>

View File

@ -0,0 +1,34 @@
<script>
import { getSaliencyColor } from "../../utils/helpers";
export let value, interpretation, theme;
export let choices;
</script>
<div class="input-dropdown" {theme}>
<ul class="dropdown-menu">
{#each choices as choice, i}
<li
class="dropdown-item first:rounded-t transition last:rounded-b py-2 px-3 block whitespace-nowrap cursor-pointer"
style={"background-color: " + getSaliencyColor(interpretation[i])}
key={i}
>
{choice}
</li>
{/each}
</ul>
</div>
<style lang="postcss" global>
.input-dropdown[theme="default"] {
.selector {
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
}
.dropdown-menu {
@apply shadow;
}
.dropdown-item {
@apply bg-white dark:bg-gray-800 hover:font-semibold;
}
}
</style>

View File

@ -0,0 +1,9 @@
import Component from "./Component.svelte";
import ExampleComponent from "./Example.svelte";
import Interpretation from "./Interpretation.svelte";
export default {
"component": Component,
"example": ExampleComponent,
"interpretation": Interpretation,
}

View File

@ -0,0 +1,35 @@
<script>
import Upload from "../../utils/Upload.svelte";
import ModifyUpload from "../../utils/ModifyUpload.svelte";
import { prettyBytes } from "../../utils/helpers";
export let value, setValue, theme;
</script>
<div class="input-file" {theme}>
{#if value === null}
<Upload load={setValue} {theme}>
Drop File Here
<br />- or -<br />
Click to Upload
</Upload>
{:else}
<div
class="file-preview w-full flex flex-col justify-center items-center relative"
>
<ModifyUpload clear={() => setValue(null)} {theme} />
<div class="file-name text-4xl p-6 break-all">{value.name}</div>
{#if value.size}
<div class="file-size text-2xl p-2">
{prettyBytes(value.size)}
</div>
{/if}
</div>
{/if}
</div>
<style lang="postcss">
.input-file[theme="default"] .file-preview {
@apply h-60;
}
</style>

View File

@ -0,0 +1,5 @@
<script>
export let value;
</script>
<div class="input-file-example">{value}</div>

View File

@ -0,0 +1,9 @@
import Component from "./Component.svelte";
import ExampleComponent from "./Example.svelte";
import { loadAsFile } from "../../utils/example_processors";
export default {
"component": Component,
"example": ExampleComponent,
"process_example": loadAsFile
}

View File

@ -0,0 +1,86 @@
<script>
import Cropper from "../../utils/Cropper.svelte";
import Upload from "../../utils/Upload.svelte";
import ModifyUpload from "../../utils/ModifyUpload.svelte";
import ModifySketch from "../../utils/ModifySketch.svelte";
import ImageEditor from "../../utils/ImageEditor.svelte";
import Sketch from "../../utils/Sketch.svelte";
import Webcam from "../../utils/Webcam.svelte";
export let value, setValue, theme;
export let source = "upload";
export let tool = "editor";
let mode;
let sketch;
function handle_save({ detail }) {
setValue(detail);
mode = "view";
}
</script>
<div class="input-image">
<div
class="image-preview w-full h-80 flex justify-center items-center dark:bg-gray-600 relative"
class:bg-gray-200={value}
class:h-80={source !== "webcam"}
>
{#if source === "canvas"}
<ModifySketch
on:undo={() => sketch.undo()}
on:clear={() => sketch.clear()}
/>
<Sketch bind:this={sketch} on:change={({ detail }) => setValue(detail)} />
{:else if value === null}
{#if source === "upload"}
<Upload
filetype="image/x-png,image/gif,image/jpeg"
load={setValue}
include_file_metadata={false}
{theme}
>
Drop Image Here
<br />- or -<br />
Click to Upload
</Upload>
{:else if source === "webcam"}
<Webcam on:capture={({ detail }) => setValue(detail)} />
{/if}
{:else if tool === "select"}
<Cropper image={value} on:crop={({ detail }) => setValue(detail)} />
{:else if tool === "editor"}
{#if mode === "edit"}
<ImageEditor
{value}
on:cancel={() => (mode = "view")}
on:save={handle_save}
/>
{/if}
<ModifyUpload
edit={() => (mode = "edit")}
clear={() => setValue(null)}
{theme}
/>
<img class="w-full h-full object-contain" src={value} alt="" />
{/if}
</div>
</div>
<style lang="postcss">
:global(.image_editor_buttons) {
width: 800px;
@apply flex justify-end gap-1;
}
:global(.image_editor_buttons button) {
@apply px-2 py-1 text-xl bg-black text-white font-semibold rounded-t;
}
:global(.tui-image-editor-header-buttons) {
@apply hidden;
}
:global(.tui-colorpicker-palette-button) {
width: 12px;
height: 12px;
}
</style>

View File

@ -0,0 +1,6 @@
<script>
export let value, examples_dir;
</script>
<!-- svelte-ignore a11y-missing-attribute -->
<img class="input-image-example h-24 max-w-none" src={examples_dir + value} />

View File

@ -0,0 +1,72 @@
<script>
import ModifyUpload from "../../utils/ModifyUpload.svelte";
import { getObjectFitSize, getSaliencyColor } from "../../utils/helpers";
import { afterUpdate } from "svelte";
export let value, setValue, interpretation, shape, theme;
let saliency_layer;
let image;
const paintSaliency = (data, ctx, width, height) => {
var cell_width = width / data[0].length;
var cell_height = height / data.length;
var r = 0;
data.forEach(function (row) {
var c = 0;
row.forEach(function (cell) {
ctx.fillStyle = getSaliencyColor(cell);
ctx.fillRect(c * cell_width, r * cell_height, cell_width, cell_height);
c++;
});
r++;
});
};
afterUpdate(() => {
let size = getObjectFitSize(
true,
image.width,
image.height,
image.naturalWidth,
image.naturalHeight
);
if (shape) {
size = getObjectFitSize(
true,
size.width,
size.height,
shape[0],
shape[1]
);
}
let width = size.width;
let height = size.height;
saliency_layer.setAttribute("height", height);
saliency_layer.setAttribute("width", width);
paintSaliency(
interpretation,
saliency_layer.getContext("2d"),
width,
height
);
});
</script>
<div class="input-image">
<div
class="image-preview w-full h-60 flex justify-center items-center bg-gray-200 dark:bg-gray-600 relative"
>
<!-- svelte-ignore a11y-missing-attribute -->
<div
class="interpretation w-full h-full absolute top-0 left-0 flex justify-center items-center opacity-90 hover:opacity-20 transition"
>
<canvas bind:this={saliency_layer} />
</div>
<!-- svelte-ignore a11y-missing-attribute -->
<img class="w-full h-full object-contain" bind:this={image} src={value} />
</div>
</div>
<style lang="postcss">
</style>

View File

@ -0,0 +1,11 @@
import Component from "./Component.svelte";
import ExampleComponent from "./Example.svelte";
import Interpretation from "./Interpretation.svelte";
import { loadAsData } from "../../utils/example_processors";
export default {
"component": Component,
"example": ExampleComponent,
"interpretation": Interpretation,
"process_example": loadAsData
}

View File

@ -0,0 +1,25 @@
<script>
export let value, setValue, theme;
</script>
<input
type="number"
class="input-number w-full rounded box-border p-2 focus:outline-none appearance-none"
{value}
on:change={(e) => setValue(parseFloat(e.target.value))}
{theme}
/>
<style lang="postcss" global>
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
.input-number[theme="default"] {
@apply shadow transition hover:shadow-md dark:bg-gray-800;
}
</style>

View File

@ -0,0 +1,5 @@
<script>
export let value;
</script>
<div class="input-number-example">{value}</div>

View File

@ -0,0 +1,32 @@
<script>
import { getSaliencyColor } from "../../utils/helpers";
export let value, interpretation, theme;
</script>
<div class="input-number">
<div class="interpret_range flex">
{#each interpretation as interpret_value}
<div
class="flex-1"
style={"background-color: " + getSaliencyColor(interpret_value[1])}
>
{interpret_value[0]}
</div>
{/each}
</div>
</div>
<style lang="postcss" global>
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
.input-number[theme="default"] {
@apply shadow transition hover:shadow-md dark:bg-gray-800;
}
</style>

View File

@ -0,0 +1,9 @@
import Component from "./Component.svelte";
import ExampleComponent from "./Example.svelte";
import Interpretation from "./Interpretation.svelte";
export default {
"component": Component,
"example": ExampleComponent,
"interpretation": Interpretation,
}

View File

@ -0,0 +1,38 @@
<script>
export let value, setValue, theme;
export let choices;
</script>
<div class="input-radio flex flex-wrap gap-2" {theme}>
{#each choices as choice, i}
<button
class="radio-item py-2 px-3 font-semibold rounded cursor-pointer flex items-center gap-2"
class:selected={value === choice}
key={i}
on:click={() => setValue(choice)}
>
<div class="radio-circle w-4 h-4 rounded-full box-border" />
{choice}
</button>
{/each}
</div>
<style lang="postcss">
.input-radio[theme="default"] {
.radio-item {
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
}
.radio-circle {
@apply bg-gray-50 dark:bg-gray-400 border-4 border-gray-200 dark:border-gray-600;
}
.radio-item.selected {
@apply bg-yellow-500 dark:bg-red-600 text-white shadow;
}
.radio-circle {
@apply w-4 h-4 bg-white transition rounded-full box-border;
}
.selected .radio-circle {
@apply border-yellow-600 dark:border-red-700;
}
}
</style>

View File

@ -0,0 +1,5 @@
<script>
export let value;
</script>
<div class="input-radio-example">{value}</div>

View File

@ -0,0 +1,35 @@
<script>
import { getSaliencyColor } from "../../utils/helpers";
export let value, interpretation, theme;
export let choices;
</script>
<div class="input-radio flex flex-wrap gap-2" {theme}>
{#each choices as choice, i}
<button
class="radio-item py-2 px-3 font-semibold rounded cursor-pointer flex items-center gap-2"
class:selected={value === choice}
key={i}
>
<div class="radio-circle w-4 h-4 rounded-full box-border"
style={"background-color: " + getSaliencyColor(interpretation[i])}
/>
{choice}
</button>
{/each}
</div>
<style lang="postcss">
.input-radio[theme="default"] {
.radio-item {
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
}
.radio-circle {
@apply w-4 h-4 rounded-full box-border;
}
.radio-item.selected {
@apply bg-yellow-500 dark:bg-red-600 text-white shadow;
}
}
</style>

View File

@ -0,0 +1,9 @@
import Component from "./Component.svelte";
import ExampleComponent from "./Example.svelte";
import Interpretation from "./Interpretation.svelte";
export default {
"component": Component,
"example": ExampleComponent,
"interpretation": Interpretation,
}

View File

@ -0,0 +1,42 @@
<script>
export let value, setValue, theme;
export let minimum, maximum, step;
</script>
<div class="input-slider text-center" {theme}>
<input
type="range"
class="range w-full appearance-none transition rounded h-4"
on:input={(e) => setValue(parseFloat(e.target.value))}
{value}
min={minimum}
max={maximum}
{step}
/>
<div class="value inline-block mx-auto mt-1 px-2 py-0.5 rounded">{value}</div>
</div>
<style lang="postcss">
.range::-webkit-slider-thumb {
-webkit-appearance: none;
@apply appearance-none w-5 h-5 rounded cursor-pointer;
}
.range::-moz-range-thumb {
@apply appearance-none w-5 h-5 rounded cursor-pointer;
}
.input-slider[theme="default"] {
.range {
@apply bg-white dark:bg-gray-800 shadow h-3 transition hover:shadow-md;
}
.range::-webkit-slider-thumb {
@apply bg-gradient-to-b from-yellow-400 to-yellow-500 dark:from-red-500 dark:to-red-600 shadow;
}
.range::-moz-range-thumb {
@apply bg-gradient-to-b from-yellow-400 to-yellow-500 shadow;
}
.value {
@apply bg-gray-100 dark:bg-gray-600 font-semibold;
}
}
</style>

View File

@ -0,0 +1,5 @@
<script>
export let value;
</script>
<div class="input-slider-example">{value}</div>

View File

@ -0,0 +1,49 @@
<script>
import { getSaliencyColor } from "../../utils/helpers";
export let value, interpretation, theme;
export let minimum, maximum, step;
</script>
<div class="input-slider text-center" {theme}>
<input
type="range"
class="range w-full appearance-none transition rounded h-4"
disabled
{value}
min={minimum}
max={maximum}
{step}
/>
<div class="interpret_range flex">
{#each interpretation as interpret_value}
<div class="flex-1 h-4" style={"background-color: " + getSaliencyColor(interpret_value)} />
{/each}
</div>
<div class="value inline-block mx-auto mt-1 px-2 py-0.5 rounded">{value}</div>
</div>
<style lang="postcss">
.range::-webkit-slider-thumb {
-webkit-appearance: none;
@apply appearance-none w-5 h-5 rounded cursor-pointer;
}
.range::-moz-range-thumb {
@apply appearance-none w-5 h-5 rounded cursor-pointer;
}
.input-slider[theme="default"] {
.range {
@apply bg-white dark:bg-gray-800 shadow h-3 transition hover:shadow-md;
}
.range::-webkit-slider-thumb {
@apply bg-gradient-to-b from-yellow-400 to-yellow-500 dark:from-red-500 dark:to-red-600 shadow;
}
.range::-moz-range-thumb {
@apply bg-gradient-to-b from-yellow-400 to-yellow-500 shadow;
}
.value {
@apply bg-gray-100 dark:bg-gray-600 font-semibold;
}
}
</style>

View File

@ -0,0 +1,9 @@
import Component from "./Component.svelte";
import ExampleComponent from "./Example.svelte";
import Interpretation from "./Interpretation.svelte";
export default {
"component": Component,
"example": ExampleComponent,
"interpretation": Interpretation,
}

View File

@ -0,0 +1,29 @@
<script>
export let value, setValue, theme;
export let lines, placeholder;
</script>
{#if lines > 1}
<textarea
class="input-text w-full rounded box-border p-2 focus:outline-none appearance-none"
{value}
{placeholder}
on:change={(e) => setValue(e.target.value)}
{theme}
/>
{:else}
<input
type="text"
class="input-text w-full rounded box-border p-2 focus:outline-none appearance-none"
{value}
{placeholder}
on:change={(e) => setValue(e.target.value)}
{theme}
/>
{/if}
<style lang="postcss" global>
.input-text[theme="default"] {
@apply shadow transition hover:shadow-md dark:bg-gray-800;
}
</style>

View File

@ -0,0 +1,5 @@
<script>
export let value;
</script>
<div class="input-text-example">{value}</div>

View File

@ -0,0 +1,7 @@
import Component from "./Component.svelte";
import ExampleComponent from "./Example.svelte";
export default {
"component": Component,
"example": ExampleComponent,
}

View File

@ -0,0 +1,43 @@
<script>
import Upload from "../../utils/Upload.svelte";
import ModifyUpload from "../../utils/ModifyUpload.svelte";
import { prettyBytes, playable } from "../../utils/helpers";
export let value, setValue, theme;
export let source;
</script>
<div
class="video-preview w-full h-80 object-contain flex justify-center items-center dark:bg-gray-600 relative"
class:bg-gray-200={value}
>
{#if value === null}
{#if source === "upload"}
<Upload filetype="video/mp4,video/x-m4v,video/*" load={setValue} {theme}>
Drop Video Here
<br />- or -<br />
Click to Upload
</Upload>
{/if}
{:else}
<ModifyUpload clear={() => setValue(null)} {theme} />
{#if playable(value.name)}
<!-- svelte-ignore a11y-media-has-caption -->
<video
class="w-full h-full object-contain bg-black"
controls
playsInline
preload
src={value.data}
/>
{:else}
<div class="file-name text-4xl p-6 break-all">{value.name}</div>
<div class="file-size text-2xl p-2">
{prettyBytes(value.size)}
</div>
{/if}
{/if}
</div>
<style lang="postcss">
</style>

View File

@ -0,0 +1,20 @@
<script>
import { playable } from "../../utils/helpers";
export let value, examples_dir;
let video;
</script>
<!-- svelte-ignore a11y-media-has-caption -->
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
{#if playable(value)}
<video
bind:this={video}
on:mouseover={video.play}
on:mouseout={video.pause}
class="input-video-example h-24 max-w-none"
src={examples_dir + value}
/>
{:else}
<div class="input-video-example">{value}</div>
{/if}

View File

@ -0,0 +1,9 @@
import Component from "./Component.svelte";
import ExampleComponent from "./Example.svelte";
import { loadAsFile } from "../../utils/example_processors";
export default {
"component": Component,
"example": ExampleComponent,
"process_example": loadAsFile
}

View File

@ -1,275 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import { FileComponentExample } from "../component_example";
import Recorder from "recorder-js";
import { getSaliencyColor } from "../../utils";
import classNames from "classnames";
import edit_icon from "../../static/img/edit.svg";
import clear_icon from "../../static/img/clear.svg";
import MultiRangeSlider from "./../../vendor/MultiRangeSlider/MultiRangeSlider";
class AudioInput extends BaseComponent {
constructor(props) {
super(props);
this.state = {
recording: false,
editorMode: false
};
this.src = {};
this.key = 0; // needed to prevent audio caching
this.uploader = React.createRef();
this.audioRef = React.createRef();
this.started = false;
}
static memo = (a, b) => {
if (a.value instanceof Object && b.value instanceof Object) {
return (
a.value["name"] === b.value["name"] &&
a.value["data"] === b.value["data"] &&
a.value["crop_min"] === b.value["crop_min"] &&
a.value["crop_max"] === b.value["crop_max"]
);
} else {
return a === b;
}
};
start = () => {
if (!this.started) {
const audioContext = new (window.AudioContext ||
window.webkitAudioContext)();
this.recorder = new Recorder(audioContext);
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
this.recorder.init(stream);
this.recorder.start();
});
this.started = true;
} else {
this.recorder.start();
}
this.setState({
recording: true
});
};
stop = () => {
this.recorder.stop().then(({ blob, buffer }) => {
let reader = new FileReader();
reader.onload = function (e) {
this.props.handleChange({
name: "sample.wav",
data: e.target.result,
is_example: false
});
}.bind(this);
reader.readAsDataURL(blob);
});
this.setState({
recording: false
});
};
openFileUpload = () => {
this.uploader.current.click();
};
toggleEditor = () => {
let [crop_min, crop_max] = [0, 100];
this.props.handleChange({
name: this.props.value["name"],
data: this.props.value["data"],
is_example: this.props.value["is_example"],
crop_min: crop_min,
crop_max: crop_max
});
this.setState({ editorMode: !this.state.editorMode });
};
crop = (min, max, lastChange) => {
if (this.state.duration) {
if (lastChange === "min") {
this.audioRef.current.currentTime = (min / 100) * this.state.duration;
} else {
this.audioRef.current.currentTime = (max / 100) * this.state.duration;
}
}
this.props.handleChange({
name: this.props.value["name"],
data: this.props.value["data"],
is_example: this.props.value["is_example"],
crop_min: min,
crop_max: max
});
};
reset_playback_within_crop = () => {
let position_ratio =
this.audioRef.current.currentTime / this.state.duration;
let min_ratio = this.props.value.crop_min / 100;
let max_ratio = this.props.value.crop_max / 100;
if (
position_ratio > max_ratio - 0.00001 ||
position_ratio < min_ratio - 0.00001
) {
this.audioRef.current.currentTime = this.state.duration * min_ratio;
return true;
} else {
return false;
}
};
render() {
if (this.props.value !== null) {
if (
this.props.value["name"] != this.src["name"] ||
this.props.value["data"] !== this.src["data"]
) {
this.key += 1;
this.src = {
name: this.props.value["name"],
data: this.props.value["data"]
};
}
return (
<div className="input_audio">
<div className="edit_buttons">
<button
className={classNames("edit_button", {
active: this.state.editorMode
})}
onClick={this.toggleEditor}
>
<img src={edit_icon} />
</button>
<button
className="clear_button"
onClick={this.props.handleChange.bind(this, null)}
>
<img src={clear_icon} />
</button>
</div>
<audio
controls
key={this.key}
ref={this.audioRef}
onLoadedMetadata={(e) =>
this.setState({ duration: e.nativeEvent.target.duration })
}
onPlay={() => {
this.reset_playback_within_crop();
this.audioRef.current.play();
}}
onTimeUpdate={() => {
if (this.audioRef.current.paused) {
return;
}
let out_of_crop = this.reset_playback_within_crop();
if (out_of_crop) {
this.audioRef.current.pause();
}
}}
>
<source src={this.props.value["data"]}></source>
</audio>
{this.props.interpretation === null ? (
false
) : (
<div class="interpret_range">
{this.props.interpretation.map((value) => (
<div
style={{ "background-color": getSaliencyColor(value) }}
></div>
))}
</div>
)}
{this.state.editorMode ? (
<MultiRangeSlider
min={0}
max={100}
onChange={({ min, max, lastChange }) =>
this.crop(min, max, lastChange)
}
/>
) : (
false
)}
</div>
);
} else {
if (this.props.source === "microphone") {
return (
<div className="input_audio">
{this.state.recording ? (
<button className="stop" onClick={this.stop}>
Recording...
</button>
) : (
<button className="start" onClick={this.start}>
Record
</button>
)}
</div>
);
} else if (this.props.source === "upload") {
let no_action = (evt) => {
evt.preventDefault();
evt.stopPropagation();
};
return (
<div
className="input_image"
onDrag={no_action}
onDragStart={no_action}
onDragEnd={no_action}
onDragOver={no_action}
onDragEnter={no_action}
onDragLeave={no_action}
onDrop={no_action}
>
<div
className="upload_zone"
onClick={this.openFileUpload}
onDrop={this.load_preview_from_drop}
>
Drop Audio Here
<br />- or -<br />
Click to Upload
</div>
<input
className="hidden_upload"
type="file"
ref={this.uploader}
onChange={this.load_preview_from_upload}
accept="audio/*"
style={{ display: "none" }}
/>
</div>
);
}
}
}
load_preview_from_drop = (evt) => {
this.load_preview_from_files(evt.dataTransfer.files);
};
load_preview_from_upload = (evt) => {
this.load_preview_from_files(evt.target.files);
};
load_preview_from_files = (files) => {
if (!files.length || !window.FileReader) {
return;
}
var component = this;
var ReaderObj = new FileReader();
let file = files[0];
ReaderObj.readAsDataURL(file);
ReaderObj.onloadend = function () {
component.props.handleChange({
name: file.name,
data: this.result,
is_example: false
});
};
};
}
class AudioInputExample extends FileComponentExample {
render() {
return <div className="input_audio_example">{this.props.value}</div>;
}
}
export { AudioInput, AudioInputExample };

View File

@ -1,71 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import ComponentExample from "../component_example";
import classNames from "classnames";
import { getSaliencyColor } from "../../utils";
class CheckboxInput extends BaseComponent {
constructor(props) {
super(props);
}
handleChange = () => {
this.props.handleChange(this.props.value !== true);
};
render() {
return (
<div className="input_checkbox">
<div
className={classNames("checkbox_item", {
selected: this.props.value
})}
onClick={this.handleChange}
>
{this.props.interpretation === null ? (
<div className="checkbox">
<svg className="check" viewBox="-10 -10 20 20">
<line x1="-7.5" y1="0" x2="-2.5" y2="5"></line>
<line x1="-2.5" y1="5" x2="7.5" y2="-7.5"></line>
</svg>
</div>
) : (
<div class="interpretation">
<div
class="interpretation_box"
style={{
backgroundColor: getSaliencyColor(
this.props.interpretation[0]
)
}}
></div>
<div
class="interpretation_box"
style={{
backgroundColor: getSaliencyColor(
this.props.interpretation[1]
)
}}
>
<svg className="interpret_check" viewBox="-10 -10 20 20">
<line x1="-7.5" y1="0" x2="-2.5" y2="5"></line>
<line x1="-2.5" y1="5" x2="7.5" y2="-7.5"></line>
</svg>
</div>
</div>
)}
</div>
</div>
);
}
}
class CheckboxInputExample extends ComponentExample {
render() {
return (
<div className="input_checkbox_example">
{JSON.stringify(this.props.value)}
</div>
);
}
}
export { CheckboxInput, CheckboxInputExample };

View File

@ -1,83 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import ComponentExample from "../component_example";
import classNames from "classnames";
import { getSaliencyColor } from "../../utils";
class CheckboxGroupInput extends BaseComponent {
constructor(props) {
super(props);
}
handleChange = (selected_item) => {
let all_selected = [...this.props.value];
if (all_selected.includes(selected_item)) {
all_selected = all_selected.filter((item) => item !== selected_item);
} else {
all_selected.push(selected_item);
}
this.props.handleChange(all_selected);
};
render() {
return (
<div className="input_checkbox_group">
{this.props.choices.map((item, index) => {
return (
<div
className={classNames("checkbox_item", {
selected: this.props.value.includes(item)
})}
onClick={this.handleChange.bind(this, item)}
key={index}
>
{this.props.interpretation === null ? (
<div className="checkbox">
<svg className="check" viewBox="-10 -10 20 20">
<line x1="-7.5" y1="0" x2="-2.5" y2="5"></line>
<line x1="-2.5" y1="5" x2="7.5" y2="-7.5"></line>
</svg>
</div>
) : (
<div class="interpretation">
<div
class="interpretation_box"
style={{
backgroundColor: getSaliencyColor(
this.props.interpretation[index][0]
)
}}
></div>
<div
class="interpretation_box"
style={{
backgroundColor: getSaliencyColor(
this.props.interpretation[index][1]
)
}}
>
<svg className="interpret_check" viewBox="-10 -10 20 20">
<line x1="-7.5" y1="0" x2="-2.5" y2="5"></line>
<line x1="-2.5" y1="5" x2="7.5" y2="-7.5"></line>
</svg>
</div>
</div>
)}
{item}
</div>
);
})}
</div>
);
}
}
class CheckboxGroupInputExample extends ComponentExample {
render() {
return (
<div className="input_checkbox_group_example">
{this.props.value.join(", ")}
</div>
);
}
}
export { CheckboxGroupInput, CheckboxGroupInputExample };

View File

@ -1,115 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import ComponentExample from "../component_example";
import jspreadsheet from "jspreadsheet-ce";
import "../../../node_modules/jspreadsheet-ce/dist/jexcel.css";
import "../../../node_modules/jspreadsheet-ce/dist/jspreadsheet.css";
class DataframeInput extends BaseComponent {
constructor(props) {
super(props);
this.wrapper = React.createRef();
}
componentDidMount() {
this.el = jspreadsheet(this.wrapper.current, this.getConfig());
}
getConfig = () => {
let col_count = this.props.col_count;
let config = {};
if (this.props.headers || this.props.datatype) {
let column_config = [];
for (let i = 0; i < col_count; i++) {
let column = {};
if (this.props.datatype) {
let datatype =
this.props.datatype instanceof Array
? this.props.datatype[i]
: this.props.datatype;
let col_width =
this.props.col_width instanceof Array
? this.props.col_width[i]
: this.props.col_width;
let datatype_map = {
str: "text",
bool: "checkbox",
number: "numeric",
date: "calendar"
};
column.type = datatype_map[datatype];
column.width = col_width;
}
if (this.props.headers) {
column.title = this.props.headers[i];
}
column_config.push(column);
}
config.columns = column_config;
}
config.data = this.props.value;
return config;
};
resetData(new_data) {
let [new_rows, new_cols] = [new_data.length, new_data[0].length];
let current_data = this.el.getData();
let [cur_rows, cur_cols] = [current_data.length, current_data[0].length];
if (cur_rows < new_rows) {
this.el.insertRow(new_rows - cur_rows);
} else if (cur_rows > new_rows) {
this.el.deleteRow(0, cur_rows - new_rows);
}
if (cur_cols < new_cols) {
this.el.insertColumn(new_cols - cur_cols);
} else if (cur_cols > new_cols) {
this.el.deleteColumn(0, cur_cols - new_cols);
}
this.el.setData(new_data);
}
render() {
if (
JSON.stringify(this.props.value) !== JSON.stringify(this.data) &&
this.el
) {
this.resetData(this.props.value);
this.data = this.props.value;
}
return (
<div className="input_dataframe">
<div ref={this.wrapper} />
</div>
);
}
}
class DataframeInputExample extends ComponentExample {
render() {
let data_copy = [];
for (let row of this.props.value.slice(0, 3)) {
let new_row = row.slice(0, 3);
if (row.length > 3) {
new_row.push("...");
}
data_copy.push(new_row);
}
if (this.props.value.length > 3) {
let new_row = Array(data_copy[0].length).fill("...");
data_copy.push(new_row);
}
return (
<table className="input_dataframe_example">
<tbody>
{data_copy.map((row) => {
return (
<tr>
{row.map((cell) => {
return <td>{cell}</td>;
})}
</tr>
);
})}
</tbody>
</table>
);
}
}
export { DataframeInput, DataframeInputExample };

View File

@ -1,68 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import ComponentExample from "../component_example";
import classNames from "classnames";
import { getSaliencyColor } from "../../utils";
class DropdownInput extends BaseComponent {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(selected_item) {
this.props.handleChange(selected_item);
}
render() {
return (
<div className="input_dropdown">
{this.props.interpretation === null ? (
<div className="dropdown">
<button className="selector">
{this.props.value}
<svg className="caret" viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" />{" "}
</svg>
</button>
<div class="dropdown_menu_holder">
<ul className="dropdown_menu">
{this.props.choices.map((item, index) => {
return (
<li
className={classNames("dropdown_item", {
selected: item === this.props.value
})}
onClick={this.handleChange.bind(this, item)}
key={index}
>
{item}
</li>
);
})}
</ul>
</div>
</div>
) : (
<div class="interpretation">
{this.props.interpretation.map((value, index) => (
<div
class="interpretation_box"
key={index}
style={{ backgroundColor: getSaliencyColor(value) }}
>
{this.props.choices[index]}
</div>
))}
</div>
)}
</div>
);
}
}
class DropdownInputExample extends ComponentExample {
render() {
return <div className="input_dropdown_example">{this.props.value}</div>;
}
}
export { DropdownInput, DropdownInputExample };

View File

@ -1,147 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import { FileComponentExample } from "../component_example";
import { prettyBytes } from "../../utils";
import clear_icon from "../../static/img/clear.svg";
class FileInput extends BaseComponent {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.uploader = React.createRef();
this.openFileUpload = this.openFileUpload.bind(this);
this.load_preview_from_files = this.load_preview_from_files.bind(this);
this.load_preview_from_upload = this.load_preview_from_upload.bind(this);
this.load_preview_from_drop = this.load_preview_from_drop.bind(this);
}
handleChange(data) {
this.props.handleChange(data);
}
openFileUpload() {
this.uploader.current.click();
}
render() {
let no_action = (evt) => {
evt.preventDefault();
evt.stopPropagation();
};
let file_name, file_size;
if (this.props.value !== null) {
if (this.props.file_count === "single") {
let file = Array.isArray(this.props.value)
? this.props.value[0]
: this.props.value;
file_name = file.name;
file_size = file.size;
} else {
file_name = this.props.value.length + " files.";
file_size = 0;
let fileset = Array.isArray(this.props.value)
? this.props.value
: [this.props.value];
for (let file of fileset) {
if (file.size === null) {
file_size = null;
break;
} else {
file_size += file.size;
}
}
}
return (
<div className="input_file">
<div className="file_preview_holder">
<div class="edit_buttons">
<button
className="clear_button"
onClick={this.handleChange.bind(this, null)}
>
<img src={clear_icon} />
</button>
</div>
<div className="file_name">{file_name}</div>
<div className="file_size">
{file_size === null || file_size === undefined
? ""
: prettyBytes(file_size)}
</div>
</div>
</div>
);
} else {
return (
<div
className="input_file"
onDrag={no_action}
onDragStart={no_action}
onDragEnd={no_action}
onDragOver={no_action}
onDragEnter={no_action}
onDragLeave={no_action}
onDrop={no_action}
>
<div
className="upload_zone"
onClick={this.openFileUpload}
onDrop={this.load_preview_from_drop}
>
Drop File Here
<br />- or -<br />
Click to Upload
</div>
<input
className="hidden_upload"
type="file"
multiple={this.props.file_count === "multiple"}
webkitdirectory={this.props.file_count === "directory"}
mozdirectory={this.props.file_count === "directory"}
ref={this.uploader}
onChange={this.load_preview_from_upload}
style={{ display: "none" }}
/>
</div>
);
}
}
load_preview_from_drop(evt) {
this.load_preview_from_files(evt.dataTransfer.files);
}
load_preview_from_upload(evt) {
this.load_preview_from_files(evt.target.files);
}
add_file_to_list(reader, file, file_count) {
this.file_data.push({
name: file.name,
size: file.size,
data: reader.result,
is_example: false
});
if (this.file_data.length === file_count) {
this.handleChange(this.file_data);
}
}
load_preview_from_files(files) {
if (!files.length || !window.FileReader) {
return;
}
this.file_data = [];
for (let file of files) {
let ReaderObj = new FileReader();
ReaderObj.readAsDataURL(file);
ReaderObj.onloadend = this.add_file_to_list.bind(
this,
ReaderObj,
file,
files.length
);
}
}
}
class FileInputExample extends FileComponentExample {
render() {
return <div className="input_file_example">{this.props.value}</div>;
}
}
export { FileInput, FileInputExample };

View File

@ -1,298 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import { DataURLComponentExample } from "../component_example";
import Webcam from "react-webcam";
import { SketchField, Tools } from "../../vendor/ReactSketch";
import { getObjectFitSize, paintSaliency } from "../../utils";
import "tui-image-editor/dist/tui-image-editor.css";
import ImageEditor from "@toast-ui/react-image-editor";
import Cropper from "react-cropper";
import "cropperjs/dist/cropper.css";
import edit_icon from "../../static/img/edit.svg";
import clear_icon from "../../static/img/clear.svg";
class ImageInput extends BaseComponent {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.uploader = React.createRef();
this.openFileUpload = this.openFileUpload.bind(this);
this.onImgLoad = this.onImgLoad.bind(this);
this.load_preview_from_files = this.load_preview_from_files.bind(this);
this.load_preview_from_upload = this.load_preview_from_upload.bind(this);
this.load_preview_from_drop = this.load_preview_from_drop.bind(this);
this.saveEditor = this.saveEditor.bind(this);
this.cancelEditor = this.cancelEditor.bind(this);
this.snapshot = this.snapshot.bind(this);
this.getSketch = this.getSketch.bind(this);
this.openEditor = this.openEditor.bind(this);
this.imgRef = React.createRef();
this.webcamRef = React.createRef();
this.sketchRef = React.createRef();
this.editorRef = React.createRef();
this.cropperRef = React.createRef();
this.sketchKey = 0;
this.state = { editorMode: false };
}
static memo = (a, b) => {
if (a.interpretation != b.interpretation) {
return false;
} else if (a.value === null && b.value === null) {
return true;
} else if (a.value === null || b.value === null) {
return false;
} else {
return a.value.src === b.value.src;
}
};
handleChange(data) {
this.props.handleChange(data);
}
openFileUpload() {
this.uploader.current.click();
}
snapshot() {
let imageSrc = this.webcamRef.current.getScreenshot();
this.handleChange({ src: imageSrc, crop: null });
}
getSketch() {
let imageSrc = this.sketchRef.current.toDataURL();
this.handleChange({ src: imageSrc, crop: null });
}
cancelEditor() {
this.setState({ editorMode: false });
}
saveEditor() {
const editorInstance = this.editorRef.current.getInstance();
this.handleChange({ src: editorInstance.toDataURL(), crop: null });
this.setState({ editorMode: false });
}
onImgLoad({ target: img }) {
this.setState({
dimensions: {
height: img.offsetHeight,
width: img.offsetWidth
}
});
}
openEditor() {
this.setState({ editorMode: true });
}
onCrop = () => {
const crop = this.cropperRef.current.cropper.getCroppedCanvas().toDataURL();
this.handleChange({ src: this.props.value.src, crop: crop });
};
render() {
let no_action = (evt) => {
evt.preventDefault();
evt.stopPropagation();
};
if (this.props.value !== null && this.props.source !== "canvas") {
let interpretation = false;
if (this.props.interpretation !== null) {
let img = this.imgRef.current;
let size = getObjectFitSize(
true,
img.width,
img.height,
img.naturalWidth,
img.naturalHeight
);
if (this.props.shape) {
size = getObjectFitSize(
true,
size.width,
size.height,
this.props.shape[0],
this.props.shape[1]
);
}
let width = size.width;
let height = size.height;
let canvas = document.createElement("canvas");
canvas.setAttribute("height", height);
canvas.setAttribute("width", width);
paintSaliency(
this.props.interpretation,
canvas.getContext("2d"),
width,
height
);
interpretation = (
<div class="interpretation">
<img src={canvas.toDataURL()}></img>
</div>
);
}
return (
<div className="input_image">
<div className="image_preview_holder">
{this.props.tool === "editor" ? (
this.state.editorMode ? (
<div className="image_editor">
<div className="image_editor_buttons">
<button onClick={this.saveEditor}>Save</button>
<button onClick={this.cancelEditor}>Cancel</button>
</div>
<ImageEditor
ref={this.editorRef}
includeUI={{
loadImage: { path: this.props.value.src, name: "value" },
uiSize: {
width: "800px",
height: "600px"
},
menuBarPosition: "left"
}}
cssMaxHeight={500}
cssMaxWidth={700}
usageStatistics={false}
/>
</div>
) : (
<div className="edit_buttons">
<button className="edit_button" onClick={this.openEditor}>
<img src={edit_icon} />
</button>
<button
className="clear_button"
onClick={this.handleChange.bind(this, null)}
>
<img src={clear_icon} />
</button>
</div>
)
) : (
false
)}
{this.props.tool === "select" ? (
<Cropper
src={this.props.value.src}
style={{ height: "100%", width: "100%" }}
ref={this.cropperRef}
crop={this.onCrop}
autoCropArea={1}
/>
) : (
<img
ref={this.imgRef}
onLoad={this.onImgLoad}
className="image_preview"
alt=""
src={this.props.value.src}
/>
)}
</div>
{interpretation}
</div>
);
} else {
if (this.props.source === "upload") {
return (
<div
className="input_image"
onDrag={no_action}
onDragStart={no_action}
onDragEnd={no_action}
onDragOver={no_action}
onDragEnter={no_action}
onDragLeave={no_action}
onDrop={no_action}
>
<div
className="upload_zone"
onClick={this.openFileUpload}
onDrop={this.load_preview_from_drop}
>
Drop Image Here
<br />- or -<br />
Click to Upload
</div>
<input
className="hidden_upload"
type="file"
ref={this.uploader}
onChange={this.load_preview_from_upload}
accept="image/x-png,image/gif,image/jpeg"
style={{ display: "none" }}
/>
</div>
);
} else if (this.props.source === "webcam") {
return (
<div className="input_image">
<div className="image_preview_holder">
<Webcam ref={this.webcamRef} />
<div class="snapshot">
<button onClick={this.snapshot}>Click to Take Snapshot</button>
</div>
</div>
</div>
);
} else if (this.props.source === "canvas") {
if (
this.props.value === null &&
this.sketchRef &&
this.sketchRef.current
) {
this.sketchKey += 1;
}
return (
<div className="input_image">
<div className="image_preview_holder sketch">
<SketchField
ref={this.sketchRef}
key={this.sketchKey}
width="320px"
height="100%"
tool={Tools.Pencil}
lineColor="black"
lineWidth={20}
backgroundColor="white"
onChange={this.getSketch}
/>
</div>
</div>
);
}
}
}
load_preview_from_drop(evt) {
this.load_preview_from_files(evt.dataTransfer.files);
}
load_preview_from_upload(evt) {
this.load_preview_from_files(evt.target.files);
}
load_preview_from_files(files) {
if (!files.length || !window.FileReader || !/^image/.test(files[0].type)) {
return;
}
var component = this;
var ReaderObj = new FileReader();
ReaderObj.readAsDataURL(files[0]);
ReaderObj.onloadend = function () {
component.props.handleChange({ src: this.result, crop: null });
};
}
static postprocess = (y) => {
return y === null ? null : y.crop === null ? y.src : y.crop;
};
}
class ImageInputExample extends DataURLComponentExample {
static async preprocess(x, examples_dir) {
let src = await DataURLComponentExample.preprocess(x, examples_dir);
return { src: src, crop: null };
}
render() {
return (
<img
className="input_image_example"
src={this.props.examples_dir + "/" + this.props.value}
alt=""
/>
);
}
}
export { ImageInput, ImageInputExample };

View File

@ -1,51 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import ComponentExample from "../component_example";
import { getSaliencyColor } from "../../utils";
class NumberInput extends BaseComponent {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(evt) {
let value = evt.target.value;
let valid_non_numbers = ["", "-", ".", "-."];
if (!isNaN(parseFloat(value)) || valid_non_numbers.includes(value)) {
this.props.handleChange(evt.target.value);
}
}
render() {
return (
<div className="input_number">
{this.props.interpretation === null ? (
<input
type="text"
onChange={this.handleChange}
value={this.props.value === null ? "" : this.props.value}
></input>
) : (
<div class="interpretation">
{this.props.interpretation.map((value, index) => (
<div
class="interpretation_box"
key={index}
style={{ backgroundColor: getSaliencyColor(value[1]) }}
>
{value[0]}
</div>
))}
</div>
)}
</div>
);
}
}
class NumberInputExample extends ComponentExample {
render() {
return <div className="input_number_example">{this.props.value}</div>;
}
}
export { NumberInput, NumberInputExample };

View File

@ -1,55 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import ComponentExample from "../component_example";
import classNames from "classnames";
import { getSaliencyColor } from "../../utils";
class RadioInput extends BaseComponent {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(selected_item) {
this.props.handleChange(selected_item);
}
render() {
return (
<div className="input_radio">
{this.props.choices.map((item, index) => {
return (
<div key={index}>
<div
className={classNames("radio_item", {
selected: item === this.props.value
})}
onClick={this.handleChange.bind(this, item)}
>
{this.props.interpretation === null ? (
<div className="radio_circle"></div>
) : (
<div
className="radio_circle"
style={{
backgroundColor: getSaliencyColor(
this.props.interpretation[index]
)
}}
></div>
)}
{item}
</div>
</div>
);
})}
</div>
);
}
}
class RadioInputExample extends ComponentExample {
render() {
return <div className="input_radio_example">{this.props.value}</div>;
}
}
export { RadioInput, RadioInputExample };

View File

@ -1,50 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import ComponentExample from "../component_example";
import { getSaliencyColor } from "../../utils";
class SliderInput extends BaseComponent {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(evt) {
this.props.handleChange(parseFloat(evt.target.value));
}
render() {
return (
<div className="input_slider">
{this.props.interpretation === null ? (
<>
<input
type="range"
className="range"
onChange={this.handleChange}
value={this.props.value}
min={this.props.minimum}
max={this.props.maximum}
step={this.props.step}
></input>
<div className="value">{this.props.value}</div>
</>
) : (
<div class="interpret_range">
{this.props.interpretation.map((value) => (
<div
style={{ "background-color": getSaliencyColor(value) }}
></div>
))}
</div>
)}
</div>
);
}
}
class SliderInputExample extends ComponentExample {
render() {
return <div className="input_slider_example">{this.props.value}</div>;
}
}
export { SliderInput, SliderInputExample };

View File

@ -1,63 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import ComponentExample from "../component_example";
import { getSaliencyColor } from "../../utils";
class TextboxInput extends BaseComponent {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(evt) {
this.props.handleChange(evt.target.value);
}
render() {
if (this.props.interpretation !== null) {
return (
<div className="input_text">
<div class="interpretation">
{this.props.interpretation.map((item, index) => (
<div
class="interpretation_box"
key={index}
style={{ backgroundColor: getSaliencyColor(item[1]) }}
>
{item[0]}
</div>
))}
</div>
</div>
);
} else if (this.props.lines > 1) {
return (
<div className="input_text">
<textarea
value={this.props.value || ""}
rows={this.props.lines}
onChange={this.handleChange}
placeholder={this.props.placeholder || ""}
></textarea>
</div>
);
} else {
return (
<div className="input_text">
<input
type="text"
onChange={this.handleChange}
value={this.props.value || ""}
placeholder={this.props.placeholder || ""}
></input>
</div>
);
}
}
}
class TextboxInputExample extends ComponentExample {
render() {
return <div className="input_textbox_example">{this.props.value}</div>;
}
}
export { TextboxInput, TextboxInputExample };

View File

@ -1,163 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import FileComponentExample from "../component_example";
import { CSVToArray } from "../../utils";
import { Scatter } from "react-chartjs-2";
import { getNextColor } from "../../utils";
class TimeseriesInput extends BaseComponent {
constructor(props) {
super(props);
this.uploader = React.createRef();
}
handleChange = (data) => {
this.props.handleChange(data);
};
openFileUpload = () => {
this.uploader.current.click();
};
render = () => {
let no_action = (evt) => {
evt.preventDefault();
evt.stopPropagation();
};
if (this.props.value !== null) {
return (
<div className="input_timeseries">
<Scatter
data={{
datasets: this.props.y.map((header, i) => {
return {
label: header,
borderColor: getNextColor(i),
showLine: true,
fill: true,
backgroundColor: getNextColor(i, 0.25),
data: this.props.value["data"].map((row) => {
return {
x: row[0],
y: row[i + 1]
};
})
};
})
}}
/>
</div>
);
} else {
return (
<div
className="input_timeseries"
onDrag={no_action}
onDragStart={no_action}
onDragEnd={no_action}
onDragOver={no_action}
onDragEnter={no_action}
onDragLeave={no_action}
onDrop={no_action}
>
<div
className="upload_zone"
onClick={this.openFileUpload}
onDrop={this.load_preview_from_drop}
>
Upload Timeseries CSV
{this.props.x !== null ? (
<>
<br />X Column: {this.props.x}
<br />Y Column: {this.props.y.join(", ")}
</>
) : (
false
)}
</div>
<input
className="hidden_upload"
type="file"
multiple={this.props.file_count === "multiple"}
webkitdirectory={this.props.file_count === "directory"}
mozdirectory={this.props.file_count === "directory"}
ref={this.uploader}
onChange={this.load_preview_from_upload}
style={{ display: "none" }}
/>
</div>
);
}
};
load_preview_from_drop = (evt) => {
this.load_preview_from_files(evt.dataTransfer.files);
};
load_preview_from_upload = (evt) => {
this.load_preview_from_files(evt.target.files);
};
load_file = (reader) => {
let lines = reader.result;
this.handleChange(load_data(lines, this.props.x, this.props.y));
};
load_preview_from_files = (files) => {
if (!files.length || !window.FileReader) {
return;
}
this.file_data = [];
for (let file of files) {
let ReaderObj = new FileReader();
ReaderObj.readAsBinaryString(file);
ReaderObj.onloadend = this.load_file.bind(this, ReaderObj);
}
};
static memo = (a, b) => {
if (a.value instanceof Object && b.value instanceof Object) {
return (
a.value["data"] === b.value["data"] &&
a.value["headers"] === b.value["headers"]
);
} else {
return a === b;
}
};
}
class TimeseriesInputExample extends FileComponentExample {
static async preprocess(x, examples_dir, component_config) {
let file_url = examples_dir + "/" + x;
let response = await fetch(file_url);
response = await response.text();
return load_data(response, component_config.x, component_config.y);
}
render() {
return <div className="input_file_example">{this.props.value}</div>;
}
}
var load_data = (lines, x, y) => {
let headers = null;
let data = null;
let line_array = CSVToArray(lines);
if (line_array.length === 0) {
return;
}
if (x === null) {
data = line_array;
} else {
let x_index = line_array[0].indexOf(x);
let y_indices = y.map((y_col) => line_array[0].indexOf(y_col));
if (x_index === -1) {
alert("Missing x column: " + x);
return;
}
if (y_indices.includes(-1)) {
alert("Missing y column: " + y[y_indices.indexOf(-1)]);
return;
}
line_array = line_array.map((line) =>
[line[x_index]].concat(y_indices.map((y_index) => line[y_index]))
);
headers = line_array[0];
data = line_array.slice(1);
}
return { headers: headers, data: data, range: null };
};
export { TimeseriesInput, TimeseriesInputExample };

View File

@ -1,223 +0,0 @@
import React from "react";
import BaseComponent from "../base_component";
import { FileComponentExample } from "../component_example";
import { isPlayable } from "../../utils";
import clear_icon from "../../static/img/clear.svg";
class VideoInput extends BaseComponent {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.uploader = React.createRef();
this.videoRecorder = React.createRef();
this.openFileUpload = this.openFileUpload.bind(this);
this.load_preview_from_files = this.load_preview_from_files.bind(this);
this.load_preview_from_upload = this.load_preview_from_upload.bind(this);
this.load_preview_from_drop = this.load_preview_from_drop.bind(this);
this.camera_stream = null;
this.state = {
recording: false
};
}
handleChange(evt) {
this.props.handleChange(evt.target.value);
}
openFileUpload() {
this.uploader.current.click();
}
record = async () => {
if (this.state.recording) {
this.media_recorder.stop();
let video_blob = new Blob(this.blobs_recorded, { type: this.mimeType });
var ReaderObj = new FileReader();
ReaderObj.onload = function (e) {
let file_name = "sample." + this.mimeType.substring(6);
this.props.handleChange({
name: file_name,
data: e.target.result,
is_example: false
});
}.bind(this);
ReaderObj.readAsDataURL(video_blob);
this.setState({ recording: false });
} else {
this.blobs_recorded = [];
this.camera_stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
this.videoRecorder.current.srcObject = this.camera_stream;
this.videoRecorder.current.volume = 0;
let selectedMimeType = null;
let validMimeTypes = ["video/webm", "video/mp4"];
for (let mimeType of validMimeTypes) {
if (MediaRecorder.isTypeSupported(mimeType)) {
selectedMimeType = mimeType;
break;
}
}
if (selectedMimeType === null) {
console.error("No supported MediaRecorder mimeType");
return;
}
this.media_recorder = new MediaRecorder(this.camera_stream, {
mimeType: selectedMimeType
});
this.mimeType = selectedMimeType;
this.media_recorder.addEventListener(
"dataavailable",
function (e) {
this.blobs_recorded.push(e.data);
}.bind(this)
);
this.media_recorder.start(200);
this.videoRecorder.current.play();
this.setState({ recording: true });
}
};
render() {
let no_action = (evt) => {
evt.preventDefault();
evt.stopPropagation();
};
if (this.props.value != null) {
return (
<div className="input_video">
<div className="edit_buttons">
<button
className="clear_button"
onClick={this.props.handleChange.bind(this, null)}
>
<img src={clear_icon} />
</button>
</div>
{isPlayable("video", this.props.value["name"]) ? (
<div className="video_preview_holder">
<video
className="video_preview"
controls
playsInline
preload
src={this.props.value["data"]}
></video>
</div>
) : (
<div className="video_file_holder">{this.props.value["name"]}</div>
)}
</div>
);
} else if (this.props.source == "upload") {
return (
<div
className="input_video"
onDrag={no_action}
onDragStart={no_action}
onDragEnd={no_action}
onDragOver={no_action}
onDragEnter={no_action}
onDragLeave={no_action}
onDrop={no_action}
>
<div
className="upload_zone"
onClick={this.openFileUpload}
onDrop={this.load_preview_from_drop}
>
Drop Video Here
<br />- or -<br />
Click to Upload
</div>
<input
className="hidden_upload"
type="file"
ref={this.uploader}
onChange={this.load_preview_from_upload}
accept="video/mp4,video/x-m4v,video/*"
style={{ display: "none" }}
/>
</div>
);
} else if (this.props.source == "webcam") {
return (
<div className="input_video">
<video
ref={this.videoRecorder}
class="video_recorder"
autoPlay
playsInline
muted
></video>
<div class="record_holder">
<div class="record_message">
{this.state.recording ? (
<>Stop Recording</>
) : (
<>Click to Record</>
)}
</div>
<button class="record" onClick={this.record}></button>
</div>
</div>
);
}
}
load_preview_from_drop(evt) {
this.load_preview_from_files(evt.dataTransfer.files);
}
load_preview_from_upload(evt) {
this.load_preview_from_files(evt.target.files);
}
load_preview_from_files(files) {
if (!files.length || !window.FileReader || !/^video/.test(files[0].type)) {
return;
}
var component = this;
var ReaderObj = new FileReader();
let file = files[0];
ReaderObj.readAsDataURL(file);
ReaderObj.onloadend = function () {
component.props.handleChange({
name: file.name,
data: this.result,
is_example: false
});
};
}
}
class VideoInputExample extends FileComponentExample {
constructor(props) {
super(props);
this.video = React.createRef();
}
render() {
if (isPlayable("video", this.props.value)) {
return (
<div className="input_video_example">
<div className="video_holder">
<video
ref={this.video}
className="video_preview"
onMouseOver={() => {
this.video.current.play();
}}
onMouseOut={() => {
this.video.current.pause();
}}
preload="metadata"
>
<source
src={this.props.examples_dir + "/" + this.props.value}
></source>
</video>
</div>
</div>
);
} else {
return <div className="input_video_example">{this.props.value}</div>;
}
}
}
export { VideoInput, VideoInputExample };

View File

@ -0,0 +1,10 @@
<script>
export let value, theme;
</script>
<audio {theme} controls>
<source src={value} />
</audio>
<style lang="postcss">
</style>

View File

@ -0,0 +1,5 @@
import Component from "./Component.svelte";
export default {
"component": Component,
}

View File

@ -0,0 +1,56 @@
<script>
import { output_component_map } from "../../directory";
export let value, theme;
export let components;
let carousel_index = 0;
const next = () => {
carousel_index = (carousel_index + 1) % value.length;
};
const prev = () => {
carousel_index = (carousel_index - 1 + value.length) % value.length;
};
</script>
<div class="output-carousel flex flex-col gap-2" {theme}>
{#each components as component, i}
<div class="component" key={i}>
{#if component.label}
<div class="panel-header">{component.label}</div>
{/if}
<svelte:component
this={output_component_map[component.name].component}
{...component}
{theme}
value={value[carousel_index][i]}
/>
</div>
{/each}
<div class="carousel-control flex gap-4 justify-center items-center my-1">
<button on:click={prev}>
<svg class="caret h-3 mt-0.5 fill-current" viewBox="0 0 9.1457395 15.999842">
<path
d="M 0.32506616,7.2360106 7.1796187,0.33129769 c 0.4360247,-0.439451 1.1455702,-0.442056 1.5845974,-0.0058 0.4390612,0.435849 0.441666,1.14535901 0.00582,1.58438501 l -6.064985,6.1096644 6.10968,6.0646309 c 0.4390618,0.436026 0.4416664,1.145465 0.00582,1.584526 -0.4358485,0.439239 -1.1453586,0.441843 -1.5845975,0.0058 L 0.33088256,8.8203249 C 0.11135166,8.6022941 0.00105996,8.3161928 7.554975e-6,8.0295489 -0.00104244,7.7427633 0.10735446,7.4556467 0.32524356,7.2361162"
/>
</svg>
</button>
<div class="carousel_index text-xl text-center font-semibold" style="min-width: 60px">
{carousel_index + 1} / {value.length}
</div>
<button on:click={next}>
<svg
class="caret h-3 mt-0.5 fill-current"
viewBox="0 0 9.1457395 15.999842"
transform="scale(-1, 1)"
>
<path
d="M 0.32506616,7.2360106 7.1796187,0.33129769 c 0.4360247,-0.439451 1.1455702,-0.442056 1.5845974,-0.0058 0.4390612,0.435849 0.441666,1.14535901 0.00582,1.58438501 l -6.064985,6.1096644 6.10968,6.0646309 c 0.4390618,0.436026 0.4416664,1.145465 0.00582,1.584526 -0.4358485,0.439239 -1.1453586,0.441843 -1.5845975,0.0058 L 0.33088256,8.8203249 C 0.11135166,8.6022941 0.00105996,8.3161928 7.554975e-6,8.0295489 -0.00104244,7.7427633 0.10735446,7.4556467 0.32524356,7.2361162"
/>
</svg>
</button>
</div>
</div>
<style lang="postcss">
</style>

View File

@ -0,0 +1,5 @@
import Component from "./Component.svelte";
export default {
"component": Component,
}

View File

@ -0,0 +1,18 @@
<script>
import DataFrame from "../../input/DataFrame/Component.svelte";
export let headers,
value,
theme,
setValue = () => {};
$: console.log(headers, value);
</script>
<DataFrame
headers={headers || value.headers}
values={value.data}
{setValue}
editable={false}
{theme}
/>

View File

@ -0,0 +1,5 @@
import Component from "./Component.svelte";
export default {
"component": Component,
}

View File

@ -0,0 +1,23 @@
<script>
import { prettyBytes } from "../../utils/helpers";
export let value, theme;
</script>
<a
class="output-file w-full h-full flex flex-col justify-center items-center relative"
href={value.data}
download={value.name}
{theme}
>
<div class="file-name text-4xl p-6 break-all">{value.name}</div>
<div class="text-2xl p-2">
{isNaN(value.size) ? "" : prettyBytes(value.size)}
</div>
</a>
<style lang="postcss">
.output-file[theme="default"] {
@apply h-60 hover:text-gray-500;
}
</style>

View File

@ -0,0 +1,5 @@
import Component from "./Component.svelte";
export default {
"component": Component,
}

View File

@ -0,0 +1,105 @@
<script>
import { getNextColor } from "../../utils/helpers";
export let value, theme;
export let show_legend, color_map;
color_map = color_map || {};
let mode;
if (value.length > 0) {
for (let [_, label] of value) {
if (label !== null) {
if (typeof label === "string") {
mode = "categories";
if (!(label in color_map)) {
let color = getNextColor(Object.keys(color_map).length);
color_map[label] = color;
}
} else {
mode = "scores";
}
}
}
}
console.log(color_map);
</script>
<div class="output-highlightedtext" {theme}>
{#if mode === "categories"}
{#if show_legend}
<div class="category-legend flex flex-wrap gap-1 mb-2">
{#each color_map.entries() as [category, color], i}
<div
class="category-label px-2 py-1 rounded text-white font-semibold"
style={"background-color" + color}
key={i}
>
{category}
</div>
{/each}
</div>
{/if}
<div
class="textfield p-2 bg-white dark:bg-gray-800 rounded box-border max-w-full leading-8 break-all"
>
{#each value as [text, category], i}
<span
class="textspan p-1 mr-0.5 bg-opacity-20 dark:bg-opacity-80 rounded-sm"
title={category}
style={category === null
? ""
: `color: ${color_map[category]}; background-color: ${color_map[
category
].replace("1)", "var(--tw-bg-opacity))")}`}
key={i}
>
<span class="text dark:text-white">{text}</span>
{#if !show_legend && category !== null}
<span
class="inline-category text-xs text-white ml-0.5 px-0.5 rounded-sm"
style={category === null
? ""
: `background-color: ${color_map[category]}`}
>
{category}
</span>
{/if}
</span>
{/each}
</div>
{:else}
{#if show_legend}
<div
class="color_legend flex px-2 py-1 justify-between rounded mb-3 font-semibold"
style="background: -webkit-linear-gradient(to right,#8d83d6,(255,255,255,0),#eb4d4b); background: linear-gradient(to right,#8d83d6,rgba(255,255,255,0),#eb4d4b);"
>
<span>-1</span>
<span>0</span>
<span>+1</span>
</div>
{/if}
<div
class="textfield p-2 bg-white dark:bg-gray-800 rounded box-border max-w-full leading-8 break-all"
>
{#each value as [text, score], i}
<span
class="textspan p-1 mr-0.5 bg-opacity-20 dark:bg-opacity-80 rounded-sm"
title={value}
style={"background-color: rgba(" +
(score < 0 ? "141, 131, 214," + -score : "235, 77, 75," + score) +
")"}
>
<span class="text dark:text-white">{text}</span>
</span>
{/each}
</div>
{/if}
</div>
<style lang="postcss">
.output-highlightedtext[theme="default"] {
.textfield {
@apply shadow;
}
}
</style>

View File

@ -0,0 +1,5 @@
import Component from "./Component.svelte";
export default {
"component": Component,
}

View File

@ -0,0 +1,10 @@
<script>
export let value, theme;
</script>
<div
class="output-html"
{theme}
>
{@html value}
</div>

View File

@ -0,0 +1,5 @@
import Component from "./Component.svelte";
export default {
"component": Component,
}

View File

@ -0,0 +1,11 @@
<script>
export let value, theme;
</script>
<div class="output-image w-full h-60 flex justify-center items-center bg-gray-200 dark:bg-gray-600 relative" {theme}>
<!-- svelte-ignore a11y-missing-attribute -->
<img class="w-full h-full object-contain" src={value} />
</div>
<style lang="postcss">
</style>

View File

@ -0,0 +1,5 @@
import Component from "./Component.svelte";
export default {
"component": Component,
}

Some files were not shown because too many files have changed in this diff Show More