Skip to content

Commit af10a59

Browse files
committed
feat(texteditor)[image]: add storage validation before upload
1 parent b4aa20b commit af10a59

3 files changed

Lines changed: 41 additions & 8 deletions

File tree

contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/TipTapEditorStrings.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,10 @@ const MESSAGES = {
321321
message: 'Failed to process the image file.',
322322
context: 'Error message when image processing fails',
323323
},
324+
noEnoughStorageSpace: {
325+
message: 'Not enough storage space available. File size exceeds remaining storage.',
326+
context: 'Error message when there is insufficient storage space for the file',
327+
},
324328

325329
// for MobileFormattingBar
326330
collapseFormattingBar: {

contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/components/image/ImageUploadModal.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@
144144

145145
<script>
146146
147-
import { defineComponent, ref, computed, onMounted, inject } from 'vue';
147+
import { defineComponent, ref, computed, onMounted, inject, getCurrentInstance } from 'vue';
148148
import { useFocusTrap } from '../../composables/useFocusTrap';
149149
import { getTipTapEditorStrings } from '../../TipTapEditorStrings';
150150
import ImageDropZone from './ImageDropZone.vue';
@@ -198,6 +198,9 @@
198198
const imageProcessor = inject('imageProcessor', {});
199199
const { processFile, ACCEPTED_MIME_TYPES } = imageProcessor;
200200
201+
const instance = getCurrentInstance();
202+
const store = instance.proxy.$store;
203+
201204
onMounted(() => {
202205
originalData.value = { ...props.initialData };
203206
previewSrc.value = props.initialData.src || '';
@@ -223,7 +226,7 @@
223226
if (!selectedFile) return;
224227
modalState.value = 'loading';
225228
try {
226-
const { src, file: processedFile } = await processFile(selectedFile);
229+
const { src, file: processedFile } = await processFile(selectedFile, { $store: store });
227230
previewSrc.value = src;
228231
file.value = processedFile;
229232
modalState.value = 'preview';

contentcuration/contentcuration/frontend/shared/views/TipTapEditor/TipTapEditor/services/imageService.js

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,30 +11,47 @@ const MAX_FILE_SIZE_MB = 10;
1111
// see: shared/leUtils/FormatPresets.js
1212
const ACCEPTED_MIME_TYPES = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/svg+xml'];
1313

14-
const { noFileProvided$, invalidFileType$, fileTooLarge$, fileSizeUnit$, failedToProcessImage$ } =
15-
getTipTapEditorStrings();
14+
const {
15+
noFileProvided$,
16+
invalidFileType$,
17+
fileTooLarge$,
18+
fileSizeUnit$,
19+
failedToProcessImage$,
20+
noEnoughStorageSpace$,
21+
} = getTipTapEditorStrings();
1622

1723
/**
18-
* Validates a file based on type and size.
24+
* Validates a file based on type, size, and available storage.
1925
* @param {File} file - The file to validate.
26+
* @param {number} availableSpace - Available storage space in bytes.
2027
* @returns {{isValid: boolean, error?: string}}
2128
*/
22-
function validateFile(file) {
29+
function validateFile(file, availableSpace = null) {
2330
if (!file) {
2431
return { isValid: false, error: noFileProvided$() };
2532
}
33+
2634
if (!ACCEPTED_MIME_TYPES.includes(file.type)) {
2735
return {
2836
isValid: false,
2937
error: invalidFileType$() + ACCEPTED_MIME_TYPES.join(', '),
3038
};
3139
}
40+
3241
if (file.size > MAX_FILE_SIZE_MB * 1024 * 1024) {
3342
return {
3443
isValid: false,
3544
error: fileTooLarge$() + MAX_FILE_SIZE_MB + fileSizeUnit$(),
3645
};
3746
}
47+
48+
if (availableSpace !== null && file.size > availableSpace) {
49+
return {
50+
isValid: false,
51+
error: noEnoughStorageSpace$(),
52+
};
53+
}
54+
3855
return { isValid: true };
3956
}
4057

@@ -62,10 +79,19 @@ function uploadFileToStorage({ file_format, mightSkip, checksum, file, url, cont
6279
* Main file processing function that uploads to server.
6380
* Adapted from uploadFile action but simplified for image-specific use.
6481
* @param {File} file - The image file to process.
82+
* @param {Object} context - Vue component context with $store access.
6583
* @returns {Promise<{src: string, width: number, height: number, file: File, id: string}>}
6684
*/
67-
async function processFile(file) {
68-
const validation = validateFile(file);
85+
async function processFile(file, context = null) {
86+
let availableSpace = null;
87+
88+
// Get available space if context is provided
89+
if (context && context.$store) {
90+
await context.$store.dispatch('fetchUserStorage');
91+
availableSpace = context.$store.getters.availableSpace;
92+
}
93+
94+
const validation = validateFile(file, availableSpace);
6995
if (!validation.isValid) {
7096
throw new Error(validation.error);
7197
}

0 commit comments

Comments
 (0)