Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Svelte demo project #9

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions examples/svelte/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example

# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
30 changes: 30 additions & 0 deletions examples/svelte/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module.exports = {
root: true,
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:svelte/recommended',
'prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2020,
extraFileExtensions: ['.svelte']
},
env: {
browser: true,
es2017: true,
node: true
},
overrides: [
{
files: ['*.svelte'],
parser: 'svelte-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser'
}
}
]
};
10 changes: 10 additions & 0 deletions examples/svelte/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
2 changes: 2 additions & 0 deletions examples/svelte/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
engine-strict=true
resolution-mode=highest
13 changes: 13 additions & 0 deletions examples/svelte/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example

# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
9 changes: 9 additions & 0 deletions examples/svelte/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}
38 changes: 38 additions & 0 deletions examples/svelte/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Web-gphoto2 Svelte Demo
This demo was created using [Svelte](https://svelte.dev/)+[Kit](https://kit.svelte.dev/), [Tailwindcss](https://tailwindcss.com/) and [DaisyUI](https://daisyui.com/).

## Important notes
- Check `vite.config.ts`, it has crucial configuration for using the web-gphoto2 package, see the root README for details.


## Running the demo
Install the dependencies

```bash
npm install
// or
yarn install
```

Start the demo
```bash
npm run dev
// or
yarn dev
```


## Building
To create a production version of your app make sure to install the correct [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. Then run the following command to build your app:
```bash
npm run build
// or
yarn build
```

You can preview the production build with:
```bash
npm run preview
// or
yarn preview
```
40 changes: 40 additions & 0 deletions examples/svelte/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "web-gphoto2-examples-sveltekit",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev --host",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.20.4",
"@tailwindcss/typography": "^0.5.9",
"@typescript-eslint/eslint-plugin": "^5.45.0",
"@typescript-eslint/parser": "^5.45.0",
"autoprefixer": "^10.4.14",
"daisyui": "^3.1.6",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.30.0",
"postcss": "^8.4.24",
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.10.1",
"svelte": "^4.0.5",
"svelte-check": "^3.4.3",
"tailwindcss": "^3.3.2",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^4.4.2"
},
"type": "module",
"dependencies": {
"web-gphoto2": "^0.4.1"
}
}
13 changes: 13 additions & 0 deletions examples/svelte/postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const tailwindcss = require("tailwindcss");
const autoprefixer = require("autoprefixer");

const config = {
plugins: [
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
tailwindcss(),
//But others, like autoprefixer, need to run after,
autoprefixer
]
};

module.exports = config;
12 changes: 12 additions & 0 deletions examples/svelte/src/app.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}

export {};
15 changes: 15 additions & 0 deletions examples/svelte/src/app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark">

<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
%sveltekit.head%
</head>

<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>

</html>
4 changes: 4 additions & 0 deletions examples/svelte/src/app.postcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind components;
@tailwind utilities
71 changes: 71 additions & 0 deletions examples/svelte/src/lib/camera.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { writable, get, type Writable } from 'svelte/store';
import { Camera, type Config } from 'web-gphoto2';


export const imageURL: Writable<string | null> = writable(null);
export const connected: Writable<boolean> = writable(false);
export const config: Writable<Config> = writable();
export const camera = new Camera();

export async function connectCamera() {
try {
await Camera.showPicker();
await camera.connect();
connected.set(true);
await getConfig();
await handleEvents();
} catch (err) {
console.error('Could not connect to camera:', err);
await disconnectCamera();
}
}

export async function disconnectCamera() {
try {
await camera.disconnect();
} catch (err) { }
connected.set(false);
console.log("Disconnected camera")
}

/**
* Handles camera events to update the config.
* Whenever changes to the camera occur (e.g. connecting flash, manually changing aperture, etc.),
* the changes are reflected in the browser.
*/
async function handleEvents() {
while (true) {
await new Promise((resolve) => requestIdleCallback(resolve, { timeout: 500 }));
try {
if (await camera.consumeEvents()) {
await getConfig();
}
} catch (err) {
console.error('Could not consume events:', err);
await disconnectCamera();
break;
}
}
}

export async function getConfig() {
if (!get(connected)) return;
config.set(await camera.getConfig())
}

export async function updateConfig(name: string, value: string) {
if (!get(connected)) return;
await camera.setConfigValue(name, value);
}

export async function captureImage() {
if (!get(connected)) return;
const blob = await camera.captureImageAsFile();
const url = URL.createObjectURL(blob)
imageURL.set(url);
}

export async function capturePreview() {
if (!get(connected)) return;
return await camera.capturePreviewAsBlob();
}
12 changes: 12 additions & 0 deletions examples/svelte/src/lib/components/Card.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script lang="ts">
export let title: string;
</script>

<div class="block p-6 border rounded-lg shadow-xl bg-base-200 border-gray-700">
<h5 class="mb-2 text-3xl font-bold tracking-tight text-white text-center">
{title}
</h5>
<div class="h-[1px] bg-gray-400 mx-8 mb-4" />

<slot />
</div>
9 changes: 9 additions & 0 deletions examples/svelte/src/lib/components/Footer.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<footer class="fixed bottom-0 w-full z-50">
<div class="w-full h-[1px] bg-black" />
<p class="text-center">
<a href="https://icheered.nl/" target="_blank">
Demo by <span class="underline text-primary-light dark:text-primary-dark"> ICheered </span>
- © {new Date().getFullYear()}
</a>
</p>
</footer>
12 changes: 12 additions & 0 deletions examples/svelte/src/lib/components/Header.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<h1 class="text-6xl pt-10">Web-gPhoto2 Svelte Demo</h1>
<p class="pb-10">
Don't know how you got here? Check out the <a
class="link link-primary"
href="https://web.dev/porting-libusb-to-webusb/"
>
blog post
</a>
or the
<a class="link link-primary" href="https://github.com/GoogleChromeLabs/web-gphoto2">Github repo</a
>!
</p>
59 changes: 59 additions & 0 deletions examples/svelte/src/lib/components/Setting.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script lang="ts">
import type { Config } from 'web-gphoto2';
export let name: string;
export let config: Config;
export let updateFunction: (name: string, value: string) => void;
</script>

<div class="">
<div class="form-control w-full max-w-xs">
<label class="label pb-0">
<span class="label-text capitalize">{name} - {config.label}</span>
</label>
{#if config.type === 'text' || config.type === 'datetime' || config.readonly}
<input
type="text"
value={config.value}
class="input input-bordered w-full max-w-xs select-none pointer-events-none"
/>
{:else if config.type === 'radio' || config.type === 'menu'}
<select
class="select select-bordered w-full max-w-xs"
bind:value={config.value}
on:change={() => updateFunction(name, config.value)}
>
{#each config.choices as choice}
<option value={choice}>{choice}</option>
{/each}
</select>
{:else if config.type === 'toggle'}
<div
class="h-12 w-80 flex justify-center place-items-center border border-white border-opacity-20 rounded"
>
<input
type="checkbox"
class="toggle"
disabled={config.readonly}
bind:value={config.value}
on:change={() => updateFunction(name, config.value)}
/>
</div>
{:else if config.type === 'range'}
<div
class="h-12 w-80 flex justify-center place-items-center border border-white border-opacity-20 rounded"
>
<input
type="range"
disabled={config.readonly}
min={config.min}
max={config.max}
step={config.step}
bind:value={config.value}
on:input={() => updateFunction(name, config.value)}
class="range"
/>
<div class="w-10 text-center">{config.value}</div>
</div>
{/if}
</div>
</div>
3,616 changes: 3,616 additions & 0 deletions examples/svelte/src/lib/testing.ts

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions examples/svelte/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script lang="ts">
import '../app.postcss';
import Header from '$lib/components/Header.svelte';
import Footer from '$lib/components/Footer.svelte';
import { onMount } from 'svelte';
let usbSupported = true;
onMount(() => {
try {
if (!('usb' in navigator)) {
usbSupported = false;
}
} catch (e) {
console.error('Error checking of USB support:', e);
}
});
</script>

<div class="w-full h-full flex flex-col justify-center place-items-center p-4">
<Header />
{#if usbSupported}
<slot />
{:else}
⚠ This browser is <a class="btn btn-link" href="https://caniuse.com/webusb,import-maps"
>not supported</a
>.
{/if}
<Footer />
</div>
1 change: 1 addition & 0 deletions examples/svelte/src/routes/+layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ssr = false;
152 changes: 152 additions & 0 deletions examples/svelte/src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<script lang="ts">
import {
connectCamera,
updateConfig,
config,
imageURL,
captureImage,
capturePreview,
connected,
disconnectCamera
} from '$lib/camera';
//Todo: REMOVE THIS
import { testCameraConfig } from '$lib/testing';
import Card from '$lib/components/Card.svelte';
import Setting from '$lib/components/Setting.svelte';
import { onMount } from 'svelte';
// Handling searching through the configuration
let filteredConfig = {};
let searchText = '';
$: {
filteredConfig = {};
let configuration = $config?.children;
let lowercaseSearchText = searchText.toLowerCase();
for (let sectionKey in configuration) {
let section = configuration[sectionKey];
let filteredChildren = Object.fromEntries(
Object.entries(section.children).filter(
([childKey, child]) =>
child.name.toLowerCase().includes(lowercaseSearchText) ||
child.label.toLowerCase().includes(lowercaseSearchText)
)
);
if (Object.keys(filteredChildren).length > 0) {
filteredConfig[sectionKey] = {
...section,
children: filteredChildren
};
}
}
}
// This is for streaming
let streaming = false;
let canvas: any;
async function getPreview() {
while (streaming && canvas) {
try {
const blob = await capturePreview();
const img = await createImageBitmap(blob);
const canvasCtx = canvas.getContext('bitmaprenderer');
canvasCtx.transferFromImageBitmap(img);
} catch (err) {
console.error('Could not refresh preview :', err);
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
console.log('Done streaming. Streaming: ', streaming, 'Canvas: ', canvas);
}
async function startPreview() {
if (streaming) return; // If already streaming, return
console.log('Starting preview');
streaming = true;
getPreview();
}
function stopPreview() {
console.log('Stopping preview');
streaming = false;
}
</script>

{#if !$connected}
<button class="btn btn-primary btn-lg" on:click={connectCamera} on:keydown={connectCamera}>
Connect Camera
</button>
{:else}
<button
class="btn btn-error hover:bg-error/80 text-white btn-lg"
on:click={disconnectCamera}
on:keydown={disconnectCamera}
>
Disconnect Camera
</button>
{/if}

{#if $connected}
<div class="grid grid-cols-3 gap-4 p-4 text-white">
<Card title="Configuration">
{#if $config && $config.children}
<div class="w-full flex flex-row justify-center place-items-center">
<div class="pr-4">Search</div>
<input type="text" class="input input-bordered" bind:value={searchText} />
</div>
<div class="max-h-[500px] overflow-y-auto">
{#each Object.entries(filteredConfig) as [categoryName, category]}
<h1 class="text-3xl capitalize pt-4">{categoryName}</h1>
<div class="h-[1px] bg-gray-400 w-72 mb-1" />
{#each Object.entries(category.children) as [name, config]}
<Setting {name} {config} updateFunction={updateConfig} />
{/each}
{/each}
</div>
{/if}
</Card>
<Card title="Fetching image">
<button class="btn btn-primary w-full h-24" on:click={captureImage} on:keydown={captureImage}>
Fetch image
</button>
<div
class="border mt-4 border-white rounded-md aspect-square flex justify-center place-items-center"
>
{#if $imageURL}
<img src={$imageURL} alt="Preview to adjust settings" />
{:else}
Preview image
{/if}
</div>
</Card>
<Card title="Preview video">
{#if !streaming}
<button
class="btn btn-primary w-full h-24"
on:click={startPreview}
on:keydown={startPreview}
>
Start streaming
</button>
{:else}
<button
class="btn btn-error hover:bg-error-80 w-full h-24"
on:click={stopPreview}
on:keydown={stopPreview}
>
Stop streaming
</button>
{/if}
<div class="m-4" />

<div
class="border mt-4 border-white rounded-md p-8 aspect-square flex justify-center place-items-center"
>
<canvas bind:this={canvas} class="center" class:hidden={!streaming} />
{#if !streaming}
Preview stream
{/if}
</div>
</Card>
</div>
{/if}
Binary file added examples/svelte/static/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions examples/svelte/svelte.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/kit/vite';

/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: [vitePreprocess({})],

kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
}
};

export default config;
35 changes: 35 additions & 0 deletions examples/svelte/tailwind.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const daisyui = require("daisyui");
const typography = require("@tailwindcss/typography");

/** @type {import('tailwindcss').Config}*/
const config = {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#ea580c',
}
}
},
fontFamily: {
sans: ['Poppins', 'sans-serif']
}
},
plugins: [typography, daisyui],
daisyui: {
themes: [
{
dark: {
...require("daisyui/src/theming/themes")["[data-theme=dark]"],
"primary": "#EA580C",
"success": "#33DB33",
"warning": "#FAC547",
"error": "#ED3221"
},
},
],
},
};

module.exports = config;
17 changes: 17 additions & 0 deletions examples/svelte/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}
20 changes: 20 additions & 0 deletions examples/svelte/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';

const viteServerConfig = {
name: 'add headers',
configureServer: (server) => {
server.middlewares.use((req, res, next) => {
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
next();
});
}
};

export default defineConfig({
plugins: [sveltekit(), viteServerConfig],
optimizeDeps: {
exclude: ['web-gphoto2']
}
});
1,987 changes: 1,987 additions & 0 deletions examples/svelte/yarn.lock

Large diffs are not rendered by default.