import {$t} from "./i18n.ts"; import * as util from "./util.ts"; export type UploadWidget = { clear: () => void; close: () => void; }; export type UploadFunction = ( $file_input: JQuery, night: boolean | null, icon: boolean, ) => void; const default_max_file_size = 5; // These formats do not need to be universally understood by clients; they are all // converted, server-side, currently to PNGs. This list should be kept in sync with // the THUMBNAIL_ACCEPT_IMAGE_TYPES in zerver/lib/thumbnail.py const supported_types = [ "image/avif", "image/gif", "image/heic", "image/jpeg", "image/png", "image/tiff", "image/webp", ]; function is_image_format(file: File): boolean { const type = file.type; if (!type) { return false; } return supported_types.includes(type); } export function build_widget( // function returns a jQuery file input object get_file_input: () => JQuery, // jQuery object to show file name $file_name_field: JQuery, // jQuery object for error text $input_error: JQuery, // jQuery button to clear last upload choice $clear_button: JQuery, // jQuery button to open file dialog $upload_button: JQuery, $preview_text?: JQuery, $preview_image?: JQuery, max_file_upload_size = default_max_file_size, ): UploadWidget { function accept(file: File): void { $file_name_field.text(file.name); $input_error.hide(); $clear_button.show(); $upload_button.hide(); if ($preview_text !== undefined && $preview_image !== undefined) { const image_blob = URL.createObjectURL(file); $preview_image.attr("src", image_blob); $preview_image.addClass("upload_widget_image_preview"); $preview_text.show(); } } function clear(): void { const $control = get_file_input(); $control.val(""); $file_name_field.text(""); $clear_button.hide(); $upload_button.show(); if ($preview_text !== undefined) { $preview_text.hide(); } } $clear_button.on("click", (e) => { clear(); e.preventDefault(); }); $upload_button.on("drop", (e) => { const files = e.originalEvent?.dataTransfer?.files; if (files === null || files === undefined || files.length === 0) { return false; } util.the(get_file_input()).files = files; e.preventDefault(); return false; }); get_file_input().attr("accept", supported_types.toString()); get_file_input().on("change", (e) => { if (e.target.files?.[0] === undefined) { $input_error.hide(); } else if (e.target.files.length === 1) { const file = e.target.files[0]; if (file.size > max_file_upload_size * 1024 * 1024) { $input_error.text( $t( {defaultMessage: "File size must be at most {max_file_size} MiB."}, {max_file_size: max_file_upload_size}, ), ); $input_error.show(); clear(); } else if (!is_image_format(file)) { $input_error.text($t({defaultMessage: "File type is not supported."})); $input_error.show(); clear(); } else { accept(file); } } else { $input_error.text($t({defaultMessage: "Please just upload one file."})); } }); $upload_button.on("click", (e) => { get_file_input().trigger("click"); e.preventDefault(); }); function close(): void { clear(); $clear_button.off("click"); $upload_button.off("drop"); get_file_input().off("change"); $upload_button.off("click"); } return { // Call back to clear() in situations like adding bots, when // we want to use the same widget over and over again. clear, // Call back to close() when you are truly done with the widget, // so you can release handlers. close, }; } export function build_direct_upload_widget( // function returns a jQuery file input object get_file_input: () => JQuery, // jQuery object for error text $input_error: JQuery, // jQuery button to open file dialog $upload_button: JQuery, upload_function: UploadFunction, max_file_upload_size: number, ): void { // default value of max uploaded file size function accept(): void { $input_error.hide(); const $realm_logo_section = $upload_button.closest(".image_upload_widget"); if ($realm_logo_section.attr("id") === "realm-night-logo-upload-widget") { upload_function(get_file_input(), true, false); } else if ($realm_logo_section.attr("id") === "realm-day-logo-upload-widget") { upload_function(get_file_input(), false, false); } else { upload_function(get_file_input(), null, true); } } function clear(): void { const $control = get_file_input(); $control.val(""); } $upload_button.on("drop", (e) => { const files = e.originalEvent?.dataTransfer?.files; if (files === null || files === undefined || files.length === 0) { return false; } util.the(get_file_input()).files = files; e.preventDefault(); return false; }); get_file_input().attr("accept", supported_types.toString()); get_file_input().on("change", (e) => { if (e.target.files?.[0] === undefined) { $input_error.hide(); } else if (e.target.files.length === 1) { const file = e.target.files[0]; if (file.size > max_file_upload_size * 1024 * 1024) { $input_error.text( $t( {defaultMessage: "File size must be at most {max_file_size} MiB."}, {max_file_size: max_file_upload_size}, ), ); $input_error.show(); clear(); } else if (!is_image_format(file)) { $input_error.text($t({defaultMessage: "File type is not supported."})); $input_error.show(); clear(); } else { accept(); } } else { $input_error.text($t({defaultMessage: "Please just upload one file."})); } }); $upload_button.on("click", (e) => { get_file_input().trigger("click"); e.preventDefault(); }); }