microboard-temp 0.3.1 → 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.
package/dist/esm/index.js CHANGED
@@ -6556,6 +6556,11 @@ var conf = {
6556
6556
  getDOMParser: undefined,
6557
6557
  measureCtx: undefined,
6558
6558
  i18n: instance,
6559
+ hooks: {
6560
+ beforeMediaUpload: async (...args) => false,
6561
+ beforeMediaRemove: async (...args) => false,
6562
+ onUploadMediaError: async (...args) => false
6563
+ },
6559
6564
  openModal: () => {},
6560
6565
  notify: () => "",
6561
6566
  disMissNotification: () => {},
@@ -39013,7 +39018,7 @@ class VideoItem extends BaseItem {
39013
39018
  onRemove() {
39014
39019
  const storageId = this.getStorageId();
39015
39020
  if (storageId) {
39016
- deleteMedia([storageId], this.board.getBoardId());
39021
+ conf.hooks.beforeMediaRemove([storageId], this.board.getBoardId());
39017
39022
  }
39018
39023
  super.onRemove();
39019
39024
  }
@@ -39074,158 +39079,6 @@ async function fileTosha256(file) {
39074
39079
  }
39075
39080
 
39076
39081
  // src/Items/Image/ImageHelpers.ts
39077
- var catchErrorResponse = async (response, mediaType) => {
39078
- if (response.status === 403) {
39079
- const data = await response.json();
39080
- let errorBody = conf.i18n.t("toolsPanel.addMedia.limitReached.bodyWithoutLimit");
39081
- if (!data.isOwnerRequest) {
39082
- errorBody = conf.i18n.t("toolsPanel.addMedia.limitReached.bodyOwner");
39083
- } else if (data.currentUsage && data.storageLimit) {
39084
- errorBody = conf.i18n.t(`toolsPanel.addMedia.limitReached.body.${parseInt(data.storageLimit) < 1e5 ? "basic" : "plus"}`);
39085
- }
39086
- conf.notify({
39087
- variant: "warning",
39088
- header: conf.i18n.t("toolsPanel.addMedia.limitReached.header"),
39089
- body: errorBody,
39090
- button: data.isOwnerRequest && data.storageLimit <= 100 ? {
39091
- text: conf.i18n.t("toolsPanel.addMedia.upgradeToPlus"),
39092
- onClick: () => conf.openModal("USER_PLAN_MODAL_ID")
39093
- } : undefined,
39094
- duration: 8000
39095
- });
39096
- } else if (response.status === 413) {
39097
- const data = await response.json();
39098
- let errorBody = conf.i18n.t("toolsPanel.addMedia.tooLarge.bodyWithoutLimit");
39099
- let isBasicPlan = false;
39100
- if (data.fileSizeLimit && data.fileSize) {
39101
- if (mediaType === "image") {
39102
- isBasicPlan = parseInt(data.fileSizeLimit) < 20;
39103
- errorBody = conf.i18n.t(`toolsPanel.addMedia.tooLarge.imageBody.${isBasicPlan ? "basic" : "plus"}`);
39104
- } else {
39105
- isBasicPlan = parseInt(data.fileSizeLimit) < 1000;
39106
- errorBody = conf.i18n.t(`toolsPanel.addMedia.tooLarge.audioOrVideoBody.${isBasicPlan ? "basic" : "plus"}`);
39107
- }
39108
- }
39109
- conf.notify({
39110
- variant: "warning",
39111
- header: conf.i18n.t("toolsPanel.addMedia.tooLarge.header"),
39112
- body: errorBody,
39113
- button: isBasicPlan ? {
39114
- text: conf.i18n.t("toolsPanel.addMedia.upgradeToPlus"),
39115
- onClick: () => conf.openModal("USER_PLAN_MODAL_ID")
39116
- } : undefined,
39117
- duration: 4000
39118
- });
39119
- } else if (response.status === 401) {
39120
- conf.openModal("MEDIA_UNAVAILABLE_MODAL_ID");
39121
- } else if (response.status === 415) {
39122
- conf.notify({
39123
- variant: "warning",
39124
- header: conf.i18n.t("toolsPanel.addMedia.unsupportedFormat.header"),
39125
- body: conf.i18n.t("toolsPanel.addMedia.unsupportedFormat.body"),
39126
- duration: 4000
39127
- });
39128
- } else {
39129
- conf.notify({
39130
- variant: "error",
39131
- header: conf.i18n.t("toolsPanel.addMedia.unhandled.header"),
39132
- body: conf.i18n.t("toolsPanel.addMedia.unhandled.body"),
39133
- duration: 4000
39134
- });
39135
- }
39136
- throw new Error(`HTTP status: ${response.status}`);
39137
- };
39138
- var catchDuplicateErrorResponse = async (response) => {
39139
- if (response.status === 403) {
39140
- conf.notify({
39141
- variant: "warning",
39142
- header: conf.i18n.t("toolsPanel.addMedia.limitReached.header"),
39143
- body: conf.i18n.t("toolsPanel.addMedia.limitReached.duplicateBody"),
39144
- duration: 4000
39145
- });
39146
- } else {
39147
- conf.notify({
39148
- variant: "error",
39149
- header: conf.i18n.t("toolsPanel.addMedia.unhandled.header"),
39150
- body: conf.i18n.t("toolsPanel.addMedia.unhandled.body"),
39151
- duration: 4000
39152
- });
39153
- }
39154
- throw new Error(`HTTP status: ${response.status}`);
39155
- };
39156
- var validateMediaFile = (file, account2) => {
39157
- const fileExtension = file.name.split(".").pop()?.toLowerCase() || "";
39158
- if (!file.type.startsWith("image") && !conf.AUDIO_FORMATS.includes(fileExtension) && !conf.VIDEO_FORMATS.includes(fileExtension)) {
39159
- conf.notify({
39160
- variant: "warning",
39161
- header: conf.i18n.t("toolsPanel.addMedia.unsupportedFormat.header"),
39162
- body: conf.i18n.t("toolsPanel.addMedia.unsupportedFormat.body"),
39163
- duration: 4000
39164
- });
39165
- return false;
39166
- }
39167
- const isBasicPlan = account2.billingInfo?.plan.name === "basic";
39168
- let errorBody = conf.i18n.t(`toolsPanel.addMedia.tooLarge.imageBody.${isBasicPlan ? "basic" : "plus"}`);
39169
- if (conf.AUDIO_FORMATS.includes(fileExtension) || conf.VIDEO_FORMATS.includes(fileExtension)) {
39170
- errorBody = conf.i18n.t(`toolsPanel.addMedia.tooLarge.audioOrVideoBody.${isBasicPlan ? "basic" : "plus"}`);
39171
- if (file.size / 1024 ** 2 > (account2.billingInfo?.storage.maxMediaSize || Infinity)) {
39172
- conf.notify({
39173
- variant: "warning",
39174
- header: conf.i18n.t("toolsPanel.addMedia.tooLarge.header"),
39175
- body: errorBody,
39176
- button: isBasicPlan ? {
39177
- text: conf.i18n.t("toolsPanel.addMedia.upgradeToPlus"),
39178
- onClick: () => conf.openModal("USER_PLAN_MODAL_ID")
39179
- } : undefined,
39180
- duration: 4000
39181
- });
39182
- return false;
39183
- }
39184
- } else if (file.size / 1024 ** 2 > (account2.billingInfo?.storage.maxImageSize || Infinity)) {
39185
- conf.notify({
39186
- variant: "warning",
39187
- header: conf.i18n.t("toolsPanel.addMedia.tooLarge.header"),
39188
- body: errorBody,
39189
- button: isBasicPlan ? {
39190
- text: conf.i18n.t("toolsPanel.addMedia.upgradeToPlus"),
39191
- onClick: () => conf.openModal("USER_PLAN_MODAL_ID")
39192
- } : undefined,
39193
- duration: 4000
39194
- });
39195
- return false;
39196
- }
39197
- return true;
39198
- };
39199
- var deleteMedia = async (mediaIds, boardId) => {
39200
- fetch(`${window?.location.origin}/api/v1/media/usage/${boardId}`, {
39201
- method: "POST",
39202
- headers: {
39203
- "content-type": "application/json"
39204
- },
39205
- body: JSON.stringify({ mediaIds, shouldIncrease: false })
39206
- }).catch((error) => {
39207
- console.error("Media storage error:", error);
39208
- });
39209
- };
39210
- var updateMediaUsage = async (mediaIds, boardId) => {
39211
- try {
39212
- const response = await fetch(`${window?.location.origin}/api/v1/media/usage/${boardId}`, {
39213
- method: "POST",
39214
- headers: {
39215
- "Content-Type": "application/json"
39216
- },
39217
- body: JSON.stringify({ mediaIds, shouldIncrease: true })
39218
- });
39219
- if (response.status !== 200) {
39220
- await catchDuplicateErrorResponse(response);
39221
- return false;
39222
- }
39223
- return true;
39224
- } catch (error) {
39225
- console.error("Media storage error:", error);
39226
- return false;
39227
- }
39228
- };
39229
39082
  var uploadToTheStorage = async (hash, dataURL, accessToken, boardId) => {
39230
39083
  return new Promise((resolve2, reject) => {
39231
39084
  const base64String = dataURL.split(",")[1];
@@ -39243,7 +39096,7 @@ var uploadToTheStorage = async (hash, dataURL, accessToken, boardId) => {
39243
39096
  body: blob
39244
39097
  }).then(async (response) => {
39245
39098
  if (response.status !== 200) {
39246
- return catchErrorResponse(response, "image");
39099
+ return conf.hooks.onUploadMediaError(response, "image");
39247
39100
  }
39248
39101
  return response.json();
39249
39102
  }).then((data) => {
@@ -39343,7 +39196,7 @@ var uploadVideoToStorage = async (hash, videoBlob, accessToken, boardId) => {
39343
39196
  body: videoBlob
39344
39197
  }).then(async (response) => {
39345
39198
  if (response.status !== 200) {
39346
- return catchErrorResponse(response, "video");
39199
+ return conf.hooks.onUploadMediaError(response, "video");
39347
39200
  }
39348
39201
  return response.json();
39349
39202
  }).then((data) => {
@@ -39447,6 +39300,326 @@ var captureFrame = (frameTime, video) => {
39447
39300
  return null;
39448
39301
  }
39449
39302
  };
39303
+ // src/Items/Audio/Audio.ts
39304
+ class AudioItem extends BaseItem {
39305
+ events;
39306
+ extension;
39307
+ itemType = "Audio";
39308
+ parent = "Board";
39309
+ transformation;
39310
+ linkTo;
39311
+ subject = new Subject;
39312
+ loadCallbacks = [];
39313
+ beforeLoadCallbacks = [];
39314
+ transformationRenderBlock = undefined;
39315
+ url = "";
39316
+ isPlaying = false;
39317
+ currentTime = 0;
39318
+ isStorageUrl = true;
39319
+ constructor(board, isStorageUrl, url, events, id = "", extension2) {
39320
+ super(board, id);
39321
+ this.events = events;
39322
+ this.extension = extension2;
39323
+ this.linkTo = new LinkTo(this.id, events);
39324
+ this.board = board;
39325
+ this.isStorageUrl = isStorageUrl;
39326
+ if (url) {
39327
+ this.applyUrl(url);
39328
+ }
39329
+ this.transformation = new Transformation(id, events);
39330
+ this.linkTo.subject.subscribe(() => {
39331
+ this.updateMbr();
39332
+ this.subject.publish(this);
39333
+ });
39334
+ this.transformation.subject.subscribe(this.onTransform);
39335
+ this.right = this.left + conf.AUDIO_DIMENSIONS.width;
39336
+ this.bottom = this.top + conf.AUDIO_DIMENSIONS.height;
39337
+ this.shouldUseCustomRender = true;
39338
+ }
39339
+ setCurrentTime(time) {
39340
+ this.currentTime = time;
39341
+ }
39342
+ getCurrentTime() {
39343
+ return this.currentTime;
39344
+ }
39345
+ getIsStorageUrl() {
39346
+ return this.isStorageUrl;
39347
+ }
39348
+ onTransform = () => {
39349
+ this.updateMbr();
39350
+ this.subject.publish(this);
39351
+ };
39352
+ doOnceBeforeOnLoad = (callback) => {
39353
+ this.loadCallbacks.push(callback);
39354
+ };
39355
+ doOnceOnLoad = (callback) => {
39356
+ this.loadCallbacks.push(callback);
39357
+ };
39358
+ setIsPlaying(isPlaying) {
39359
+ this.isPlaying = isPlaying;
39360
+ this.shouldRenderOutsideViewRect = isPlaying;
39361
+ this.subject.publish(this);
39362
+ }
39363
+ getIsPlaying() {
39364
+ return this.isPlaying;
39365
+ }
39366
+ applyUrl(url) {
39367
+ if (this.isStorageUrl) {
39368
+ try {
39369
+ const newUrl = new URL(url);
39370
+ this.url = `${window.location.origin}${newUrl.pathname}`;
39371
+ } catch (_) {}
39372
+ } else {
39373
+ this.url = url;
39374
+ }
39375
+ }
39376
+ setUrl(url) {
39377
+ this.emit({
39378
+ class: "Audio",
39379
+ method: "setUrl",
39380
+ item: [this.getId()],
39381
+ url
39382
+ });
39383
+ }
39384
+ getStorageId() {
39385
+ if (!this.isStorageUrl) {
39386
+ return;
39387
+ }
39388
+ return this.url.split("/").pop();
39389
+ }
39390
+ getUrl() {
39391
+ return this.url;
39392
+ }
39393
+ onLoad = async () => {
39394
+ this.shootBeforeLoadCallbacks();
39395
+ this.updateMbr();
39396
+ this.subject.publish(this);
39397
+ this.shootLoadCallbacks();
39398
+ };
39399
+ onError = (_error) => {
39400
+ this.updateMbr();
39401
+ this.subject.publish(this);
39402
+ this.shootLoadCallbacks();
39403
+ };
39404
+ updateMbr() {
39405
+ const { translateX, translateY, scaleX, scaleY } = this.transformation.matrix;
39406
+ this.left = translateX;
39407
+ this.top = translateY;
39408
+ this.right = this.left + conf.AUDIO_DIMENSIONS.width * scaleX;
39409
+ this.bottom = this.top + conf.AUDIO_DIMENSIONS.height * scaleY;
39410
+ }
39411
+ render(context) {
39412
+ if (this.transformationRenderBlock) {
39413
+ return;
39414
+ }
39415
+ const ctx = context.ctx;
39416
+ const radius = 12 * this.transformation.getScale().x;
39417
+ ctx.save();
39418
+ ctx.globalCompositeOperation = "destination-out";
39419
+ ctx.beginPath();
39420
+ ctx.moveTo(this.left + radius, this.top);
39421
+ ctx.lineTo(this.left + this.getWidth() - radius, this.top);
39422
+ ctx.quadraticCurveTo(this.left + this.getWidth(), this.top, this.left + this.getWidth(), this.top + radius);
39423
+ ctx.lineTo(this.left + this.getWidth(), this.top + this.getHeight() - radius);
39424
+ ctx.quadraticCurveTo(this.left + this.getWidth(), this.top + this.getHeight(), this.left + this.getWidth() - radius, this.top + this.getHeight());
39425
+ ctx.lineTo(this.left + radius, this.top + this.getHeight());
39426
+ ctx.quadraticCurveTo(this.left, this.top + this.getHeight(), this.left, this.top + this.getHeight() - radius);
39427
+ ctx.lineTo(this.left, this.top + radius);
39428
+ ctx.quadraticCurveTo(this.left, this.top, this.left + radius, this.top);
39429
+ ctx.closePath();
39430
+ ctx.fill();
39431
+ ctx.restore();
39432
+ }
39433
+ renderHTML(documentFactory) {
39434
+ const div = documentFactory.createElement("audio-item");
39435
+ const { translateX, translateY, scaleX, scaleY } = this.transformation.matrix;
39436
+ const transform = `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`;
39437
+ div.id = this.getId();
39438
+ div.style.width = `${conf.AUDIO_DIMENSIONS.width}px`;
39439
+ div.style.height = `${conf.AUDIO_DIMENSIONS.height}px`;
39440
+ div.style.transformOrigin = "top left";
39441
+ div.style.transform = transform;
39442
+ div.style.position = "absolute";
39443
+ div.setAttribute("audio-url", this.getUrl());
39444
+ if (this.extension) {
39445
+ div.setAttribute("extension", this.extension);
39446
+ }
39447
+ if (this.isStorageUrl) {
39448
+ div.setAttribute("is-storage-url", "true");
39449
+ }
39450
+ div.setAttribute("data-link-to", "");
39451
+ return div;
39452
+ }
39453
+ serialize() {
39454
+ return {
39455
+ itemType: "Audio",
39456
+ url: this.url,
39457
+ transformation: this.transformation.serialize(),
39458
+ isStorageUrl: this.isStorageUrl,
39459
+ extension: this.extension
39460
+ };
39461
+ }
39462
+ deserialize(data) {
39463
+ if (data.isStorageUrl) {
39464
+ this.isStorageUrl = data.isStorageUrl;
39465
+ }
39466
+ if (data.transformation) {
39467
+ this.transformation.deserialize(data.transformation);
39468
+ }
39469
+ if (data.url) {
39470
+ this.setUrl(data.url);
39471
+ }
39472
+ if (data.extension) {
39473
+ this.extension = data.extension;
39474
+ }
39475
+ return this;
39476
+ }
39477
+ apply(op) {
39478
+ switch (op.class) {
39479
+ case "Transformation":
39480
+ this.transformation.apply(op);
39481
+ break;
39482
+ case "LinkTo":
39483
+ this.linkTo.apply(op);
39484
+ break;
39485
+ case "Audio":
39486
+ if (op.method === "setUrl") {
39487
+ this.applyUrl(op.url);
39488
+ }
39489
+ this.subject.publish(this);
39490
+ break;
39491
+ }
39492
+ }
39493
+ emit(operation) {
39494
+ if (this.events) {
39495
+ const command = new AudioCommand([this], operation);
39496
+ command.apply();
39497
+ this.events.emit(operation, command);
39498
+ } else {
39499
+ this.apply(operation);
39500
+ }
39501
+ }
39502
+ setId(id) {
39503
+ this.id = id;
39504
+ this.transformation.setId(id);
39505
+ return this;
39506
+ }
39507
+ getId() {
39508
+ return this.id;
39509
+ }
39510
+ shootLoadCallbacks() {
39511
+ while (this.loadCallbacks.length > 0) {
39512
+ this.loadCallbacks.shift()(this);
39513
+ }
39514
+ }
39515
+ shootBeforeLoadCallbacks() {
39516
+ while (this.beforeLoadCallbacks.length > 0) {
39517
+ this.beforeLoadCallbacks.shift()(this);
39518
+ }
39519
+ }
39520
+ getPath() {
39521
+ const { left, top, right, bottom } = this.getMbr();
39522
+ const leftTop = new Point(left, top);
39523
+ const rightTop = new Point(right, top);
39524
+ const rightBottom = new Point(right, bottom);
39525
+ const leftBottom = new Point(left, bottom);
39526
+ return new Path([
39527
+ new Line(leftTop, rightTop),
39528
+ new Line(rightTop, rightBottom),
39529
+ new Line(rightBottom, leftBottom),
39530
+ new Line(leftBottom, leftTop)
39531
+ ], true);
39532
+ }
39533
+ getSnapAnchorPoints() {
39534
+ const mbr = this.getMbr();
39535
+ const width2 = mbr.getWidth();
39536
+ const height2 = mbr.getHeight();
39537
+ return [
39538
+ new Point(mbr.left + width2 / 2, mbr.top),
39539
+ new Point(mbr.left + width2 / 2, mbr.bottom),
39540
+ new Point(mbr.left, mbr.top + height2 / 2),
39541
+ new Point(mbr.right, mbr.top + height2 / 2)
39542
+ ];
39543
+ }
39544
+ isClosed() {
39545
+ return true;
39546
+ }
39547
+ getRichText() {
39548
+ return null;
39549
+ }
39550
+ getLinkTo() {
39551
+ return;
39552
+ }
39553
+ getExtension() {
39554
+ return this.extension;
39555
+ }
39556
+ download() {
39557
+ if (this.extension) {
39558
+ const linkElem = conf.documentFactory.createElement("a");
39559
+ linkElem.href = this.url;
39560
+ linkElem.setAttribute("download", `${this.board.getBoardId()}.${this.extension}`);
39561
+ linkElem.click();
39562
+ }
39563
+ }
39564
+ onRemove() {
39565
+ const storageId = this.getStorageId();
39566
+ if (storageId) {
39567
+ conf.hooks.beforeMediaRemove([storageId], this.board.getBoardId());
39568
+ }
39569
+ super.onRemove();
39570
+ }
39571
+ }
39572
+ // src/Items/Audio/AudioHelpers.ts
39573
+ var uploadAudioToStorage = async (hash, audioBlob, accessToken, boardId) => {
39574
+ return new Promise((resolve2, reject) => {
39575
+ fetch(`${window.location.origin}/api/v1/media/audio/${boardId}`, {
39576
+ method: "POST",
39577
+ headers: {
39578
+ "Content-Type": audioBlob.type,
39579
+ "x-audio-id": hash,
39580
+ Authorization: `Bearer ${accessToken}`
39581
+ },
39582
+ body: audioBlob
39583
+ }).then(async (response) => {
39584
+ if (response.status !== 200) {
39585
+ return conf.hooks.onUploadMediaError(response, "audio");
39586
+ }
39587
+ return response.json();
39588
+ }).then((data) => {
39589
+ console.log(data);
39590
+ resolve2(data.src);
39591
+ }).catch((error) => {
39592
+ console.error("Media storage error:", error);
39593
+ reject(error);
39594
+ });
39595
+ });
39596
+ };
39597
+ var prepareAudio = (file, accessToken, boardId) => {
39598
+ return new Promise((resolve2, reject) => {
39599
+ const audio = document.createElement("audio");
39600
+ audio.src = URL.createObjectURL(file);
39601
+ audio.onloadedmetadata = () => {
39602
+ fileTosha256(file).then((hash) => {
39603
+ uploadAudioToStorage(hash, file, accessToken, boardId).then((url) => {
39604
+ resolve2(url);
39605
+ }).catch(reject);
39606
+ }).catch(() => {
39607
+ reject(new Error("Failed to generate hash"));
39608
+ });
39609
+ };
39610
+ audio.onerror = () => {
39611
+ reject(new Error("Failed to load audio"));
39612
+ };
39613
+ });
39614
+ };
39615
+ var calculateAudioPosition = (board, audioItem) => {
39616
+ const cameraMbr = board.camera.getMbr();
39617
+ const cameraWidth = cameraMbr.getWidth();
39618
+ const translateX = cameraMbr.left + cameraWidth * 0.34;
39619
+ const translateY = cameraMbr.getCenter().y - audioItem.getHeight() / 2;
39620
+ const scale = cameraWidth * 0.32 / audioItem.getWidth();
39621
+ return new Matrix2(translateX, translateY, scale, scale);
39622
+ };
39450
39623
  // src/Items/Placeholder/Placeholder.ts
39451
39624
  var PlaceholderImg = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
39452
39625
  <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"/>
@@ -39995,331 +40168,11 @@ class ImageItem extends BaseItem {
39995
40168
  onRemove() {
39996
40169
  const storageId = this.getStorageId();
39997
40170
  if (storageId) {
39998
- deleteMedia([storageId], this.board.getBoardId());
39999
- }
40000
- super.onRemove();
40001
- }
40002
- }
40003
- // src/Items/Audio/Audio.ts
40004
- class AudioItem extends BaseItem {
40005
- events;
40006
- extension;
40007
- itemType = "Audio";
40008
- parent = "Board";
40009
- transformation;
40010
- linkTo;
40011
- subject = new Subject;
40012
- loadCallbacks = [];
40013
- beforeLoadCallbacks = [];
40014
- transformationRenderBlock = undefined;
40015
- url = "";
40016
- isPlaying = false;
40017
- currentTime = 0;
40018
- isStorageUrl = true;
40019
- constructor(board, isStorageUrl, url, events, id = "", extension2) {
40020
- super(board, id);
40021
- this.events = events;
40022
- this.extension = extension2;
40023
- this.linkTo = new LinkTo(this.id, events);
40024
- this.board = board;
40025
- this.isStorageUrl = isStorageUrl;
40026
- if (url) {
40027
- this.applyUrl(url);
40028
- }
40029
- this.transformation = new Transformation(id, events);
40030
- this.linkTo.subject.subscribe(() => {
40031
- this.updateMbr();
40032
- this.subject.publish(this);
40033
- });
40034
- this.transformation.subject.subscribe(this.onTransform);
40035
- this.right = this.left + conf.AUDIO_DIMENSIONS.width;
40036
- this.bottom = this.top + conf.AUDIO_DIMENSIONS.height;
40037
- this.shouldUseCustomRender = true;
40038
- }
40039
- setCurrentTime(time) {
40040
- this.currentTime = time;
40041
- }
40042
- getCurrentTime() {
40043
- return this.currentTime;
40044
- }
40045
- getIsStorageUrl() {
40046
- return this.isStorageUrl;
40047
- }
40048
- onTransform = () => {
40049
- this.updateMbr();
40050
- this.subject.publish(this);
40051
- };
40052
- doOnceBeforeOnLoad = (callback) => {
40053
- this.loadCallbacks.push(callback);
40054
- };
40055
- doOnceOnLoad = (callback) => {
40056
- this.loadCallbacks.push(callback);
40057
- };
40058
- setIsPlaying(isPlaying) {
40059
- this.isPlaying = isPlaying;
40060
- this.shouldRenderOutsideViewRect = isPlaying;
40061
- this.subject.publish(this);
40062
- }
40063
- getIsPlaying() {
40064
- return this.isPlaying;
40065
- }
40066
- applyUrl(url) {
40067
- if (this.isStorageUrl) {
40068
- try {
40069
- const newUrl = new URL(url);
40070
- this.url = `${window.location.origin}${newUrl.pathname}`;
40071
- } catch (_) {}
40072
- } else {
40073
- this.url = url;
40074
- }
40075
- }
40076
- setUrl(url) {
40077
- this.emit({
40078
- class: "Audio",
40079
- method: "setUrl",
40080
- item: [this.getId()],
40081
- url
40082
- });
40083
- }
40084
- getStorageId() {
40085
- if (!this.isStorageUrl) {
40086
- return;
40087
- }
40088
- return this.url.split("/").pop();
40089
- }
40090
- getUrl() {
40091
- return this.url;
40092
- }
40093
- onLoad = async () => {
40094
- this.shootBeforeLoadCallbacks();
40095
- this.updateMbr();
40096
- this.subject.publish(this);
40097
- this.shootLoadCallbacks();
40098
- };
40099
- onError = (_error) => {
40100
- this.updateMbr();
40101
- this.subject.publish(this);
40102
- this.shootLoadCallbacks();
40103
- };
40104
- updateMbr() {
40105
- const { translateX, translateY, scaleX, scaleY } = this.transformation.matrix;
40106
- this.left = translateX;
40107
- this.top = translateY;
40108
- this.right = this.left + conf.AUDIO_DIMENSIONS.width * scaleX;
40109
- this.bottom = this.top + conf.AUDIO_DIMENSIONS.height * scaleY;
40110
- }
40111
- render(context) {
40112
- if (this.transformationRenderBlock) {
40113
- return;
40114
- }
40115
- const ctx = context.ctx;
40116
- const radius = 12 * this.transformation.getScale().x;
40117
- ctx.save();
40118
- ctx.globalCompositeOperation = "destination-out";
40119
- ctx.beginPath();
40120
- ctx.moveTo(this.left + radius, this.top);
40121
- ctx.lineTo(this.left + this.getWidth() - radius, this.top);
40122
- ctx.quadraticCurveTo(this.left + this.getWidth(), this.top, this.left + this.getWidth(), this.top + radius);
40123
- ctx.lineTo(this.left + this.getWidth(), this.top + this.getHeight() - radius);
40124
- ctx.quadraticCurveTo(this.left + this.getWidth(), this.top + this.getHeight(), this.left + this.getWidth() - radius, this.top + this.getHeight());
40125
- ctx.lineTo(this.left + radius, this.top + this.getHeight());
40126
- ctx.quadraticCurveTo(this.left, this.top + this.getHeight(), this.left, this.top + this.getHeight() - radius);
40127
- ctx.lineTo(this.left, this.top + radius);
40128
- ctx.quadraticCurveTo(this.left, this.top, this.left + radius, this.top);
40129
- ctx.closePath();
40130
- ctx.fill();
40131
- ctx.restore();
40132
- }
40133
- renderHTML(documentFactory) {
40134
- const div = documentFactory.createElement("audio-item");
40135
- const { translateX, translateY, scaleX, scaleY } = this.transformation.matrix;
40136
- const transform = `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`;
40137
- div.id = this.getId();
40138
- div.style.width = `${conf.AUDIO_DIMENSIONS.width}px`;
40139
- div.style.height = `${conf.AUDIO_DIMENSIONS.height}px`;
40140
- div.style.transformOrigin = "top left";
40141
- div.style.transform = transform;
40142
- div.style.position = "absolute";
40143
- div.setAttribute("audio-url", this.getUrl());
40144
- if (this.extension) {
40145
- div.setAttribute("extension", this.extension);
40146
- }
40147
- if (this.isStorageUrl) {
40148
- div.setAttribute("is-storage-url", "true");
40149
- }
40150
- div.setAttribute("data-link-to", "");
40151
- return div;
40152
- }
40153
- serialize() {
40154
- return {
40155
- itemType: "Audio",
40156
- url: this.url,
40157
- transformation: this.transformation.serialize(),
40158
- isStorageUrl: this.isStorageUrl,
40159
- extension: this.extension
40160
- };
40161
- }
40162
- deserialize(data) {
40163
- if (data.isStorageUrl) {
40164
- this.isStorageUrl = data.isStorageUrl;
40165
- }
40166
- if (data.transformation) {
40167
- this.transformation.deserialize(data.transformation);
40168
- }
40169
- if (data.url) {
40170
- this.setUrl(data.url);
40171
- }
40172
- if (data.extension) {
40173
- this.extension = data.extension;
40174
- }
40175
- return this;
40176
- }
40177
- apply(op) {
40178
- switch (op.class) {
40179
- case "Transformation":
40180
- this.transformation.apply(op);
40181
- break;
40182
- case "LinkTo":
40183
- this.linkTo.apply(op);
40184
- break;
40185
- case "Audio":
40186
- if (op.method === "setUrl") {
40187
- this.applyUrl(op.url);
40188
- }
40189
- this.subject.publish(this);
40190
- break;
40191
- }
40192
- }
40193
- emit(operation) {
40194
- if (this.events) {
40195
- const command = new AudioCommand([this], operation);
40196
- command.apply();
40197
- this.events.emit(operation, command);
40198
- } else {
40199
- this.apply(operation);
40200
- }
40201
- }
40202
- setId(id) {
40203
- this.id = id;
40204
- this.transformation.setId(id);
40205
- return this;
40206
- }
40207
- getId() {
40208
- return this.id;
40209
- }
40210
- shootLoadCallbacks() {
40211
- while (this.loadCallbacks.length > 0) {
40212
- this.loadCallbacks.shift()(this);
40213
- }
40214
- }
40215
- shootBeforeLoadCallbacks() {
40216
- while (this.beforeLoadCallbacks.length > 0) {
40217
- this.beforeLoadCallbacks.shift()(this);
40218
- }
40219
- }
40220
- getPath() {
40221
- const { left, top, right, bottom } = this.getMbr();
40222
- const leftTop = new Point(left, top);
40223
- const rightTop = new Point(right, top);
40224
- const rightBottom = new Point(right, bottom);
40225
- const leftBottom = new Point(left, bottom);
40226
- return new Path([
40227
- new Line(leftTop, rightTop),
40228
- new Line(rightTop, rightBottom),
40229
- new Line(rightBottom, leftBottom),
40230
- new Line(leftBottom, leftTop)
40231
- ], true);
40232
- }
40233
- getSnapAnchorPoints() {
40234
- const mbr = this.getMbr();
40235
- const width2 = mbr.getWidth();
40236
- const height2 = mbr.getHeight();
40237
- return [
40238
- new Point(mbr.left + width2 / 2, mbr.top),
40239
- new Point(mbr.left + width2 / 2, mbr.bottom),
40240
- new Point(mbr.left, mbr.top + height2 / 2),
40241
- new Point(mbr.right, mbr.top + height2 / 2)
40242
- ];
40243
- }
40244
- isClosed() {
40245
- return true;
40246
- }
40247
- getRichText() {
40248
- return null;
40249
- }
40250
- getLinkTo() {
40251
- return;
40252
- }
40253
- getExtension() {
40254
- return this.extension;
40255
- }
40256
- download() {
40257
- if (this.extension) {
40258
- const linkElem = conf.documentFactory.createElement("a");
40259
- linkElem.href = this.url;
40260
- linkElem.setAttribute("download", `${this.board.getBoardId()}.${this.extension}`);
40261
- linkElem.click();
40262
- }
40263
- }
40264
- onRemove() {
40265
- const storageId = this.getStorageId();
40266
- if (storageId) {
40267
- deleteMedia([storageId], this.board.getBoardId());
40171
+ conf.hooks.beforeMediaRemove([storageId], this.board.getBoardId());
40268
40172
  }
40269
40173
  super.onRemove();
40270
40174
  }
40271
40175
  }
40272
- // src/Items/Audio/AudioHelpers.ts
40273
- var uploadAudioToStorage = async (hash, audioBlob, accessToken, boardId) => {
40274
- return new Promise((resolve2, reject) => {
40275
- fetch(`${window.location.origin}/api/v1/media/audio/${boardId}`, {
40276
- method: "POST",
40277
- headers: {
40278
- "Content-Type": audioBlob.type,
40279
- "x-audio-id": hash,
40280
- Authorization: `Bearer ${accessToken}`
40281
- },
40282
- body: audioBlob
40283
- }).then(async (response) => {
40284
- if (response.status !== 200) {
40285
- return catchErrorResponse(response, "audio");
40286
- }
40287
- return response.json();
40288
- }).then((data) => {
40289
- console.log(data);
40290
- resolve2(data.src);
40291
- }).catch((error) => {
40292
- console.error("Media storage error:", error);
40293
- reject(error);
40294
- });
40295
- });
40296
- };
40297
- var prepareAudio = (file, accessToken, boardId) => {
40298
- return new Promise((resolve2, reject) => {
40299
- const audio = document.createElement("audio");
40300
- audio.src = URL.createObjectURL(file);
40301
- audio.onloadedmetadata = () => {
40302
- fileTosha256(file).then((hash) => {
40303
- uploadAudioToStorage(hash, file, accessToken, boardId).then((url) => {
40304
- resolve2(url);
40305
- }).catch(reject);
40306
- }).catch(() => {
40307
- reject(new Error("Failed to generate hash"));
40308
- });
40309
- };
40310
- audio.onerror = () => {
40311
- reject(new Error("Failed to load audio"));
40312
- };
40313
- });
40314
- };
40315
- var calculateAudioPosition = (board, audioItem) => {
40316
- const cameraMbr = board.camera.getMbr();
40317
- const cameraWidth = cameraMbr.getWidth();
40318
- const translateX = cameraMbr.left + cameraWidth * 0.34;
40319
- const translateY = cameraMbr.getCenter().y - audioItem.getHeight() / 2;
40320
- const scale = cameraWidth * 0.32 / audioItem.getWidth();
40321
- return new Matrix2(translateX, translateY, scale, scale);
40322
- };
40323
40176
  // src/isSafari.ts
40324
40177
  function isSafari() {
40325
40178
  if (typeof navigator === "undefined") {
@@ -51734,7 +51587,7 @@ class BoardSelection {
51734
51587
  const connectors = itemIds.flatMap((id) => {
51735
51588
  return this.board.items.getLinkedConnectorsById(id);
51736
51589
  }).map((connector) => connector.getId());
51737
- deleteMedia(this.getMediaStorageIds(), this.board.getBoardId());
51590
+ conf.hooks.beforeMediaRemove(this.getMediaStorageIds(), this.board.getBoardId());
51738
51591
  this.emit({
51739
51592
  class: "Board",
51740
51593
  method: "remove",
@@ -51772,7 +51625,7 @@ class BoardSelection {
51772
51625
  }
51773
51626
  async duplicate() {
51774
51627
  const mediaIds = this.getMediaStorageIds();
51775
- const canDuplicate = mediaIds.length ? await updateMediaUsage(mediaIds, this.board.getBoardId()) : true;
51628
+ const canDuplicate = mediaIds.length ? await conf.hooks.beforeMediaUpload(mediaIds, this.board.getBoardId()) : true;
51776
51629
  if (!canDuplicate) {
51777
51630
  return;
51778
51631
  }
@@ -52653,7 +52506,7 @@ class Board {
52653
52506
  newMap[newItemId] = itemData;
52654
52507
  }
52655
52508
  if (shouldUpdateMediaUsage) {
52656
- const canDuplicate = mediaStorageIds.length ? await updateMediaUsage(mediaStorageIds, this.getBoardId()) : true;
52509
+ const canDuplicate = mediaStorageIds.length ? await conf.hooks.beforeMediaUpload(mediaStorageIds, this.getBoardId()) : true;
52657
52510
  if (!canDuplicate) {
52658
52511
  return;
52659
52512
  }
@@ -54966,7 +54819,7 @@ function handleAudioGenerate(response, board) {
54966
54819
  }
54967
54820
  function handleImageGenerate(response, board) {
54968
54821
  if (response.status === "completed" && response.base64) {
54969
- prepareImage(response.base64, account?.accessToken || null, board.getBoardId()).then((imageData) => {
54822
+ prepareImage(response.base64, null, board.getBoardId()).then((imageData) => {
54970
54823
  const placeholderId = board.aiImagePlaceholder?.getId();
54971
54824
  if (placeholderId) {
54972
54825
  const placeholderNode = board.items.getById(placeholderId);
@@ -55296,12 +55149,10 @@ function initI18N(i18nInstance) {
55296
55149
  }
55297
55150
  export {
55298
55151
  validateRichTextData,
55299
- validateMediaFile,
55300
55152
  validateItemsMap,
55301
55153
  uploadVideoToStorage,
55302
55154
  uploadToTheStorage,
55303
55155
  updateRects,
55304
- updateMediaUsage,
55305
55156
  translateElementBy,
55306
55157
  transformHtmlOrTextToMarkdown,
55307
55158
  toRelativePoint,
@@ -55350,7 +55201,6 @@ export {
55350
55201
  forceNumberIntoInterval,
55351
55202
  fileTosha256,
55352
55203
  exportBoardSnapshot,
55353
- deleteMedia,
55354
55204
  decodeHtml,
55355
55205
  defaultCursors as cursors,
55356
55206
  createVideoItem,
@@ -55358,8 +55208,6 @@ export {
55358
55208
  conf,
55359
55209
  checkHotkeys,
55360
55210
  catmullRomInterpolate,
55361
- catchErrorResponse,
55362
- catchDuplicateErrorResponse,
55363
55211
  captureFrame,
55364
55212
  calculatePosition,
55365
55213
  calculateAudioPosition,