Skip to content

Commit 5881194

Browse files
committed
feat: add drag and drop support in windows and mac desktop apps
1 parent 48ba526 commit 5881194

File tree

4 files changed

+207
-5
lines changed

4 files changed

+207
-5
lines changed

src/desktop-metrics.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@
120120
}
121121
window.__TAURI__.event.listen("health", processRequest);
122122
setInterval(async ()=>{
123-
// close window if the metrics hidden window is the only one around.
123+
// close window if the metrics hidden window and file drop window is the only one around.
124124
const allTauriWindowsLabels = await window.__TAURI__.invoke('_get_window_labels');
125-
if(allTauriWindowsLabels.length === 1){
125+
if(allTauriWindowsLabels.length === 2 || allTauriWindowsLabels.length === 1){
126126
window.__TAURI__.window.getCurrent().close();
127127
}
128128
}, 1000);

src/drop-files.html

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Project Name</title>
7+
<script>
8+
let windowLabelOfListener;
9+
window.__TAURI__.event.listen('tauri://file-drop', (event) => {
10+
__TAURI__.window.appWindow.hide();
11+
if(!event || !event.payload || !event.payload.length || !windowLabelOfListener){
12+
return;
13+
}
14+
window.__TAURI__.event.emit('file-drop-event-phoenix', {
15+
windowLabelOfListener,
16+
pathList: event.payload
17+
});
18+
});
19+
window.__TAURI__.event.listen("drop-attach-on-window", ({payload})=> {
20+
document.getElementById("projectName").innerText = payload.projectName;
21+
document.getElementById("dropMessage").innerText = payload.dropMessage;
22+
windowLabelOfListener = payload.windowLabelOfListener;
23+
});
24+
window.addEventListener('mouseout', function(event) {
25+
// Check if the mouse is leaving the window (relatedTarget is null)
26+
__TAURI__.window.appWindow.hide();
27+
});
28+
window.addEventListener('dragleave', ()=>{
29+
__TAURI__.window.appWindow.hide();
30+
});
31+
window.addEventListener('dragend', ()=>{
32+
__TAURI__.window.appWindow.hide();
33+
});
34+
window.addEventListener('click', ()=>{
35+
__TAURI__.window.appWindow.hide();
36+
});
37+
document.addEventListener('visibilitychange', () => {
38+
if (document.hidden) {
39+
__TAURI__.window.appWindow.hide();
40+
}
41+
});
42+
setInterval(async ()=>{
43+
// close window if the metrics hidden window and file drop window is the only one around.
44+
const allTauriWindowsLabels = await window.__TAURI__.invoke('_get_window_labels');
45+
if(allTauriWindowsLabels.length === 2 || allTauriWindowsLabels.length === 1){
46+
window.__TAURI__.window.getCurrent().close();
47+
}
48+
}, 1000);
49+
</script>
50+
<style>
51+
* {
52+
margin: 0;
53+
padding: 0;
54+
box-sizing: border-box;
55+
}
56+
57+
html,
58+
body {
59+
height: 100%;
60+
font-family: Arial, sans-serif;
61+
background-color: #212123;
62+
color: #ffffff; /* Default text color */
63+
}
64+
65+
.container {
66+
display: flex;
67+
flex-direction: column;
68+
height: 100%;
69+
justify-content: center;
70+
align-items: center;
71+
background-color: #212123;
72+
}
73+
74+
header {
75+
position: absolute;
76+
top: 20px;
77+
width: 100%;
78+
text-align: center;
79+
}
80+
81+
header h1 {
82+
font-size: 2rem;
83+
color: #cccccc;
84+
}
85+
86+
.drop-area {
87+
display: flex;
88+
flex-direction: column;
89+
justify-content: center;
90+
align-items: center;
91+
border: 2px dashed #555555;
92+
border-radius: 10px;
93+
padding: 50px;
94+
background-color: #333333;
95+
width: 80%;
96+
height: 80%;
97+
}
98+
99+
.drop-area .icon {
100+
font-size: 3rem;
101+
color: #aaaaaa;
102+
margin-bottom: 20px;
103+
}
104+
105+
.drop-area p {
106+
font-size: 1.2rem;
107+
color: #cccccc;
108+
}
109+
</style>
110+
</head>
111+
<body>
112+
<div class="container">
113+
<header>
114+
<h1 id="projectName">Project Name</h1>
115+
</header>
116+
<div class="drop-area">
117+
<div class="icon">&#128193;</div>
118+
<!-- This is a Unicode character for a folder icon -->
119+
<p id="dropMessage">Drop file here to open</p>
120+
</div>
121+
</div>
122+
</body>
123+
</html>
124+

src/phoenix/shell.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ async function openURLInPhoenixWindow(url, {
7878
minHeight: minHeight || 600,
7979
width: width || defaultWidth,
8080
minWidth: minWidth || 800,
81-
acceptFirstMouse: acceptFirstMouse === undefined ? true : acceptFirstMouse
81+
acceptFirstMouse: acceptFirstMouse === undefined ? true : acceptFirstMouse,
82+
fileDropEnabled: false
8283
});
8384
tauriWindow.isTauriWindow = true;
8485
return tauriWindow;

src/utils/DragAndDrop.js

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ define(function (require, exports, module) {
112112
}
113113

114114
CommandManager.execute(Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN,
115-
{fullPath: path, silent: true})
115+
{fullPath: path, silent: true})
116116
.done(function () {
117117
result.resolve();
118118
})
@@ -153,7 +153,7 @@ define(function (require, exports, module) {
153153
message += "<ul class='dialog-list'>";
154154
errorFiles.forEach(function (info) {
155155
message += "<li><span class='dialog-filename'>" +
156-
StringUtils.breakableUrl(ProjectManager.makeProjectRelativeIfPossible(info.path)) +
156+
StringUtils.breakableUrl(ProjectManager.getProjectRelativeOrDisplayPath(info.path)) +
157157
"</span> - " + errorToString(info.error) +
158158
"</li>";
159159
});
@@ -168,6 +168,77 @@ define(function (require, exports, module) {
168168
});
169169
}
170170

171+
if(Phoenix.isNativeApp){
172+
window.__TAURI__.event.listen('file-drop-event-phoenix', ({payload})=> {
173+
if(!payload || !payload.pathList || !payload.pathList.length || !payload.windowLabelOfListener
174+
|| payload.windowLabelOfListener !== window.__TAURI__.window.appWindow.label){
175+
return;
176+
}
177+
const droppedVirtualPaths = [];
178+
for(const droppedPath of payload.pathList) {
179+
try{
180+
droppedVirtualPaths.push(window.fs.getTauriVirtualPath(droppedPath));
181+
} catch (e) {
182+
console.error("Error resolving dropped path: ", droppedPath);
183+
}
184+
}
185+
openDroppedFiles(droppedVirtualPaths);
186+
});
187+
}
188+
189+
async function showAndResizeFileDropWindow(event) {
190+
// Get the current window
191+
const currentWindow = window.__TAURI__.window.getCurrent();
192+
193+
// Get the bounds of the current window
194+
const size = await currentWindow.innerSize();
195+
// in mac, the innerSize api in tauri gets the full size including titlebar. Since our sidebar is full size
196+
const titlebarHeightIfAny = size.height - window.innerHeight;
197+
const currentWindowPos = await currentWindow.innerPosition();
198+
199+
let $activeElement;
200+
const fileDropWindow = window.__TAURI__.window.WebviewWindow.getByLabel('fileDrop');
201+
if($("#editor-holder").has(event.target).length) {
202+
$activeElement = $("#editor-holder");
203+
} else if($("#sidebar").has(event.target).length) {
204+
$activeElement = $("#sidebar");
205+
} else {
206+
await fileDropWindow.hide();
207+
}
208+
if(!$activeElement){
209+
return;
210+
}
211+
212+
const offset = $activeElement.offset();
213+
const width = $activeElement.outerWidth();
214+
const height = $activeElement.outerHeight();
215+
const x = currentWindowPos.x + offset.left,
216+
y =currentWindowPos.y + titlebarHeightIfAny + offset.top;
217+
const newSize = new window.__TAURI__.window.LogicalSize(width, height);
218+
const newPosition = new window.__TAURI__.window.LogicalPosition(x, y);
219+
220+
const currentSize = await fileDropWindow.innerSize();
221+
const currentPosition = await fileDropWindow.innerPosition();
222+
const isSameSize = currentSize.width === newSize.width && currentSize.height === newSize.height;
223+
const isSamePosition = currentPosition.x === newPosition.x && currentPosition.y === newPosition.y;
224+
window.__TAURI__.event.emit("drop-attach-on-window", {
225+
projectName: window.path.basename(ProjectManager.getProjectRoot().fullPath),
226+
dropMessage: "Drop files to open or drop a folder to open it as a project",
227+
windowLabelOfListener: window.__TAURI__.window.appWindow.label
228+
});
229+
if (isSameSize && isSamePosition && (await fileDropWindow.isVisible())) {
230+
return; // Do nothing if the window is already at the correct size and position and visible
231+
}
232+
233+
// Resize the fileDrop window to match the current window
234+
await fileDropWindow.setSize(newSize);
235+
await fileDropWindow.setPosition(newPosition);
236+
237+
// Show the fileDrop window
238+
await fileDropWindow.show();
239+
await fileDropWindow.setAlwaysOnTop(true);
240+
await fileDropWindow.setAlwaysOnTop(false);
241+
}
171242

172243
/**
173244
* Attaches global drag & drop handlers to this window. This enables dropping files/folders to open them, and also
@@ -181,6 +252,12 @@ define(function (require, exports, module) {
181252
var files = event.dataTransfer.files;
182253

183254
stopURIListPropagation(files, event);
255+
if(Phoenix.isNativeApp && Phoenix.platform !== "linux" &&
256+
event.dataTransfer.types && event.dataTransfer.types.includes("Files")){
257+
// in linux, there is a bug in ubuntu 24 where dropping a file will cause a ghost icon which only
258+
// goes away on reboot. So we dont support drop files in linux for now.
259+
showAndResizeFileDropWindow(event);
260+
}
184261

185262
if (files && files.length) {
186263
event.stopPropagation();

0 commit comments

Comments
 (0)