microboard-temp 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -627,12 +627,10 @@ var require_escape_html = __commonJS((exports2, module2) => {
627
627
  var exports_browser = {};
628
628
  __export(exports_browser, {
629
629
  validateRichTextData: () => validateRichTextData,
630
- validateMediaFile: () => validateMediaFile,
631
630
  validateItemsMap: () => validateItemsMap,
632
631
  uploadVideoToStorage: () => uploadVideoToStorage,
633
632
  uploadToTheStorage: () => uploadToTheStorage,
634
633
  updateRects: () => updateRects,
635
- updateMediaUsage: () => updateMediaUsage,
636
634
  translateElementBy: () => translateElementBy,
637
635
  transformHtmlOrTextToMarkdown: () => transformHtmlOrTextToMarkdown,
638
636
  toRelativePoint: () => toRelativePoint,
@@ -681,7 +679,6 @@ __export(exports_browser, {
681
679
  forceNumberIntoInterval: () => forceNumberIntoInterval,
682
680
  fileTosha256: () => fileTosha256,
683
681
  exportBoardSnapshot: () => exportBoardSnapshot,
684
- deleteMedia: () => deleteMedia,
685
682
  decodeHtml: () => decodeHtml,
686
683
  cursors: () => defaultCursors,
687
684
  createVideoItem: () => createVideoItem,
@@ -689,8 +686,6 @@ __export(exports_browser, {
689
686
  conf: () => conf,
690
687
  checkHotkeys: () => checkHotkeys,
691
688
  catmullRomInterpolate: () => catmullRomInterpolate,
692
- catchErrorResponse: () => catchErrorResponse,
693
- catchDuplicateErrorResponse: () => catchDuplicateErrorResponse,
694
689
  captureFrame: () => captureFrame,
695
690
  calculatePosition: () => calculatePosition,
696
691
  calculateAudioPosition: () => calculateAudioPosition,
@@ -6724,6 +6719,11 @@ var conf = {
6724
6719
  getDOMParser: undefined,
6725
6720
  measureCtx: undefined,
6726
6721
  i18n: instance,
6722
+ hooks: {
6723
+ beforeMediaUpload: async (...args) => false,
6724
+ beforeMediaRemove: async (...args) => false,
6725
+ onUploadMediaError: async (...args) => false
6726
+ },
6727
6727
  openModal: () => {},
6728
6728
  notify: () => "",
6729
6729
  disMissNotification: () => {},
@@ -39172,7 +39172,7 @@ class VideoItem extends BaseItem {
39172
39172
  onRemove() {
39173
39173
  const storageId = this.getStorageId();
39174
39174
  if (storageId) {
39175
- deleteMedia([storageId], this.board.getBoardId());
39175
+ conf.hooks.beforeMediaRemove([storageId], this.board.getBoardId());
39176
39176
  }
39177
39177
  super.onRemove();
39178
39178
  }
@@ -39233,158 +39233,6 @@ async function fileTosha256(file) {
39233
39233
  }
39234
39234
 
39235
39235
  // src/Items/Image/ImageHelpers.ts
39236
- var catchErrorResponse = async (response, mediaType) => {
39237
- if (response.status === 403) {
39238
- const data = await response.json();
39239
- let errorBody = conf.i18n.t("toolsPanel.addMedia.limitReached.bodyWithoutLimit");
39240
- if (!data.isOwnerRequest) {
39241
- errorBody = conf.i18n.t("toolsPanel.addMedia.limitReached.bodyOwner");
39242
- } else if (data.currentUsage && data.storageLimit) {
39243
- errorBody = conf.i18n.t(`toolsPanel.addMedia.limitReached.body.${parseInt(data.storageLimit) < 1e5 ? "basic" : "plus"}`);
39244
- }
39245
- conf.notify({
39246
- variant: "warning",
39247
- header: conf.i18n.t("toolsPanel.addMedia.limitReached.header"),
39248
- body: errorBody,
39249
- button: data.isOwnerRequest && data.storageLimit <= 100 ? {
39250
- text: conf.i18n.t("toolsPanel.addMedia.upgradeToPlus"),
39251
- onClick: () => conf.openModal("USER_PLAN_MODAL_ID")
39252
- } : undefined,
39253
- duration: 8000
39254
- });
39255
- } else if (response.status === 413) {
39256
- const data = await response.json();
39257
- let errorBody = conf.i18n.t("toolsPanel.addMedia.tooLarge.bodyWithoutLimit");
39258
- let isBasicPlan = false;
39259
- if (data.fileSizeLimit && data.fileSize) {
39260
- if (mediaType === "image") {
39261
- isBasicPlan = parseInt(data.fileSizeLimit) < 20;
39262
- errorBody = conf.i18n.t(`toolsPanel.addMedia.tooLarge.imageBody.${isBasicPlan ? "basic" : "plus"}`);
39263
- } else {
39264
- isBasicPlan = parseInt(data.fileSizeLimit) < 1000;
39265
- errorBody = conf.i18n.t(`toolsPanel.addMedia.tooLarge.audioOrVideoBody.${isBasicPlan ? "basic" : "plus"}`);
39266
- }
39267
- }
39268
- conf.notify({
39269
- variant: "warning",
39270
- header: conf.i18n.t("toolsPanel.addMedia.tooLarge.header"),
39271
- body: errorBody,
39272
- button: isBasicPlan ? {
39273
- text: conf.i18n.t("toolsPanel.addMedia.upgradeToPlus"),
39274
- onClick: () => conf.openModal("USER_PLAN_MODAL_ID")
39275
- } : undefined,
39276
- duration: 4000
39277
- });
39278
- } else if (response.status === 401) {
39279
- conf.openModal("MEDIA_UNAVAILABLE_MODAL_ID");
39280
- } else if (response.status === 415) {
39281
- conf.notify({
39282
- variant: "warning",
39283
- header: conf.i18n.t("toolsPanel.addMedia.unsupportedFormat.header"),
39284
- body: conf.i18n.t("toolsPanel.addMedia.unsupportedFormat.body"),
39285
- duration: 4000
39286
- });
39287
- } else {
39288
- conf.notify({
39289
- variant: "error",
39290
- header: conf.i18n.t("toolsPanel.addMedia.unhandled.header"),
39291
- body: conf.i18n.t("toolsPanel.addMedia.unhandled.body"),
39292
- duration: 4000
39293
- });
39294
- }
39295
- throw new Error(`HTTP status: ${response.status}`);
39296
- };
39297
- var catchDuplicateErrorResponse = async (response) => {
39298
- if (response.status === 403) {
39299
- conf.notify({
39300
- variant: "warning",
39301
- header: conf.i18n.t("toolsPanel.addMedia.limitReached.header"),
39302
- body: conf.i18n.t("toolsPanel.addMedia.limitReached.duplicateBody"),
39303
- duration: 4000
39304
- });
39305
- } else {
39306
- conf.notify({
39307
- variant: "error",
39308
- header: conf.i18n.t("toolsPanel.addMedia.unhandled.header"),
39309
- body: conf.i18n.t("toolsPanel.addMedia.unhandled.body"),
39310
- duration: 4000
39311
- });
39312
- }
39313
- throw new Error(`HTTP status: ${response.status}`);
39314
- };
39315
- var validateMediaFile = (file, account2) => {
39316
- const fileExtension = file.name.split(".").pop()?.toLowerCase() || "";
39317
- if (!file.type.startsWith("image") && !conf.AUDIO_FORMATS.includes(fileExtension) && !conf.VIDEO_FORMATS.includes(fileExtension)) {
39318
- conf.notify({
39319
- variant: "warning",
39320
- header: conf.i18n.t("toolsPanel.addMedia.unsupportedFormat.header"),
39321
- body: conf.i18n.t("toolsPanel.addMedia.unsupportedFormat.body"),
39322
- duration: 4000
39323
- });
39324
- return false;
39325
- }
39326
- const isBasicPlan = account2.billingInfo?.plan.name === "basic";
39327
- let errorBody = conf.i18n.t(`toolsPanel.addMedia.tooLarge.imageBody.${isBasicPlan ? "basic" : "plus"}`);
39328
- if (conf.AUDIO_FORMATS.includes(fileExtension) || conf.VIDEO_FORMATS.includes(fileExtension)) {
39329
- errorBody = conf.i18n.t(`toolsPanel.addMedia.tooLarge.audioOrVideoBody.${isBasicPlan ? "basic" : "plus"}`);
39330
- if (file.size / 1024 ** 2 > (account2.billingInfo?.storage.maxMediaSize || Infinity)) {
39331
- conf.notify({
39332
- variant: "warning",
39333
- header: conf.i18n.t("toolsPanel.addMedia.tooLarge.header"),
39334
- body: errorBody,
39335
- button: isBasicPlan ? {
39336
- text: conf.i18n.t("toolsPanel.addMedia.upgradeToPlus"),
39337
- onClick: () => conf.openModal("USER_PLAN_MODAL_ID")
39338
- } : undefined,
39339
- duration: 4000
39340
- });
39341
- return false;
39342
- }
39343
- } else if (file.size / 1024 ** 2 > (account2.billingInfo?.storage.maxImageSize || Infinity)) {
39344
- conf.notify({
39345
- variant: "warning",
39346
- header: conf.i18n.t("toolsPanel.addMedia.tooLarge.header"),
39347
- body: errorBody,
39348
- button: isBasicPlan ? {
39349
- text: conf.i18n.t("toolsPanel.addMedia.upgradeToPlus"),
39350
- onClick: () => conf.openModal("USER_PLAN_MODAL_ID")
39351
- } : undefined,
39352
- duration: 4000
39353
- });
39354
- return false;
39355
- }
39356
- return true;
39357
- };
39358
- var deleteMedia = async (mediaIds, boardId) => {
39359
- fetch(`${window?.location.origin}/api/v1/media/usage/${boardId}`, {
39360
- method: "POST",
39361
- headers: {
39362
- "content-type": "application/json"
39363
- },
39364
- body: JSON.stringify({ mediaIds, shouldIncrease: false })
39365
- }).catch((error) => {
39366
- console.error("Media storage error:", error);
39367
- });
39368
- };
39369
- var updateMediaUsage = async (mediaIds, boardId) => {
39370
- try {
39371
- const response = await fetch(`${window?.location.origin}/api/v1/media/usage/${boardId}`, {
39372
- method: "POST",
39373
- headers: {
39374
- "Content-Type": "application/json"
39375
- },
39376
- body: JSON.stringify({ mediaIds, shouldIncrease: true })
39377
- });
39378
- if (response.status !== 200) {
39379
- await catchDuplicateErrorResponse(response);
39380
- return false;
39381
- }
39382
- return true;
39383
- } catch (error) {
39384
- console.error("Media storage error:", error);
39385
- return false;
39386
- }
39387
- };
39388
39236
  var uploadToTheStorage = async (hash, dataURL, accessToken, boardId) => {
39389
39237
  return new Promise((resolve2, reject) => {
39390
39238
  const base64String = dataURL.split(",")[1];
@@ -39402,7 +39250,7 @@ var uploadToTheStorage = async (hash, dataURL, accessToken, boardId) => {
39402
39250
  body: blob
39403
39251
  }).then(async (response) => {
39404
39252
  if (response.status !== 200) {
39405
- return catchErrorResponse(response, "image");
39253
+ return conf.hooks.onUploadMediaError(response, "image");
39406
39254
  }
39407
39255
  return response.json();
39408
39256
  }).then((data) => {
@@ -39502,7 +39350,7 @@ var uploadVideoToStorage = async (hash, videoBlob, accessToken, boardId) => {
39502
39350
  body: videoBlob
39503
39351
  }).then(async (response) => {
39504
39352
  if (response.status !== 200) {
39505
- return catchErrorResponse(response, "video");
39353
+ return conf.hooks.onUploadMediaError(response, "video");
39506
39354
  }
39507
39355
  return response.json();
39508
39356
  }).then((data) => {
@@ -39606,6 +39454,326 @@ var captureFrame = (frameTime, video) => {
39606
39454
  return null;
39607
39455
  }
39608
39456
  };
39457
+ // src/Items/Audio/Audio.ts
39458
+ class AudioItem extends BaseItem {
39459
+ events;
39460
+ extension;
39461
+ itemType = "Audio";
39462
+ parent = "Board";
39463
+ transformation;
39464
+ linkTo;
39465
+ subject = new Subject;
39466
+ loadCallbacks = [];
39467
+ beforeLoadCallbacks = [];
39468
+ transformationRenderBlock = undefined;
39469
+ url = "";
39470
+ isPlaying = false;
39471
+ currentTime = 0;
39472
+ isStorageUrl = true;
39473
+ constructor(board, isStorageUrl, url, events, id = "", extension2) {
39474
+ super(board, id);
39475
+ this.events = events;
39476
+ this.extension = extension2;
39477
+ this.linkTo = new LinkTo(this.id, events);
39478
+ this.board = board;
39479
+ this.isStorageUrl = isStorageUrl;
39480
+ if (url) {
39481
+ this.applyUrl(url);
39482
+ }
39483
+ this.transformation = new Transformation(id, events);
39484
+ this.linkTo.subject.subscribe(() => {
39485
+ this.updateMbr();
39486
+ this.subject.publish(this);
39487
+ });
39488
+ this.transformation.subject.subscribe(this.onTransform);
39489
+ this.right = this.left + conf.AUDIO_DIMENSIONS.width;
39490
+ this.bottom = this.top + conf.AUDIO_DIMENSIONS.height;
39491
+ this.shouldUseCustomRender = true;
39492
+ }
39493
+ setCurrentTime(time) {
39494
+ this.currentTime = time;
39495
+ }
39496
+ getCurrentTime() {
39497
+ return this.currentTime;
39498
+ }
39499
+ getIsStorageUrl() {
39500
+ return this.isStorageUrl;
39501
+ }
39502
+ onTransform = () => {
39503
+ this.updateMbr();
39504
+ this.subject.publish(this);
39505
+ };
39506
+ doOnceBeforeOnLoad = (callback) => {
39507
+ this.loadCallbacks.push(callback);
39508
+ };
39509
+ doOnceOnLoad = (callback) => {
39510
+ this.loadCallbacks.push(callback);
39511
+ };
39512
+ setIsPlaying(isPlaying) {
39513
+ this.isPlaying = isPlaying;
39514
+ this.shouldRenderOutsideViewRect = isPlaying;
39515
+ this.subject.publish(this);
39516
+ }
39517
+ getIsPlaying() {
39518
+ return this.isPlaying;
39519
+ }
39520
+ applyUrl(url) {
39521
+ if (this.isStorageUrl) {
39522
+ try {
39523
+ const newUrl = new URL(url);
39524
+ this.url = `${window.location.origin}${newUrl.pathname}`;
39525
+ } catch (_) {}
39526
+ } else {
39527
+ this.url = url;
39528
+ }
39529
+ }
39530
+ setUrl(url) {
39531
+ this.emit({
39532
+ class: "Audio",
39533
+ method: "setUrl",
39534
+ item: [this.getId()],
39535
+ url
39536
+ });
39537
+ }
39538
+ getStorageId() {
39539
+ if (!this.isStorageUrl) {
39540
+ return;
39541
+ }
39542
+ return this.url.split("/").pop();
39543
+ }
39544
+ getUrl() {
39545
+ return this.url;
39546
+ }
39547
+ onLoad = async () => {
39548
+ this.shootBeforeLoadCallbacks();
39549
+ this.updateMbr();
39550
+ this.subject.publish(this);
39551
+ this.shootLoadCallbacks();
39552
+ };
39553
+ onError = (_error) => {
39554
+ this.updateMbr();
39555
+ this.subject.publish(this);
39556
+ this.shootLoadCallbacks();
39557
+ };
39558
+ updateMbr() {
39559
+ const { translateX, translateY, scaleX, scaleY } = this.transformation.matrix;
39560
+ this.left = translateX;
39561
+ this.top = translateY;
39562
+ this.right = this.left + conf.AUDIO_DIMENSIONS.width * scaleX;
39563
+ this.bottom = this.top + conf.AUDIO_DIMENSIONS.height * scaleY;
39564
+ }
39565
+ render(context) {
39566
+ if (this.transformationRenderBlock) {
39567
+ return;
39568
+ }
39569
+ const ctx = context.ctx;
39570
+ const radius = 12 * this.transformation.getScale().x;
39571
+ ctx.save();
39572
+ ctx.globalCompositeOperation = "destination-out";
39573
+ ctx.beginPath();
39574
+ ctx.moveTo(this.left + radius, this.top);
39575
+ ctx.lineTo(this.left + this.getWidth() - radius, this.top);
39576
+ ctx.quadraticCurveTo(this.left + this.getWidth(), this.top, this.left + this.getWidth(), this.top + radius);
39577
+ ctx.lineTo(this.left + this.getWidth(), this.top + this.getHeight() - radius);
39578
+ ctx.quadraticCurveTo(this.left + this.getWidth(), this.top + this.getHeight(), this.left + this.getWidth() - radius, this.top + this.getHeight());
39579
+ ctx.lineTo(this.left + radius, this.top + this.getHeight());
39580
+ ctx.quadraticCurveTo(this.left, this.top + this.getHeight(), this.left, this.top + this.getHeight() - radius);
39581
+ ctx.lineTo(this.left, this.top + radius);
39582
+ ctx.quadraticCurveTo(this.left, this.top, this.left + radius, this.top);
39583
+ ctx.closePath();
39584
+ ctx.fill();
39585
+ ctx.restore();
39586
+ }
39587
+ renderHTML(documentFactory) {
39588
+ const div = documentFactory.createElement("audio-item");
39589
+ const { translateX, translateY, scaleX, scaleY } = this.transformation.matrix;
39590
+ const transform = `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`;
39591
+ div.id = this.getId();
39592
+ div.style.width = `${conf.AUDIO_DIMENSIONS.width}px`;
39593
+ div.style.height = `${conf.AUDIO_DIMENSIONS.height}px`;
39594
+ div.style.transformOrigin = "top left";
39595
+ div.style.transform = transform;
39596
+ div.style.position = "absolute";
39597
+ div.setAttribute("audio-url", this.getUrl());
39598
+ if (this.extension) {
39599
+ div.setAttribute("extension", this.extension);
39600
+ }
39601
+ if (this.isStorageUrl) {
39602
+ div.setAttribute("is-storage-url", "true");
39603
+ }
39604
+ div.setAttribute("data-link-to", "");
39605
+ return div;
39606
+ }
39607
+ serialize() {
39608
+ return {
39609
+ itemType: "Audio",
39610
+ url: this.url,
39611
+ transformation: this.transformation.serialize(),
39612
+ isStorageUrl: this.isStorageUrl,
39613
+ extension: this.extension
39614
+ };
39615
+ }
39616
+ deserialize(data) {
39617
+ if (data.isStorageUrl) {
39618
+ this.isStorageUrl = data.isStorageUrl;
39619
+ }
39620
+ if (data.transformation) {
39621
+ this.transformation.deserialize(data.transformation);
39622
+ }
39623
+ if (data.url) {
39624
+ this.setUrl(data.url);
39625
+ }
39626
+ if (data.extension) {
39627
+ this.extension = data.extension;
39628
+ }
39629
+ return this;
39630
+ }
39631
+ apply(op) {
39632
+ switch (op.class) {
39633
+ case "Transformation":
39634
+ this.transformation.apply(op);
39635
+ break;
39636
+ case "LinkTo":
39637
+ this.linkTo.apply(op);
39638
+ break;
39639
+ case "Audio":
39640
+ if (op.method === "setUrl") {
39641
+ this.applyUrl(op.url);
39642
+ }
39643
+ this.subject.publish(this);
39644
+ break;
39645
+ }
39646
+ }
39647
+ emit(operation) {
39648
+ if (this.events) {
39649
+ const command = new AudioCommand([this], operation);
39650
+ command.apply();
39651
+ this.events.emit(operation, command);
39652
+ } else {
39653
+ this.apply(operation);
39654
+ }
39655
+ }
39656
+ setId(id) {
39657
+ this.id = id;
39658
+ this.transformation.setId(id);
39659
+ return this;
39660
+ }
39661
+ getId() {
39662
+ return this.id;
39663
+ }
39664
+ shootLoadCallbacks() {
39665
+ while (this.loadCallbacks.length > 0) {
39666
+ this.loadCallbacks.shift()(this);
39667
+ }
39668
+ }
39669
+ shootBeforeLoadCallbacks() {
39670
+ while (this.beforeLoadCallbacks.length > 0) {
39671
+ this.beforeLoadCallbacks.shift()(this);
39672
+ }
39673
+ }
39674
+ getPath() {
39675
+ const { left, top, right, bottom } = this.getMbr();
39676
+ const leftTop = new Point(left, top);
39677
+ const rightTop = new Point(right, top);
39678
+ const rightBottom = new Point(right, bottom);
39679
+ const leftBottom = new Point(left, bottom);
39680
+ return new Path([
39681
+ new Line(leftTop, rightTop),
39682
+ new Line(rightTop, rightBottom),
39683
+ new Line(rightBottom, leftBottom),
39684
+ new Line(leftBottom, leftTop)
39685
+ ], true);
39686
+ }
39687
+ getSnapAnchorPoints() {
39688
+ const mbr = this.getMbr();
39689
+ const width2 = mbr.getWidth();
39690
+ const height2 = mbr.getHeight();
39691
+ return [
39692
+ new Point(mbr.left + width2 / 2, mbr.top),
39693
+ new Point(mbr.left + width2 / 2, mbr.bottom),
39694
+ new Point(mbr.left, mbr.top + height2 / 2),
39695
+ new Point(mbr.right, mbr.top + height2 / 2)
39696
+ ];
39697
+ }
39698
+ isClosed() {
39699
+ return true;
39700
+ }
39701
+ getRichText() {
39702
+ return null;
39703
+ }
39704
+ getLinkTo() {
39705
+ return;
39706
+ }
39707
+ getExtension() {
39708
+ return this.extension;
39709
+ }
39710
+ download() {
39711
+ if (this.extension) {
39712
+ const linkElem = conf.documentFactory.createElement("a");
39713
+ linkElem.href = this.url;
39714
+ linkElem.setAttribute("download", `${this.board.getBoardId()}.${this.extension}`);
39715
+ linkElem.click();
39716
+ }
39717
+ }
39718
+ onRemove() {
39719
+ const storageId = this.getStorageId();
39720
+ if (storageId) {
39721
+ conf.hooks.beforeMediaRemove([storageId], this.board.getBoardId());
39722
+ }
39723
+ super.onRemove();
39724
+ }
39725
+ }
39726
+ // src/Items/Audio/AudioHelpers.ts
39727
+ var uploadAudioToStorage = async (hash, audioBlob, accessToken, boardId) => {
39728
+ return new Promise((resolve2, reject) => {
39729
+ fetch(`${window.location.origin}/api/v1/media/audio/${boardId}`, {
39730
+ method: "POST",
39731
+ headers: {
39732
+ "Content-Type": audioBlob.type,
39733
+ "x-audio-id": hash,
39734
+ Authorization: `Bearer ${accessToken}`
39735
+ },
39736
+ body: audioBlob
39737
+ }).then(async (response) => {
39738
+ if (response.status !== 200) {
39739
+ return conf.hooks.onUploadMediaError(response, "audio");
39740
+ }
39741
+ return response.json();
39742
+ }).then((data) => {
39743
+ console.log(data);
39744
+ resolve2(data.src);
39745
+ }).catch((error) => {
39746
+ console.error("Media storage error:", error);
39747
+ reject(error);
39748
+ });
39749
+ });
39750
+ };
39751
+ var prepareAudio = (file, accessToken, boardId) => {
39752
+ return new Promise((resolve2, reject) => {
39753
+ const audio = document.createElement("audio");
39754
+ audio.src = URL.createObjectURL(file);
39755
+ audio.onloadedmetadata = () => {
39756
+ fileTosha256(file).then((hash) => {
39757
+ uploadAudioToStorage(hash, file, accessToken, boardId).then((url) => {
39758
+ resolve2(url);
39759
+ }).catch(reject);
39760
+ }).catch(() => {
39761
+ reject(new Error("Failed to generate hash"));
39762
+ });
39763
+ };
39764
+ audio.onerror = () => {
39765
+ reject(new Error("Failed to load audio"));
39766
+ };
39767
+ });
39768
+ };
39769
+ var calculateAudioPosition = (board, audioItem) => {
39770
+ const cameraMbr = board.camera.getMbr();
39771
+ const cameraWidth = cameraMbr.getWidth();
39772
+ const translateX = cameraMbr.left + cameraWidth * 0.34;
39773
+ const translateY = cameraMbr.getCenter().y - audioItem.getHeight() / 2;
39774
+ const scale = cameraWidth * 0.32 / audioItem.getWidth();
39775
+ return new Matrix2(translateX, translateY, scale, scale);
39776
+ };
39609
39777
  // src/Items/Placeholder/Placeholder.ts
39610
39778
  var PlaceholderImg = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
39611
39779
  <path d="M5 11.1L7 9.1L12.5 14.6L16 11.1L19 14.1V5H5V11.1ZM4 3H20C20.2652 3 20.5196 3.10536 20.7071 3.29289C20.8946 3.48043 21 3.73478 21 4V20C21 20.2652 20.8946 20.5196 20.7071 20.7071C20.5196 20.8946 20.2652 21 20 21H4C3.73478 21 3.48043 20.8946 3.29289 20.7071C3.10536 20.5196 3 20.2652 3 20V4C3 3.73478 3.10536 3.48043 3.29289 3.29289C3.48043 3.10536 3.73478 3 4 3ZM15.5 10C15.1022 10 14.7206 9.84196 14.4393 9.56066C14.158 9.27936 14 8.89782 14 8.5C14 8.10218 14.158 7.72064 14.4393 7.43934C14.7206 7.15804 15.1022 7 15.5 7C15.8978 7 16.2794 7.15804 16.5607 7.43934C16.842 7.72064 17 8.10218 17 8.5C17 8.89782 16.842 9.27936 16.5607 9.56066C16.2794 9.84196 15.8978 10 15.5 10Z" fill="white" fill-opacity="0.6"/>
@@ -40154,331 +40322,11 @@ class ImageItem extends BaseItem {
40154
40322
  onRemove() {
40155
40323
  const storageId = this.getStorageId();
40156
40324
  if (storageId) {
40157
- deleteMedia([storageId], this.board.getBoardId());
40158
- }
40159
- super.onRemove();
40160
- }
40161
- }
40162
- // src/Items/Audio/Audio.ts
40163
- class AudioItem extends BaseItem {
40164
- events;
40165
- extension;
40166
- itemType = "Audio";
40167
- parent = "Board";
40168
- transformation;
40169
- linkTo;
40170
- subject = new Subject;
40171
- loadCallbacks = [];
40172
- beforeLoadCallbacks = [];
40173
- transformationRenderBlock = undefined;
40174
- url = "";
40175
- isPlaying = false;
40176
- currentTime = 0;
40177
- isStorageUrl = true;
40178
- constructor(board, isStorageUrl, url, events, id = "", extension2) {
40179
- super(board, id);
40180
- this.events = events;
40181
- this.extension = extension2;
40182
- this.linkTo = new LinkTo(this.id, events);
40183
- this.board = board;
40184
- this.isStorageUrl = isStorageUrl;
40185
- if (url) {
40186
- this.applyUrl(url);
40187
- }
40188
- this.transformation = new Transformation(id, events);
40189
- this.linkTo.subject.subscribe(() => {
40190
- this.updateMbr();
40191
- this.subject.publish(this);
40192
- });
40193
- this.transformation.subject.subscribe(this.onTransform);
40194
- this.right = this.left + conf.AUDIO_DIMENSIONS.width;
40195
- this.bottom = this.top + conf.AUDIO_DIMENSIONS.height;
40196
- this.shouldUseCustomRender = true;
40197
- }
40198
- setCurrentTime(time) {
40199
- this.currentTime = time;
40200
- }
40201
- getCurrentTime() {
40202
- return this.currentTime;
40203
- }
40204
- getIsStorageUrl() {
40205
- return this.isStorageUrl;
40206
- }
40207
- onTransform = () => {
40208
- this.updateMbr();
40209
- this.subject.publish(this);
40210
- };
40211
- doOnceBeforeOnLoad = (callback) => {
40212
- this.loadCallbacks.push(callback);
40213
- };
40214
- doOnceOnLoad = (callback) => {
40215
- this.loadCallbacks.push(callback);
40216
- };
40217
- setIsPlaying(isPlaying) {
40218
- this.isPlaying = isPlaying;
40219
- this.shouldRenderOutsideViewRect = isPlaying;
40220
- this.subject.publish(this);
40221
- }
40222
- getIsPlaying() {
40223
- return this.isPlaying;
40224
- }
40225
- applyUrl(url) {
40226
- if (this.isStorageUrl) {
40227
- try {
40228
- const newUrl = new URL(url);
40229
- this.url = `${window.location.origin}${newUrl.pathname}`;
40230
- } catch (_) {}
40231
- } else {
40232
- this.url = url;
40233
- }
40234
- }
40235
- setUrl(url) {
40236
- this.emit({
40237
- class: "Audio",
40238
- method: "setUrl",
40239
- item: [this.getId()],
40240
- url
40241
- });
40242
- }
40243
- getStorageId() {
40244
- if (!this.isStorageUrl) {
40245
- return;
40246
- }
40247
- return this.url.split("/").pop();
40248
- }
40249
- getUrl() {
40250
- return this.url;
40251
- }
40252
- onLoad = async () => {
40253
- this.shootBeforeLoadCallbacks();
40254
- this.updateMbr();
40255
- this.subject.publish(this);
40256
- this.shootLoadCallbacks();
40257
- };
40258
- onError = (_error) => {
40259
- this.updateMbr();
40260
- this.subject.publish(this);
40261
- this.shootLoadCallbacks();
40262
- };
40263
- updateMbr() {
40264
- const { translateX, translateY, scaleX, scaleY } = this.transformation.matrix;
40265
- this.left = translateX;
40266
- this.top = translateY;
40267
- this.right = this.left + conf.AUDIO_DIMENSIONS.width * scaleX;
40268
- this.bottom = this.top + conf.AUDIO_DIMENSIONS.height * scaleY;
40269
- }
40270
- render(context) {
40271
- if (this.transformationRenderBlock) {
40272
- return;
40273
- }
40274
- const ctx = context.ctx;
40275
- const radius = 12 * this.transformation.getScale().x;
40276
- ctx.save();
40277
- ctx.globalCompositeOperation = "destination-out";
40278
- ctx.beginPath();
40279
- ctx.moveTo(this.left + radius, this.top);
40280
- ctx.lineTo(this.left + this.getWidth() - radius, this.top);
40281
- ctx.quadraticCurveTo(this.left + this.getWidth(), this.top, this.left + this.getWidth(), this.top + radius);
40282
- ctx.lineTo(this.left + this.getWidth(), this.top + this.getHeight() - radius);
40283
- ctx.quadraticCurveTo(this.left + this.getWidth(), this.top + this.getHeight(), this.left + this.getWidth() - radius, this.top + this.getHeight());
40284
- ctx.lineTo(this.left + radius, this.top + this.getHeight());
40285
- ctx.quadraticCurveTo(this.left, this.top + this.getHeight(), this.left, this.top + this.getHeight() - radius);
40286
- ctx.lineTo(this.left, this.top + radius);
40287
- ctx.quadraticCurveTo(this.left, this.top, this.left + radius, this.top);
40288
- ctx.closePath();
40289
- ctx.fill();
40290
- ctx.restore();
40291
- }
40292
- renderHTML(documentFactory) {
40293
- const div = documentFactory.createElement("audio-item");
40294
- const { translateX, translateY, scaleX, scaleY } = this.transformation.matrix;
40295
- const transform = `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`;
40296
- div.id = this.getId();
40297
- div.style.width = `${conf.AUDIO_DIMENSIONS.width}px`;
40298
- div.style.height = `${conf.AUDIO_DIMENSIONS.height}px`;
40299
- div.style.transformOrigin = "top left";
40300
- div.style.transform = transform;
40301
- div.style.position = "absolute";
40302
- div.setAttribute("audio-url", this.getUrl());
40303
- if (this.extension) {
40304
- div.setAttribute("extension", this.extension);
40305
- }
40306
- if (this.isStorageUrl) {
40307
- div.setAttribute("is-storage-url", "true");
40308
- }
40309
- div.setAttribute("data-link-to", "");
40310
- return div;
40311
- }
40312
- serialize() {
40313
- return {
40314
- itemType: "Audio",
40315
- url: this.url,
40316
- transformation: this.transformation.serialize(),
40317
- isStorageUrl: this.isStorageUrl,
40318
- extension: this.extension
40319
- };
40320
- }
40321
- deserialize(data) {
40322
- if (data.isStorageUrl) {
40323
- this.isStorageUrl = data.isStorageUrl;
40324
- }
40325
- if (data.transformation) {
40326
- this.transformation.deserialize(data.transformation);
40327
- }
40328
- if (data.url) {
40329
- this.setUrl(data.url);
40330
- }
40331
- if (data.extension) {
40332
- this.extension = data.extension;
40333
- }
40334
- return this;
40335
- }
40336
- apply(op) {
40337
- switch (op.class) {
40338
- case "Transformation":
40339
- this.transformation.apply(op);
40340
- break;
40341
- case "LinkTo":
40342
- this.linkTo.apply(op);
40343
- break;
40344
- case "Audio":
40345
- if (op.method === "setUrl") {
40346
- this.applyUrl(op.url);
40347
- }
40348
- this.subject.publish(this);
40349
- break;
40350
- }
40351
- }
40352
- emit(operation) {
40353
- if (this.events) {
40354
- const command = new AudioCommand([this], operation);
40355
- command.apply();
40356
- this.events.emit(operation, command);
40357
- } else {
40358
- this.apply(operation);
40359
- }
40360
- }
40361
- setId(id) {
40362
- this.id = id;
40363
- this.transformation.setId(id);
40364
- return this;
40365
- }
40366
- getId() {
40367
- return this.id;
40368
- }
40369
- shootLoadCallbacks() {
40370
- while (this.loadCallbacks.length > 0) {
40371
- this.loadCallbacks.shift()(this);
40372
- }
40373
- }
40374
- shootBeforeLoadCallbacks() {
40375
- while (this.beforeLoadCallbacks.length > 0) {
40376
- this.beforeLoadCallbacks.shift()(this);
40377
- }
40378
- }
40379
- getPath() {
40380
- const { left, top, right, bottom } = this.getMbr();
40381
- const leftTop = new Point(left, top);
40382
- const rightTop = new Point(right, top);
40383
- const rightBottom = new Point(right, bottom);
40384
- const leftBottom = new Point(left, bottom);
40385
- return new Path([
40386
- new Line(leftTop, rightTop),
40387
- new Line(rightTop, rightBottom),
40388
- new Line(rightBottom, leftBottom),
40389
- new Line(leftBottom, leftTop)
40390
- ], true);
40391
- }
40392
- getSnapAnchorPoints() {
40393
- const mbr = this.getMbr();
40394
- const width2 = mbr.getWidth();
40395
- const height2 = mbr.getHeight();
40396
- return [
40397
- new Point(mbr.left + width2 / 2, mbr.top),
40398
- new Point(mbr.left + width2 / 2, mbr.bottom),
40399
- new Point(mbr.left, mbr.top + height2 / 2),
40400
- new Point(mbr.right, mbr.top + height2 / 2)
40401
- ];
40402
- }
40403
- isClosed() {
40404
- return true;
40405
- }
40406
- getRichText() {
40407
- return null;
40408
- }
40409
- getLinkTo() {
40410
- return;
40411
- }
40412
- getExtension() {
40413
- return this.extension;
40414
- }
40415
- download() {
40416
- if (this.extension) {
40417
- const linkElem = conf.documentFactory.createElement("a");
40418
- linkElem.href = this.url;
40419
- linkElem.setAttribute("download", `${this.board.getBoardId()}.${this.extension}`);
40420
- linkElem.click();
40421
- }
40422
- }
40423
- onRemove() {
40424
- const storageId = this.getStorageId();
40425
- if (storageId) {
40426
- deleteMedia([storageId], this.board.getBoardId());
40325
+ conf.hooks.beforeMediaRemove([storageId], this.board.getBoardId());
40427
40326
  }
40428
40327
  super.onRemove();
40429
40328
  }
40430
40329
  }
40431
- // src/Items/Audio/AudioHelpers.ts
40432
- var uploadAudioToStorage = async (hash, audioBlob, accessToken, boardId) => {
40433
- return new Promise((resolve2, reject) => {
40434
- fetch(`${window.location.origin}/api/v1/media/audio/${boardId}`, {
40435
- method: "POST",
40436
- headers: {
40437
- "Content-Type": audioBlob.type,
40438
- "x-audio-id": hash,
40439
- Authorization: `Bearer ${accessToken}`
40440
- },
40441
- body: audioBlob
40442
- }).then(async (response) => {
40443
- if (response.status !== 200) {
40444
- return catchErrorResponse(response, "audio");
40445
- }
40446
- return response.json();
40447
- }).then((data) => {
40448
- console.log(data);
40449
- resolve2(data.src);
40450
- }).catch((error) => {
40451
- console.error("Media storage error:", error);
40452
- reject(error);
40453
- });
40454
- });
40455
- };
40456
- var prepareAudio = (file, accessToken, boardId) => {
40457
- return new Promise((resolve2, reject) => {
40458
- const audio = document.createElement("audio");
40459
- audio.src = URL.createObjectURL(file);
40460
- audio.onloadedmetadata = () => {
40461
- fileTosha256(file).then((hash) => {
40462
- uploadAudioToStorage(hash, file, accessToken, boardId).then((url) => {
40463
- resolve2(url);
40464
- }).catch(reject);
40465
- }).catch(() => {
40466
- reject(new Error("Failed to generate hash"));
40467
- });
40468
- };
40469
- audio.onerror = () => {
40470
- reject(new Error("Failed to load audio"));
40471
- };
40472
- });
40473
- };
40474
- var calculateAudioPosition = (board, audioItem) => {
40475
- const cameraMbr = board.camera.getMbr();
40476
- const cameraWidth = cameraMbr.getWidth();
40477
- const translateX = cameraMbr.left + cameraWidth * 0.34;
40478
- const translateY = cameraMbr.getCenter().y - audioItem.getHeight() / 2;
40479
- const scale = cameraWidth * 0.32 / audioItem.getWidth();
40480
- return new Matrix2(translateX, translateY, scale, scale);
40481
- };
40482
40330
  // src/isSafari.ts
40483
40331
  function isSafari() {
40484
40332
  if (typeof navigator === "undefined") {
@@ -51893,7 +51741,7 @@ class BoardSelection {
51893
51741
  const connectors = itemIds.flatMap((id) => {
51894
51742
  return this.board.items.getLinkedConnectorsById(id);
51895
51743
  }).map((connector) => connector.getId());
51896
- deleteMedia(this.getMediaStorageIds(), this.board.getBoardId());
51744
+ conf.hooks.beforeMediaRemove(this.getMediaStorageIds(), this.board.getBoardId());
51897
51745
  this.emit({
51898
51746
  class: "Board",
51899
51747
  method: "remove",
@@ -51931,7 +51779,7 @@ class BoardSelection {
51931
51779
  }
51932
51780
  async duplicate() {
51933
51781
  const mediaIds = this.getMediaStorageIds();
51934
- const canDuplicate = mediaIds.length ? await updateMediaUsage(mediaIds, this.board.getBoardId()) : true;
51782
+ const canDuplicate = mediaIds.length ? await conf.hooks.beforeMediaUpload(mediaIds, this.board.getBoardId()) : true;
51935
51783
  if (!canDuplicate) {
51936
51784
  return;
51937
51785
  }
@@ -52812,7 +52660,7 @@ class Board {
52812
52660
  newMap[newItemId] = itemData;
52813
52661
  }
52814
52662
  if (shouldUpdateMediaUsage) {
52815
- const canDuplicate = mediaStorageIds.length ? await updateMediaUsage(mediaStorageIds, this.getBoardId()) : true;
52663
+ const canDuplicate = mediaStorageIds.length ? await conf.hooks.beforeMediaUpload(mediaStorageIds, this.getBoardId()) : true;
52816
52664
  if (!canDuplicate) {
52817
52665
  return;
52818
52666
  }
@@ -55125,7 +54973,7 @@ function handleAudioGenerate(response, board) {
55125
54973
  }
55126
54974
  function handleImageGenerate(response, board) {
55127
54975
  if (response.status === "completed" && response.base64) {
55128
- prepareImage(response.base64, account?.accessToken || null, board.getBoardId()).then((imageData) => {
54976
+ prepareImage(response.base64, null, board.getBoardId()).then((imageData) => {
55129
54977
  const placeholderId = board.aiImagePlaceholder?.getId();
55130
54978
  if (placeholderId) {
55131
54979
  const placeholderNode = board.items.getById(placeholderId);