Bunch of updates in preparation for publish
This commit is contained in:
354
src/api.cpp
Normal file
354
src/api.cpp
Normal file
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
#include <gphoto2/gphoto2.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
using emscripten::val;
|
||||
|
||||
template <typename T, auto Deleter>
|
||||
using gpp_unique_ptr =
|
||||
std::unique_ptr<T, std::integral_constant<decltype(Deleter), Deleter>>;
|
||||
|
||||
using GPPWidget = gpp_unique_ptr<CameraWidget, gp_widget_unref>;
|
||||
|
||||
void gpp_try(int status) {
|
||||
if (status != GP_OK) {
|
||||
throw std::runtime_error(gp_result_as_string(status));
|
||||
}
|
||||
}
|
||||
|
||||
void gpp_log_error(GPContext *context, const char *text, void *data) {
|
||||
std::cerr << text << std::endl;
|
||||
}
|
||||
|
||||
#define GPP_CALL(RET, EXPR) \
|
||||
({ \
|
||||
RET OUT; \
|
||||
RET *_ = &OUT; \
|
||||
gpp_try(EXPR); \
|
||||
OUT; \
|
||||
})
|
||||
|
||||
EM_JS([[noreturn]] void, throw_msg, (const char *msg),
|
||||
{ throw new Error(UTF8ToString(msg)); });
|
||||
|
||||
// This is workaround for Emscripten throwing C++ exceptions as meaningless
|
||||
// numbers (pointers) instead of actual messages.
|
||||
// To rethrow actual message, we manually wrap all Embind exports into
|
||||
// callbacks, those callbacks are passed to this function,
|
||||
// it ensures that all destructors are executed correctly, catches C++ exception
|
||||
// if any, extracts the message and rethrows it as a JS one. Easy-peasy...
|
||||
template <typename Func>
|
||||
auto gpp_rethrow(Func func) {
|
||||
try {
|
||||
return func();
|
||||
} catch (std::exception &e) {
|
||||
throw_msg(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
const thread_local val Uint8Array = val::global("Uint8Array");
|
||||
const thread_local val Blob = val::global("Blob");
|
||||
const thread_local val File = val::global("File");
|
||||
const thread_local val arrayOf = val::global("Array")["of"];
|
||||
|
||||
class Context {
|
||||
public:
|
||||
Context() : camera(nullptr), context(nullptr) {
|
||||
gpp_rethrow([=]() {
|
||||
camera.reset(GPP_CALL(Camera *, gp_camera_new(_)));
|
||||
context.reset(gp_context_new());
|
||||
gp_context_set_error_func(context.get(), gpp_log_error, nullptr);
|
||||
gpp_try(gp_camera_init(camera.get(), context.get()));
|
||||
});
|
||||
}
|
||||
|
||||
val supportedOps() {
|
||||
return gpp_rethrow([=]() {
|
||||
auto ops =
|
||||
GPP_CALL(CameraAbilities, gp_camera_get_abilities(camera.get(), _))
|
||||
.operations;
|
||||
|
||||
val result = val::object();
|
||||
result.set("captureImage", (ops & GP_OPERATION_CAPTURE_IMAGE) != 0);
|
||||
result.set("captureVideo", (ops & GP_OPERATION_CAPTURE_VIDEO) != 0);
|
||||
result.set("captureAudio", (ops & GP_OPERATION_CAPTURE_AUDIO) != 0);
|
||||
result.set("capturePreview", (ops & GP_OPERATION_CAPTURE_PREVIEW) != 0);
|
||||
result.set("config", (ops & GP_OPERATION_CONFIG) != 0);
|
||||
result.set("triggerCapture", (ops & GP_OPERATION_TRIGGER_CAPTURE) != 0);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
bool consumeEvents() {
|
||||
return gpp_rethrow([=]() {
|
||||
bool had_events = false;
|
||||
for (;;) {
|
||||
CameraEventType event_type = GP_EVENT_UNKNOWN;
|
||||
gpp_unique_ptr<void, free> event_data(GPP_CALL(
|
||||
void *, gp_camera_wait_for_event(camera.get(), 0, &event_type, _,
|
||||
context.get())));
|
||||
if (event_type == GP_EVENT_TIMEOUT) {
|
||||
break;
|
||||
}
|
||||
if (event_type == GP_EVENT_UNKNOWN) {
|
||||
// EM_ASM({ console.log(UTF8ToString($0)); }, event_data.get());
|
||||
}
|
||||
had_events = true;
|
||||
}
|
||||
return had_events;
|
||||
});
|
||||
}
|
||||
|
||||
val configToJS() {
|
||||
return gpp_rethrow([=]() {
|
||||
GPPWidget config(
|
||||
GPP_CALL(CameraWidget *,
|
||||
gp_camera_get_config(camera.get(), _, context.get())));
|
||||
return walk_config(config.get()).second;
|
||||
});
|
||||
}
|
||||
|
||||
void setConfigValue(std::string name, val value) {
|
||||
gpp_rethrow([=]() {
|
||||
GPPWidget widget(GPP_CALL(
|
||||
CameraWidget *, gp_camera_get_single_config(
|
||||
camera.get(), name.c_str(), _, context.get())));
|
||||
auto type =
|
||||
GPP_CALL(CameraWidgetType, gp_widget_get_type(widget.get(), _));
|
||||
switch (type) {
|
||||
case GP_WIDGET_RANGE: {
|
||||
float number = value.as<float>();
|
||||
gpp_try(gp_widget_set_value(widget.get(), &number));
|
||||
break;
|
||||
}
|
||||
case GP_WIDGET_MENU:
|
||||
case GP_WIDGET_RADIO:
|
||||
case GP_WIDGET_TEXT: {
|
||||
auto str = value.as<std::string>();
|
||||
gpp_try(gp_widget_set_value(widget.get(), str.c_str()));
|
||||
break;
|
||||
}
|
||||
case GP_WIDGET_TOGGLE: {
|
||||
int on = value.as<bool>() ? 1 : 0;
|
||||
gpp_try(gp_widget_set_value(widget.get(), &on));
|
||||
break;
|
||||
}
|
||||
case GP_WIDGET_DATE: {
|
||||
auto seconds = static_cast<int>(value.as<double>() / 1000);
|
||||
gpp_try(gp_widget_set_value(widget.get(), &seconds));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw std::logic_error("unimplemented");
|
||||
}
|
||||
}
|
||||
gpp_try(gp_camera_set_single_config(camera.get(), name.c_str(),
|
||||
widget.get(), context.get()));
|
||||
});
|
||||
}
|
||||
|
||||
val capturePreviewAsBlob() {
|
||||
return gpp_rethrow([=]() {
|
||||
auto &file = get_file();
|
||||
|
||||
gpp_try(gp_camera_capture_preview(camera.get(), &file, context.get()));
|
||||
|
||||
auto params = blob_chunks_and_opts(file);
|
||||
return Blob.new_(std::move(params.first), std::move(params.second));
|
||||
});
|
||||
}
|
||||
|
||||
val captureImageAsFile() {
|
||||
return gpp_rethrow([=]() {
|
||||
auto &file = get_file();
|
||||
|
||||
{
|
||||
struct TempCameraFile {
|
||||
Camera &camera;
|
||||
GPContext &context;
|
||||
CameraFilePath path;
|
||||
|
||||
~TempCameraFile() {
|
||||
gp_camera_file_delete(&camera, path.folder, path.name, &context);
|
||||
}
|
||||
};
|
||||
|
||||
TempCameraFile camera_file{
|
||||
.camera = *camera,
|
||||
.context = *context,
|
||||
};
|
||||
|
||||
strcpy(camera_file.path.folder, "/");
|
||||
strcpy(camera_file.path.name, "web-gphoto2");
|
||||
|
||||
gpp_try(gp_camera_capture(camera.get(), GP_CAPTURE_IMAGE,
|
||||
&camera_file.path, context.get()));
|
||||
|
||||
gpp_try(gp_camera_file_get(camera.get(), camera_file.path.folder,
|
||||
camera_file.path.name, GP_FILE_TYPE_NORMAL,
|
||||
&file, context.get()));
|
||||
}
|
||||
|
||||
gpp_try(gp_file_set_name(&file, "image."));
|
||||
gpp_try(gp_file_adjust_name_for_mime_type(&file));
|
||||
auto name = val(GPP_CALL(const char *, gp_file_get_name(&file, _)));
|
||||
|
||||
auto params = blob_chunks_and_opts(file);
|
||||
return File.new_(std::move(params.first), std::move(name),
|
||||
std::move(params.second));
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
gpp_unique_ptr<Camera, gp_camera_unref> camera;
|
||||
gpp_unique_ptr<GPContext, gp_context_unref> context;
|
||||
gpp_unique_ptr<CameraFile, gp_file_unref> file;
|
||||
|
||||
static std::pair<val, val> walk_config(CameraWidget *widget) {
|
||||
val result = val::object();
|
||||
|
||||
val name(GPP_CALL(const char *, gp_widget_get_name(widget, _)));
|
||||
result.set("name", name);
|
||||
result.set("info", GPP_CALL(const char *, gp_widget_get_info(widget, _)));
|
||||
result.set("label", GPP_CALL(const char *, gp_widget_get_label(widget, _)));
|
||||
result.set("readonly",
|
||||
GPP_CALL(int, gp_widget_get_readonly(widget, _)) != 0);
|
||||
|
||||
auto type = GPP_CALL(CameraWidgetType, gp_widget_get_type(widget, _));
|
||||
|
||||
switch (type) {
|
||||
case GP_WIDGET_RANGE: {
|
||||
result.set("type", "range");
|
||||
result.set("value", GPP_CALL(float, gp_widget_get_value(widget, _)));
|
||||
|
||||
float min, max, step;
|
||||
gpp_try(gp_widget_get_range(widget, &min, &max, &step));
|
||||
result.set("min", min);
|
||||
result.set("max", max);
|
||||
result.set("step", step);
|
||||
|
||||
break;
|
||||
}
|
||||
case GP_WIDGET_MENU:
|
||||
case GP_WIDGET_RADIO: {
|
||||
result.set("type", type == GP_WIDGET_MENU ? "menu" : "radio");
|
||||
result.set("value",
|
||||
GPP_CALL(const char *, gp_widget_get_value(widget, _)));
|
||||
|
||||
val choices = val::array();
|
||||
for (int i = 0, n = gp_widget_count_choices(widget); i < n; i++) {
|
||||
choices.call<void>(
|
||||
"push",
|
||||
val(GPP_CALL(const char *, gp_widget_get_choice(widget, i, _))));
|
||||
}
|
||||
result.set("choices", choices);
|
||||
|
||||
break;
|
||||
}
|
||||
case GP_WIDGET_TOGGLE: {
|
||||
result.set("type", "toggle");
|
||||
int value = GPP_CALL(int, gp_widget_get_value(widget, _));
|
||||
// Note: explicitly not adding `value` for any other values
|
||||
// (e.g. camera actions often would return 2 here...)
|
||||
switch (value) {
|
||||
case 0:
|
||||
result.set("value", false);
|
||||
break;
|
||||
case 1:
|
||||
result.set("value", true);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case GP_WIDGET_TEXT: {
|
||||
result.set("type", "text");
|
||||
result.set("value",
|
||||
GPP_CALL(const char *, gp_widget_get_value(widget, _)));
|
||||
|
||||
break;
|
||||
}
|
||||
case GP_WIDGET_WINDOW:
|
||||
case GP_WIDGET_SECTION: {
|
||||
result.set("type", type == GP_WIDGET_WINDOW ? "window" : "section");
|
||||
|
||||
val children = val::object();
|
||||
for (int i = 0, n = gp_widget_count_children(widget); i < n; i++) {
|
||||
auto child =
|
||||
GPP_CALL(CameraWidget *, gp_widget_get_child(widget, i, _));
|
||||
auto kv = walk_config(child);
|
||||
children.set(kv.first, kv.second);
|
||||
}
|
||||
result.set("children", children);
|
||||
|
||||
break;
|
||||
}
|
||||
case GP_WIDGET_DATE: {
|
||||
auto seconds = GPP_CALL(int, gp_widget_get_value(widget, _));
|
||||
result.set("type", "datetime");
|
||||
result.set("value", static_cast<double>(seconds) * 1000);
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw std::logic_error("unimplemented");
|
||||
}
|
||||
}
|
||||
|
||||
return {name, result};
|
||||
}
|
||||
|
||||
CameraFile &get_file() {
|
||||
if (file.get() == nullptr) {
|
||||
file.reset(GPP_CALL(CameraFile *, gp_file_new(_)));
|
||||
}
|
||||
return *file;
|
||||
}
|
||||
|
||||
std::pair<val, val> blob_chunks_and_opts(CameraFile &file) {
|
||||
auto mime_type = GPP_CALL(const char *, gp_file_get_mime_type(&file, _));
|
||||
|
||||
const char *data;
|
||||
unsigned long size;
|
||||
gpp_try(gp_file_get_data_and_size(&file, &data, &size));
|
||||
|
||||
val blob_opts = val::object();
|
||||
blob_opts.set("type", mime_type);
|
||||
|
||||
return {arrayOf(Uint8Array.new_(emscripten::typed_memory_view(size, data))),
|
||||
std::move(blob_opts)};
|
||||
}
|
||||
};
|
||||
|
||||
EMSCRIPTEN_BINDINGS(gphoto2_js_api) {
|
||||
emscripten::class_<Context>("Context")
|
||||
.constructor<>()
|
||||
.function("configToJS", &Context::configToJS)
|
||||
.function("setConfigValue", &Context::setConfigValue)
|
||||
.function("capturePreviewAsBlob", &Context::capturePreviewAsBlob)
|
||||
.function("captureImageAsFile", &Context::captureImageAsFile)
|
||||
.function("consumeEvents", &Context::consumeEvents)
|
||||
.function("supportedOps", &Context::supportedOps);
|
||||
}
|
||||
103
src/camera.js
103
src/camera.js
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
import initModule from "../build/libapi.mjs";
|
||||
|
||||
export function rethrowIfCritical(err) {
|
||||
// If it's precisely Error, it's a custom error; anything else - SyntaxError,
|
||||
// WebAssembly.RuntimeError, TypeError, etc. - is treated as critical here.
|
||||
if (err.constructor !== Error) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const INTERFACE_CLASS = 6; // PTP
|
||||
const INTERFACE_SUBCLASS = 1; // MTP
|
||||
|
||||
let ModulePromise = null;
|
||||
|
||||
export class Camera {
|
||||
constructor() {
|
||||
this.queue = Promise.resolve();
|
||||
this.Module = null;
|
||||
this.context = null;
|
||||
}
|
||||
|
||||
static async showPicker() {
|
||||
// @ts-ignore
|
||||
await navigator.usb.requestDevice({
|
||||
filters: [
|
||||
{
|
||||
classCode: INTERFACE_CLASS,
|
||||
subclassCode: INTERFACE_SUBCLASS,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
async connect() {
|
||||
if (!ModulePromise) {
|
||||
ModulePromise = initModule()
|
||||
}
|
||||
this.Module = await ModulePromise;
|
||||
this.context = await new this.Module.Context();
|
||||
}
|
||||
|
||||
async schedule(op) {
|
||||
let res = this.queue.then(() => op(this.context));
|
||||
this.queue = res.catch(rethrowIfCritical);
|
||||
return res;
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
if (!this.context.isDeleted()) {
|
||||
this.context.delete();
|
||||
}
|
||||
}
|
||||
|
||||
async getConfig() {
|
||||
return this.schedule((context) => context.configToJS());
|
||||
}
|
||||
|
||||
async getSupportedOps() {
|
||||
if (this.context) {
|
||||
return await this.context.supportedOps();
|
||||
}
|
||||
throw new Error("You need to connect to the camera first");
|
||||
}
|
||||
|
||||
async setConfigValue(name, value) {
|
||||
const uiTimeout = new Promise((resolve) => setTimeout(resolve, 800));
|
||||
const setResult = this.schedule((context) =>
|
||||
context.setConfigValue(name, value)
|
||||
);
|
||||
return Promise.all([setResult, uiTimeout]);
|
||||
}
|
||||
|
||||
async capturePreviewAsBlob() {
|
||||
return this.schedule((context) => context.capturePreviewAsBlob());
|
||||
}
|
||||
|
||||
async captureImageAsFile() {
|
||||
return this.schedule((context) => context.captureImageAsFile());
|
||||
}
|
||||
|
||||
async consumeEvents() {
|
||||
return this.schedule((context) => context.consumeEvents());
|
||||
}
|
||||
}
|
||||
106
src/camera.ts
Normal file
106
src/camera.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
import initModule, { Context, Module } from '../build/libapi.mjs';
|
||||
|
||||
export type { Config, SupportedOps } from '../build/libapi.mjs';
|
||||
|
||||
// A helper that allows to distinguish critical errors from library errors.
|
||||
export function rethrowIfCritical(err: any) {
|
||||
// If it's precisely Error, it's a custom error; anything else - SyntaxError,
|
||||
// WebAssembly.RuntimeError, TypeError, etc. - is treated as critical here.
|
||||
if (err?.constructor !== Error) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const INTERFACE_CLASS = 6; // PTP
|
||||
const INTERFACE_SUBCLASS = 1; // MTP
|
||||
|
||||
let ModulePromise: Promise<Module>;
|
||||
|
||||
export class Camera {
|
||||
#queue: Promise<any> = Promise.resolve();
|
||||
#context: Context | null = null;
|
||||
|
||||
static async showPicker() {
|
||||
// @ts-ignore
|
||||
await navigator.usb.requestDevice({
|
||||
filters: [
|
||||
{
|
||||
classCode: INTERFACE_CLASS,
|
||||
subclassCode: INTERFACE_SUBCLASS
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async connect() {
|
||||
if (!ModulePromise) {
|
||||
ModulePromise = initModule();
|
||||
}
|
||||
let Module = await ModulePromise;
|
||||
this.#context = await new Module.Context();
|
||||
}
|
||||
|
||||
async #schedule<T>(op: (context: Context) => Promise<T>): Promise<T> {
|
||||
let res = this.#queue.then(() => op(this.#context!));
|
||||
this.#queue = res.catch(rethrowIfCritical);
|
||||
return res;
|
||||
}
|
||||
|
||||
async disconnect() {
|
||||
if (this.#context && !this.#context.isDeleted()) {
|
||||
this.#context.delete();
|
||||
}
|
||||
}
|
||||
|
||||
async getConfig() {
|
||||
return this.#schedule(context => context.configToJS());
|
||||
}
|
||||
|
||||
async getSupportedOps() {
|
||||
if (this.#context) {
|
||||
return await this.#context.supportedOps();
|
||||
}
|
||||
throw new Error('You need to connect to the camera first');
|
||||
}
|
||||
|
||||
async setConfigValue(name: string, value: string | number | boolean) {
|
||||
let uiTimeout: Promise<void> | undefined;
|
||||
await this.#schedule(context => {
|
||||
// This is terrible, yes... but some configs return too quickly before they're actually updated.
|
||||
// We want to wait some time before updating the UI in that case, but not block subsequent ops.
|
||||
uiTimeout = new Promise(resolve => setTimeout(resolve, 800));
|
||||
return context.setConfigValue(name, value);
|
||||
});
|
||||
await uiTimeout;
|
||||
}
|
||||
|
||||
async capturePreviewAsBlob() {
|
||||
return this.#schedule(context => context.capturePreviewAsBlob());
|
||||
}
|
||||
|
||||
async captureImageAsFile() {
|
||||
return this.#schedule(context => context.captureImageAsFile());
|
||||
}
|
||||
|
||||
async consumeEvents() {
|
||||
return this.#schedule(context => context.consumeEvents());
|
||||
}
|
||||
}
|
||||
65
src/libapi.mjs.d.ts
vendored
65
src/libapi.mjs.d.ts
vendored
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
type Config = {
|
||||
name: string;
|
||||
info: string;
|
||||
label: string;
|
||||
readonly: boolean;
|
||||
} & (
|
||||
| { type: 'range'; value: number; min: number; max: number; step: number }
|
||||
| { type: 'menu' | 'radio'; value: string; choices: string[] }
|
||||
| { type: 'toggle'; value: boolean }
|
||||
| { type: 'text'; value: string }
|
||||
| { type: 'window'; children: Record<string, Config> }
|
||||
| { type: 'section'; children: Record<string, Config> }
|
||||
| { type: 'datetime'; value: number }
|
||||
);
|
||||
|
||||
declare interface SupportedOps {
|
||||
captureImage: boolean;
|
||||
captureVideo: boolean;
|
||||
captureAudio: boolean;
|
||||
capturePreview: boolean;
|
||||
config: boolean;
|
||||
triggerCapture: boolean;
|
||||
}
|
||||
|
||||
declare class Context {
|
||||
configToJS(): Promise<Config & { type: 'window' }>;
|
||||
setConfigValue(
|
||||
name: string,
|
||||
value: number | string | boolean
|
||||
): Promise<undefined>;
|
||||
capturePreviewAsBlob(): Promise<Blob>;
|
||||
captureImageAsFile(): Promise<File>;
|
||||
consumeEvents(): Promise<boolean>;
|
||||
supportedOps(): SupportedOps;
|
||||
|
||||
delete(): void;
|
||||
isDeleted(): boolean;
|
||||
}
|
||||
|
||||
export interface Module extends EmscriptenModule {
|
||||
Context: typeof Context;
|
||||
}
|
||||
|
||||
export type { Config, Context, SupportedOps };
|
||||
|
||||
declare const initModule: EmscriptenModuleFactory<Module>;
|
||||
export default initModule;
|
||||
25
src/types.d.ts
vendored
25
src/types.d.ts
vendored
@@ -1,25 +0,0 @@
|
||||
// Import types Context and SupportedOps ./libapi.mjs.d.ts
|
||||
import type { Config, Context, SupportedOps } from './libapi.mjs.d.ts';
|
||||
|
||||
export declare class Camera {
|
||||
constructor();
|
||||
|
||||
static showPicker(): Promise<void>;
|
||||
connect(): Promise<void>;
|
||||
disconnect(): Promise<void>;
|
||||
|
||||
getConfig(): Promise<Config>;
|
||||
|
||||
getSupportedOps(): SupportedOps;
|
||||
|
||||
setConfigValue(name: string, value: number | string | boolean): Promise<void>;
|
||||
|
||||
capturePreviewAsBlob(): Promise<Blob>;
|
||||
captureImageAsFile(): Promise<File>;
|
||||
consumeEvents(): Promise<boolean>;
|
||||
|
||||
private rethrowIfCritical(err: any): void; // any error type can be replaced by more specific if available
|
||||
private schedule<T>(op: (ctx: any) => Promise<T>): Promise<T>; // ctx and T types can be replaced by more specific if available
|
||||
}
|
||||
|
||||
export type { Config, Context, SupportedOps };
|
||||
Reference in New Issue
Block a user