mirror of
https://github.com/Qortal/qortal-mobile.git
synced 2025-04-28 13:57:52 +00:00
changed logic for file save qortalrequest
This commit is contained in:
parent
72281c7fad
commit
d57ab6e9d3
12
src/App.tsx
12
src/App.tsx
@ -716,10 +716,8 @@ function App() {
|
|||||||
message?.isFromExtension
|
message?.isFromExtension
|
||||||
) {
|
) {
|
||||||
qortalRequestPermissonFromExtension(message, event);
|
qortalRequestPermissonFromExtension(message, event);
|
||||||
} else if(message?.action === 'SHOW_SAVE_FILE_PICKER'){
|
}
|
||||||
showSaveFilePicker(message?.payload)
|
else if(message?.action === 'getFileFromIndexedDB'){
|
||||||
|
|
||||||
} else if(message?.action === 'getFileFromIndexedDB'){
|
|
||||||
handleGetFileFromIndexedDB(event);
|
handleGetFileFromIndexedDB(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1521,7 +1519,11 @@ function App() {
|
|||||||
show,
|
show,
|
||||||
message,
|
message,
|
||||||
rootHeight,
|
rootHeight,
|
||||||
showInfo
|
showInfo,
|
||||||
|
openSnackGlobal: openSnack,
|
||||||
|
setOpenSnackGlobal: setOpenSnack,
|
||||||
|
infoSnackCustom: infoSnack,
|
||||||
|
setInfoSnackCustom: setInfoSnack
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
|
@ -1,19 +1,60 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { executeEvent } from '../../utils/events';
|
import { executeEvent } from '../../utils/events';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
import { navigationControllerAtom } from '../../atoms/global';
|
import { navigationControllerAtom } from '../../atoms/global';
|
||||||
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
|
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
|
||||||
import { Browser } from '@capacitor/browser';
|
import { Browser } from '@capacitor/browser';
|
||||||
|
import { saveFile } from '../../qortalRequests/get';
|
||||||
|
import { mimeToExtensionMap } from '../../utils/memeTypes';
|
||||||
|
import { MyContext } from '../../App';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const saveFileInChunks = async (blob: Blob, fileName: string, chunkSize = 1024 * 1024) => {
|
export const saveFileInChunks = async (
|
||||||
const base64Prefix = 'data:video/mp4;base64,';
|
blob: Blob,
|
||||||
|
fileName: string,
|
||||||
|
chunkSize = 1024 * 1024
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
let isFirstChunk = true;
|
let isFirstChunk = true;
|
||||||
const fullFileName = fileName + Date.now() + '.mp4'
|
|
||||||
|
// Extract the MIME type from the blob
|
||||||
|
const mimeType = blob.type || 'application/octet-stream';
|
||||||
|
|
||||||
|
// Create the dynamic base64 prefix
|
||||||
|
const base64Prefix = `data:${mimeType};base64,`;
|
||||||
|
|
||||||
|
// Function to extract extension from fileName
|
||||||
|
const getExtensionFromFileName = (name: string): string => {
|
||||||
|
const lastDotIndex = name.lastIndexOf('.');
|
||||||
|
if (lastDotIndex !== -1) {
|
||||||
|
return name.substring(lastDotIndex); // includes the dot
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract existing extension from fileName
|
||||||
|
const existingExtension = getExtensionFromFileName(fileName);
|
||||||
|
|
||||||
|
// Remove existing extension from fileName to avoid duplication
|
||||||
|
if (existingExtension) {
|
||||||
|
fileName = fileName.substring(0, fileName.lastIndexOf('.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map MIME type to file extension
|
||||||
|
const mimeTypeToExtension = (mimeType: string): string => {
|
||||||
|
|
||||||
|
return mimeToExtensionMap[mimeType] || existingExtension || ''; // Use existing extension if MIME type not found
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine the final extension to use
|
||||||
|
const extension = mimeTypeToExtension(mimeType);
|
||||||
|
|
||||||
|
// Construct the full file name with timestamp and extension
|
||||||
|
const fullFileName = `${fileName}_${Date.now()}${extension}`;
|
||||||
|
|
||||||
// Read the blob in chunks
|
// Read the blob in chunks
|
||||||
while (offset < blob.size) {
|
while (offset < blob.size) {
|
||||||
// Extract the current chunk
|
// Extract the current chunk
|
||||||
@ -28,7 +69,7 @@ export const saveFileInChunks = async (blob: Blob, fileName: string, chunkSize =
|
|||||||
data: isFirstChunk ? base64Prefix + base64Chunk : base64Chunk,
|
data: isFirstChunk ? base64Prefix + base64Chunk : base64Chunk,
|
||||||
directory: Directory.Documents,
|
directory: Directory.Documents,
|
||||||
recursive: true,
|
recursive: true,
|
||||||
append: !isFirstChunk // Append after the first chunk
|
append: !isFirstChunk, // Append after the first chunk
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update offset and flag
|
// Update offset and flag
|
||||||
@ -36,13 +77,14 @@ export const saveFileInChunks = async (blob: Blob, fileName: string, chunkSize =
|
|||||||
isFirstChunk = false;
|
isFirstChunk = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("File saved successfully in chunks:", fileName);
|
console.log('File saved successfully in chunks:', fullFileName);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving file in chunks:", error);
|
console.error('Error saving file in chunks:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Helper function to convert a Blob to a Base64 string
|
// Helper function to convert a Blob to a Base64 string
|
||||||
const blobToBase64 = (blob: Blob): Promise<string> => {
|
const blobToBase64 = (blob: Blob): Promise<string> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -220,19 +262,42 @@ const UIQortalRequests = [
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const showSaveFilePicker = async (data) => {
|
export const showSaveFilePicker = async (data, {openSnackGlobal,
|
||||||
let blob;
|
setOpenSnackGlobal,
|
||||||
let fileName;
|
infoSnackCustom,
|
||||||
|
setInfoSnackCustom}) => {
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { filename, mimeType, fileId } = data;
|
const { filename, mimeType, blob } = data;
|
||||||
|
|
||||||
// Retrieve file from IndexedDB or any other source
|
setInfoSnackCustom({
|
||||||
blob = await retrieveFileFromIndexedDB(fileId);
|
type: "info",
|
||||||
fileName = filename;
|
message:
|
||||||
|
"Saving file...",
|
||||||
|
});
|
||||||
|
|
||||||
await saveFileInChunks(blob, fileName)
|
|
||||||
|
setOpenSnackGlobal(true);
|
||||||
|
|
||||||
|
await saveFileInChunks(blob, filename)
|
||||||
|
setInfoSnackCustom({
|
||||||
|
type: "success",
|
||||||
|
message:
|
||||||
|
"Saving file success!",
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
setOpenSnackGlobal(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setInfoSnackCustom({
|
||||||
|
type: "error",
|
||||||
|
message:
|
||||||
|
error?.message ? `Error saving file: ${error?.message}` : 'Error saving file',
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
setOpenSnackGlobal(true);
|
||||||
console.error("Error saving file:", error);
|
console.error("Error saving file:", error);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -323,6 +388,10 @@ currentIndex: -1,
|
|||||||
isDOMContentLoaded: false
|
isDOMContentLoaded: false
|
||||||
})
|
})
|
||||||
const setHasSettingsChangedAtom = useSetRecoilState(navigationControllerAtom);
|
const setHasSettingsChangedAtom = useSetRecoilState(navigationControllerAtom);
|
||||||
|
const { openSnackGlobal,
|
||||||
|
setOpenSnackGlobal,
|
||||||
|
infoSnackCustom,
|
||||||
|
setInfoSnackCustom } = useContext(MyContext);
|
||||||
|
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(()=> {
|
||||||
@ -391,10 +460,23 @@ isDOMContentLoaded: false
|
|||||||
{ action: event.data.action, type: 'qortalRequest', payload: event.data, isExtension: true },
|
{ action: event.data.action, type: 'qortalRequest', payload: event.data, isExtension: true },
|
||||||
event.ports[0]
|
event.ports[0]
|
||||||
);
|
);
|
||||||
|
} else if(event?.data?.action === 'SAVE_FILE'
|
||||||
|
){
|
||||||
|
try {
|
||||||
|
const res = await saveFile( event.data, null, true, {
|
||||||
|
openSnackGlobal,
|
||||||
|
setOpenSnackGlobal,
|
||||||
|
infoSnackCustom,
|
||||||
|
setInfoSnackCustom
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
} else if (
|
} else if (
|
||||||
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
|
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
|
||||||
event?.data?.action === 'PUBLISH_QDN_RESOURCE' ||
|
event?.data?.action === 'PUBLISH_QDN_RESOURCE' ||
|
||||||
event?.data?.action === 'ENCRYPT_DATA' || event?.data?.action === 'SAVE_FILE'
|
event?.data?.action === 'ENCRYPT_DATA'
|
||||||
|
|
||||||
) {
|
) {
|
||||||
let data;
|
let data;
|
||||||
|
@ -303,25 +303,7 @@ function setLocalStorage(key, data) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "SAVE_FILE": {
|
|
||||||
try {
|
|
||||||
const res = await saveFile(request.payload, event.source, isFromExtension);
|
|
||||||
event.source.postMessage({
|
|
||||||
requestId: request.requestId,
|
|
||||||
action: request.action,
|
|
||||||
payload: res,
|
|
||||||
type: "backgroundMessageResponse",
|
|
||||||
}, event.origin);
|
|
||||||
} catch (error) {
|
|
||||||
event.source.postMessage({
|
|
||||||
requestId: request.requestId,
|
|
||||||
action: request.action,
|
|
||||||
error: error.message,
|
|
||||||
type: "backgroundMessageResponse",
|
|
||||||
}, event.origin);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "DEPLOY_AT": {
|
case "DEPLOY_AT": {
|
||||||
try {
|
try {
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
isUsingLocal
|
isUsingLocal
|
||||||
} from "../background";
|
} from "../background";
|
||||||
import { getNameInfo } from "../backgroundFunctions/encryption";
|
import { getNameInfo } from "../backgroundFunctions/encryption";
|
||||||
|
import { showSaveFilePicker } from "../components/Apps/useQortalMessageListener";
|
||||||
import { QORT_DECIMALS } from "../constants/constants";
|
import { QORT_DECIMALS } from "../constants/constants";
|
||||||
import Base58 from "../deps/Base58";
|
import Base58 from "../deps/Base58";
|
||||||
import {
|
import {
|
||||||
@ -223,12 +224,12 @@ function getFileFromContentScript(fileId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function sendToSaveFilePicker(data) {
|
// function sendToSaveFilePicker(data) {
|
||||||
window.postMessage({
|
// window.postMessage({
|
||||||
action: "SHOW_SAVE_FILE_PICKER",
|
// action: "SHOW_SAVE_FILE_PICKER",
|
||||||
payload: data,
|
// payload: data,
|
||||||
}, "*");
|
// }, "*");
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
const responseResolvers = new Map();
|
const responseResolvers = new Map();
|
||||||
@ -1149,9 +1150,9 @@ export const joinGroup = async (data, isFromExtension) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const saveFile = async (data, sender, isFromExtension) => {
|
export const saveFile = async (data, sender, isFromExtension, snackMethods) => {
|
||||||
try {
|
try {
|
||||||
const requiredFields = ["filename", "fileId"];
|
const requiredFields = ['filename', 'blob']
|
||||||
const missingFields: string[] = [];
|
const missingFields: string[] = [];
|
||||||
requiredFields.forEach((field) => {
|
requiredFields.forEach((field) => {
|
||||||
if (!data[field]) {
|
if (!data[field]) {
|
||||||
@ -1194,15 +1195,20 @@ export const saveFile = async (data, sender, isFromExtension) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
sendToSaveFilePicker(
|
|
||||||
{
|
showSaveFilePicker( {
|
||||||
filename,
|
filename,
|
||||||
mimeType,
|
mimeType,
|
||||||
blob,
|
blob
|
||||||
fileId,
|
}, snackMethods)
|
||||||
fileHandleOptions,
|
// sendToSaveFilePicker(
|
||||||
}
|
// {
|
||||||
);
|
// filename,
|
||||||
|
// mimeType,
|
||||||
|
// blob,
|
||||||
|
// fileId
|
||||||
|
// }
|
||||||
|
// );
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
throw new Error("User declined to save file");
|
throw new Error("User declined to save file");
|
||||||
|
@ -12,10 +12,13 @@ export const mimeToExtensionMap = {
|
|||||||
"application/vnd.oasis.opendocument.presentation": ".odp",
|
"application/vnd.oasis.opendocument.presentation": ".odp",
|
||||||
"text/plain": ".txt",
|
"text/plain": ".txt",
|
||||||
"text/csv": ".csv",
|
"text/csv": ".csv",
|
||||||
"text/html": ".html",
|
|
||||||
"application/xhtml+xml": ".xhtml",
|
"application/xhtml+xml": ".xhtml",
|
||||||
"application/xml": ".xml",
|
"application/xml": ".xml",
|
||||||
"application/json": ".json",
|
"application/rtf": ".rtf",
|
||||||
|
"application/vnd.apple.pages": ".pages",
|
||||||
|
"application/vnd.google-apps.document": ".gdoc",
|
||||||
|
"application/vnd.google-apps.spreadsheet": ".gsheet",
|
||||||
|
"application/vnd.google-apps.presentation": ".gslides",
|
||||||
|
|
||||||
// Images
|
// Images
|
||||||
"image/jpeg": ".jpg",
|
"image/jpeg": ".jpg",
|
||||||
@ -25,6 +28,11 @@ export const mimeToExtensionMap = {
|
|||||||
"image/svg+xml": ".svg",
|
"image/svg+xml": ".svg",
|
||||||
"image/tiff": ".tif",
|
"image/tiff": ".tif",
|
||||||
"image/bmp": ".bmp",
|
"image/bmp": ".bmp",
|
||||||
|
"image/x-icon": ".ico",
|
||||||
|
"image/heic": ".heic",
|
||||||
|
"image/heif": ".heif",
|
||||||
|
"image/apng": ".apng",
|
||||||
|
"image/avif": ".avif",
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
"audio/mpeg": ".mp3",
|
"audio/mpeg": ".mp3",
|
||||||
@ -32,6 +40,11 @@ export const mimeToExtensionMap = {
|
|||||||
"audio/wav": ".wav",
|
"audio/wav": ".wav",
|
||||||
"audio/webm": ".weba",
|
"audio/webm": ".weba",
|
||||||
"audio/aac": ".aac",
|
"audio/aac": ".aac",
|
||||||
|
"audio/flac": ".flac",
|
||||||
|
"audio/x-m4a": ".m4a",
|
||||||
|
"audio/x-ms-wma": ".wma",
|
||||||
|
"audio/midi": ".midi",
|
||||||
|
"audio/x-midi": ".mid",
|
||||||
|
|
||||||
// Video
|
// Video
|
||||||
"video/mp4": ".mp4",
|
"video/mp4": ".mp4",
|
||||||
@ -45,6 +58,7 @@ export const mimeToExtensionMap = {
|
|||||||
"video/3gpp2": ".3g2",
|
"video/3gpp2": ".3g2",
|
||||||
"video/x-matroska": ".mkv",
|
"video/x-matroska": ".mkv",
|
||||||
"video/x-flv": ".flv",
|
"video/x-flv": ".flv",
|
||||||
|
"video/x-ms-asf": ".asf",
|
||||||
|
|
||||||
// Archives
|
// Archives
|
||||||
"application/zip": ".zip",
|
"application/zip": ".zip",
|
||||||
@ -53,4 +67,57 @@ export const mimeToExtensionMap = {
|
|||||||
"application/x-7z-compressed": ".7z",
|
"application/x-7z-compressed": ".7z",
|
||||||
"application/x-gzip": ".gz",
|
"application/x-gzip": ".gz",
|
||||||
"application/x-bzip2": ".bz2",
|
"application/x-bzip2": ".bz2",
|
||||||
}
|
"application/x-apple-diskimage": ".dmg",
|
||||||
|
"application/vnd.android.package-archive": ".apk",
|
||||||
|
"application/x-iso9660-image": ".iso",
|
||||||
|
|
||||||
|
// Code Files
|
||||||
|
"text/javascript": ".js",
|
||||||
|
"text/css": ".css",
|
||||||
|
"text/html": ".html",
|
||||||
|
"application/json": ".json",
|
||||||
|
"text/xml": ".xml",
|
||||||
|
"application/x-sh": ".sh",
|
||||||
|
"application/x-csh": ".csh",
|
||||||
|
"text/x-python": ".py",
|
||||||
|
"text/x-java-source": ".java",
|
||||||
|
"application/java-archive": ".jar",
|
||||||
|
"application/vnd.microsoft.portable-executable": ".exe",
|
||||||
|
"application/x-msdownload": ".msi",
|
||||||
|
"text/x-c": ".c",
|
||||||
|
"text/x-c++": ".cpp",
|
||||||
|
"text/x-go": ".go",
|
||||||
|
"application/x-perl": ".pl",
|
||||||
|
"text/x-php": ".php",
|
||||||
|
"text/x-ruby": ".rb",
|
||||||
|
"text/x-sql": ".sql",
|
||||||
|
"application/x-httpd-php": ".php",
|
||||||
|
"application/x-python-code": ".pyc",
|
||||||
|
|
||||||
|
// ROM Files
|
||||||
|
"application/x-nintendo-nes-rom": ".nes",
|
||||||
|
"application/x-snes-rom": ".smc",
|
||||||
|
"application/x-gameboy-rom": ".gb",
|
||||||
|
"application/x-gameboy-advance-rom": ".gba",
|
||||||
|
"application/x-n64-rom": ".n64",
|
||||||
|
"application/x-sega-genesis-rom": ".gen",
|
||||||
|
"application/x-sega-master-system-rom": ".sms",
|
||||||
|
"application/x-psx-rom": ".iso", // PlayStation ROMs
|
||||||
|
"application/x-bios-rom": ".rom",
|
||||||
|
"application/x-flash-rom": ".bin",
|
||||||
|
"application/x-eeprom": ".eep",
|
||||||
|
"application/x-c64-rom": ".prg",
|
||||||
|
|
||||||
|
// Miscellaneous
|
||||||
|
"application/octet-stream": ".bin", // General binary files
|
||||||
|
"application/x-shockwave-flash": ".swf",
|
||||||
|
"application/x-silverlight-app": ".xap",
|
||||||
|
"application/x-ms-shortcut": ".lnk",
|
||||||
|
"application/vnd.ms-fontobject": ".eot",
|
||||||
|
"font/woff": ".woff",
|
||||||
|
"font/woff2": ".woff2",
|
||||||
|
"font/ttf": ".ttf",
|
||||||
|
"font/otf": ".otf",
|
||||||
|
"application/vnd.visio": ".vsd",
|
||||||
|
"application/vnd.ms-project": ".mpp",
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user