mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-24 20:07:51 +00:00
added preview mode
This commit is contained in:
parent
a41e5049ba
commit
52f7a3a640
@ -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');
|
@ -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
|
||||
}
|
||||
});
|
||||
|
||||
|
9
package-lock.json
generated
9
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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 };
|
||||
}
|
||||
|
@ -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){
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -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
|
||||
}}>
|
||||
|
||||
<Spacer height="30px" />
|
||||
<AppsDevModeHome availableQapps={availableQapps} setMode={setMode} myApp={null} myWebsite={null} />
|
||||
<AppsDevModeHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={null} myWebsite={null} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@ -315,7 +348,7 @@ export const AppsDevMode = ({ mode, setMode, show , myName, goToHome, setDesktop
|
||||
}}>
|
||||
|
||||
<Spacer height="30px" />
|
||||
<AppsDevModeHome availableQapps={availableQapps} setMode={setMode} myApp={null} myWebsite={null} />
|
||||
<AppsDevModeHome myName={myName} availableQapps={availableQapps} setMode={setMode} myApp={null} myWebsite={null} />
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
@ -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();
|
||||
@ -83,6 +108,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 (
|
||||
<>
|
||||
<AppsContainer
|
||||
@ -118,7 +307,39 @@ export const AppsDevModeHome = ({
|
||||
<AppCircle>
|
||||
<Add>+</Add>
|
||||
</AppCircle>
|
||||
<AppCircleLabel>App</AppCircleLabel>
|
||||
<AppCircleLabel>Server</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
addPreviewApp();
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? "10px" : "5px",
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
<Add>+</Add>
|
||||
</AppCircle>
|
||||
<AppCircleLabel>Zip</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
addPreviewAppWithDirectory();
|
||||
}}
|
||||
>
|
||||
<AppCircleContainer
|
||||
sx={{
|
||||
gap: !isMobile ? "10px" : "5px",
|
||||
}}
|
||||
>
|
||||
<AppCircle>
|
||||
<Add>+</Add>
|
||||
</AppCircle>
|
||||
<AppCircleLabel>Directory</AppCircleLabel>
|
||||
</AppCircleContainer>
|
||||
</ButtonBase>
|
||||
</AppsContainer>
|
||||
|
@ -189,9 +189,14 @@ export const AppsDevModeNavBar = () => {
|
||||
|
||||
<ButtonBase
|
||||
onClick={(e) => {
|
||||
if(selectedTab?.refreshFunc){
|
||||
selectedTab.refreshFunc(selectedTab?.tabId)
|
||||
} else {
|
||||
executeEvent("refreshApp", {
|
||||
tabId: selectedTab?.tabId,
|
||||
});
|
||||
}
|
||||
|
||||
}}
|
||||
>
|
||||
<RefreshIcon
|
||||
|
@ -396,6 +396,7 @@ isDOMContentLoaded: false
|
||||
setInfoSnackCustom } = useContext(MyContext);
|
||||
|
||||
|
||||
|
||||
useEffect(()=> {
|
||||
if(tabId && !isNaN(history?.currentIndex)){
|
||||
setHasSettingsChangedAtom((prev)=> {
|
||||
@ -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
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user