UIDesktop.js 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459
  1. /**
  2. * Copyright (C) 2024 Puter Technologies Inc.
  3. *
  4. * This file is part of Puter.
  5. *
  6. * Puter is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as published
  8. * by the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. import path from "../lib/path.js"
  20. import UIWindowClaimReferral from "./UIWindowClaimReferral.js"
  21. import UIContextMenu from './UIContextMenu.js'
  22. import UIItem from './UIItem.js'
  23. import UIAlert from './UIAlert.js'
  24. import UIWindow from './UIWindow.js'
  25. import UIWindowSaveAccount from './UIWindowSaveAccount.js';
  26. import UIWindowDesktopBGSettings from "./UIWindowDesktopBGSettings.js"
  27. import UIWindowMyWebsites from "./UIWindowMyWebsites.js"
  28. import UIWindowChangePassword from "./UIWindowChangePassword.js"
  29. import UIWindowChangeUsername from "./UIWindowChangeUsername.js"
  30. import UIWindowFeedback from "./UIWindowFeedback.js"
  31. import UIWindowLogin from "./UIWindowLogin.js"
  32. import UIWindowQR from "./UIWindowQR.js"
  33. import UIWindowRefer from "./UIWindowRefer.js"
  34. import UITaskbar from "./UITaskbar.js"
  35. async function UIDesktop(options){
  36. let h = '';
  37. // connect socket.
  38. window.socket = io(gui_origin + '/', {
  39. query: {
  40. auth_token: auth_token
  41. }
  42. });
  43. window.socket.on('error', (error) => {
  44. console.error('GUI Socket Error:', error);
  45. });
  46. window.socket.on('connect', function(){
  47. console.log('GUI Socket: Connected', window.socket.id);
  48. });
  49. window.socket.on('reconnect', function(){
  50. console.log('GUI Socket: Reconnected', window.socket.id);
  51. });
  52. window.socket.on('disconnect', () => {
  53. console.log('GUI Socket: Disconnected');
  54. });
  55. window.socket.on('reconnect', (attempt) => {
  56. console.log('GUI Socket: Reconnection', attempt);
  57. });
  58. window.socket.on('reconnect_attempt', (attempt) => {
  59. console.log('GUI Socket: Reconnection Attemps', attempt);
  60. });
  61. window.socket.on('reconnect_error', (error) => {
  62. console.log('GUI Socket: Reconnection Error', error);
  63. });
  64. window.socket.on('reconnect_failed', () => {
  65. console.log('GUI Socket: Reconnection Failed');
  66. });
  67. window.socket.on('error', (error) => {
  68. console.error('GUI Socket Error:', error);
  69. });
  70. socket.on('upload.progress', (msg) => {
  71. if(window.progress_tracker[msg.operation_id]){
  72. window.progress_tracker[msg.operation_id].cloud_uploaded += msg.loaded_diff
  73. if(window.progress_tracker[msg.operation_id][msg.item_upload_id]){
  74. window.progress_tracker[msg.operation_id][msg.item_upload_id].cloud_uploaded = msg.loaded;
  75. }
  76. }
  77. });
  78. socket.on('download.progress', (msg) => {
  79. if(window.progress_tracker[msg.operation_id]){
  80. if(window.progress_tracker[msg.operation_id][msg.item_upload_id]){
  81. window.progress_tracker[msg.operation_id][msg.item_upload_id].downloaded = msg.loaded;
  82. window.progress_tracker[msg.operation_id][msg.item_upload_id].total = msg.total;
  83. }
  84. }
  85. });
  86. socket.on('trash.is_empty', async (msg) => {
  87. $(`.item[data-path="${html_encode(trash_path)}" i]`).find('.item-icon > img').attr('src', msg.is_empty ? window.icons['trash.svg'] : window.icons['trash-full.svg']);
  88. $(`.window[data-path="${html_encode(trash_path)}" i]`).find('.window-head-icon').attr('src', msg.is_empty ? window.icons['trash.svg'] : window.icons['trash-full.svg']);
  89. // empty trash windows if needed
  90. if(msg.is_empty)
  91. $(`.window[data-path="${html_encode(trash_path)}" i]`).find('.item-container').empty();
  92. })
  93. socket.on('app.opened', async (app) => {
  94. // don't update if this is the original client that initiated the action
  95. if(app.original_client_socket_id === window.socket.id)
  96. return;
  97. // add the app to the beginning of the array
  98. launch_apps.recent.unshift(app);
  99. // dedupe the array by uuid, uid, and id
  100. launch_apps.recent = _.uniqBy(launch_apps.recent, 'name');
  101. // limit to 5
  102. launch_apps.recent = launch_apps.recent.slice(0, window.launch_recent_apps_count);
  103. })
  104. socket.on('item.removed', async (item) => {
  105. // don't update if this is the original client that initiated the action
  106. if(item.original_client_socket_id === window.socket.id)
  107. return;
  108. // don't remove items if this was a descendants_only operation
  109. if(item.descendants_only)
  110. return;
  111. // hide all UIItems with matching uids
  112. $(`.item[data-path='${item.path}']`).fadeOut(150, function(){
  113. // close all windows with matching uids
  114. // $('.window-' + item.uid).close();
  115. // close all windows that belong to a descendant of this item
  116. // todo this has to be case-insensitive but the `i` selector doesn't work on ^=
  117. $(`.window[data-path^="${item.path}/"]`).close();
  118. });
  119. })
  120. socket.on('item.updated', async (item) => {
  121. // Don't update if this is the original client that initiated the action
  122. if(item.original_client_socket_id === window.socket.id)
  123. return;
  124. // Update matching items
  125. // set new item name
  126. $(`.item[data-uid='${html_encode(item.uid)}'] .item-name`).html(html_encode(truncate_filename(item.name, TRUNCATE_LENGTH)).replaceAll(' ', '&nbsp;'));
  127. // Set new icon
  128. const new_icon = (item.is_dir ? window.icons['folder.svg'] : (await item_icon(item)).image);
  129. $(`.item[data-uid='${item.uid}']`).find('.item-icon-thumb').attr('src', new_icon);
  130. $(`.item[data-uid='${item.uid}']`).find('.item-icon-icon').attr('src', new_icon);
  131. // Set new data-name
  132. $(`.item[data-uid='${item.uid}']`).attr('data-name', html_encode(item.name));
  133. $(`.window-${item.uid}`).attr('data-name', html_encode(item.name));
  134. // Set new title attribute
  135. $(`.item[data-uid='${item.uid}']`).attr('title', html_encode(item.name));
  136. $(`.window-${options.uid}`).attr('title', html_encode(item.name));
  137. // Set new value for item-name-editor
  138. $(`.item[data-uid='${item.uid}'] .item-name-editor`).val(html_encode(item.name));
  139. $(`.item[data-uid='${item.uid}'] .item-name`).attr('title', html_encode(item.name));
  140. // Set new data-path
  141. const new_path = item.path;
  142. $(`.item[data-uid='${item.uid}']`).attr('data-path', new_path);
  143. $(`.window-${item.uid}`).attr('data-path', new_path);
  144. // Update all elements that have matching paths
  145. $(`[data-path="${html_encode(item.old_path)}" i]`).each(function(){
  146. $(this).attr('data-path', new_path)
  147. if($(this).hasClass('window-navbar-path-dirname'))
  148. $(this).text(item.name);
  149. });
  150. // Update all elements whose paths start with old_path
  151. $(`[data-path^="${html_encode(item.old_path) + '/'}"]`).each(function(){
  152. const new_el_path = _.replace($(this).attr('data-path'), item.old_path + '/', new_path+'/');
  153. $(this).attr('data-path', new_el_path);
  154. });
  155. // Update all exact-matching windows
  156. $(`.window-${item.uid}`).each(function(){
  157. update_window_path(this, new_path);
  158. })
  159. // Set new name for matching open windows
  160. $(`.window-${item.uid} .window-head-title`).text(item.name);
  161. // Re-sort all matching item containers
  162. $(`.item[data-uid='${item.uid}']`).parent('.item-container').each(function(){
  163. sort_items(this, $(this).closest('.item-container').attr('data-sort_by'), $(this).closest('.item-container').attr('data-sort_order'));
  164. })
  165. })
  166. socket.on('item.moved', async (resp) => {
  167. let fsentry = resp;
  168. // Notify all apps that are watching this item
  169. sendItemChangeEventToWatchingApps(fsentry.uid, {
  170. event: 'moved',
  171. uid: fsentry.uid,
  172. name: fsentry.name,
  173. })
  174. // don't update if this is the original client that initiated the action
  175. if(resp.original_client_socket_id === window.socket.id)
  176. return;
  177. let dest_path = path.dirname(fsentry.path);
  178. let metadata = fsentry.metadata;
  179. // path must use the real name from DB
  180. fsentry.path = fsentry.path;
  181. // update all shortcut_to_path
  182. $(`.item[data-shortcut_to_path="${html_encode(resp.old_path)}" i]`).attr(`data-shortcut_to_path`, html_encode(fsentry.path));
  183. // remove all items with matching uids
  184. $(`.item[data-uid='${fsentry.uid}']`).fadeOut(150, function(){
  185. // find all parent windows that contain this item
  186. let parent_windows = $(`.item[data-uid='${fsentry.uid}']`).closest('.window');
  187. // remove this item
  188. $(this).removeItems();
  189. // update parent windows' item counts
  190. $(parent_windows).each(function(index){
  191. update_explorer_footer_item_count(this);
  192. update_explorer_footer_selected_items_count(this)
  193. });
  194. })
  195. // if trashing, close windows of trashed items and its descendants
  196. if(dest_path === trash_path){
  197. $(`.window[data-path="${html_encode(resp.old_path)}" i]`).close();
  198. // todo this has to be case-insensitive but the `i` selector doesn't work on ^=
  199. $(`.window[data-path^="${html_encode(resp.old_path)}/"]`).close();
  200. }
  201. // update all paths of its and its descendants' open windows
  202. else{
  203. // todo this has to be case-insensitive but the `i` selector doesn't work on ^=
  204. $(`.window[data-path^="${html_encode(resp.old_path)}/"], .window[data-path="${html_encode(resp.old_path)}" i]`).each(function(){
  205. update_window_path(this, $(this).attr('data-path').replace(resp.old_path, fsentry.path));
  206. })
  207. }
  208. if(dest_path === trash_path){
  209. $(`.item[data-uid="${fsentry.uid}"]`).find('.item-is-shared').fadeOut(300);
  210. // if trashing dir...
  211. if(fsentry.is_dir){
  212. // remove website badge
  213. $(`.mywebsites-dir-path[data-uuid="${fsentry.uid}"]`).remove();
  214. // remove the website badge from all instances of the dir
  215. $(`.item[data-uid="${fsentry.uid}"]`).find('.item-has-website-badge').fadeOut(300);
  216. // remove File Rrequest Token
  217. // todo, some client-side check to see if this dir has an FR associated with it before sending a whole ajax req
  218. }
  219. }
  220. // if replacing an existing item, remove the old item that was just replaced
  221. if(fsentry.overwritten_uid !== undefined)
  222. $(`.item[data-uid=${fsentry.overwritten_uid}]`).removeItems();
  223. // if this is trash, get original name from item metadata
  224. fsentry.name = (metadata && metadata.original_name) ? metadata.original_name : fsentry.name;
  225. // create new item on matching containers
  226. UIItem({
  227. appendTo: $(`.item-container[data-path='${html_encode(dest_path)}' i]`),
  228. immutable: fsentry.immutable,
  229. uid: fsentry.uid,
  230. path: fsentry.path,
  231. icon: await item_icon(fsentry),
  232. name: (dest_path === trash_path) ? metadata.original_name : fsentry.name,
  233. is_dir: fsentry.is_dir,
  234. size: fsentry.size,
  235. type: fsentry.type,
  236. modified: fsentry.modified,
  237. is_selected: false,
  238. is_shared: (dest_path === trash_path) ? false : fsentry.is_shared,
  239. is_shortcut: fsentry.is_shortcut,
  240. shortcut_to: fsentry.shortcut_to,
  241. shortcut_to_path: fsentry.shortcut_to_path,
  242. // has_website: $(el_item).attr('data-has_website') === '1',
  243. metadata: JSON.stringify(fsentry.metadata) ?? '',
  244. });
  245. if(fsentry.parent_dirs_created && fsentry.parent_dirs_created.length > 0){
  246. // this operation may have created some missing directories,
  247. // see if any of the directories in the path of this file is new AND
  248. // if these new path have any open parents that need to be updated
  249. fsentry.parent_dirs_created.forEach(async dir => {
  250. let item_container = $(`.item-container[data-path='${html_encode(path.dirname(dir.path))}' i]`);
  251. if(item_container.length > 0 && $(`.item[data-path="${html_encode(dir.path)}" i]`).length === 0){
  252. UIItem({
  253. appendTo: item_container,
  254. immutable: false,
  255. uid: dir.uid,
  256. path: dir.path,
  257. icon: await item_icon(dir),
  258. name: dir.name,
  259. size: dir.size,
  260. type: dir.type,
  261. modified: dir.modified,
  262. is_dir: true,
  263. is_selected: false,
  264. is_shared: dir.is_shared,
  265. has_website: false,
  266. });
  267. }
  268. sort_items(item_container, $(item_container).attr('data-sort_by'), $(item_container).attr('data-sort_order'));
  269. });
  270. }
  271. //sort each container
  272. $(`.item-container[data-path='${html_encode(dest_path)}' i]`).each(function(){
  273. sort_items(this, $(this).attr('data-sort_by'), $(this).attr('data-sort_order'))
  274. })
  275. });
  276. socket.on('user.email_confirmed', (msg) => {
  277. // don't update if this is the original client that initiated the action
  278. if(msg.original_client_socket_id === window.socket.id)
  279. return;
  280. refresh_user_data(window.auth_token);
  281. });
  282. socket.on('item.renamed', async (item) => {
  283. // Notify all apps that are watching this item
  284. sendItemChangeEventToWatchingApps(item.uid, {
  285. event: 'rename',
  286. uid: item.uid,
  287. // path: item.path,
  288. new_name: item.name,
  289. // old_path: item.old_path,
  290. })
  291. // Don't update if this is the original client that initiated the action
  292. if(item.original_client_socket_id === window.socket.id)
  293. return;
  294. // Update matching items
  295. // Set new item name
  296. $(`.item[data-uid='${html_encode(item.uid)}'] .item-name`).html(html_encode(truncate_filename(item.name, TRUNCATE_LENGTH)).replaceAll(' ', '&nbsp;'));
  297. // Set new icon
  298. const new_icon = (item.is_dir ? window.icons['folder.svg'] : (await item_icon(item)).image);
  299. $(`.item[data-uid='${item.uid}']`).find('.item-icon-icon').attr('src', new_icon);
  300. // Set new data-name
  301. $(`.item[data-uid='${item.uid}']`).attr('data-name', html_encode(item.name));
  302. $(`.window-${item.uid}`).attr('data-name', html_encode(item.name));
  303. // Set new title attribute
  304. $(`.item[data-uid='${item.uid}']`).attr('title', html_encode(item.name));
  305. $(`.window-${options.uid}`).attr('title', html_encode(item.name));
  306. // Set new value for item-name-editor
  307. $(`.item[data-uid='${item.uid}'] .item-name-editor`).val(html_encode(item.name));
  308. $(`.item[data-uid='${item.uid}'] .item-name`).attr('title', html_encode(item.name));
  309. // Set new data-path
  310. const new_path = item.path;
  311. $(`.item[data-uid='${item.uid}']`).attr('data-path', new_path);
  312. $(`.window-${item.uid}`).attr('data-path', new_path);
  313. // Update all elements that have matching paths
  314. $(`[data-path="${html_encode(item.old_path)}" i]`).each(function(){
  315. $(this).attr('data-path', new_path)
  316. if($(this).hasClass('window-navbar-path-dirname'))
  317. $(this).text(item.name);
  318. });
  319. // Update all elements whose paths start with old_path
  320. $(`[data-path^="${html_encode(item.old_path) + '/'}"]`).each(function(){
  321. const new_el_path = _.replace($(this).attr('data-path'), item.old_path + '/', new_path+'/');
  322. $(this).attr('data-path', new_el_path);
  323. });
  324. // Update all exact-matching windows
  325. $(`.window-${item.uid}`).each(function(){
  326. update_window_path(this, new_path);
  327. })
  328. // Set new name for matching open windows
  329. $(`.window-${item.uid} .window-head-title`).text(item.name);
  330. // Re-sort all matching item containers
  331. $(`.item[data-uid='${item.uid}']`).parent('.item-container').each(function(){
  332. sort_items(this, $(this).closest('.item-container').attr('data-sort_by'), $(this).closest('.item-container').attr('data-sort_order'));
  333. })
  334. });
  335. socket.on('item.added', async (item) => {
  336. // if item is empty, don't proceed
  337. if(_.isEmpty(item))
  338. return;
  339. // Notify all apps that are watching this item
  340. sendItemChangeEventToWatchingApps(item.uid, {
  341. event: 'write',
  342. uid: item.uid,
  343. // path: item.path,
  344. new_size: item.size,
  345. modified: item.modified,
  346. // old_path: item.old_path,
  347. });
  348. // Don't update if this is the original client that initiated the action
  349. if(item.original_client_socket_id === window.socket.id)
  350. return;
  351. // Update replaced items with matching uids
  352. if(item.overwritten_uid){
  353. $(`.item[data-uid='${item.overwritten_uid}']`).attr({
  354. 'data-immutable': item.immutable,
  355. 'data-path': item.path,
  356. 'data-name': item.name,
  357. 'data-size': item.size,
  358. 'data-modified': item.modified,
  359. 'data-is_shared': item.is_shared,
  360. 'data-type': item.type,
  361. })
  362. // set new icon
  363. const new_icon = (item.is_dir ? window.icons['folder.svg'] : (await item_icon(item)).image);
  364. $(`.item[data-uid="${item.overwritten_uid}"]`).find('.item-icon > img').attr('src', new_icon);
  365. //sort each window
  366. $(`.item-container[data-path='${html_encode(item.dirpath)}' i]`).each(function(){
  367. sort_items(this, $(this).attr('data-sort_by'), $(this).attr('data-sort_order'))
  368. })
  369. }
  370. else{
  371. UIItem({
  372. appendTo: $(`.item-container[data-path='${html_encode(item.dirpath)}' i]`),
  373. uid: item.uid,
  374. immutable: item.immutable,
  375. associated_app_name: item.associated_app?.name,
  376. path: item.path,
  377. icon: await item_icon(item),
  378. name: item.name,
  379. size: item.size,
  380. type: item.type,
  381. modified: item.modified,
  382. is_dir: item.is_dir,
  383. is_shared: item.is_shared,
  384. is_shortcut: item.is_shortcut,
  385. associated_app_name: item.associated_app?.name,
  386. shortcut_to: item.shortcut_to,
  387. shortcut_to_path: item.shortcut_to_path,
  388. });
  389. //sort each window
  390. $(`.item-container[data-path='${html_encode(item.dirpath)}' i]`).each(function(){
  391. sort_items(this, $(this).attr('data-sort_by'), $(this).attr('data-sort_order'))
  392. })
  393. }
  394. });
  395. // Hidden file dialog
  396. h += `<form name="upload-form" id="upload-form" style="display:hidden;">
  397. <input type="hidden" name="name" id="upload-filename" value="">
  398. <input type="hidden" name="path" id="upload-target-path" value="">
  399. <input type="file" name="file" id="upload-file-dialog" style="display: none;" multiple="multiple">
  400. </form>`;
  401. h += `<div class="window-container"></div>`;
  402. // Desktop
  403. // If desktop is not in fullpage/embedded mode, we hide it until files and directories are loaded and then fade in the UI
  404. // This gives a calm and smooth experience for the user
  405. h += `<div class="desktop item-container disable-user-select"
  406. data-uid="${options.desktop_fsentry.uid}"
  407. data-sort_by="${!options.desktop_fsentry.sort_by ? 'name' : options.desktop_fsentry.sort_by}"
  408. data-sort_order="${!options.desktop_fsentry.sort_order ? 'asc' : options.desktop_fsentry.sort_order}"
  409. data-path="${html_encode(desktop_path)}"
  410. >`;
  411. h += `</div>`;
  412. // Get window sidebar width
  413. getItem({
  414. key: "window_sidebar_width",
  415. success: async function(res){
  416. let value = parseInt(res.value);
  417. // if value is a valid number
  418. if(!isNaN(value) && value > 0){
  419. window.window_sidebar_width = value;
  420. }
  421. }
  422. })
  423. // Remove `?ref=...` from navbar URL
  424. if(url_query_params.has('ref')){
  425. window.history.pushState(null, document.title, '/');
  426. }
  427. // Append to <body>
  428. $('body').append(h);
  429. // Set desktop height based on taskbar height
  430. $('.desktop').css('height', `calc(100vh - ${window.taskbar_height + window.toolbar_height}px)`)
  431. // ---------------------------------------------------------------
  432. // Taskbar
  433. // ---------------------------------------------------------------
  434. UITaskbar();
  435. const el_desktop = document.querySelector('.desktop');
  436. window.active_element = el_desktop;
  437. window.active_item_container = el_desktop;
  438. // --------------------------------------------------------
  439. // Dragster
  440. // Allow dragging of local files onto desktop.
  441. // --------------------------------------------------------
  442. $(el_desktop).dragster({
  443. enter: function (dragsterEvent, event) {
  444. $('.context-menu').remove();
  445. },
  446. leave: function (dragsterEvent, event) {
  447. },
  448. drop: async function (dragsterEvent, event) {
  449. const e = event.originalEvent;
  450. // no drop on item
  451. if($(event.target).hasClass('item') || $(event.target).parent('.item').length > 0)
  452. return false;
  453. // recursively create directories and upload files
  454. if(e.dataTransfer?.items?.length>0){
  455. upload_items(e.dataTransfer.items, desktop_path);
  456. }
  457. e.stopPropagation();
  458. e.preventDefault();
  459. return false;
  460. }
  461. });
  462. // --------------------------------------------------------
  463. // Droppable
  464. // --------------------------------------------------------
  465. $(el_desktop).droppable({
  466. accept: '.item',
  467. tolerance: "intersect",
  468. drop: function( event, ui ) {
  469. // Check if item was actually dropped on desktop and not a window
  470. if(mouseover_window !== undefined)
  471. return;
  472. // Can't drop anything but UIItems on desktop
  473. if(!$(ui.draggable).hasClass('item'))
  474. return;
  475. // Don't move an item to its current directory
  476. if( path.dirname($(ui.draggable).attr('data-path')) === desktop_path && !event.ctrlKey)
  477. return;
  478. // If ctrl is pressed and source is Trashed, cancel whole operation
  479. if(event.ctrlKey && path.dirname($(ui.draggable).attr('data-path')) === window.trash_path)
  480. return;
  481. // Unselect previously selected items
  482. $(el_desktop).children('.item-selected').removeClass('item-selected');
  483. const items_to_move = []
  484. // first item
  485. items_to_move.push(ui.draggable);
  486. // all subsequent items
  487. const cloned_items = document.getElementsByClassName('item-selected-clone');
  488. for(let i =0; i<cloned_items.length; i++){
  489. const source_item = document.getElementById('item-' + $(cloned_items[i]).attr('data-id'));
  490. if(source_item !== null)
  491. items_to_move.push(source_item);
  492. }
  493. // if ctrl key is down, copy items
  494. if(event.ctrlKey){
  495. // unless source is Trash
  496. if(path.dirname($(ui.draggable).attr('data-path')) === window.trash_path)
  497. return;
  498. copy_items(items_to_move, desktop_path)
  499. }
  500. // otherwise, move items
  501. else{
  502. move_items(items_to_move, desktop_path);
  503. }
  504. }
  505. });
  506. //--------------------------------------------------
  507. // ContextMenu
  508. //--------------------------------------------------
  509. $(el_desktop).bind("contextmenu taphold", function (event) {
  510. // dismiss taphold on regular devices
  511. if(event.type==='taphold' && !isMobile.phone && !isMobile.tablet)
  512. return;
  513. const $target = $(event.target);
  514. // elements that should retain native ctxmenu
  515. if($target.is('input') || $target.is('textarea'))
  516. return true
  517. // custom ctxmenu for all other elements
  518. if(event.target === el_desktop){
  519. event.preventDefault();
  520. UIContextMenu({
  521. items: [
  522. // -------------------------------------------
  523. // Sort by
  524. // -------------------------------------------
  525. {
  526. html: "Sort by",
  527. items: [
  528. {
  529. html: `Name`,
  530. icon: $(el_desktop).attr('data-sort_by') === 'name' ? '✓' : '',
  531. onClick: async function(){
  532. sort_items(el_desktop, 'name', $(el_desktop).attr('data-sort_order'));
  533. set_sort_by(options.desktop_fsentry.uid, 'name', $(el_desktop).attr('data-sort_order'))
  534. }
  535. },
  536. {
  537. html: `Date modified`,
  538. icon: $(el_desktop).attr('data-sort_by') === 'modified' ? '✓' : '',
  539. onClick: async function(){
  540. sort_items(el_desktop, 'modified', $(el_desktop).attr('data-sort_order'));
  541. set_sort_by(options.desktop_fsentry.uid, 'modified', $(el_desktop).attr('data-sort_order'))
  542. }
  543. },
  544. {
  545. html: `Type`,
  546. icon: $(el_desktop).attr('data-sort_by') === 'type' ? '✓' : '',
  547. onClick: async function(){
  548. sort_items(el_desktop, 'type', $(el_desktop).attr('data-sort_order'));
  549. set_sort_by(options.desktop_fsentry.uid, 'type', $(el_desktop).attr('data-sort_order'))
  550. }
  551. },
  552. {
  553. html: `Size`,
  554. icon: $(el_desktop).attr('data-sort_by') === 'size' ? '✓' : '',
  555. onClick: async function(){
  556. sort_items(el_desktop, 'size', $(el_desktop).attr('data-sort_order'));
  557. set_sort_by(options.desktop_fsentry.uid, 'size', $(el_desktop).attr('data-sort_order'))
  558. }
  559. },
  560. // -------------------------------------------
  561. // -
  562. // -------------------------------------------
  563. '-',
  564. {
  565. html: `Ascending`,
  566. icon: $(el_desktop).attr('data-sort_order') === 'asc' ? '✓' : '',
  567. onClick: async function(){
  568. const sort_by = $(el_desktop).attr('data-sort_by')
  569. sort_items(el_desktop, sort_by, 'asc');
  570. set_sort_by(options.desktop_fsentry.uid, sort_by, 'asc')
  571. }
  572. },
  573. {
  574. html: `Descending`,
  575. icon: $(el_desktop).attr('data-sort_order') === 'desc' ? '✓' : '',
  576. onClick: async function(){
  577. const sort_by = $(el_desktop).attr('data-sort_by')
  578. sort_items(el_desktop, sort_by, 'desc');
  579. set_sort_by(options.desktop_fsentry.uid, sort_by, 'desc')
  580. }
  581. },
  582. ]
  583. },
  584. // -------------------------------------------
  585. // Refresh
  586. // -------------------------------------------
  587. {
  588. html: "Refresh",
  589. onClick: function(){
  590. refresh_item_container(el_desktop);
  591. }
  592. },
  593. // -------------------------------------------
  594. // Show/Hide hidden files
  595. // -------------------------------------------
  596. {
  597. html: `${window.user_preferences.show_hidden_files ? 'Hide' : 'Show'} hidden files`,
  598. onClick: function(){
  599. window.mutate_user_preferences({
  600. show_hidden_files : !window.user_preferences.show_hidden_files,
  601. });
  602. window.show_or_hide_files(document.querySelectorAll('.item-container'));
  603. }
  604. },
  605. // -------------------------------------------
  606. // -
  607. // -------------------------------------------
  608. '-',
  609. // -------------------------------------------
  610. // New File
  611. // -------------------------------------------
  612. window.new_context_menu_item(desktop_path, el_desktop),
  613. // -------------------------------------------
  614. // -
  615. // -------------------------------------------
  616. '-',
  617. // -------------------------------------------
  618. // Paste
  619. // -------------------------------------------
  620. {
  621. html: "Paste",
  622. disabled: clipboard.length > 0 ? false : true,
  623. onClick: function(){
  624. if(clipboard_op === 'copy')
  625. copy_clipboard_items(desktop_path, el_desktop);
  626. else if(clipboard_op === 'move')
  627. move_clipboard_items(el_desktop)
  628. }
  629. },
  630. // -------------------------------------------
  631. // Upload Here
  632. // -------------------------------------------
  633. {
  634. html: "Upload Here",
  635. onClick: function(){
  636. init_upload_using_dialog(el_desktop);
  637. }
  638. },
  639. // -------------------------------------------
  640. // Request Files
  641. // -------------------------------------------
  642. // {
  643. // html: "Request Files",
  644. // onClick: function(){
  645. // UIWindowRequestFiles({dir_path: desktop_path})
  646. // }
  647. // },
  648. // -------------------------------------------
  649. // -
  650. // -------------------------------------------
  651. '-',
  652. // -------------------------------------------
  653. // Change Desktop Background…
  654. // -------------------------------------------
  655. {
  656. html: "Change Desktop Background…",
  657. onClick: function(){
  658. UIWindowDesktopBGSettings();
  659. }
  660. },
  661. ]
  662. });
  663. }
  664. });
  665. //-------------------------------------------
  666. // Desktop Files/Folders
  667. // we don't need to get the desktop items if we're in embedded or fullpage mode
  668. // because the items aren't visible anyway and we don't need to waste bandwidth/server resources
  669. //-------------------------------------------
  670. if(!is_embedded && !window.is_fullpage_mode){
  671. refresh_item_container(el_desktop, {fadeInItems: true})
  672. window.launch_download_from_url();
  673. }
  674. // -------------------------------------------
  675. // Selectable
  676. // Only for desktop
  677. // -------------------------------------------
  678. if(!isMobile.phone && !isMobile.tablet){
  679. let selected_ctrl_items = [];
  680. const selection = new SelectionArea({
  681. selectionContainerClass: '.selection-area-container',
  682. container: '.desktop',
  683. selectables: ['.desktop.item-container > .item'],
  684. startareas: ['.desktop'],
  685. boundaries: ['.desktop'],
  686. behaviour: {
  687. overlap: 'drop',
  688. intersect: 'touch',
  689. startThreshold: 10,
  690. scrolling: {
  691. speedDivider: 10,
  692. manualSpeed: 750,
  693. startScrollMargins: {x: 0, y: 0}
  694. }
  695. },
  696. features: {
  697. touch: true,
  698. range: true,
  699. singleTap: {
  700. allow: true,
  701. intersect: 'native'
  702. }
  703. }
  704. });
  705. selection.on('beforestart', ({event}) => {
  706. selected_ctrl_items = [];
  707. // Returning false prevents a selection
  708. return $(event.target).hasClass('item-container');
  709. })
  710. .on('beforedrag', evt => {
  711. })
  712. .on('start', ({store, event}) => {
  713. if (!event.ctrlKey && !event.metaKey) {
  714. for (const el of store.stored) {
  715. el.classList.remove('item-selected');
  716. }
  717. selection.clearSelection();
  718. }
  719. })
  720. .on('move', ({store: {changed: {added, removed}}, event}) => {
  721. for (const el of added) {
  722. // if ctrl or meta key is pressed and the item is already selected, then unselect it
  723. if((event.ctrlKey || event.metaKey) && $(el).hasClass('item-selected')){
  724. el.classList.remove('item-selected');
  725. selected_ctrl_items.push(el);
  726. }
  727. // otherwise select it
  728. else{
  729. el.classList.add('item-selected');
  730. }
  731. }
  732. for (const el of removed) {
  733. el.classList.remove('item-selected');
  734. // in case this item was selected by ctrl+click before, then reselect it again
  735. if(selected_ctrl_items.includes(el))
  736. $(el).not('.item-disabled').addClass('item-selected');
  737. }
  738. })
  739. .on('stop', evt => {
  740. });
  741. }
  742. // ----------------------------------------------------
  743. // User options
  744. // ----------------------------------------------------
  745. let ht = '';
  746. ht += `<div class="toolbar" style="height:${window.toolbar_height}px;">`;
  747. // logo
  748. ht += `<div class="toolbar-btn toolbar-puter-logo" title="Puter" style="margin-left: 10px; margin-right: auto;"><img src="${window.icons['logo-white.svg']}" draggable="false" style="display:block; width:17px; height:17px"></div>`;
  749. // create account button
  750. ht += `<div class="toolbar-btn user-options-create-account-btn ${window.user.is_temp ? '' : 'hidden' }" style="padding:0; opacity:1;" title="Save Account">`;
  751. ht += `<svg style="width: 17px; height: 17px;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px" height="48px" viewBox="0 0 48 48"><g transform="translate(0, 0)"><path d="M45.521,39.04L27.527,5.134c-1.021-1.948-3.427-2.699-5.375-1.679-.717,.376-1.303,.961-1.679,1.679L2.479,39.04c-.676,1.264-.635,2.791,.108,4.017,.716,1.207,2.017,1.946,3.42,1.943H41.993c1.403,.003,2.704-.736,3.42-1.943,.743-1.226,.784-2.753,.108-4.017ZM23.032,15h1.937c.565,0,1.017,.467,1,1.031l-.438,14c-.017,.54-.459,.969-1,.969h-1.062c-.54,0-.983-.429-1-.969l-.438-14c-.018-.564,.435-1.031,1-1.031Zm.968,25c-1.657,0-3-1.343-3-3s1.343-3,3-3,3,1.343,3,3-1.343,3-3,3Z" fill="#ffbb00"></path></g></svg>`;
  752. ht += `</div>`;
  753. // 'show desktop'
  754. if(window.is_fullpage_mode){
  755. ht += `<a href="/" class="show-desktop-btn toolbar-btn antialiased" target="_blank" title="Open Desktop">Open Desktop</a>`;
  756. }
  757. // refer
  758. if(user.referral_code){
  759. ht += `<div class="toolbar-btn refer-btn" title="Refer" style="background-image:url(${window.icons['gift.svg']});"></div>`;
  760. }
  761. // do not show the fullscreen button on mobile devices since it's broken
  762. if(!isMobile.phone){
  763. // fullscreen button
  764. ht += `<div class="toolbar-btn fullscreen-btn" title="Enter Full Screen" style="background-image:url(${window.icons['fullscreen.svg']})"></div>`;
  765. }
  766. // qr code button -- only show if not embedded
  767. if(!is_embedded)
  768. ht += `<div class="toolbar-btn qr-btn" title="QR code" style="background-image:url(${window.icons['qr.svg']})"></div>`;
  769. // user options menu
  770. ht += `<div class="toolbar-btn user-options-menu-btn" style="background-image:url(${window.icons['profile.svg']})">`;
  771. h += `<span class="user-options-menu-username">${window.user.username}</span>`;
  772. ht += `</div>`;
  773. ht += `</div>`;
  774. // prepend toolbar to desktop
  775. $(ht).insertBefore(el_desktop);
  776. // adjust window container to take into account the toolbar height
  777. $('.window-container').css('top', window.toolbar_height);
  778. // ---------------------------------------------
  779. // Run apps from insta-login URL
  780. // ---------------------------------------------
  781. if(url_query_params.has('app')){
  782. let url_app_name = url_query_params.get('app');
  783. if(url_app_name === 'explorer'){
  784. let predefined_path = home_path;
  785. if(url_query_params.has('path'))
  786. predefined_path =url_query_params.get('path')
  787. // launch explorer
  788. UIWindow({
  789. path: predefined_path,
  790. title: path.basename(predefined_path),
  791. icon: await item_icon({is_dir: true, path: predefined_path}),
  792. // todo
  793. // uid: $(el_item).attr('data-uid'),
  794. is_dir: true,
  795. // todo
  796. // sort_by: $(el_item).attr('data-sort_by'),
  797. app: 'explorer',
  798. });
  799. }
  800. }
  801. // ---------------------------------------------
  802. // load from direct app URLs: /app/app-name
  803. // ---------------------------------------------
  804. else if(window.app_launched_from_url){
  805. let qparams = new URLSearchParams(window.location.search);
  806. if(!qparams.has('c')){
  807. launch_app({
  808. name: app_launched_from_url,
  809. readURL: qparams.get('readURL'),
  810. maximized: qparams.get('maximized'),
  811. is_fullpage: window.is_fullpage_mode,
  812. window_options: {
  813. stay_on_top: false,
  814. }
  815. });
  816. }
  817. }
  818. $(el_desktop).on('mousedown touchstart', function(e){
  819. // dimiss touchstart on regular devices
  820. if(e.type==='taphold' && !isMobile.phone && !isMobile.tablet)
  821. return;
  822. // disable pointer-events for all app iframes, this is to make sure selectable works
  823. $('.window-app-iframe').css('pointer-events', 'none');
  824. $('.window').find('.item-selected').addClass('item-blurred');
  825. $('.desktop').find('.item-blurred').removeClass('item-blurred');
  826. })
  827. $(el_desktop).on('click', function(e){
  828. // blur all windows
  829. $('.window-active').removeClass('window-active');
  830. })
  831. function display_ct() {
  832. var x = new Date()
  833. var ampm = x.getHours( ) >= 12 ? ' PM' : ' AM';
  834. let hours = x.getHours( ) % 12;
  835. hours = hours ? hours : 12;
  836. hours=hours.toString().length==1? 0+hours.toString() : hours;
  837. var minutes=x.getMinutes().toString()
  838. minutes=minutes.length==1 ? 0+minutes : minutes;
  839. var seconds=x.getSeconds().toString()
  840. seconds=seconds.length==1 ? 0+seconds : seconds;
  841. var month=(x.getMonth() +1).toString();
  842. month=month.length==1 ? 0+month : month;
  843. var dt=x.getDate().toString();
  844. dt=dt.length==1 ? 0+dt : dt;
  845. var x1=month + "/" + dt + "/" + x.getFullYear();
  846. x1 = x1 + " - " + hours + ":" + minutes + ":" + seconds + " " + ampm;
  847. $('#clock').html(x1);
  848. $('#clock').css('line-height', taskbar_height + 'px');
  849. }
  850. setInterval(display_ct, 1000);
  851. // show referral notice window
  852. if(window.show_referral_notice && !user.email_confirmed){
  853. getItem({
  854. key: "shown_referral_notice",
  855. success: async function(res){
  856. if(!res){
  857. setTimeout(() => {
  858. UIWindowClaimReferral();
  859. }, 1000);
  860. setItem({
  861. key: "shown_referral_notice",
  862. value: true,
  863. })
  864. }
  865. }
  866. })
  867. }
  868. }
  869. $(document).on('contextmenu taphold', '.taskbar', function(event){
  870. // dismiss taphold on regular devices
  871. if(event.type==='taphold' && !isMobile.phone && !isMobile.tablet)
  872. return;
  873. event.preventDefault();
  874. event.stopPropagation();
  875. UIContextMenu({
  876. parent_element: $('.taskbar'),
  877. items: [
  878. //--------------------------------------------------
  879. // Show open windows
  880. //--------------------------------------------------
  881. {
  882. html: "Show open windows",
  883. onClick: function(){
  884. $(`.window`).showWindow();
  885. }
  886. },
  887. //--------------------------------------------------
  888. // Show the desktop
  889. //--------------------------------------------------
  890. {
  891. html: "Show the desktop",
  892. onClick: function(){
  893. $(`.window`).hideWindow();
  894. }
  895. }
  896. ]
  897. });
  898. return false;
  899. })
  900. $(document).on('click', '.qr-btn', async function (e) {
  901. UIWindowQR();
  902. })
  903. $(document).on('click', '.user-options-menu-btn', async function(e){
  904. const pos = this.getBoundingClientRect();
  905. if($('.context-menu[data-id="user-options-menu"]').length > 0)
  906. return;
  907. let items = [];
  908. let parent_element = this;
  909. //--------------------------------------------------
  910. // Save Session
  911. //--------------------------------------------------
  912. if(window.user.is_temp){
  913. items.push(
  914. {
  915. html: `Save Session`,
  916. icon: `<svg style="margin-bottom: -4px; width: 16px; height: 16px;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px" height="48px" viewBox="0 0 48 48"><g transform="translate(0, 0)"><path d="M45.521,39.04L27.527,5.134c-1.021-1.948-3.427-2.699-5.375-1.679-.717,.376-1.303,.961-1.679,1.679L2.479,39.04c-.676,1.264-.635,2.791,.108,4.017,.716,1.207,2.017,1.946,3.42,1.943H41.993c1.403,.003,2.704-.736,3.42-1.943,.743-1.226,.784-2.753,.108-4.017ZM23.032,15h1.937c.565,0,1.017,.467,1,1.031l-.438,14c-.017,.54-.459,.969-1,.969h-1.062c-.54,0-.983-.429-1-.969l-.438-14c-.018-.564,.435-1.031,1-1.031Zm.968,25c-1.657,0-3-1.343-3-3s1.343-3,3-3,3,1.343,3,3-1.343,3-3,3Z" fill="#ffbb00"></path></g></svg>`,
  917. icon_active: `<svg style="margin-bottom: -4px; width: 16px; height: 16px;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="48px" height="48px" viewBox="0 0 48 48"><g transform="translate(0, 0)"><path d="M45.521,39.04L27.527,5.134c-1.021-1.948-3.427-2.699-5.375-1.679-.717,.376-1.303,.961-1.679,1.679L2.479,39.04c-.676,1.264-.635,2.791,.108,4.017,.716,1.207,2.017,1.946,3.42,1.943H41.993c1.403,.003,2.704-.736,3.42-1.943,.743-1.226,.784-2.753,.108-4.017ZM23.032,15h1.937c.565,0,1.017,.467,1,1.031l-.438,14c-.017,.54-.459,.969-1,.969h-1.062c-.54,0-.983-.429-1-.969l-.438-14c-.018-.564,.435-1.031,1-1.031Zm.968,25c-1.657,0-3-1.343-3-3s1.343-3,3-3,3,1.343,3,3-1.343,3-3,3Z" fill="#ffbb00"></path></g></svg>`,
  918. onClick: async function(){
  919. UIWindowSaveAccount({
  920. send_confirmation_code: false,
  921. default_username: window.user.username
  922. });
  923. }
  924. },
  925. )
  926. // -------------------------------------------
  927. // -
  928. // -------------------------------------------
  929. items.push('-')
  930. }
  931. // -------------------------------------------
  932. // Logged in users
  933. // -------------------------------------------
  934. if(window.logged_in_users.length > 0){
  935. let users_arr = window.logged_in_users;
  936. // bring logged in user's item to top
  937. users_arr.sort(function(x,y){ return x.uuid === window.user.uuid ? -1 : y.uuid == window.user.uuid ? 1 : 0; });
  938. // create menu items
  939. users_arr.forEach(l_user => {
  940. items.push(
  941. {
  942. html: l_user.username,
  943. icon: l_user.username === user.username ? '✓' : '',
  944. onClick: async function(val){
  945. // don't reload everything if clicked on already-logged-in user
  946. if(l_user.username === user.username)
  947. return;
  948. // update auth data
  949. update_auth_data(l_user.auth_token, l_user);
  950. // refresh
  951. location.reload();
  952. }
  953. },
  954. )
  955. });
  956. // -------------------------------------------
  957. // -
  958. // -------------------------------------------
  959. items.push('-')
  960. items.push(
  961. {
  962. html: 'Add existing account',
  963. // icon: l_user.username === user.username ? '✓' : '',
  964. onClick: async function(val){
  965. await UIWindowLogin({
  966. reload_on_success: true,
  967. send_confirmation_code: false,
  968. window_options:{
  969. has_head: true
  970. }
  971. });
  972. }
  973. },
  974. )
  975. // -------------------------------------------
  976. // -
  977. // -------------------------------------------
  978. items.push('-')
  979. }
  980. UIContextMenu({
  981. id: 'user-options-menu',
  982. parent_element: parent_element,
  983. position: {top: pos.top + 28, left: pos.left + pos.width - 15},
  984. items: [
  985. ...items,
  986. //--------------------------------------------------
  987. // My Websites
  988. //--------------------------------------------------
  989. {
  990. html: "My Websites",
  991. onClick: async function(){
  992. UIWindowMyWebsites();
  993. }
  994. },
  995. //--------------------------------------------------
  996. // Change Username
  997. //--------------------------------------------------
  998. {
  999. html: "Change Username",
  1000. onClick: async function(){
  1001. UIWindowChangeUsername();
  1002. }
  1003. },
  1004. //--------------------------------------------------
  1005. // Change Password
  1006. //--------------------------------------------------
  1007. {
  1008. html: "Change Password",
  1009. onClick: async function(){
  1010. UIWindowChangePassword();
  1011. }
  1012. },
  1013. //--------------------------------------------------
  1014. // Contact Us
  1015. //--------------------------------------------------
  1016. {
  1017. html: "Contact Us",
  1018. onClick: async function(){
  1019. UIWindowFeedback();
  1020. }
  1021. },
  1022. // -------------------------------------------
  1023. // -
  1024. // -------------------------------------------
  1025. '-',
  1026. //--------------------------------------------------
  1027. // Log Out
  1028. //--------------------------------------------------
  1029. {
  1030. html: "Log Out",
  1031. onClick: async function(){
  1032. // see if there are any open windows, if yes notify user
  1033. if($('.window-app').length > 0){
  1034. const alert_resp = await UIAlert({
  1035. message: `<p>You have open apps. Are you sure you want to log out?</p>`,
  1036. buttons:[
  1037. {
  1038. label: 'Close Windows and Log Out',
  1039. type: 'primary',
  1040. },
  1041. {
  1042. label: 'Cancel'
  1043. },
  1044. ]
  1045. })
  1046. if(alert_resp === 'Close Windows and Log Out')
  1047. logout();
  1048. }
  1049. // no open windows
  1050. else
  1051. logout();
  1052. }
  1053. },
  1054. ]
  1055. });
  1056. })
  1057. $(document).on('click', '.fullscreen-btn', async function (e) {
  1058. if(!is_fullscreen()) {
  1059. var elem = document.documentElement;
  1060. if (elem.requestFullscreen) {
  1061. elem.requestFullscreen();
  1062. } else if (elem.webkitRequestFullscreen) { /* Safari */
  1063. elem.webkitRequestFullscreen();
  1064. } else if (elem.mozRequestFullScreen) { /* moz */
  1065. elem.mozRequestFullScreen();
  1066. } else if (elem.msRequestFullscreen) { /* IE11 */
  1067. elem.msRequestFullscreen();
  1068. }
  1069. }
  1070. else{
  1071. if (document.exitFullscreen) {
  1072. document.exitFullscreen();
  1073. } else if (document.webkitExitFullscreen) {
  1074. document.webkitExitFullscreen();
  1075. } else if (document.mozCancelFullScreen) {
  1076. document.mozCancelFullScreen();
  1077. } else if (document.msExitFullscreen) {
  1078. document.msExitFullscreen();
  1079. }
  1080. }
  1081. })
  1082. $(document).on('click', '.close-launch-popover', function(){
  1083. $(".launch-popover").closest('.popover').fadeOut(200, function(){
  1084. $(".launch-popover").closest('.popover').remove();
  1085. });
  1086. });
  1087. $(document).on('click', '.toolbar-puter-logo', function(){
  1088. // launch the about app
  1089. launch_app({name: 'about', window_options:{
  1090. single_instance: true,
  1091. }});
  1092. })
  1093. $(document).on('click', '.user-options-create-account-btn', async function(e){
  1094. UIWindowSaveAccount({
  1095. send_confirmation_code: false,
  1096. default_username: window.user.username,
  1097. });
  1098. })
  1099. $(document).on('click', '.refer-btn', async function(e){
  1100. UIWindowRefer();
  1101. })
  1102. $(document).on('click', '.start-app', async function(e){
  1103. launch_app({
  1104. name: $(this).attr('data-app-name')
  1105. })
  1106. // close popovers
  1107. $(".popover").fadeOut(200, function(){
  1108. $(".popover").remove();
  1109. });
  1110. })
  1111. $(document).on('click', '.user-options-login-btn', async function(e){
  1112. const alert_resp = await UIAlert({
  1113. message: `<strong>Save session before exiting!</strong><p>You are in a temporary session and logging into another account will erase all data in your current session.</p>`,
  1114. buttons:[
  1115. {
  1116. label: 'Save session',
  1117. value: 'save-session',
  1118. type: 'primary',
  1119. },
  1120. {
  1121. label: 'Log into another account anyway',
  1122. value: 'login',
  1123. },
  1124. {
  1125. label: 'Cancel'
  1126. },
  1127. ]
  1128. })
  1129. if(alert_resp === 'save-session'){
  1130. let saved = await UIWindowSaveAccount({
  1131. send_confirmation_code: false,
  1132. });
  1133. if(saved)
  1134. UIWindowLogin({show_signup_button: false, reload_on_success: true});
  1135. }else if (alert_resp === 'login'){
  1136. UIWindowLogin({
  1137. show_signup_button: false,
  1138. reload_on_success: true,
  1139. window_options: {
  1140. backdrop: true,
  1141. close_on_backdrop_click: false,
  1142. }
  1143. });
  1144. }
  1145. })
  1146. $(document).on('click mousedown', '.launch-search, .launch-popover', function(e){
  1147. $(this).focus();
  1148. e.stopPropagation();
  1149. e.preventDefault();
  1150. // don't let click bubble up to window
  1151. e.stopImmediatePropagation();
  1152. })
  1153. $(document).on('focus', '.launch-search', function(e){
  1154. // remove all selected items in start menu
  1155. $('.launch-app-selected').removeClass('launch-app-selected');
  1156. // scroll popover to top
  1157. $('.launch-popover').scrollTop(0);
  1158. })
  1159. $(document).on('change keyup keypress keydown paste', '.launch-search', function(e){
  1160. // search launch_apps.recommended for query
  1161. const query = $(this).val().toLowerCase();
  1162. if(query === ''){
  1163. $('.launch-search-clear').hide();
  1164. $(`.start-app-card`).show();
  1165. $('.launch-apps-recent').show();
  1166. $('.start-section-heading').show();
  1167. }else{
  1168. $('.launch-apps-recent').hide();
  1169. $('.start-section-heading').hide();
  1170. $('.launch-search-clear').show();
  1171. launch_apps.recommended.forEach((app)=>{
  1172. if(app.title.toLowerCase().includes(query.toLowerCase())){
  1173. $(`.start-app-card[data-name="${app.name}"]`).show();
  1174. }else{
  1175. $(`.start-app-card[data-name="${app.name}"]`).hide();
  1176. }
  1177. })
  1178. }
  1179. })
  1180. $(document).on('click', '.launch-search-clear', function(e){
  1181. $('.launch-search').val('');
  1182. $('.launch-search').trigger('change');
  1183. $('.launch-search').focus();
  1184. })
  1185. document.addEventListener('fullscreenchange', (event) => {
  1186. // document.fullscreenElement will point to the element that
  1187. // is in fullscreen mode if there is one. If there isn't one,
  1188. // the value of the property is null.
  1189. if (document.fullscreenElement) {
  1190. $('.fullscreen-btn').css('background-image', `url(${window.icons['shrink.svg']})`);
  1191. $('.fullscreen-btn').attr('title', 'Exit Full Screen');
  1192. $('#clock').show();
  1193. } else {
  1194. $('.fullscreen-btn').css('background-image', `url(${window.icons['fullscreen.svg']})`);
  1195. $('.fullscreen-btn').attr('title', 'Enter Full Screen');
  1196. $('#clock').hide();
  1197. }
  1198. })
  1199. window.set_desktop_background = function(options){
  1200. if(options.fit){
  1201. let fit = options.fit;
  1202. if(fit === 'cover' || fit === 'contain'){
  1203. $('body').css('background-size', fit);
  1204. $('body').css('background-repeat', `no-repeat`);
  1205. $('body').css('background-position', `center center`);
  1206. }
  1207. else if(fit === 'center'){
  1208. $('body').css('background-size', 'auto');
  1209. $('body').css('background-repeat', `no-repeat`);
  1210. $('body').css('background-position', `center center`);
  1211. }
  1212. else if( fit === 'repeat'){
  1213. $('body').css('background-size', `auto`);
  1214. $('body').css('background-repeat', `repeat`);
  1215. }
  1216. window.desktop_bg_fit = fit;
  1217. }
  1218. if(options.url){
  1219. $('body').css('background-image', `url(${options.url})`);
  1220. window.desktop_bg_url = options.url;
  1221. window.desktop_bg_color = undefined;
  1222. }
  1223. else if(options.color){
  1224. $('body').css({
  1225. 'background-image': `none`,
  1226. 'background-color': options.color,
  1227. });
  1228. window.desktop_bg_color = options.color;
  1229. window.desktop_bg_url = undefined;
  1230. }
  1231. }
  1232. window.update_taskbar = function(){
  1233. let items = []
  1234. $('.taskbar-item-sortable[data-keep-in-taskbar="true"]').each(function(index){
  1235. items.push({
  1236. name: $( this ).attr('data-app'),
  1237. type: 'app',
  1238. })
  1239. })
  1240. // update taskbar in the server-side
  1241. $.ajax({
  1242. url: api_origin + "/update-taskbar-items",
  1243. type: 'POST',
  1244. data: JSON.stringify({
  1245. items: items,
  1246. }),
  1247. async: true,
  1248. contentType: "application/json",
  1249. headers: {
  1250. "Authorization": "Bearer "+auth_token
  1251. },
  1252. })
  1253. }
  1254. window.remove_taskbar_item = function(item){
  1255. $(item).find('*').fadeOut(100, function(){});
  1256. $(item).animate({width: 0}, 200, function(){
  1257. $(item).remove();
  1258. })
  1259. }
  1260. window.enter_fullpage_mode = (el_window)=>{
  1261. $('.taskbar').hide();
  1262. $(el_window).find('.window-head').hide();
  1263. $('body').addClass('fullpage-mode');
  1264. $(el_window).css({
  1265. width: '100%',
  1266. height: '100%',
  1267. top: toolbar_height + 'px',
  1268. left: 0,
  1269. 'border-radius': 0,
  1270. });
  1271. }
  1272. window.exit_fullpage_mode = (el_window)=>{
  1273. $('body').removeClass('fullpage-mode');
  1274. window.taskbar_height = window.default_taskbar_height;
  1275. $('.taskbar').css('height', window.taskbar_height);
  1276. $('.taskbar').show();
  1277. refresh_item_container($('.desktop.item-container'), {fadeInItems: true});
  1278. $(el_window).removeAttr('data-is_fullpage');
  1279. if(el_window){
  1280. reset_window_size_and_position(el_window)
  1281. $(el_window).find('.window-head').show();
  1282. }
  1283. // reset dektop height to take into account the taskbar height
  1284. $('.desktop').css('height', `calc(100vh - ${window.taskbar_height + window.toolbar_height}px)`);
  1285. // hide the 'Show Desktop' button in toolbar
  1286. $('.show-desktop-btn').hide();
  1287. // refresh desktop background
  1288. refresh_desktop_background();
  1289. }
  1290. window.reset_window_size_and_position = (el_window)=>{
  1291. $(el_window).css({
  1292. width: 680,
  1293. height: 380,
  1294. 'border-radius': window_border_radius,
  1295. top: 'calc(50% - 190px)',
  1296. left: 'calc(50% - 340px)',
  1297. });
  1298. }
  1299. export default UIDesktop;