Skip to content

Latest commit

 

History

History
589 lines (456 loc) · 17.6 KB

README-zh.md

File metadata and controls

589 lines (456 loc) · 17.6 KB

@netless/fastboard

本库用于快速开始一个白板应用,基于 white-web-sdk@netless/window-managernetless-app 实现。

从0.3.22版本开始, fastboard 集成了@netless/appliance-plugin插件,以便于提供更优性能及更丰富的教具功能

从0.3.22版本开始, fastboard 新增全打包文件, @netless/fastboard/full or @netless/fastboard-react/full, 用于解决内外依赖冲突问题.

目录

安装

分包方式引用

npm add @netless/fastboard @netless/window-manager white-web-sdk @netless/appliance-plugin

注意: @netless/appliance-plugin 是开启 性能优化版本 才需要安装。

全打包方式引用

npm add @netless/fastboard @netless/appliance-plugin

注意: 全打包方式引用,则 @netless/window-managerwhite-web-sdk 可以不用安装。而 @netless/appliance-plugin 是开启性能优化版本 才需要安装。

@netless/window-managerwhite-web-sdk@netless/appliance-plugin 是 peerDependency,如果你不清楚 peerDependency 是什么意思,可以阅读 《为什么使用 peerDependency ?》

使用

原生 JavaScript

// 全打包方式引用
import { createFastboard, createUI } from "@netless/fastboard/full";

// 分包引用
import { createFastboard, createUI } from "@netless/fastboard";

async function main() {
  const fastboard = await createFastboard({
    // [1]
    sdkConfig: {
      appIdentifier: "whiteboard-appid",
      region: "cn-hz", // "cn-hz" | "us-sv" | "sg" | "in-mum" | "eu"
    },
    // [2]
    joinRoom: {
      uid: "unique_id_for_each_client",
      uuid: "room-uuid",
      roomToken: "NETLESSROOM_...",
      // (可选)
      userPayload: {
        nickName: "喵喵",
      },
    },
    // [3] (可选)
    managerConfig: {
      cursor: true,
      // (可选), 开启appliance-plugin, 从0.3.22开始
      supportAppliancePlugin: true,
    },
    // [4] (可选)
    netlessApps: [],
    // [5] (可选), 开启appliance-plugin, 从0.3.22开始
    enableAppliancePlugin: {
      ...
    },
  });

  const container = createContainer();

  const ui = createUI(fastboard, container);

  // .....

  // 关闭 Fastboard UI
  ui.destroy();

  // .....

  // 退出 Fastboard (从白板房间断开)
  fastboard.destroy();
}

function createContainer() {
  const container = document.createElement("div");
  // 白板元素必须具有可见的大小
  Object.assign(container.style, {
    height: "400px",
    border: "1px solid",
    background: "#f1f2f3",
  });
  document.body.appendChild(container);
  return container;
}

main().catch(console.error);

[1] 关于 SDK 更多配置请看 构造 WhiteWebSDK
[2] 加入房间更多配置请看 构造 Room 与 Player 对象
[3] 配置 WindowManager 请看 WindowManager.mount()

关于窗口最小化后显示的小图标,可以通过 CSS 覆盖样式的方式修改它的位置:

.telebox-collector {
  right: 20px;
  bottom: 40px;
}

使用 React

先安装 @netless/fastboard-react,再使用里面提供的 <Fastboard /> 组件。

分包方式引用

npm add @netless/fastboard-react @netless/window-manager white-web-sdk react react-dom @netless/appliance-plugin

注意: @netless/appliance-plugin 是开启 性能优化版本 才需要安装。

全打包方式引用

npm add @netless/fastboard-react react react-dom @netless/appliance-plugin

注意: 全打包方式引用,则 @netless/window-managerwhite-web-sdk 可以不用安装。而 @netless/appliance-plugin 是开启性能优化版本 才需要安装。

@netless/window-managerwhite-web-sdk@netless/appliance-pluginreactreact-dom 是 peerDependency,如果你不清楚 peerDependency 是什么意思,可以阅读 《为什么使用 peerDependency ?》

// 全打包方式引用
import { useFastboard, Fastboard } from "@netless/fastboard-react/full";
// 分包引用
import { useFastboard, Fastboard } from "@netless/fastboard-react";

import React from "react";
import { createRoot } from "react-dom/client";

function App() {
  const fastboard = useFastboard(() => ({
    sdkConfig: {
      appIdentifier: "whiteboard-appid",
      region: "cn-hz", // "cn-hz" | "us-sv" | "sg" | "in-mum" | "eu"
    },
    joinRoom: {
      uid: "unique_id_for_each_client",
      uuid: "room-uuid",
      roomToken: "NETLESSROOM_...",
    },
    //  开启 appliance-plugin 插件
    managerConfig: {
      cursor: true,
      supportAppliancePlugin: true,
    }
    //  开启 appliance-plugin 插件
    enableAppliancePlugin: {
      ...
    },
  }));

  // 白板元素必须具有可见的大小
  return (
    <div
      style={{
        height: "400px",
        border: "1px solid",
        background: "#f1f2f3",
      }}
    >
      <Fastboard app={fastboard} />
    </div>
  );
}

createRoot(document.getElementById("app")).render(<App />);

使用白板功能

详细 API 文档

插入图片

await fastboard.insertImage(fileUrl);

其中 fileUrl 是该图片文件的 CDN 地址。本库并不包含任何上传、保存文件的逻辑和功能。

撤销、重做

fastboard.undo();
fastboard.redo();

移动视角

fastboard.moveCamera({ centerX: 0, centerY: 0, scale: 1 });
fastboard.moveCameraToContain({ originX: -300, originY: -200, width: 600, height: 400 });

设置教具

fastboard.setAppliance("pencil");
fastboard.setAppliance("shape", "triangle");
fastboard.setStrokeWidth(2);
fastboard.setStrokeColor([r, g, b]);

使用 Apps

如果你需要自己开发插件,请参考文档:如何开发自定义窗口插件 Netless App

注册与使用 Apps

除了 Fastboard 内置的一些官方 app,你也可以定义并使用自己编写的 app。在进入房间(createFastboard)前需要按以下操作先在各客户端注册 app:

import { register } from "@netless/fastboard";
import MyApp from "my-app";

register({ kind: MyApp.kind, src: MyApp });

或者你也可以在 createFastboard 时设置 netlessApps

createFastboard({
  ..., // 其他配置
  netlessApps: [MyApp],
});

接着在房间内调用以下接口插入 app:

fastboard.manager.addApp({ kind: MyApp.kind });

在这里阅读更多有关开发 Netless App 的信息

插入 PDF、PPT 和 PPTX 文档

// 插入 PDF/PPT/PPTX 至主白板
const appId = await fastboard.insertDocs("文件名.pptx", conversionResponse);

其中 conversionResponse转码 结果。

注意: 如果你使用的是 projector 转码服务,也可以使用以下方式插入:

const appId1 = await fastboard.insertDocs({
  fileType: "pdf",
  scenePath: `/pdf/${response.uuid}`,
  scenes: [
    { name: "1", ppt: { width: 714, height: 1010, src: images[1].url } },
    { name: "2", ppt: { width: 714, height: 1010, src: images[2].url } },
  ],
  title: "filename.pdf",
});

const appId2 = await fastboard.insertDocs({
  fileType: "pptx",
  scenePath: `/pptx/${response.uuid}`,
  taskId: response.uuid,
  title: "filename.pptx",
  // 默认为 "https://convertcdn.netless.link/dynamicConvert"
  url: response.prefix,
});

监听 PDF、PPTX 文档跳页事件

注意: 该功能需要你把以下依赖升级到对应版本以上

  • @netless/app-slide ≥ 0.2.50
  • @netless/window-manager ≥ 0.4.66
// PDF / 静态转码的文档
const dispose = fastboard.manager.onAppEvent("DocsViewer", event => {
  if (event.type === "pageStateChange") console.log(event.value);
});
// PPTX / 动态转码的文档
const dispose = fastboard.manager.onAppEvent("Slide", console.log);

onExitRoom(() => dispose());

上面的 event 对象结构如下:

{
  "kind": "Slide",
  "appId": "Slide-aa1840ba",
  "type": "pageStateChange",
  "value": {
    "index": 0,
    "length": 12
  }
}

该方法返回一个取消监听的函数。

操作 PDF、PPTX 文档

import { dispatchDocsEvent } from "@netless/fastboard";

dispatchDocsEvent(fastboard, "nextPage"); // prevPage, nextStep, prevStep
dispatchDocsEvent(fastboard, "jumpToPage", { page: 2 });

默认情况下会发送事件给当前焦点所在的文档,如果需要指定文档,可以传入 appId

dispatchDocsEvent(fastboard, "nextPage", { appId });

设置 PPTX 渲染参数

import { register, SlideApp, addSlideHooks } from "@netless/fastboard";

register({
  kind: SlideApp.kind,
  src: SlideApp,
  appOptions: {
    // ... 自定义渲染参数
    // 引入 TypeScript 类型 SlideOptions 以获得提示
  },
  addHooks: addSlideHooks,
});

更多有关该渲染参数的信息

插入音频、视频

const appId = await fastboard.insertMedia("文件名.mp3", fileUrl);

其中 fileUrl 是该媒体文件的 CDN 地址。本库并不包含任何上传、保存文件的逻辑和功能。

const appId = await fastboard.manager.addApp({
  kind: "Monaco",
  options: { title: "Code Editor" },
});
const appId = await fastboard.manager.addApp({
  kind: "Countdown",
  options: { title: "Countdown" },
});

Note

GeoGebra 使用 GPLv3 协议并且仅允许非商业的免费使用。如果要商用,请先阅读 GeoGebra 的许可协议:https://www.geogebra.org/license

const appId = await fastboard.manager.addApp({
  kind: "GeoGebra",
  options: { title: "GeoGebra" },
});
const appId = await fastboard.manager.addApp({
  kind: "Plyr",
  options: { title: "YouTube" },
  attributes: {
    src: "https://www.youtube.com/embed/bTqVqk7FSmY",
    provider: "youtube",
  },
});
const appId = await fastboard.manager.addApp({
  kind: "EmbeddedPage",
  options: { title: "Google Docs" },
  attributes: {
    src: "https://docs.google.com/document/d/1bd4SRb5BmTUjPGrFxU2V7KI2g_mQ-HQUBxKTxsEn5e4/edit?usp=sharing",
  },
});

注意: EmbeddedPage 使用 <iframe> 来显示外部资源,由于浏览器限制,您最好不要嵌套使用 iframe(即 iframe 内还有 iframe,通常来说浏览器会对第二层 iframe 增加十分多的限制或者根本无法使用)。

更多 app 请看 netless-app。

也可以参考文档:如何开发自定义窗口插件 Netless App

使用性能优化版本

通过 enableAppliancePluginmanagerConfig.supportAppliancePlugin 配置项开启 appliance-plugin 插件,以提升性能以及提供新的白板功能, 也可以参考文档:appliance-plugin了解更多内容。

注意: 开启使用性能优化版本,需要安装 @netless/appliance-plugin

示例代码

// 直接通过raw-loader引入
import fullWorkerString from '@netless/appliance-plugin/dist/fullWorker.js?raw';
import subWorkerString from '@netless/appliance-plugin/dist/subWorker.js?raw';
const fullWorkerBlob = new Blob([fullWorkerString], {type: 'text/javascript'});
const fullWorkerUrl = URL.createObjectURL(fullWorkerBlob);
const subWorkerBlob = new Blob([subWorkerString], {type: 'text/javascript'});
const subWorkerUrl = URL.createObjectURL(subWorkerBlob);

// CDN 引入, 需要部署到自己的CDN服务器上, 它必须遵守 同源策略 。
const subWorkerUrl = "https://cdn.jsdelivr.net/npm/@netless/appliance-plugin@latest/dist/subWorker.js";
const fullWorkerUrl = "https://cdn.jsdelivr.net/npm/@netless/appliance-plugin@latest/dist/fullWorker.js";

function App() {
  const fastboard = useFastboard(() => ({
    sdkConfig: {
      ...
    },
    joinRoom: {
      ...
    },
    //  开启 appliance-plugin 插件, 和windowManager 配置
    managerConfig: {
      supportAppliancePlugin: true
    },
    //  开启 appliance-plugin 插件
    enableAppliancePlugin: {
        cdn: {
            fullWorkerUrl,
            subWorkerUrl,
        }
    }
  }));
  ....
}

注意

  • 首先必需保证在安卓\ios\web,三端都开启 appliance-plugin 配置. appliance-plugin 开启后绘制的笔记在未开启的白板上不会显示.
  • 在开启 appliance-plugin 插件后, 之前白板上旧绘制的内容会显示,但是无法操作和升级成新的笔记. 所以为了不影响体验,请在一个无任何历史数据的白板上使用。同理插件关闭后, 新绘制的内容会丢失。
  • 只有浏览器对 web API offscreenCanvas 的完全支持,才能体验到更加的性能及丰富的教具功能体验。

自定义

Fastboard 为了上手快,不支持高度定制化。不过有一些轻量化配置:

// vanilla js
const ui = createUI(fastboard, container);
ui.update({ config: { ...ui_config } });

// react
<Fastboard app={fastboard} config={{ ...ui_config }} />;

上面的 ui_config 长这样:

{
  toolbar: {
    enable: true,
    placement: 'left',
    items: ['pencil', 'eraser'],
    apps: { enable: true },
  },
  redo_undo: { enable: true },
  zoom_control: { enable: true },
  page_control: { enable: true },
}

例如,你可以像这样隐藏缩放栏:

// vanilla js
ui.update({ config: { zoom_control: { enable: false } } });
// react
<Fastboard app={fastboard} config={{ zoom_control: { enable: false } }} />;

或者改变工具栏上的按钮:

可用配置项:
clickerselectorpenciltextshapeseraserclearhandlaserPointer.

const toolbar_items = ["pencil", "eraser"];
// vanilla js
ui.update({ config: { toolbar: { items: toolbar_items } } });
// react
<Fastboard app={fastboard} config={{ toolbar: { items: toolbar_items } }} />;

也可以自己实现相关组件,请参考:如何为 fastboard 实现 UI以及如何自定义fastboard UI

回放模式

白板本身有基于指令的录制功能,Fastboard 也为此实现了类似的组件和接口:

const player = await replayFastboard(...)
const ui = createReplayUI(player, container);

const player = useReplayFastboard(() => ({...}))
return <ReplayFastboard player={player} />

上面的 player 实例和原生的视频播放器类似,也有 play() seek() pause() 等方法。

如果要让白板和其他视频播放器同步进度条,请参考库 @netless/sync-player

异常处理

你会因为以下情况收到异常回调:

  • 因为断网等无法连上白板服务器
  • 因为网络不稳自动进入重连状态
  • 房间被封禁

注意,白板内部自带重试逻辑,用户无需手写,如果白板真的断开连接,你必须直接退出房间。

请参考以下代码适当地处理这些异常情况:

try {
  fastboard = await createFastboard({
    sdkConfig: {
      onWhiteSetupFailed(error) {
        console.error("Failed to find the whiteboard server", error);
      },
    },
    joinRoom: {
      callbacks: {
        onPhaseChanged(phase) {
          if (phase === "reconnecting") console.log("Whiteboard connection lost, reconnecting...");
        },
        onDisconnectWithError(error) {
          console.error("Failed to connect to whiteboard server", error);
        },
        onKickedWithReason(reason) {
          console.log("You're kicked by", reason);
          // 正常退出房间
          leaveRoom();
        },
      },
    },
  });
} catch (error) {
  console.error("Failed to join whiteboard room", error);
}

React 组件的写法应该类似,这里不赘述。

License

MIT @ netless