UIDesktop.js 60 KB

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