plusui-native 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/index.js +21 -24
- package/templates/base/README.md.template +26 -4
- package/templates/react/frontend/src/App.tsx +89 -3
- package/templates/react/frontend/src/plusui.ts +47 -0
- package/templates/react/frontend/src/styles/app.css +188 -0
- package/templates/react/main.cpp.template +23 -0
- package/templates/react/package.json.template +2 -2
- package/templates/solid/frontend/src/App.tsx +91 -2
- package/templates/solid/frontend/src/plusui.ts +47 -0
- package/templates/solid/frontend/src/styles/app.css +188 -0
- package/templates/solid/main.cpp.template +23 -0
- package/templates/solid/package.json.template +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plusui-native",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "PlusUI CLI - Build C++ desktop apps modern UI ",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"semver": "^7.6.0",
|
|
28
28
|
"which": "^4.0.0",
|
|
29
29
|
"execa": "^8.0.1",
|
|
30
|
-
"plusui-native-builder": "^0.1.
|
|
31
|
-
"plusui-native-bindgen": "^0.1.
|
|
30
|
+
"plusui-native-builder": "^0.1.10",
|
|
31
|
+
"plusui-native-bindgen": "^0.1.10"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"plusui-native-bindgen": "^0.1.
|
|
34
|
+
"plusui-native-bindgen": "^0.1.10"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { mkdir, readFile, stat, rm, readdir, writeFile, copyFile } from 'fs/promises';
|
|
4
4
|
import { existsSync, watch, statSync, mkdirSync } from 'fs';
|
|
@@ -198,8 +198,8 @@ function runCMake(args, options = {}) {
|
|
|
198
198
|
function getAppBindgenPaths() {
|
|
199
199
|
return {
|
|
200
200
|
featuresDir: join(process.cwd(), 'src', 'features'),
|
|
201
|
-
outputDir: join(process.cwd(), 'src', '
|
|
202
|
-
frontendOutputDir: join(process.cwd(), 'frontend', 'src', '
|
|
201
|
+
outputDir: join(process.cwd(), 'src', 'Bindings'),
|
|
202
|
+
frontendOutputDir: join(process.cwd(), 'frontend', 'src', 'Bindings'),
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -261,11 +261,20 @@ function ensureBuildLayout() {
|
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
function getDevBuildDir() {
|
|
265
|
+
const platformFolder = PLATFORMS[process.platform]?.folder || 'Windows';
|
|
266
|
+
return join('.plusui', 'dev', platformFolder);
|
|
267
|
+
}
|
|
268
|
+
|
|
264
269
|
function resolveBindgenScriptPath() {
|
|
265
270
|
const candidates = [
|
|
271
|
+
resolve(__dirname, '../../plusui-bindgen/src/advanced-bindgen.js'),
|
|
266
272
|
resolve(__dirname, '../../plusui-bindgen/src/index.js'),
|
|
273
|
+
resolve(__dirname, '../../plusui-native-bindgen/src/advanced-bindgen.js'),
|
|
267
274
|
resolve(__dirname, '../../plusui-native-bindgen/src/index.js'),
|
|
275
|
+
resolve(__dirname, '../../../plusui-native-bindgen/src/advanced-bindgen.js'),
|
|
268
276
|
resolve(__dirname, '../../../plusui-native-bindgen/src/index.js'),
|
|
277
|
+
resolve(process.cwd(), 'node_modules', 'plusui-native-bindgen', 'src', 'advanced-bindgen.js'),
|
|
269
278
|
resolve(process.cwd(), 'node_modules', 'plusui-native-bindgen', 'src', 'index.js'),
|
|
270
279
|
];
|
|
271
280
|
|
|
@@ -556,8 +565,7 @@ async function startBackend() {
|
|
|
556
565
|
const projectName = getProjectName();
|
|
557
566
|
killProcessByName(projectName);
|
|
558
567
|
|
|
559
|
-
const
|
|
560
|
-
const buildDir = join('build', platformFolder, 'dev');
|
|
568
|
+
const buildDir = getDevBuildDir();
|
|
561
569
|
|
|
562
570
|
// Configure with dev mode if not configured
|
|
563
571
|
if (!existsSync(join(buildDir, 'CMakeCache.txt'))) {
|
|
@@ -733,8 +741,7 @@ function devBackend() {
|
|
|
733
741
|
const projectName = getProjectName();
|
|
734
742
|
killProcessByName(projectName);
|
|
735
743
|
|
|
736
|
-
const
|
|
737
|
-
const buildDir = join('build', platformFolder, 'dev');
|
|
744
|
+
const buildDir = getDevBuildDir();
|
|
738
745
|
|
|
739
746
|
if (!existsSync(join(buildDir, 'CMakeCache.txt'))) {
|
|
740
747
|
log('Configuring CMake...', 'blue');
|
|
@@ -841,7 +848,7 @@ async function clean() {
|
|
|
841
848
|
ensureProjectRoot('clean');
|
|
842
849
|
logSection('Cleaning Build Artifacts');
|
|
843
850
|
|
|
844
|
-
const dirs = ['build', 'frontend/dist'];
|
|
851
|
+
const dirs = ['build', '.plusui', 'frontend/dist'];
|
|
845
852
|
|
|
846
853
|
for (const dir of dirs) {
|
|
847
854
|
if (existsSync(dir)) {
|
|
@@ -881,22 +888,12 @@ async function runBindgen(providedArgs = null, options = {}) {
|
|
|
881
888
|
let defaultFrontendOutputDir = null;
|
|
882
889
|
|
|
883
890
|
if (bindgenArgs.length === 0) {
|
|
884
|
-
const {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
defaultFrontendOutputDir = frontendOutputDir;
|
|
891
|
-
log(`App mode: ${appFeaturesDir} -> ${appOutputDir}`, 'dim');
|
|
892
|
-
} else {
|
|
893
|
-
if (skipIfNoInput) {
|
|
894
|
-
log(`No src/features folder found; skipping binding refresh for ${source}.`, 'dim');
|
|
895
|
-
return;
|
|
896
|
-
} else {
|
|
897
|
-
error('No bindgen input found. Create src/features in your app or pass paths: plusui bindgen <featuresDir> <outputDir>');
|
|
898
|
-
}
|
|
899
|
-
}
|
|
891
|
+
const { outputDir: appOutputDir, frontendOutputDir } = getAppBindgenPaths();
|
|
892
|
+
bindgenArgs = [process.cwd(), appOutputDir];
|
|
893
|
+
usedDefaultAppMode = true;
|
|
894
|
+
defaultOutputDir = appOutputDir;
|
|
895
|
+
defaultFrontendOutputDir = frontendOutputDir;
|
|
896
|
+
log(`Project mode: ${process.cwd()} -> ${appOutputDir}`, 'dim');
|
|
900
897
|
}
|
|
901
898
|
|
|
902
899
|
// Spawn node process
|
|
@@ -27,22 +27,44 @@ This will:
|
|
|
27
27
|
- Changes to frontend code reflect instantly
|
|
28
28
|
- Auto-refresh app bindings when `src/features` exists
|
|
29
29
|
|
|
30
|
+
Dev build intermediates are stored in `.plusui/dev/...` so your `build/` folder stays focused on release/platform outputs.
|
|
31
|
+
|
|
30
32
|
Note: You can still run `npm run bind` manually anytime.
|
|
31
33
|
|
|
32
34
|
## Bindings (App-level)
|
|
33
35
|
|
|
34
|
-
Generate bindings for your app
|
|
36
|
+
Generate bidirectional bindings for your app:
|
|
35
37
|
```bash
|
|
36
38
|
npm run bind
|
|
37
39
|
```
|
|
38
40
|
|
|
39
41
|
Default bindgen paths:
|
|
40
|
-
- Input
|
|
41
|
-
- Output: `src/
|
|
42
|
+
- Input: project root scan (frontend + backend files)
|
|
43
|
+
- Output: `src/Bindings`
|
|
44
|
+
|
|
45
|
+
Generated structure:
|
|
46
|
+
- `src/Bindings/NativeBindings/CPP_IO`
|
|
47
|
+
- `src/Bindings/NativeBindings/WEB_IO`
|
|
48
|
+
- `src/Bindings/CustomBindings/CPP_IO`
|
|
49
|
+
- `src/Bindings/CustomBindings/WEB_IO`
|
|
50
|
+
- `include/Bindings/NativeBindings/CPP_IO` (generated `.hpp` headers)
|
|
51
|
+
- `include/Bindings/CustomBindings/CPP_IO` (generated `.hpp` headers)
|
|
52
|
+
|
|
53
|
+
Scan extensions:
|
|
54
|
+
- `WEB_IO`: `.ts`, `.tsx`, `.js`, `.jsx`, `.mts`, `.cts`, `.html`
|
|
55
|
+
- `CPP_IO`: `.h`, `.hpp`, `.hh`, `.hxx`, `.cpp`, `.cc`, `.cxx`
|
|
56
|
+
|
|
57
|
+
Custom binding kinds detected:
|
|
58
|
+
- `method`
|
|
59
|
+
- `service`
|
|
60
|
+
- `stream`
|
|
61
|
+
- `event`
|
|
62
|
+
|
|
63
|
+
`plusui bind` scans your whole project structure and does not require a specific feature folder.
|
|
42
64
|
|
|
43
65
|
You can also pass custom paths:
|
|
44
66
|
```bash
|
|
45
|
-
plusui bindgen <
|
|
67
|
+
plusui bindgen <projectRoot> <outputDir>
|
|
46
68
|
```
|
|
47
69
|
|
|
48
70
|
## Assets & Icons
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
-
import { win, browser, router, app } from './plusui';
|
|
2
|
+
import { win, browser, router, app, fileDrop, formatFileSize, type FileInfo } from './plusui';
|
|
3
3
|
|
|
4
4
|
// Define routes for your app (optional - for SPA routing)
|
|
5
5
|
const routes = {
|
|
@@ -14,6 +14,11 @@ function App() {
|
|
|
14
14
|
const [currentUrl, setCurrentUrl] = useState('');
|
|
15
15
|
const [canGoBack, setCanGoBack] = useState(false);
|
|
16
16
|
const [canGoForward, setCanGoForward] = useState(false);
|
|
17
|
+
|
|
18
|
+
// FileDrop state
|
|
19
|
+
const [files, setFiles] = useState<FileInfo[]>([]);
|
|
20
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
21
|
+
const [dropZoneStyle, setDropZoneStyle] = useState('');
|
|
17
22
|
|
|
18
23
|
useEffect(() => {
|
|
19
24
|
// Setup routes
|
|
@@ -26,7 +31,27 @@ function App() {
|
|
|
26
31
|
browser.canGoForward().then(setCanGoForward);
|
|
27
32
|
});
|
|
28
33
|
|
|
29
|
-
//
|
|
34
|
+
// Setup FileDrop listeners
|
|
35
|
+
const unsubDrop = fileDrop.onFilesDropped((droppedFiles) => {
|
|
36
|
+
console.log('Files dropped:', droppedFiles);
|
|
37
|
+
setFiles(prev => [...prev, ...droppedFiles]);
|
|
38
|
+
setIsDragging(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const unsubEnter = fileDrop.onDragEnter(() => {
|
|
42
|
+
setIsDragging(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const unsubLeave = fileDrop.onDragLeave(() => {
|
|
46
|
+
setIsDragging(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
unsub();
|
|
51
|
+
unsubDrop();
|
|
52
|
+
unsubEnter();
|
|
53
|
+
unsubLeave();
|
|
54
|
+
}al state
|
|
30
55
|
browser.getUrl().then(setCurrentUrl);
|
|
31
56
|
browser.canGoBack().then(setCanGoBack);
|
|
32
57
|
browser.canGoForward().then(setCanGoForward);
|
|
@@ -101,7 +126,68 @@ function App() {
|
|
|
101
126
|
<div className="button-group">
|
|
102
127
|
<button onClick={handleGoBack} className="button" disabled={!canGoBack}>Back</button>
|
|
103
128
|
<button onClick={handleGoForward} className="button" disabled={!canGoForward}>Forward</button>
|
|
104
|
-
<button
|
|
129
|
+
<button onClcard">
|
|
130
|
+
<h2>FileDrop - Drag & Drop Files</h2>
|
|
131
|
+
|
|
132
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
133
|
+
<label style={{ marginRight: '0.5rem', fontSize: '0.9em' }}>Style:</label>
|
|
134
|
+
<select
|
|
135
|
+
value={dropZoneStyle}
|
|
136
|
+
onChange={(e) => setDropZoneStyle(e.target.value)}
|
|
137
|
+
style={{
|
|
138
|
+
padding: '0.5rem',
|
|
139
|
+
borderRadius: '0.25rem',
|
|
140
|
+
border: '1px solid rgba(255,255,255,0.3)',
|
|
141
|
+
background: 'rgba(255,255,255,0.1)',
|
|
142
|
+
color: '#fff',
|
|
143
|
+
fontSize: '0.9em'
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
<option value="">Default</option>
|
|
147
|
+
<option value="filedrop-compact">Compact</option>
|
|
148
|
+
<option value="filedrop-inline">Inline</option>
|
|
149
|
+
<option value="filedrop-minimal">Minimal</option>
|
|
150
|
+
<option value="filedrop-bold">Bold</option>
|
|
151
|
+
</select>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div className={`filedrop-zone ${dropZoneStyle} ${isDragging ? 'filedrop-active' : ''}`}>
|
|
155
|
+
<div className="filedrop-content">
|
|
156
|
+
<svg className="filedrop-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
157
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
|
158
|
+
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
|
159
|
+
</svg>
|
|
160
|
+
<div className="filedrop-text">
|
|
161
|
+
{isDragging ? 'Drop files here' : 'Drag & drop files'}
|
|
162
|
+
</div>
|
|
163
|
+
<div className="filedrop-hint">All file types supported</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
{files.length > 0 && (
|
|
168
|
+
<div className="filedrop-files">
|
|
169
|
+
{files.map((file, i) => (
|
|
170
|
+
<div key={i} className="filedrop-file-item">
|
|
171
|
+
<div className="filedrop-file-icon">📄</div>
|
|
172
|
+
<div className="filedrop-file-info">
|
|
173
|
+
<div className="filedrop-file-name">{file.name}</div>
|
|
174
|
+
<div className="filedrop-file-meta">
|
|
175
|
+
{formatFileSize(file.size)} • {file.type}
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
<button
|
|
179
|
+
className="filedrop-file-remove"
|
|
180
|
+
onClick={() => setFiles(files.filter((_, idx) => idx !== i))}
|
|
181
|
+
>
|
|
182
|
+
✕
|
|
183
|
+
</button>
|
|
184
|
+
</div>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div className="ick={handleReload} className="button">Reload</button>
|
|
105
191
|
</div>
|
|
106
192
|
</div>
|
|
107
193
|
|
|
@@ -115,3 +115,50 @@ export const router = {
|
|
|
115
115
|
export const app = {
|
|
116
116
|
quit: async () => invoke('app.quit', []),
|
|
117
117
|
};
|
|
118
|
+
|
|
119
|
+
// FileDrop API
|
|
120
|
+
export interface FileInfo {
|
|
121
|
+
path: string;
|
|
122
|
+
name: string;
|
|
123
|
+
type: string;
|
|
124
|
+
size: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const fileDrop = {
|
|
128
|
+
setEnabled: async (enabled: boolean) => invoke('fileDrop.setEnabled', [enabled]),
|
|
129
|
+
isEnabled: async (): Promise<boolean> => invoke('fileDrop.isEnabled', []) as Promise<boolean>,
|
|
130
|
+
onFilesDropped: (handler: (files: FileInfo[]) => void) => {
|
|
131
|
+
if (typeof window === 'undefined') return () => {};
|
|
132
|
+
const eventHandler = (event: Event) => {
|
|
133
|
+
const custom = event as CustomEvent<{ files?: FileInfo[] }>;
|
|
134
|
+
handler(custom.detail?.files ?? []);
|
|
135
|
+
};
|
|
136
|
+
window.addEventListener('plusui:fileDrop.filesDropped', eventHandler);
|
|
137
|
+
return () => window.removeEventListener('plusui:fileDrop.filesDropped', eventHandler);
|
|
138
|
+
},
|
|
139
|
+
onDragEnter: (handler: () => void) => {
|
|
140
|
+
if (typeof window === 'undefined') return () => {};
|
|
141
|
+
const eventHandler = () => handler();
|
|
142
|
+
window.addEventListener('plusui:fileDrop.dragEnter', eventHandler);
|
|
143
|
+
return () => window.removeEventListener('plusui:fileDrop.dragEnter', eventHandler);
|
|
144
|
+
},
|
|
145
|
+
onDragLeave: (handler: () => void) => {
|
|
146
|
+
if (typeof window === 'undefined') return () => {};
|
|
147
|
+
const eventHandler = () => handler();
|
|
148
|
+
window.addEventListener('plusui:fileDrop.dragLeave', eventHandler);
|
|
149
|
+
return () => window.removeEventListener('plusui:fileDrop.dragLeave', eventHandler);
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Helper functions
|
|
154
|
+
export function formatFileSize(bytes: number): string {
|
|
155
|
+
if (bytes === 0) return '0 Bytes';
|
|
156
|
+
const k = 1024;
|
|
157
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
158
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
159
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function isImageFile(file: FileInfo): boolean {
|
|
163
|
+
return file.type.startsWith('image/');
|
|
164
|
+
}
|
|
@@ -138,3 +138,191 @@ body {
|
|
|
138
138
|
border-radius: 0.25rem;
|
|
139
139
|
font-family: 'Courier New', monospace;
|
|
140
140
|
}
|
|
141
|
+
|
|
142
|
+
/* ============================================================================
|
|
143
|
+
* FILEDROP STYLES
|
|
144
|
+
* ============================================================================ */
|
|
145
|
+
|
|
146
|
+
.filedrop-zone {
|
|
147
|
+
position: relative;
|
|
148
|
+
display: flex;
|
|
149
|
+
flex-direction: column;
|
|
150
|
+
align-items: center;
|
|
151
|
+
justify-content: center;
|
|
152
|
+
min-height: 180px;
|
|
153
|
+
padding: 2rem;
|
|
154
|
+
border: 2px dashed rgba(255, 255, 255, 0.3);
|
|
155
|
+
border-radius: 0.75rem;
|
|
156
|
+
background-color: rgba(255, 255, 255, 0.05);
|
|
157
|
+
transition: all 0.2s ease-in-out;
|
|
158
|
+
cursor: pointer;
|
|
159
|
+
user-select: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.filedrop-zone:hover {
|
|
163
|
+
border-color: rgba(255, 255, 255, 0.5);
|
|
164
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.filedrop-zone.filedrop-active {
|
|
168
|
+
border-color: #60a5fa;
|
|
169
|
+
background-color: rgba(96, 165, 250, 0.1);
|
|
170
|
+
border-width: 3px;
|
|
171
|
+
transform: scale(1.02);
|
|
172
|
+
box-shadow: 0 0 0 4px rgba(96, 165, 250, 0.2);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.filedrop-content {
|
|
176
|
+
display: flex;
|
|
177
|
+
flex-direction: column;
|
|
178
|
+
align-items: center;
|
|
179
|
+
gap: 1rem;
|
|
180
|
+
text-align: center;
|
|
181
|
+
pointer-events: none;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.filedrop-icon {
|
|
185
|
+
width: 3rem;
|
|
186
|
+
height: 3rem;
|
|
187
|
+
color: rgba(255, 255, 255, 0.6);
|
|
188
|
+
transition: all 0.2s ease-in-out;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.filedrop-zone.filedrop-active .filedrop-icon {
|
|
192
|
+
color: #60a5fa;
|
|
193
|
+
transform: scale(1.2);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.filedrop-text {
|
|
197
|
+
font-size: 1rem;
|
|
198
|
+
font-weight: 500;
|
|
199
|
+
color: rgba(255, 255, 255, 0.9);
|
|
200
|
+
transition: color 0.2s ease-in-out;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.filedrop-zone.filedrop-active .filedrop-text {
|
|
204
|
+
color: #93c5fd;
|
|
205
|
+
font-weight: 600;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.filedrop-hint {
|
|
209
|
+
font-size: 0.875rem;
|
|
210
|
+
color: rgba(255, 255, 255, 0.6);
|
|
211
|
+
transition: color 0.2s ease-in-out;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.filedrop-zone.filedrop-active .filedrop-hint {
|
|
215
|
+
color: rgba(255, 255, 255, 0.8);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.filedrop-zone.filedrop-compact {
|
|
219
|
+
min-height: 120px;
|
|
220
|
+
padding: 1.5rem;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.filedrop-zone.filedrop-compact .filedrop-icon {
|
|
224
|
+
width: 2rem;
|
|
225
|
+
height: 2rem;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.filedrop-zone.filedrop-inline {
|
|
229
|
+
min-height: 80px;
|
|
230
|
+
padding: 1rem;
|
|
231
|
+
flex-direction: row;
|
|
232
|
+
justify-content: flex-start;
|
|
233
|
+
gap: 1rem;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.filedrop-zone.filedrop-inline .filedrop-content {
|
|
237
|
+
flex-direction: row;
|
|
238
|
+
align-items: center;
|
|
239
|
+
text-align: left;
|
|
240
|
+
gap: 0.75rem;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.filedrop-zone.filedrop-minimal {
|
|
244
|
+
border-style: solid;
|
|
245
|
+
border-width: 1px;
|
|
246
|
+
background-color: transparent;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.filedrop-zone.filedrop-bold {
|
|
250
|
+
border-width: 3px;
|
|
251
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.filedrop-files {
|
|
255
|
+
margin-top: 1rem;
|
|
256
|
+
width: 100%;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.filedrop-file-item {
|
|
260
|
+
display: flex;
|
|
261
|
+
align-items: center;
|
|
262
|
+
gap: 0.75rem;
|
|
263
|
+
padding: 0.75rem;
|
|
264
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
265
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
266
|
+
border-radius: 0.5rem;
|
|
267
|
+
margin-bottom: 0.5rem;
|
|
268
|
+
transition: all 0.2s ease-in-out;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.filedrop-file-item:hover {
|
|
272
|
+
background-color: rgba(255, 255, 255, 0.15);
|
|
273
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.filedrop-file-icon {
|
|
277
|
+
width: 2rem;
|
|
278
|
+
height: 2rem;
|
|
279
|
+
flex-shrink: 0;
|
|
280
|
+
display: flex;
|
|
281
|
+
align-items: center;
|
|
282
|
+
justify-content: center;
|
|
283
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
284
|
+
border-radius: 0.375rem;
|
|
285
|
+
font-size: 1.2rem;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.filedrop-file-info {
|
|
289
|
+
flex: 1;
|
|
290
|
+
min-width: 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.filedrop-file-name {
|
|
294
|
+
font-size: 0.875rem;
|
|
295
|
+
font-weight: 500;
|
|
296
|
+
color: #fff;
|
|
297
|
+
white-space: nowrap;
|
|
298
|
+
overflow: hidden;
|
|
299
|
+
text-overflow: ellipsis;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.filedrop-file-meta {
|
|
303
|
+
font-size: 0.75rem;
|
|
304
|
+
color: rgba(255, 255, 255, 0.6);
|
|
305
|
+
margin-top: 0.125rem;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.filedrop-file-remove {
|
|
309
|
+
flex-shrink: 0;
|
|
310
|
+
width: 1.5rem;
|
|
311
|
+
height: 1.5rem;
|
|
312
|
+
display: flex;
|
|
313
|
+
align-items: center;
|
|
314
|
+
justify-content: center;
|
|
315
|
+
border-radius: 0.25rem;
|
|
316
|
+
color: rgba(255, 255, 255, 0.6);
|
|
317
|
+
cursor: pointer;
|
|
318
|
+
transition: all 0.2s ease-in-out;
|
|
319
|
+
pointer-events: auto;
|
|
320
|
+
background: transparent;
|
|
321
|
+
border: none;
|
|
322
|
+
font-size: 1rem;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.filedrop-file-remove:hover {
|
|
326
|
+
background-color: rgba(239, 68, 68, 0.2);
|
|
327
|
+
color: #fca5a5;
|
|
328
|
+
}
|
|
@@ -75,6 +75,7 @@ struct ServicesConfig {
|
|
|
75
75
|
bool enableDisplay = true; // Enable multi-display detection
|
|
76
76
|
bool enableKeyboard = true; // Enable keyboard shortcuts/hotkeys
|
|
77
77
|
bool enableMenu = true; // Enable custom menu bar
|
|
78
|
+
bool enableFileDrop = true; // Enable drag & drop file handling
|
|
78
79
|
} servicesConfig;
|
|
79
80
|
|
|
80
81
|
// ============================================================================
|
|
@@ -169,6 +170,24 @@ int main() {
|
|
|
169
170
|
// });
|
|
170
171
|
// Call from JS: const version = await app.invoke('getVersion');
|
|
171
172
|
|
|
173
|
+
// ========================================
|
|
174
|
+
// FILE DROP EVENTS (Drag & Drop)
|
|
175
|
+
// ========================================
|
|
176
|
+
// Listen for files dropped into the window
|
|
177
|
+
plusui::event::on("fileDrop.filesDropped", [](const std::string& data) {
|
|
178
|
+
std::cout << "Files dropped: " << data << std::endl;
|
|
179
|
+
// Parse the JSON data to get file info
|
|
180
|
+
// You can process files here in C++
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
plusui::event::on("fileDrop.dragEnter", [](const std::string&) {
|
|
184
|
+
std::cout << "Drag entered window" << std::endl;
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
plusui::event::on("fileDrop.dragLeave", [](const std::string&) {
|
|
188
|
+
std::cout << "Drag left window" << std::endl;
|
|
189
|
+
});
|
|
190
|
+
|
|
172
191
|
// ========================================
|
|
173
192
|
// RUN APPLICATION
|
|
174
193
|
// ========================================
|
|
@@ -198,4 +217,8 @@ int main() {
|
|
|
198
217
|
// DISPLAY: display.getAll(), display.getPrimary(), display.getCurrent()
|
|
199
218
|
//
|
|
200
219
|
// CLIPBOARD: clipboard.writeText(str), clipboard.readText(), clipboard.clear()
|
|
220
|
+
//
|
|
221
|
+
// FILEDROP: fileDrop.onFilesDropped(callback), fileDrop.setEnabled(bool),
|
|
222
|
+
// fileDrop.onDragEnter(callback), fileDrop.onDragLeave(callback),
|
|
223
|
+
// fileDrop.startDrag([paths])
|
|
201
224
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createSignal, onMount, Show } from 'solid-js';
|
|
2
|
-
import { win, browser, router, app } from './plusui';
|
|
1
|
+
import { createSignal, onMount, onCleanup, Show, For } from 'solid-js';
|
|
2
|
+
import { win, browser, router, app, fileDrop, formatFileSize, type FileInfo } from './plusui';
|
|
3
3
|
|
|
4
4
|
// Define routes for your app (optional - for SPA routing)
|
|
5
5
|
const routes = {
|
|
@@ -14,6 +14,11 @@ function App() {
|
|
|
14
14
|
const [currentUrl, setCurrentUrl] = createSignal('');
|
|
15
15
|
const [canGoBack, setCanGoBack] = createSignal(false);
|
|
16
16
|
const [canGoForward, setCanGoForward] = createSignal(false);
|
|
17
|
+
|
|
18
|
+
// FileDrop state
|
|
19
|
+
const [files, setFiles] = createSignal<FileInfo[]>([]);
|
|
20
|
+
const [isDragging, setIsDragging] = createSignal(false);
|
|
21
|
+
const [dropZoneStyle, setDropZoneStyle] = createSignal('');
|
|
17
22
|
|
|
18
23
|
onMount(() => {
|
|
19
24
|
// Setup routes
|
|
@@ -30,6 +35,27 @@ function App() {
|
|
|
30
35
|
browser.getUrl().then(setCurrentUrl);
|
|
31
36
|
browser.canGoBack().then(setCanGoBack);
|
|
32
37
|
browser.canGoForward().then(setCanGoForward);
|
|
38
|
+
|
|
39
|
+
// Setup FileDrop listeners
|
|
40
|
+
const unsubDrop = fileDrop.onFilesDropped((droppedFiles) => {
|
|
41
|
+
console.log('Files dropped:', droppedFiles);
|
|
42
|
+
setFiles(prev => [...prev, ...droppedFiles]);
|
|
43
|
+
setIsDragging(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const unsubEnter = fileDrop.onDragEnter(() => {
|
|
47
|
+
setIsDragging(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const unsubLeave = fileDrop.onDragLeave(() => {
|
|
51
|
+
setIsDragging(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
onCleanup(() => {
|
|
55
|
+
unsubDrop();
|
|
56
|
+
unsubEnter();
|
|
57
|
+
unsubLeave();
|
|
58
|
+
});
|
|
33
59
|
});
|
|
34
60
|
|
|
35
61
|
const handleMinimize = async () => await win.minimize();
|
|
@@ -119,6 +145,69 @@ function App() {
|
|
|
119
145
|
<button onClick={handleQuit} class="button button-danger">Quit App</button>
|
|
120
146
|
</div>
|
|
121
147
|
|
|
148
|
+
<div class="card">
|
|
149
|
+
<h2>FileDrop - Drag & Drop Files</h2>
|
|
150
|
+
|
|
151
|
+
<div style={{ 'margin-bottom': '1rem' }}>
|
|
152
|
+
<label style={{ 'margin-right': '0.5rem', 'font-size': '0.9em' }}>Style:</label>
|
|
153
|
+
<select
|
|
154
|
+
value={dropZoneStyle()}
|
|
155
|
+
onChange={(e) => setDropZoneStyle(e.target.value)}
|
|
156
|
+
style={{
|
|
157
|
+
padding: '0.5rem',
|
|
158
|
+
'border-radius': '0.25rem',
|
|
159
|
+
border: '1px solid rgba(255,255,255,0.3)',
|
|
160
|
+
background: 'rgba(255,255,255,0.1)',
|
|
161
|
+
color: '#fff',
|
|
162
|
+
'font-size': '0.9em'
|
|
163
|
+
}}
|
|
164
|
+
>
|
|
165
|
+
<option value="">Default</option>
|
|
166
|
+
<option value="filedrop-compact">Compact</option>
|
|
167
|
+
<option value="filedrop-inline">Inline</option>
|
|
168
|
+
<option value="filedrop-minimal">Minimal</option>
|
|
169
|
+
<option value="filedrop-bold">Bold</option>
|
|
170
|
+
</select>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
<div class={`filedrop-zone ${dropZoneStyle()} ${isDragging() ? 'filedrop-active' : ''}`}>
|
|
174
|
+
<div class="filedrop-content">
|
|
175
|
+
<svg class="filedrop-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
176
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width={2}
|
|
177
|
+
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
|
178
|
+
</svg>
|
|
179
|
+
<div class="filedrop-text">
|
|
180
|
+
{isDragging() ? 'Drop files here' : 'Drag & drop files'}
|
|
181
|
+
</div>
|
|
182
|
+
<div class="filedrop-hint">All file types supported</div>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<Show when={files().length > 0}>
|
|
187
|
+
<div class="filedrop-files">
|
|
188
|
+
<For each={files()}>
|
|
189
|
+
{(file, i) => (
|
|
190
|
+
<div class="filedrop-file-item">
|
|
191
|
+
<div class="filedrop-file-icon">📄</div>
|
|
192
|
+
<div class="filedrop-file-info">
|
|
193
|
+
<div class="filedrop-file-name">{file.name}</div>
|
|
194
|
+
<div class="filedrop-file-meta">
|
|
195
|
+
{formatFileSize(file.size)} • {file.type}
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
<button
|
|
199
|
+
class="filedrop-file-remove"
|
|
200
|
+
onClick={() => setFiles(files().filter((_, idx) => idx !== i()))}
|
|
201
|
+
>
|
|
202
|
+
✕
|
|
203
|
+
</button>
|
|
204
|
+
</div>
|
|
205
|
+
)}
|
|
206
|
+
</For>
|
|
207
|
+
</div>
|
|
208
|
+
</Show>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
122
211
|
<div class="info">
|
|
123
212
|
<p>Edit <code>frontend/src/App.tsx</code> to modify the UI.</p>
|
|
124
213
|
<p>Edit <code>main.cpp</code> to add C++ functionality.</p>
|
|
@@ -115,3 +115,50 @@ export const router = {
|
|
|
115
115
|
export const app = {
|
|
116
116
|
quit: async () => invoke('app.quit', []),
|
|
117
117
|
};
|
|
118
|
+
|
|
119
|
+
// FileDrop API
|
|
120
|
+
export interface FileInfo {
|
|
121
|
+
path: string;
|
|
122
|
+
name: string;
|
|
123
|
+
type: string;
|
|
124
|
+
size: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const fileDrop = {
|
|
128
|
+
setEnabled: async (enabled: boolean) => invoke('fileDrop.setEnabled', [enabled]),
|
|
129
|
+
isEnabled: async (): Promise<boolean> => invoke('fileDrop.isEnabled', []) as Promise<boolean>,
|
|
130
|
+
onFilesDropped: (handler: (files: FileInfo[]) => void) => {
|
|
131
|
+
if (typeof window === 'undefined') return () => {};
|
|
132
|
+
const eventHandler = (event: Event) => {
|
|
133
|
+
const custom = event as CustomEvent<{ files?: FileInfo[] }>;
|
|
134
|
+
handler(custom.detail?.files ?? []);
|
|
135
|
+
};
|
|
136
|
+
window.addEventListener('plusui:fileDrop.filesDropped', eventHandler);
|
|
137
|
+
return () => window.removeEventListener('plusui:fileDrop.filesDropped', eventHandler);
|
|
138
|
+
},
|
|
139
|
+
onDragEnter: (handler: () => void) => {
|
|
140
|
+
if (typeof window === 'undefined') return () => {};
|
|
141
|
+
const eventHandler = () => handler();
|
|
142
|
+
window.addEventListener('plusui:fileDrop.dragEnter', eventHandler);
|
|
143
|
+
return () => window.removeEventListener('plusui:fileDrop.dragEnter', eventHandler);
|
|
144
|
+
},
|
|
145
|
+
onDragLeave: (handler: () => void) => {
|
|
146
|
+
if (typeof window === 'undefined') return () => {};
|
|
147
|
+
const eventHandler = () => handler();
|
|
148
|
+
window.addEventListener('plusui:fileDrop.dragLeave', eventHandler);
|
|
149
|
+
return () => window.removeEventListener('plusui:fileDrop.dragLeave', eventHandler);
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Helper functions
|
|
154
|
+
export function formatFileSize(bytes: number): string {
|
|
155
|
+
if (bytes === 0) return '0 Bytes';
|
|
156
|
+
const k = 1024;
|
|
157
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
158
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
159
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function isImageFile(file: FileInfo): boolean {
|
|
163
|
+
return file.type.startsWith('image/');
|
|
164
|
+
}
|
|
@@ -138,3 +138,191 @@ body {
|
|
|
138
138
|
border-radius: 0.25rem;
|
|
139
139
|
font-family: 'Courier New', monospace;
|
|
140
140
|
}
|
|
141
|
+
|
|
142
|
+
/* ============================================================================
|
|
143
|
+
* FILEDROP STYLES
|
|
144
|
+
* ============================================================================ */
|
|
145
|
+
|
|
146
|
+
.filedrop-zone {
|
|
147
|
+
position: relative;
|
|
148
|
+
display: flex;
|
|
149
|
+
flex-direction: column;
|
|
150
|
+
align-items: center;
|
|
151
|
+
justify-content: center;
|
|
152
|
+
min-height: 180px;
|
|
153
|
+
padding: 2rem;
|
|
154
|
+
border: 2px dashed rgba(255, 255, 255, 0.3);
|
|
155
|
+
border-radius: 0.75rem;
|
|
156
|
+
background-color: rgba(255, 255, 255, 0.05);
|
|
157
|
+
transition: all 0.2s ease-in-out;
|
|
158
|
+
cursor: pointer;
|
|
159
|
+
user-select: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.filedrop-zone:hover {
|
|
163
|
+
border-color: rgba(255, 255, 255, 0.5);
|
|
164
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.filedrop-zone.filedrop-active {
|
|
168
|
+
border-color: #60a5fa;
|
|
169
|
+
background-color: rgba(96, 165, 250, 0.1);
|
|
170
|
+
border-width: 3px;
|
|
171
|
+
transform: scale(1.02);
|
|
172
|
+
box-shadow: 0 0 0 4px rgba(96, 165, 250, 0.2);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.filedrop-content {
|
|
176
|
+
display: flex;
|
|
177
|
+
flex-direction: column;
|
|
178
|
+
align-items: center;
|
|
179
|
+
gap: 1rem;
|
|
180
|
+
text-align: center;
|
|
181
|
+
pointer-events: none;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.filedrop-icon {
|
|
185
|
+
width: 3rem;
|
|
186
|
+
height: 3rem;
|
|
187
|
+
color: rgba(255, 255, 255, 0.6);
|
|
188
|
+
transition: all 0.2s ease-in-out;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.filedrop-zone.filedrop-active .filedrop-icon {
|
|
192
|
+
color: #60a5fa;
|
|
193
|
+
transform: scale(1.2);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.filedrop-text {
|
|
197
|
+
font-size: 1rem;
|
|
198
|
+
font-weight: 500;
|
|
199
|
+
color: rgba(255, 255, 255, 0.9);
|
|
200
|
+
transition: color 0.2s ease-in-out;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.filedrop-zone.filedrop-active .filedrop-text {
|
|
204
|
+
color: #93c5fd;
|
|
205
|
+
font-weight: 600;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.filedrop-hint {
|
|
209
|
+
font-size: 0.875rem;
|
|
210
|
+
color: rgba(255, 255, 255, 0.6);
|
|
211
|
+
transition: color 0.2s ease-in-out;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.filedrop-zone.filedrop-active .filedrop-hint {
|
|
215
|
+
color: rgba(255, 255, 255, 0.8);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.filedrop-zone.filedrop-compact {
|
|
219
|
+
min-height: 120px;
|
|
220
|
+
padding: 1.5rem;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.filedrop-zone.filedrop-compact .filedrop-icon {
|
|
224
|
+
width: 2rem;
|
|
225
|
+
height: 2rem;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.filedrop-zone.filedrop-inline {
|
|
229
|
+
min-height: 80px;
|
|
230
|
+
padding: 1rem;
|
|
231
|
+
flex-direction: row;
|
|
232
|
+
justify-content: flex-start;
|
|
233
|
+
gap: 1rem;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.filedrop-zone.filedrop-inline .filedrop-content {
|
|
237
|
+
flex-direction: row;
|
|
238
|
+
align-items: center;
|
|
239
|
+
text-align: left;
|
|
240
|
+
gap: 0.75rem;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.filedrop-zone.filedrop-minimal {
|
|
244
|
+
border-style: solid;
|
|
245
|
+
border-width: 1px;
|
|
246
|
+
background-color: transparent;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.filedrop-zone.filedrop-bold {
|
|
250
|
+
border-width: 3px;
|
|
251
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.filedrop-files {
|
|
255
|
+
margin-top: 1rem;
|
|
256
|
+
width: 100%;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.filedrop-file-item {
|
|
260
|
+
display: flex;
|
|
261
|
+
align-items: center;
|
|
262
|
+
gap: 0.75rem;
|
|
263
|
+
padding: 0.75rem;
|
|
264
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
265
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
266
|
+
border-radius: 0.5rem;
|
|
267
|
+
margin-bottom: 0.5rem;
|
|
268
|
+
transition: all 0.2s ease-in-out;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.filedrop-file-item:hover {
|
|
272
|
+
background-color: rgba(255, 255, 255, 0.15);
|
|
273
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.filedrop-file-icon {
|
|
277
|
+
width: 2rem;
|
|
278
|
+
height: 2rem;
|
|
279
|
+
flex-shrink: 0;
|
|
280
|
+
display: flex;
|
|
281
|
+
align-items: center;
|
|
282
|
+
justify-content: center;
|
|
283
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
284
|
+
border-radius: 0.375rem;
|
|
285
|
+
font-size: 1.2rem;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.filedrop-file-info {
|
|
289
|
+
flex: 1;
|
|
290
|
+
min-width: 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.filedrop-file-name {
|
|
294
|
+
font-size: 0.875rem;
|
|
295
|
+
font-weight: 500;
|
|
296
|
+
color: #fff;
|
|
297
|
+
white-space: nowrap;
|
|
298
|
+
overflow: hidden;
|
|
299
|
+
text-overflow: ellipsis;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.filedrop-file-meta {
|
|
303
|
+
font-size: 0.75rem;
|
|
304
|
+
color: rgba(255, 255, 255, 0.6);
|
|
305
|
+
margin-top: 0.125rem;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.filedrop-file-remove {
|
|
309
|
+
flex-shrink: 0;
|
|
310
|
+
width: 1.5rem;
|
|
311
|
+
height: 1.5rem;
|
|
312
|
+
display: flex;
|
|
313
|
+
align-items: center;
|
|
314
|
+
justify-content: center;
|
|
315
|
+
border-radius: 0.25rem;
|
|
316
|
+
color: rgba(255, 255, 255, 0.6);
|
|
317
|
+
cursor: pointer;
|
|
318
|
+
transition: all 0.2s ease-in-out;
|
|
319
|
+
pointer-events: auto;
|
|
320
|
+
background: transparent;
|
|
321
|
+
border: none;
|
|
322
|
+
font-size: 1rem;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.filedrop-file-remove:hover {
|
|
326
|
+
background-color: rgba(239, 68, 68, 0.2);
|
|
327
|
+
color: #fca5a5;
|
|
328
|
+
}
|
|
@@ -75,6 +75,7 @@ struct ServicesConfig {
|
|
|
75
75
|
bool enableDisplay = true; // Enable multi-display detection
|
|
76
76
|
bool enableKeyboard = true; // Enable keyboard shortcuts/hotkeys
|
|
77
77
|
bool enableMenu = true; // Enable custom menu bar
|
|
78
|
+
bool enableFileDrop = true; // Enable drag & drop file handling
|
|
78
79
|
} servicesConfig;
|
|
79
80
|
|
|
80
81
|
// ============================================================================
|
|
@@ -160,6 +161,24 @@ int main() {
|
|
|
160
161
|
// });
|
|
161
162
|
// Call from JS: const version = await app.invoke('getVersion');
|
|
162
163
|
|
|
164
|
+
// ========================================
|
|
165
|
+
// FILE DROP EVENTS (Drag & Drop)
|
|
166
|
+
// ========================================
|
|
167
|
+
// Listen for files dropped into the window
|
|
168
|
+
plusui::event::on("fileDrop.filesDropped", [](const std::string& data) {
|
|
169
|
+
std::cout << "Files dropped: " << data << std::endl;
|
|
170
|
+
// Parse the JSON data to get file info
|
|
171
|
+
// You can process files here in C++
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
plusui::event::on("fileDrop.dragEnter", [](const std::string&) {
|
|
175
|
+
std::cout << "Drag entered window" << std::endl;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
plusui::event::on("fileDrop.dragLeave", [](const std::string&) {
|
|
179
|
+
std::cout << "Drag left window" << std::endl;
|
|
180
|
+
});
|
|
181
|
+
|
|
163
182
|
// ========================================
|
|
164
183
|
// RUN APPLICATION
|
|
165
184
|
// ========================================
|
|
@@ -189,4 +208,8 @@ int main() {
|
|
|
189
208
|
// DISPLAY: display.getAll(), display.getPrimary(), display.getCurrent()
|
|
190
209
|
//
|
|
191
210
|
// CLIPBOARD: clipboard.writeText(str), clipboard.readText(), clipboard.clear()
|
|
211
|
+
//
|
|
212
|
+
// FILEDROP: fileDrop.onFilesDropped(callback), fileDrop.setEnabled(bool),
|
|
213
|
+
// fileDrop.onDragEnter(callback), fileDrop.onDragLeave(callback),
|
|
214
|
+
// fileDrop.startDrag([paths])
|
|
192
215
|
|