2023-10-20 20:38:04 +08:00
|
|
|
import { UploadingPresentations } from '/imports/api/presentations';
|
2017-05-04 00:36:16 +08:00
|
|
|
import Auth from '/imports/ui/services/auth';
|
2020-04-30 00:34:37 +08:00
|
|
|
import logger from '/imports/startup/client/logger';
|
2023-02-22 01:57:34 +08:00
|
|
|
import { partition } from '/imports/utils/array-utils';
|
2022-08-03 04:25:15 +08:00
|
|
|
import update from 'immutability-helper';
|
|
|
|
import { Random } from 'meteor/random';
|
2022-06-25 00:47:17 +08:00
|
|
|
import Meetings from '/imports/api/meetings';
|
2023-02-23 22:23:51 +08:00
|
|
|
import { uniqueId } from '/imports/utils/string-utils';
|
2023-02-23 04:16:43 +08:00
|
|
|
import { isPresentationEnabled } from '/imports/ui/services/features';
|
2023-02-24 00:52:51 +08:00
|
|
|
import { notify } from '/imports/ui/services/notification';
|
2024-04-24 03:51:08 +08:00
|
|
|
import apolloContextHolder from '/imports/ui/core/graphql/apolloContextHolder/apolloContextHolder';
|
|
|
|
import { getPresentationUploadToken } from './queries';
|
|
|
|
import { requestPresentationUploadTokenMutation } from './mutation';
|
2024-06-12 23:16:00 +08:00
|
|
|
import useMeeting from '/imports/ui/core/hooks/useMeeting';
|
2017-05-04 00:36:16 +08:00
|
|
|
|
2018-09-05 00:56:10 +08:00
|
|
|
const TOKEN_TIMEOUT = 5000;
|
2023-10-12 02:41:20 +08:00
|
|
|
const POD_ID = 'DEFAULT_PRESENTATION_POD';
|
|
|
|
|
2019-02-21 06:20:04 +08:00
|
|
|
// fetch doesn't support progress. So we use xhr which support progress.
|
2017-09-08 02:18:14 +08:00
|
|
|
const futch = (url, opts = {}, onProgress) => new Promise((res, rej) => {
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
|
|
|
|
xhr.open(opts.method || 'get', url);
|
|
|
|
|
|
|
|
Object.keys(opts.headers || {})
|
2023-03-10 19:30:46 +08:00
|
|
|
.forEach((k) => xhr.setRequestHeader(k, opts.headers[k]));
|
2017-09-08 02:18:14 +08:00
|
|
|
|
|
|
|
xhr.onload = (e) => {
|
|
|
|
if (e.target.status !== 200) {
|
2023-03-10 19:30:46 +08:00
|
|
|
return rej(new Error({ code: e.target.status, message: e.target.statusText }));
|
2017-09-08 02:18:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return res(e.target.responseText);
|
|
|
|
};
|
|
|
|
xhr.onerror = rej;
|
|
|
|
if (xhr.upload && onProgress) {
|
|
|
|
xhr.upload.addEventListener('progress', onProgress, false);
|
|
|
|
}
|
|
|
|
xhr.send(opts.body);
|
|
|
|
});
|
|
|
|
|
2019-07-12 01:46:44 +08:00
|
|
|
const requestPresentationUploadToken = (
|
2022-08-22 20:21:33 +08:00
|
|
|
temporaryPresentationId,
|
2019-07-12 01:46:44 +08:00
|
|
|
meetingId,
|
|
|
|
filename,
|
|
|
|
) => new Promise((resolve, reject) => {
|
2024-04-24 03:51:08 +08:00
|
|
|
const client = apolloContextHolder.getClient();
|
|
|
|
client.mutate({
|
|
|
|
mutation: requestPresentationUploadTokenMutation,
|
|
|
|
variables: {
|
|
|
|
podId: POD_ID,
|
|
|
|
filename,
|
|
|
|
uploadTemporaryId: temporaryPresentationId,
|
|
|
|
},
|
|
|
|
});
|
2019-07-12 01:46:44 +08:00
|
|
|
|
|
|
|
const timeout = setTimeout(() => {
|
2023-03-10 19:30:46 +08:00
|
|
|
reject(new Error({ code: 408, message: 'requestPresentationUploadToken timeout' }));
|
2019-07-12 01:46:44 +08:00
|
|
|
}, TOKEN_TIMEOUT);
|
|
|
|
|
2024-04-24 03:51:08 +08:00
|
|
|
const getData = (n = 0) => {
|
|
|
|
if (n > 10) return;
|
|
|
|
let recursiveTimeout = null;
|
|
|
|
client.query({
|
|
|
|
query: getPresentationUploadToken,
|
|
|
|
variables: {
|
|
|
|
uploadTemporaryId: temporaryPresentationId,
|
|
|
|
},
|
|
|
|
fetchPolicy: 'network-only',
|
|
|
|
}).then((result) => {
|
|
|
|
if (result.data.pres_presentation_uploadToken.length > 0) {
|
|
|
|
clearTimeout(recursiveTimeout);
|
|
|
|
clearTimeout(timeout);
|
|
|
|
resolve(result.data.pres_presentation_uploadToken[0].uploadToken);
|
|
|
|
}
|
2018-09-05 00:56:10 +08:00
|
|
|
});
|
2024-04-24 03:51:08 +08:00
|
|
|
recursiveTimeout = setTimeout(() => {
|
|
|
|
getData(n + 1);
|
|
|
|
}, 1000);
|
|
|
|
};
|
|
|
|
setTimeout(getData, 100);
|
2019-07-12 01:46:44 +08:00
|
|
|
});
|
2018-09-05 00:56:10 +08:00
|
|
|
|
2019-07-12 01:46:44 +08:00
|
|
|
const uploadAndConvertPresentation = (
|
|
|
|
file,
|
|
|
|
downloadable,
|
|
|
|
meetingId,
|
|
|
|
endpoint,
|
|
|
|
onUpload,
|
|
|
|
onProgress,
|
|
|
|
onConversion,
|
2023-10-17 03:27:22 +08:00
|
|
|
current,
|
2019-07-12 01:46:44 +08:00
|
|
|
) => {
|
2023-10-17 03:27:22 +08:00
|
|
|
if (!file) return Promise.resolve();
|
|
|
|
|
2023-04-05 20:42:05 +08:00
|
|
|
const temporaryPresentationId = uniqueId(Random.id(20));
|
2022-04-08 22:28:47 +08:00
|
|
|
|
2017-06-07 20:28:41 +08:00
|
|
|
const data = new FormData();
|
2017-05-04 00:36:16 +08:00
|
|
|
data.append('fileUpload', file);
|
2018-09-05 00:56:10 +08:00
|
|
|
data.append('conference', meetingId);
|
|
|
|
data.append('room', meetingId);
|
2022-08-22 20:21:33 +08:00
|
|
|
data.append('temporaryPresentationId', temporaryPresentationId);
|
2018-09-05 00:56:10 +08:00
|
|
|
|
|
|
|
// TODO: Currently the uploader is not related to a POD so the id is fixed to the default
|
2023-10-12 02:41:20 +08:00
|
|
|
data.append('pod_id', POD_ID);
|
2018-09-05 00:56:10 +08:00
|
|
|
|
2019-02-23 05:04:35 +08:00
|
|
|
data.append('is_downloadable', downloadable);
|
2023-10-17 03:27:22 +08:00
|
|
|
data.append('current', current);
|
2017-05-04 00:36:16 +08:00
|
|
|
|
2017-09-08 02:18:14 +08:00
|
|
|
const opts = {
|
2017-05-04 00:36:16 +08:00
|
|
|
method: 'POST',
|
|
|
|
body: data,
|
2017-09-08 02:18:14 +08:00
|
|
|
};
|
|
|
|
|
2022-10-06 20:58:42 +08:00
|
|
|
// If the presentation is from sharedNotes I don't want to
|
2022-09-22 04:41:31 +08:00
|
|
|
// insert another one, I just need to update it.
|
|
|
|
UploadingPresentations.upsert({
|
2023-03-10 19:30:46 +08:00
|
|
|
filename: file.name,
|
|
|
|
lastModifiedUploader: false,
|
|
|
|
}, {
|
|
|
|
$set: {
|
|
|
|
temporaryPresentationId,
|
|
|
|
progress: 0,
|
2022-09-22 04:41:31 +08:00
|
|
|
filename: file.name,
|
2023-03-10 19:30:46 +08:00
|
|
|
lastModifiedUploader: true,
|
|
|
|
upload: {
|
|
|
|
done: false,
|
|
|
|
error: false,
|
|
|
|
},
|
|
|
|
uploadTimestamp: new Date(),
|
|
|
|
},
|
|
|
|
});
|
2022-08-03 04:25:15 +08:00
|
|
|
|
2023-10-12 02:41:20 +08:00
|
|
|
return requestPresentationUploadToken(temporaryPresentationId, meetingId, file.name)
|
2019-07-12 01:46:44 +08:00
|
|
|
.then((token) => {
|
2022-10-05 03:25:54 +08:00
|
|
|
UploadingPresentations.upsert({
|
2023-03-10 19:30:46 +08:00
|
|
|
temporaryPresentationId,
|
|
|
|
}, {
|
|
|
|
$set: {
|
|
|
|
id: token,
|
|
|
|
},
|
|
|
|
});
|
2022-08-03 04:25:15 +08:00
|
|
|
return futch(endpoint.replace('upload', `${token}/upload`), opts, (e) => {
|
2022-08-08 19:50:52 +08:00
|
|
|
onProgress(e);
|
2023-03-10 19:30:46 +08:00
|
|
|
const pr = (e.loaded / e.total) * 100;
|
|
|
|
if (pr !== 100) {
|
|
|
|
UploadingPresentations.upsert({ temporaryPresentationId }, { $set: { progress: pr } });
|
|
|
|
} else {
|
|
|
|
UploadingPresentations.upsert({ temporaryPresentationId }, {
|
|
|
|
$set: {
|
|
|
|
progress: pr,
|
|
|
|
upload: {
|
|
|
|
done: true,
|
|
|
|
error: false,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
2022-08-03 04:25:15 +08:00
|
|
|
});
|
2019-07-12 01:46:44 +08:00
|
|
|
})
|
2017-09-27 03:45:33 +08:00
|
|
|
// Trap the error so we can have parallel upload
|
|
|
|
.catch((error) => {
|
2020-04-30 00:34:37 +08:00
|
|
|
logger.debug({
|
|
|
|
logCode: 'presentation_uploader_service',
|
|
|
|
extraInfo: {
|
|
|
|
error,
|
|
|
|
},
|
|
|
|
}, 'Generic presentation upload exception catcher');
|
2017-11-28 01:44:45 +08:00
|
|
|
onUpload({ error: true, done: true, status: error.code });
|
|
|
|
return Promise.resolve();
|
2017-09-27 03:45:33 +08:00
|
|
|
});
|
2017-05-04 00:36:16 +08:00
|
|
|
};
|
|
|
|
|
2019-07-12 01:46:44 +08:00
|
|
|
const uploadAndConvertPresentations = (
|
|
|
|
presentationsToUpload,
|
|
|
|
meetingId,
|
|
|
|
uploadEndpoint,
|
2021-04-21 01:21:12 +08:00
|
|
|
) => Promise.all(presentationsToUpload.map((p) => uploadAndConvertPresentation(
|
2023-10-17 03:27:22 +08:00
|
|
|
p.file, p.downloadable, meetingId, uploadEndpoint,
|
|
|
|
p.onUpload, p.onProgress, p.onConversion, p.current,
|
2019-07-12 01:46:44 +08:00
|
|
|
)));
|
2017-05-04 04:51:17 +08:00
|
|
|
|
2019-07-12 01:46:44 +08:00
|
|
|
const removePresentations = (
|
|
|
|
presentationsToRemove,
|
2024-01-23 21:55:40 +08:00
|
|
|
removePresentation,
|
|
|
|
) => Promise.all(presentationsToRemove.map((p) => removePresentation(p.presentationId)));
|
2017-05-04 00:36:16 +08:00
|
|
|
|
2024-01-23 21:55:40 +08:00
|
|
|
const persistPresentationChanges = (
|
|
|
|
oldState,
|
|
|
|
newState,
|
|
|
|
uploadEndpoint,
|
|
|
|
setPresentation,
|
|
|
|
removePresentation,
|
|
|
|
) => {
|
2023-10-17 03:27:22 +08:00
|
|
|
const presentationsToUpload = newState.filter((p) => !p.uploadCompleted);
|
|
|
|
const presentationsToRemove = oldState.filter((p) => !newState.find((u) => { return u.presentationId === p.presentationId }));
|
2017-11-28 01:44:45 +08:00
|
|
|
|
2023-10-17 03:27:22 +08:00
|
|
|
let currentPresentation = newState.find((p) => p.current);
|
|
|
|
return uploadAndConvertPresentations(presentationsToUpload, Auth.meetingID, uploadEndpoint)
|
2017-11-28 01:44:45 +08:00
|
|
|
.then((presentations) => {
|
|
|
|
if (!presentations.length && !currentPresentation) return Promise.resolve();
|
|
|
|
|
|
|
|
// Update the presentation with their new ids
|
|
|
|
presentations.forEach((p, i) => {
|
|
|
|
if (p === undefined) return;
|
2023-10-17 03:27:22 +08:00
|
|
|
presentationsToUpload[i].onDone(p.presentationId);
|
2017-11-28 01:44:45 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
return Promise.resolve(presentations);
|
|
|
|
})
|
|
|
|
.then((presentations) => {
|
2017-12-16 02:49:47 +08:00
|
|
|
if (currentPresentation === undefined) {
|
2024-01-23 21:55:40 +08:00
|
|
|
setPresentation('');
|
2017-12-16 02:49:47 +08:00
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2017-11-28 01:44:45 +08:00
|
|
|
// If its a newly uploaded presentation we need to get it from promise result
|
2023-10-18 00:35:48 +08:00
|
|
|
if (currentPresentation?.uploadInProgress) {
|
2021-04-21 01:21:12 +08:00
|
|
|
const currentIndex = presentationsToUpload.findIndex((p) => p === currentPresentation);
|
2017-11-28 01:44:45 +08:00
|
|
|
currentPresentation = presentations[currentIndex];
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip setting as current if error happened
|
2023-10-17 03:27:22 +08:00
|
|
|
if (currentPresentation?.conversion?.error) {
|
2017-11-28 01:44:45 +08:00
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2024-01-23 21:55:40 +08:00
|
|
|
return setPresentation(currentPresentation?.presentationId);
|
2017-11-28 01:44:45 +08:00
|
|
|
})
|
2024-01-23 21:55:40 +08:00
|
|
|
.then(removePresentations.bind(null, presentationsToRemove, removePresentation));
|
2017-05-04 00:36:16 +08:00
|
|
|
};
|
|
|
|
|
2023-03-10 19:30:46 +08:00
|
|
|
const handleSavePresentation = (
|
2024-01-23 20:51:14 +08:00
|
|
|
presentations = [],
|
|
|
|
isFromPresentationUploaderInterface = true,
|
|
|
|
newPres = {},
|
|
|
|
currentPresentations = [],
|
|
|
|
setPresentation,
|
2024-01-23 21:55:40 +08:00
|
|
|
removePresentation,
|
2023-03-10 19:30:46 +08:00
|
|
|
) => {
|
2023-02-23 04:16:43 +08:00
|
|
|
if (!isPresentationEnabled()) {
|
2023-03-10 19:30:46 +08:00
|
|
|
return null;
|
2023-02-21 20:38:44 +08:00
|
|
|
}
|
2023-03-10 19:30:46 +08:00
|
|
|
|
2024-05-29 21:26:11 +08:00
|
|
|
const PRESENTATION_CONFIG = window.meetingClientSettings.public.presentation;
|
|
|
|
|
2022-08-08 19:50:52 +08:00
|
|
|
if (!isFromPresentationUploaderInterface) {
|
|
|
|
if (presentations.length === 0) {
|
|
|
|
presentations = [...currentPresentations];
|
|
|
|
}
|
2023-03-10 19:30:46 +08:00
|
|
|
presentations = presentations.map((p) => update(p, {
|
2023-10-17 03:27:22 +08:00
|
|
|
current: {
|
2023-03-10 19:30:46 +08:00
|
|
|
$set: false,
|
|
|
|
},
|
2022-08-08 19:50:52 +08:00
|
|
|
}));
|
2023-10-17 03:27:22 +08:00
|
|
|
newPres.current = true;
|
2022-08-08 19:50:52 +08:00
|
|
|
presentations.push(newPres);
|
|
|
|
}
|
|
|
|
return persistPresentationChanges(
|
2023-03-10 19:30:46 +08:00
|
|
|
currentPresentations,
|
|
|
|
presentations,
|
|
|
|
PRESENTATION_CONFIG.uploadEndpoint,
|
2024-01-23 20:51:14 +08:00
|
|
|
setPresentation,
|
2024-01-23 21:55:40 +08:00
|
|
|
removePresentation,
|
2023-03-10 19:30:46 +08:00
|
|
|
);
|
|
|
|
};
|
2022-08-08 19:50:52 +08:00
|
|
|
|
2022-06-25 00:47:17 +08:00
|
|
|
const getExternalUploadData = () => {
|
2024-03-26 19:57:28 +08:00
|
|
|
const {
|
|
|
|
presentationUploadExternalDescription,
|
|
|
|
presentationUploadExternalUrl,
|
|
|
|
} = Meetings.findOne(
|
2022-06-25 00:47:17 +08:00
|
|
|
{ meetingId: Auth.meetingID },
|
|
|
|
{
|
|
|
|
fields: {
|
2024-03-26 19:57:28 +08:00
|
|
|
presentationUploadExternalDescription: 1,
|
|
|
|
presentationUploadExternalUrl: 1,
|
2022-06-25 00:47:17 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
return {
|
2022-09-12 22:04:13 +08:00
|
|
|
presentationUploadExternalDescription,
|
|
|
|
presentationUploadExternalUrl,
|
2023-03-10 19:30:46 +08:00
|
|
|
};
|
2022-06-25 00:47:17 +08:00
|
|
|
};
|
|
|
|
|
2024-06-12 23:16:00 +08:00
|
|
|
const useExternalUploadData = () => {
|
|
|
|
const { data: meeting } = useMeeting((m) => ({
|
|
|
|
presentationUploadExternalDescription: m.presentationUploadExternalDescription,
|
|
|
|
presentationUploadExternalUrl: m.presentationUploadExternalUrl,
|
|
|
|
}));
|
|
|
|
|
|
|
|
const {
|
|
|
|
presentationUploadExternalDescription,
|
|
|
|
presentationUploadExternalUrl,
|
|
|
|
} = meeting || {};
|
|
|
|
|
|
|
|
return {
|
|
|
|
presentationUploadExternalDescription,
|
|
|
|
presentationUploadExternalUrl,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2023-02-24 00:52:51 +08:00
|
|
|
function handleFiledrop(files, files2, that, intl, intlMessages) {
|
2023-03-10 19:30:46 +08:00
|
|
|
if (that) {
|
|
|
|
const { fileValidMimeTypes } = that.props;
|
|
|
|
const { toUploadCount } = that.state;
|
|
|
|
const validMimes = fileValidMimeTypes.map((fileValid) => fileValid.mime);
|
|
|
|
const validExtentions = fileValidMimeTypes.map((fileValid) => fileValid.extension);
|
2023-04-05 20:42:05 +08:00
|
|
|
const [accepted, rejected] = partition(
|
2023-03-10 19:30:46 +08:00
|
|
|
files.concat(files2), (f) => (
|
|
|
|
validMimes.includes(f.type) || validExtentions.includes(`.${f.name.split('.').pop()}`)
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
const presentationsToUpload = accepted.map((file) => {
|
2023-04-05 20:42:05 +08:00
|
|
|
const id = uniqueId(file.name);
|
2023-03-10 19:30:46 +08:00
|
|
|
|
|
|
|
return {
|
|
|
|
file,
|
2023-10-17 03:27:22 +08:00
|
|
|
downloadable: false, // by default new presentations are set not to be downloadable
|
2023-03-10 19:30:46 +08:00
|
|
|
isRemovable: true,
|
2023-10-17 03:27:22 +08:00
|
|
|
presentationId: id,
|
|
|
|
name: file.name,
|
|
|
|
current: false,
|
2023-03-10 19:30:46 +08:00
|
|
|
conversion: { done: false, error: false },
|
|
|
|
upload: { done: false, error: false, progress: 0 },
|
|
|
|
exportation: { error: false },
|
|
|
|
onProgress: (event) => {
|
|
|
|
if (!event.lengthComputable) {
|
|
|
|
that.deepMergeUpdateFileKey(id, 'upload', {
|
|
|
|
progress: 100,
|
|
|
|
done: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2022-12-08 22:20:17 +08:00
|
|
|
|
|
|
|
that.deepMergeUpdateFileKey(id, 'upload', {
|
2023-03-10 19:30:46 +08:00
|
|
|
progress: (event.loaded / event.total) * 100,
|
|
|
|
done: event.loaded === event.total,
|
2022-12-08 22:20:17 +08:00
|
|
|
});
|
2023-03-10 19:30:46 +08:00
|
|
|
},
|
|
|
|
onConversion: (conversion) => {
|
|
|
|
that.deepMergeUpdateFileKey(id, 'conversion', conversion);
|
|
|
|
},
|
|
|
|
onUpload: (upload) => {
|
|
|
|
that.deepMergeUpdateFileKey(id, 'upload', upload);
|
|
|
|
},
|
|
|
|
onDone: (newId) => {
|
|
|
|
that.updateFileKey(id, 'id', newId);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
});
|
2022-12-08 22:20:17 +08:00
|
|
|
|
2023-03-10 19:30:46 +08:00
|
|
|
that.setState(({ presentations }) => ({
|
|
|
|
presentations: presentations.concat(presentationsToUpload),
|
|
|
|
toUploadCount: (toUploadCount + presentationsToUpload.length),
|
|
|
|
}), () => {
|
|
|
|
// after the state is set (files have been dropped),
|
|
|
|
// make the first of the new presentations current
|
|
|
|
if (presentationsToUpload && presentationsToUpload.length) {
|
2023-10-17 03:27:22 +08:00
|
|
|
that.handleCurrentChange(presentationsToUpload[0].presentationId);
|
2023-03-10 19:30:46 +08:00
|
|
|
}
|
|
|
|
});
|
2022-12-08 22:20:17 +08:00
|
|
|
|
2023-03-10 19:30:46 +08:00
|
|
|
if (rejected.length > 0) {
|
|
|
|
notify(intl.formatMessage(intlMessages.rejectedError), 'error');
|
2022-12-08 22:20:17 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-04 00:36:16 +08:00
|
|
|
export default {
|
2022-08-08 19:50:52 +08:00
|
|
|
handleSavePresentation,
|
2017-05-04 00:36:16 +08:00
|
|
|
persistPresentationChanges,
|
2022-03-08 23:29:11 +08:00
|
|
|
requestPresentationUploadToken,
|
2022-06-25 00:47:17 +08:00
|
|
|
getExternalUploadData,
|
2022-08-08 19:50:52 +08:00
|
|
|
uploadAndConvertPresentation,
|
2022-12-08 22:20:17 +08:00
|
|
|
handleFiledrop,
|
2024-06-12 23:16:00 +08:00
|
|
|
useExternalUploadData,
|
2017-05-04 00:36:16 +08:00
|
|
|
};
|