diff --git a/electron/src/preload.ts b/electron/src/preload.ts
index f3e42a9..ff9f42d 100644
--- a/electron/src/preload.ts
+++ b/electron/src/preload.ts
@@ -14,7 +14,11 @@ contextBridge.exposeInMainWorld('electronAPI', {
contextBridge.exposeInMainWorld('electron', {
onUpdateAvailable: (callback) => ipcRenderer.on('update_available', callback),
onUpdateDownloaded: (callback) => ipcRenderer.on('update_downloaded', callback),
- restartApp: () => ipcRenderer.send('restart_app')
+ restartApp: () => ipcRenderer.send('restart_app'),
+ selectFile: async () => ipcRenderer.invoke('dialog:openFile'),
+ readFile: async (filePath) => ipcRenderer.invoke('fs:readFile', filePath),
+ selectAndZipDirectory: async (filePath) => ipcRenderer.invoke('fs:selectAndZip', filePath),
+
});
ipcRenderer.send('test-ipc');
\ No newline at end of file
diff --git a/electron/src/setup.ts b/electron/src/setup.ts
index 38ca57a..c9a1bcf 100644
--- a/electron/src/setup.ts
+++ b/electron/src/setup.ts
@@ -6,13 +6,15 @@ import {
} from '@capacitor-community/electron';
import chokidar from 'chokidar';
import type { MenuItemConstructorOptions } from 'electron';
-import { app, BrowserWindow, Menu, MenuItem, nativeImage, Tray, session, ipcMain } from 'electron';
+import { app, BrowserWindow, Menu, MenuItem, nativeImage, Tray, session, ipcMain, dialog } from 'electron';
import electronIsDev from 'electron-is-dev';
import electronServe from 'electron-serve';
import windowStateKeeper from 'electron-window-state';
+const AdmZip = require('adm-zip');
import { join } from 'path';
import { myCapacitorApp } from '.';
-
+const fs = require('fs');
+const path = require('path')
const defaultDomains = [
'capacitor-electron://-',
@@ -361,3 +363,70 @@ ipcMain.on('set-allowed-domains', (event, domains: string[]) => {
});
+ipcMain.handle('dialog:openFile', async () => {
+ const result = await dialog.showOpenDialog({
+ properties: ['openFile'],
+ filters: [
+ { name: 'ZIP Files', extensions: ['zip'] } // Restrict to ZIP files
+ ],
+ });
+ return result.filePaths[0];
+});
+
+ipcMain.handle('fs:readFile', async (_, filePath) => {
+ try {
+ // Ensure the file exists
+ if (!fs.existsSync(filePath)) {
+ throw new Error('File does not exist.');
+ }
+
+ // Ensure the filePath is an absolute path (optional but recommended for safety)
+ const absolutePath = path.resolve(filePath);
+
+ // Read the file as a Buffer
+ const fileBuffer = fs.readFileSync(absolutePath);
+
+ return fileBuffer
+
+ } catch (error) {
+ console.error('Error reading file:', error.message);
+ return null; // Return null on error
+ }
+});
+
+ipcMain.handle('fs:selectAndZip', async (_, path) => {
+ let directoryPath = path
+ if(!directoryPath){
+ const { canceled, filePaths } = await dialog.showOpenDialog({
+ properties: ['openDirectory'],
+ });
+ if (canceled || filePaths.length === 0) {
+ console.log('No directory selected');
+ return null;
+}
+
+ directoryPath = filePaths[0];
+ }
+
+
+
+
+
+
+try {
+
+ // Add the entire directory to the zip
+ const zip = new AdmZip();
+
+ // Add the entire directory to the zip
+ zip.addLocalFolder(directoryPath);
+
+ // Generate the zip file as a buffer
+ const zipBuffer = zip.toBuffer();
+
+ return {buffer: zipBuffer, directoryPath}
+} catch (error) {
+ return null
+}
+});
+
diff --git a/package-lock.json b/package-lock.json
index bd6bd42..0fb4089 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -41,6 +41,7 @@
"@tiptap/starter-kit": "^2.5.9",
"@transistorsoft/capacitor-background-fetch": "^6.0.1",
"@types/chrome": "^0.0.263",
+ "adm-zip": "^0.5.16",
"asmcrypto.js": "2.3.2",
"axios": "^1.7.7",
"bcryptjs": "2.4.3",
@@ -4830,6 +4831,14 @@
"node": ">=0.4.0"
}
},
+ "node_modules/adm-zip": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
+ "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
+ "engines": {
+ "node": ">=12.0"
+ }
+ },
"node_modules/agent-base": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
diff --git a/package.json b/package.json
index 0cf5df4..e36681e 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"@tiptap/starter-kit": "^2.5.9",
"@transistorsoft/capacitor-background-fetch": "^6.0.1",
"@types/chrome": "^0.0.263",
+ "adm-zip": "^0.5.16",
"asmcrypto.js": "2.3.2",
"axios": "^1.7.7",
"bcryptjs": "2.4.3",
diff --git a/src/chatComputePow.worker.js b/src/chatComputePow.worker.js
index 00f5f72..f42908e 100644
--- a/src/chatComputePow.worker.js
+++ b/src/chatComputePow.worker.js
@@ -78,9 +78,7 @@ async function computePow(chatBytes, difficulty) {
workBufferPtr = sbrk(workBufferLength);
}
- console.log('Starting POW computation...');
const nonce = compute(hashPtr, workBufferPtr, workBufferLength, difficulty);
- console.log('POW computation finished.');
return { nonce, chatBytesArray };
}
diff --git a/src/components/Apps/AppViewer.tsx b/src/components/Apps/AppViewer.tsx
index e27810e..6b55ed0 100644
--- a/src/components/Apps/AppViewer.tsx
+++ b/src/components/Apps/AppViewer.tsx
@@ -15,31 +15,44 @@ export const AppViewer = React.forwardRef(({ app , hide, isDevMode}, iframeRef)
const { rootHeight } = useContext(MyContext);
// const iframeRef = useRef(null);
const { document, window: frameWindow } = useFrame();
- const {path, history, changeCurrentIndex} = useQortalMessageListener(frameWindow, iframeRef, app?.tabId, isDevMode, app?.name, app?.service)
+ const {path, history, changeCurrentIndex, resetHistory} = useQortalMessageListener(frameWindow, iframeRef, app?.tabId, isDevMode, app?.name, app?.service)
const [url, setUrl] = useState('')
+
useEffect(()=> {
+ if(app?.isPreview) return
if(isDevMode){
setUrl(app?.url)
return
}
setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? `/${app?.path}` : ''}?theme=dark&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}`)
- }, [app?.service, app?.name, app?.identifier, app?.path])
+ }, [app?.service, app?.name, app?.identifier, app?.path, app?.isPreview])
+
+ useEffect(()=> {
+ if(app?.isPreview && app?.url){
+ resetHistory()
+ setUrl(app.url)
+ }
+ }, [app?.url, app?.isPreview])
const defaultUrl = useMemo(()=> {
return url
}, [url, isDevMode])
-
const refreshAppFunc = (e) => {
const {tabId} = e.detail
if(tabId === app?.tabId){
if(isDevMode){
- setUrl(app?.url + `?time=${Date.now()}`)
+
+ resetHistory()
+ if(!app?.isPreview){
+ setUrl(app?.url + `?time=${Date.now()}`)
+ }
return
}
+
const constructUrl = `${getBaseApiReact()}/render/${app?.service}/${app?.name}${path != null ? path : ''}?theme=dark&identifier=${app?.identifier != null ? app?.identifier : ''}&time=${new Date().getMilliseconds()}`
setUrl(constructUrl)
}
@@ -123,7 +136,10 @@ export const AppViewer = React.forwardRef(({ app , hide, isDevMode}, iframeRef)
try {
await navigationPromise;
} catch (error) {
-
+ if(isDevMode){
+ setUrl(`${url}${previousPath != null ? previousPath : ''}?theme=dark&time=${new Date().getMilliseconds()}&isManualNavigation=false`)
+ return
+ }
setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${previousPath != null ? previousPath : ''}?theme=dark&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}&time=${new Date().getMilliseconds()}&isManualNavigation=false`)
// iframeRef.current.contentWindow.location.href = previousPath; // Fallback URL update
}
diff --git a/src/components/Apps/AppsDevMode.tsx b/src/components/Apps/AppsDevMode.tsx
index 08782f0..297db33 100644
--- a/src/components/Apps/AppsDevMode.tsx
+++ b/src/components/Apps/AppsDevMode.tsx
@@ -3,6 +3,7 @@ import { AppsDevModeHome } from "./AppsDevModeHome";
import { Spacer } from "../../common/Spacer";
import { MyContext, getBaseApiReact } from "../../App";
import { AppInfo } from "./AppInfo";
+
import {
executeEvent,
subscribeToEvent,
@@ -113,6 +114,38 @@ export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktop
unsubscribeFromEvent("appsDevModeAddTab", addTabFunc);
};
}, [tabs]);
+
+ const updateTabFunc = (e) => {
+ const data = e.detail?.data;
+ if(!data.tabId) return
+ const findIndexTab = tabs.findIndex((tab)=> tab?.tabId === data?.tabId)
+ if(findIndexTab === -1) return
+ const copyTabs = [...tabs]
+ const newTab ={
+ ...copyTabs[findIndexTab],
+ url: data.url
+
+ }
+ copyTabs[findIndexTab] = newTab
+
+ setTabs(copyTabs);
+ setSelectedTab(newTab);
+ setMode("viewer");
+
+ setIsNewTabWindow(false);
+ };
+
+
+
+ useEffect(() => {
+ subscribeToEvent("appsDevModeUpdateTab", updateTabFunc);
+
+ return () => {
+ unsubscribeFromEvent("appsDevModeUpdateTab", updateTabFunc);
+ };
+ }, [tabs]);
+
+
const setSelectedTabFunc = (e) => {
const data = e.detail?.data;
if(!e.detail?.isDevMode) return
@@ -281,7 +314,7 @@ export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktop
}}>
-
+
)}
@@ -315,7 +348,7 @@ export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktop
}}>
-
+
>
)}
diff --git a/src/components/Apps/AppsDevModeHome.tsx b/src/components/Apps/AppsDevModeHome.tsx
index b1c53c1..db1bab2 100644
--- a/src/components/Apps/AppsDevModeHome.tsx
+++ b/src/components/Apps/AppsDevModeHome.tsx
@@ -7,6 +7,8 @@ import {
AppsContainer,
AppsParent,
} from "./Apps-styles";
+import {Buffer} from 'buffer'
+
import {
Avatar,
Box,
@@ -24,9 +26,8 @@ import { MyContext, getBaseApiReact, isMobile } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { executeEvent } from "../../utils/events";
import { Spacer } from "../../common/Spacer";
-import { AppsDevModeSortablePinnedApps } from "./AppsDevModeSortablePinnedApps";
import { useModal } from "../../common/useModal";
-import { isUsingLocal } from "../../background";
+import { createEndpoint, isUsingLocal } from "../../background";
import { Label } from "../Group/AddGroup";
export const AppsDevModeHome = ({
@@ -34,10 +35,13 @@ export const AppsDevModeHome = ({
myApp,
myWebsite,
availableQapps,
+ myName
}) => {
const [domain, setDomain] = useState("127.0.0.1");
const [port, setPort] = useState("");
+ const [selectedPreviewFile, setSelectedPreviewFile] = useState(null);
+
const { isShow, onCancel, onOk, show, message } = useModal();
const {
openSnackGlobal,
@@ -46,6 +50,27 @@ export const AppsDevModeHome = ({
setInfoSnackCustom,
} = useContext(MyContext);
+ const handleSelectFile = async (existingFilePath) => {
+ const filePath = existingFilePath || await window.electron.selectFile();
+ if (filePath) {
+
+ const content = await window.electron.readFile(filePath);
+ return {buffer: content, filePath}
+ } else {
+ console.log('No file selected.');
+ }
+ };
+ const handleSelectDirectry = async (existingDirectoryPath) => {
+ const {buffer, directoryPath} = await window.electron.selectAndZipDirectory(existingDirectoryPath);
+ if (buffer) {
+
+
+ return {buffer, directoryPath}
+ } else {
+ console.log('No file selected.');
+ }
+ };
+
const addDevModeApp = async () => {
try {
const usingLocal = await isUsingLocal();
@@ -82,6 +107,170 @@ export const AppsDevModeHome = ({
});
} catch (error) {}
};
+
+ const addPreviewApp = async (isRefresh, existingFilePath, tabId) => {
+ try {
+ const usingLocal = await isUsingLocal();
+ if (!usingLocal) {
+ setOpenSnackGlobal(true);
+
+ setInfoSnackCustom({
+ type: "error",
+ message:
+ "Please use your local node for dev mode! Logout and use Local node.",
+ });
+ return;
+ }
+ if (!myName) {
+ setOpenSnackGlobal(true);
+
+ setInfoSnackCustom({
+ type: "error",
+ message:
+ "You need a name to use preview",
+ });
+ return;
+ }
+
+
+ const {buffer, filePath} = await handleSelectFile(existingFilePath)
+
+ if (!buffer) {
+ setOpenSnackGlobal(true);
+
+ setInfoSnackCustom({
+ type: "error",
+ message:
+ "Please select a file",
+ });
+ return;
+ }
+ const postBody = Buffer.from(buffer).toString('base64')
+
+ const endpoint = await createEndpoint(`/arbitrary/APP/${myName}/zip?preview=true`)
+ const response = await fetch(
+ endpoint
+ ,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "text/plain",
+ },
+ body: postBody,
+ }
+ );
+ if(!response?.ok) throw new Error('Invalid zip')
+ const previewPath = await response.text();
+ if(tabId){
+ executeEvent("appsDevModeUpdateTab", {
+ data: {
+ url: "http://127.0.0.1:12391" + previewPath,
+ isPreview: true,
+ filePath,
+ refreshFunc: (tabId)=> {
+ addPreviewApp(true, filePath, tabId)
+ },
+ tabId
+ },
+ });
+ return
+ }
+ executeEvent("appsDevModeAddTab", {
+ data: {
+ url: "http://127.0.0.1:12391" + previewPath,
+ isPreview: true,
+ filePath,
+ refreshFunc: (tabId)=> {
+ addPreviewApp(true, filePath, tabId)
+ }
+ },
+ });
+ } catch (error) {
+ console.error(error)
+ }
+ };
+
+ const addPreviewAppWithDirectory = async (isRefresh, existingDir, tabId) => {
+ try {
+ const usingLocal = await isUsingLocal();
+ if (!usingLocal) {
+ setOpenSnackGlobal(true);
+
+ setInfoSnackCustom({
+ type: "error",
+ message:
+ "Please use your local node for dev mode! Logout and use Local node.",
+ });
+ return;
+ }
+ if (!myName) {
+ setOpenSnackGlobal(true);
+
+ setInfoSnackCustom({
+ type: "error",
+ message:
+ "You need a name to use preview",
+ });
+ return;
+ }
+
+
+ const {buffer, directoryPath} = await handleSelectDirectry(existingDir)
+
+ if (!buffer) {
+ setOpenSnackGlobal(true);
+
+ setInfoSnackCustom({
+ type: "error",
+ message:
+ "Please select a file",
+ });
+ return;
+ }
+ const postBody = Buffer.from(buffer).toString('base64')
+
+ const endpoint = await createEndpoint(`/arbitrary/APP/${myName}/zip?preview=true`)
+ const response = await fetch(
+ endpoint
+ ,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "text/plain",
+ },
+ body: postBody,
+ }
+ );
+ if(!response?.ok) throw new Error('Invalid zip')
+ const previewPath = await response.text();
+ if(tabId){
+ executeEvent("appsDevModeUpdateTab", {
+ data: {
+ url: "http://127.0.0.1:12391" + previewPath,
+ isPreview: true,
+ directoryPath,
+ refreshFunc: (tabId)=> {
+ addPreviewAppWithDirectory(true, directoryPath, tabId)
+ },
+ tabId
+ },
+ });
+ return
+ }
+ executeEvent("appsDevModeAddTab", {
+ data: {
+ url: "http://127.0.0.1:12391" + previewPath,
+ isPreview: true,
+ directoryPath,
+ refreshFunc: (tabId)=> {
+ addPreviewAppWithDirectory(true, directoryPath, tabId)
+ }
+ },
+ });
+ } catch (error) {
+ console.error(error)
+ }
+ };
return (
<>
@@ -118,7 +307,39 @@ export const AppsDevModeHome = ({
+
- App
+ Server
+
+
+ {
+ addPreviewApp();
+ }}
+ >
+
+
+ +
+
+ Zip
+
+
+ {
+ addPreviewAppWithDirectory();
+ }}
+ >
+
+
+ +
+
+ Directory
diff --git a/src/components/Apps/AppsDevModeNavBar.tsx b/src/components/Apps/AppsDevModeNavBar.tsx
index 6908504..983aef3 100644
--- a/src/components/Apps/AppsDevModeNavBar.tsx
+++ b/src/components/Apps/AppsDevModeNavBar.tsx
@@ -189,9 +189,14 @@ export const AppsDevModeNavBar = () => {
{
- executeEvent("refreshApp", {
- tabId: selectedTab?.tabId,
- });
+ if(selectedTab?.refreshFunc){
+ selectedTab.refreshFunc(selectedTab?.tabId)
+ } else {
+ executeEvent("refreshApp", {
+ tabId: selectedTab?.tabId,
+ });
+ }
+
}}
>
{
if(tabId && !isNaN(history?.currentIndex)){
@@ -509,7 +510,7 @@ isDOMContentLoaded: false
event?.data?.action === 'QDN_RESOURCE_DISPLAYED'){
const pathUrl = event?.data?.path != null ? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path : null
setPath(pathUrl)
- if(appName.toLowerCase() === 'q-mail'){
+ if(appName?.toLowerCase() === 'q-mail'){
window.sendMessage("addEnteredQmailTimestamp").catch((error) => {
// error
});