file_selector.html 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. <html>
  2. <head>
  3. <title>File Selector</title>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
  6. <link rel="stylesheet" href="../../script/tocas/tocas.css">
  7. <link rel="stylesheet" href="../../script/ao.css">
  8. <script type="text/javascript" src="../../script/tocas/tocas.js"></script>
  9. <script type="text/javascript" src="../../script/jquery.min.js"></script>
  10. <script type="text/javascript" src="../../script/ao_module.js"></script>
  11. <style>
  12. body{
  13. background-color:white;
  14. }
  15. .navi{
  16. padding:8px;
  17. background-color:#fcfcfc;
  18. border-bottom:2px solid #34b7eb;
  19. position:fixed;
  20. left:0px;
  21. top:0px;
  22. width:100%;
  23. z-index:99;
  24. }
  25. .pusher{
  26. margin-top:46px;
  27. margin-left:200px;
  28. width: calc(100% - 200px);
  29. }
  30. .pusher .fileListWrapper{
  31. padding-left:20px;
  32. padding-right:20px;
  33. }
  34. .navi .button{
  35. box-shadow: 0 1px 1px 0px rgb(190, 190, 190) !important;
  36. }
  37. .list .item{
  38. cursor:pointer;
  39. }
  40. .list .item:hover{
  41. color:#c7c7c7 !important;
  42. }
  43. .extrapadding{
  44. padding-left:6px !important;
  45. padding-right:6px !important;
  46. }
  47. .fileObject{
  48. overflow-wrap: break-word !important;
  49. display: block !important;
  50. padding:12px !important;
  51. font-size:98%;
  52. }
  53. .fileObject .fileInfo{
  54. display:inline-block !important;
  55. word-break: break-all;
  56. text-overflow: ellipsis !important;
  57. overflow: hidden;
  58. color:black;
  59. user-select: none;
  60. }
  61. .fileObject.selected{
  62. background-color:#d2f2f7 !important;
  63. }
  64. .fileObject.item:hover{
  65. background-color:#f2f2f2;
  66. }
  67. </style>
  68. </head>
  69. <body>
  70. <div class="navi">
  71. <button id="sidebarToggleBtn" class="ts icon tiny button" onclick="ts('.sidebar').sidebar('toggle');"><i class="content icon"></i></button>
  72. <button title="Back" class="ts icon tiny button" onclick="backDir();"><i class="arrow left icon"></i></button>
  73. <button title="Parent" class="ts icon tiny button" onclick="parentDir();"><i class="arrow up icon"></i></button>
  74. <button title="Refresh" class="ts icon tiny button" onclick="refresh();"><i class="refresh icon"></i></button>
  75. <button title="New Folder" class="ts icon tiny button" onclick="newFolder();"><i class="folder icon"></i></button>
  76. <div class="ts action fluid tiny input" style="width: calc(100% - 180px); float: right;">
  77. <input id="addressbar" type="text" placeholder="" onchange="updatePath();">
  78. <button class="ts positive icon button" onclick="confirmSelection();"><i class="checkmark icon"></i></button>
  79. </div>
  80. <div id="newfilenameInput" style="width:100%; margin-top:12px;" align="right">
  81. <div class="ts fluid tiny input" style="width: calc(100% - 180px); float: right;">
  82. <input id="filename" type="text" placeholder="New Filename">
  83. </div>
  84. </div>
  85. <div id="newFolderInput" style="width:100%; margin-top:12px; display:none;" align="right">
  86. <div class="ts fluid action tiny input" style="width: calc(100% - 180px); float: right;">
  87. <input id="foldername" type="text" placeholder="New Folder" value="">
  88. <button class="ts icon button" onclick="createFolder()"><i class="add icon"></i></button>
  89. </div>
  90. </div>
  91. </div>
  92. <div id="sidebar" class="ts left static visible overlapped sidebar" style="background-color:#f5f5f5 !important;z-index:90 !important; width:200px;">
  93. <div id="sidebarPadder" style="height:46px;"></div>
  94. <details class="ts accordion" open>
  95. <summary>
  96. <i class="dropdown icon"></i> User
  97. </summary>
  98. <div class="content" >
  99. <div class="ts list" id="userlist">
  100. </div>
  101. </div>
  102. </details>
  103. <details class="ts accordion" open>
  104. <summary>
  105. <i class="dropdown icon"></i> Storage
  106. </summary>
  107. <div class="content">
  108. <div class="ts list" id="storagelist">
  109. </div>
  110. </div>
  111. </details>
  112. </div>
  113. <div class="pusher">
  114. <br>
  115. <div class="fileListWrapper" style="min-height:300px; width:100%;">
  116. <div id="folderList" class="ts segmented basic fluid list whiteTheme">
  117. <div class="fileObject">
  118. <span class="fileInfo"><i class="loading spinner icon" style="margin-right:12px;"></i> Loading</span>
  119. </div>
  120. </div>
  121. <div id="fileList" class="ts segmented fluid list whiteTheme">
  122. </div>
  123. </div>
  124. <br><br>
  125. </div>
  126. <div id="waitloader" class="ts active dimmer" style="display:none; z-index:999;">
  127. <div id="waitloadertext" class="ts indeterminate text loader">Waiting Response</div>
  128. </div>
  129. <script>
  130. var multiSelect = false;
  131. var type = "file";
  132. var currentDir = "user:/";
  133. var currentFileList = [];
  134. var pathHistory = [];
  135. var ctrlDown = false;
  136. var shiftDown = false;
  137. var lastClickedItemID = 0;
  138. var listenerUUID = "";
  139. var fileOptions = {};
  140. initSelectorObject();
  141. initRoots();
  142. updateWindowResize();
  143. function initSelectorObject(){
  144. var initInfo = loadSelectorInfoFromHash();
  145. //Load the initiation directory
  146. listDir(initInfo.root);
  147. //init global var
  148. type = initInfo.type;
  149. multiSelect = initInfo.allowMultiple;
  150. listenerUUID = initInfo.listenerUUID;
  151. if (initInfo.options != undefined){
  152. fileOptions = JSON.parse(JSON.stringify(initInfo.options));
  153. }
  154. //Load options and parse the UI
  155. if (type == "new"){
  156. //Resize the top bar
  157. $("#sidebarPadder").css("height", "90px");
  158. $(".fileListWrapper").css("padding-top", "50px");
  159. if (typeof(fileOptions.defaultName) != "undefined"){
  160. $("#filename").val(fileOptions.defaultName);
  161. }else{
  162. $("#filename").val("newfile.txt");
  163. }
  164. }else{
  165. $("#newfilenameInput").hide();
  166. }
  167. }
  168. function cancelSelection(){
  169. localStorage.setItem(listenerUUID, JSON.stringify("&&selection_canceled&&"));
  170. }
  171. function confirmSelection(){
  172. var files = [];
  173. $(".selected.fileObject").each(function(){
  174. var filename = decodeURIComponent($(this).attr('filename'));
  175. var filepath = decodeURIComponent($(this).attr('filepath'));
  176. files.push({
  177. filename: filename,
  178. filepath: filepath
  179. });
  180. });
  181. //Check if currentdir end with "/". If not, append it
  182. if(currentDir.substr(currentDir.length - 1, 1) != "/"){
  183. currentDir = currentDir + "/";
  184. }
  185. //Check for special cases
  186. if (files.length == 0 && type == "folder"){
  187. //Select the current path as target instead
  188. var currentPathname = currentDir.split("/");
  189. currentPathname.pop();
  190. currentPathname = currentPathname.pop();
  191. if (currentPathname == ""){
  192. currentPathname = currentDir;
  193. }
  194. files.push({
  195. filename: currentPathname,
  196. filepath: currentDir
  197. });
  198. }else if (files.length == 0 && type == "new"){
  199. //Push this new file into the return structure
  200. var newFilename = $("#filename").val();
  201. files.push({
  202. filename: newFilename,
  203. filepath: currentDir + newFilename
  204. });
  205. }
  206. if (ao_module_virtualDesktop){
  207. if (!ao_module_parentCallback(files)){
  208. //Parent callback not exists
  209. alert("Selection Failed. Is parent window alive?")
  210. }else{
  211. parent.closeFwProcess(ao_module_windowID);
  212. }
  213. }else{
  214. if (listenerUUID == ""){
  215. alert("Invalid listener UUID. Please re-open your file selector.")
  216. return;
  217. }
  218. var selectedFilesInJSON = JSON.stringify(files);
  219. localStorage.setItem(listenerUUID, selectedFilesInJSON);
  220. $("#waitloader").show();
  221. $(".pusher").css("overflow","hidden");
  222. setTimeout(function(){
  223. $("#waitloadertext").html("<i class='remove icon'></i> System is not responding. <br>Please close this window and retry.");
  224. },10000)
  225. }
  226. }
  227. //Handle on window close function, cancel current selection
  228. window.onbeforeunload = function(){
  229. cancelSelection();
  230. }
  231. //Overwrite the ao_module close function
  232. function ao_module_close(){
  233. if (!ao_module_virtualDesktop){
  234. return;
  235. }
  236. if (!ao_module_parentCallback(files)){
  237. alert("Selection Failed. Is parent window alive?")
  238. }else{
  239. parent.closeFwProcess(ao_module_windowID);
  240. }
  241. }
  242. function updatePath(){
  243. var newDir = $("#addressbar").val();
  244. listDir(newDir);
  245. }
  246. function refresh(){
  247. $("#fileList").html("");
  248. $("#folderList").html("");
  249. listDir(currentDir);
  250. }
  251. function newFolder(){
  252. $("#newFolderInput").toggle();
  253. if($("#newFolderInput").is(":visible")){
  254. $("#sidebarPadder").css("height", "90px");
  255. $(".fileListWrapper").css("padding-top", "50px");
  256. }else{
  257. $("#sidebarPadder").css("height", "46px");
  258. $(".fileListWrapper").css("padding-top", "0px");
  259. }
  260. }
  261. function hideFolderNameInput(){
  262. $("#newFolderInput").hide();
  263. $("#sidebarPadder").css("height", "46px");
  264. $(".fileListWrapper").css("padding-top", "0px");
  265. }
  266. function createFolder(){
  267. var folderName = $("#foldername").val();
  268. if (folderName == ""){
  269. folderName = "New Folder"
  270. $("#foldername").val("New Folder");
  271. }
  272. folderName = folderName.replace(/[<>:"/\\|?*]/g, "_");
  273. //Check if folder exists
  274. var nameAlreadyExists = false;
  275. currentFileList.forEach(fileObject => {
  276. if (fileObject.IsDir && fileObject.Filename == folderName){
  277. nameAlreadyExists = true;
  278. }
  279. });
  280. if (nameAlreadyExists){
  281. alert("Folder already exists")
  282. return
  283. }
  284. //Create the new folder request
  285. requestCSRFToken(function(token){
  286. $.ajax({
  287. url: "../../system/file_system/newItem",
  288. data: {type: "folder", src: currentDir, filename: folderName, csrft: token},
  289. success: function(data){
  290. if (data.error !== undefined){
  291. alert(data.error);
  292. }else{
  293. refresh()
  294. }
  295. hideFolderNameInput();
  296. }
  297. });
  298. });
  299. }
  300. function loadSelectorInfoFromHash(){
  301. if (window.location.hash.length == 0){
  302. return {
  303. root: "user:/",
  304. type: "file",
  305. allowMultiple: false
  306. }
  307. }else{
  308. try{
  309. var selectInfo = JSON.parse(decodeURIComponent(window.location.hash.substring(1)));
  310. return selectInfo;
  311. }catch{
  312. //Error parsing the input. Use default settings
  313. return {
  314. root: "user:/",
  315. type: "file",
  316. allowMultiple: false
  317. }
  318. }
  319. }
  320. }
  321. function listDir(dir){
  322. currentDir = dir;
  323. pathHistory.push(currentDir);
  324. $("#addressbar").val(currentDir);
  325. ao_module_setWindowTitle(`Open`);
  326. $.get("../../system/file_system/listDir?dir=" + encodeURIComponent(dir),function(data){
  327. $("#folderList").html("");
  328. $("#fileList").html("");
  329. if (data === null){
  330. $("#folderList").hide();
  331. $("#fileList").hide();
  332. return;
  333. }else{
  334. $("#folderList").show();
  335. $("#fileList").show();
  336. }
  337. if (data.error !== undefined){
  338. //Load the index instead
  339. listDir("user:/");
  340. }else{
  341. currentFileList = data;
  342. var folders = [];
  343. var files = [];
  344. for (var i =0; i < data.length; i++){
  345. if (data[i].IsDir == true){
  346. folders.push(data[i]);
  347. }else{
  348. if (fileOptions.filter != undefined){
  349. var fileExt = data[i].Filename.split(".").pop();
  350. for (var j = 0; j < fileOptions.filter.length; j++){
  351. if (fileOptions.filter[j] == fileExt){
  352. files.push(data[i]);
  353. break;
  354. }
  355. }
  356. }else{
  357. files.push(data[i]);
  358. }
  359. }
  360. }
  361. //Append folder first then files
  362. var count = 0;
  363. for (var i =0; i < folders.length; i++){
  364. var filename = folders[i].Filename;
  365. var filepath = folders[i].Filepath;
  366. var ext = filename.split(".").pop();
  367. var icon = ao_module_utils.getIconFromExt(ext);
  368. var isDir = folders[i].IsDir;
  369. if (isDir == true){
  370. icon = "folder";
  371. }
  372. var fileSize = folders[i].Displaysize;
  373. $("#folderList").append(`<div class="fileObject item" fid="${count}" ondblclick="openFolder(event,this);" onclick="selectThis(this,event);" filepath="${encodeURIComponent(filepath)}" filename="${encodeURIComponent(filename)}" isDir="${isDir}">
  374. <span class="fileInfo"><i class="${icon} icon" style="margin-right:4px;"></i> ${filename}</span>
  375. </div>`);
  376. count++;
  377. }
  378. if (folders.length == 0){
  379. $("#folderList").hide();
  380. }
  381. for (var i =0; i < files.length; i++){
  382. var filename = files[i].Filename;
  383. var filepath = files[i].Filepath;
  384. var ext = filename.split(".").pop();
  385. var icon = ao_module_utils.getIconFromExt(ext);
  386. var isDir = files[i].IsDir;
  387. if (isDir == true){
  388. icon = "folder";
  389. }
  390. var fileSize = files[i].Displaysize;
  391. $("#fileList").append(`<div class="fileObject item" fid="${count}" ondblclick="chooseThisFile(this);" onclick="selectThis(this,event);" filepath="${encodeURIComponent(filepath)}" filename="${encodeURIComponent(filename)}" isDir="${isDir}">
  392. <span class="fileInfo"><i class="${icon} icon" style="margin-right:4px;margin-top: 2px;"></i> ${filename}</span>
  393. </div>`);
  394. count++;
  395. }
  396. if (files.length == 0){
  397. $("#fileList").hide();
  398. }
  399. $('.pusher').scrollTop(0);
  400. }
  401. });
  402. }
  403. function requestCSRFToken(callback){
  404. $.ajax({
  405. url: "../../system/csrf/new",
  406. success: function(token){
  407. callback(token);
  408. }
  409. })
  410. }
  411. //Open folder
  412. function openFolder(event, object){
  413. event.preventDefault();
  414. var filepath = $(object).attr("filepath");
  415. filepath = decodeURIComponent(filepath);
  416. listDir(filepath);
  417. }
  418. function selectThis(object,event){
  419. //event.preventDefault();
  420. //event.stopImmediatePropagation();
  421. //Check if this object is in suitable selection type
  422. if ($(object).attr("IsDir") == "true" && type == "file"){
  423. return;
  424. }else if ($(object).attr("IsDir") == "false" && type == "folder"){
  425. return;
  426. }else if (type == "new" && $(object).attr("IsDir") == "false"){
  427. //Use this filename as the newfile name (aka overwrite mode)
  428. var newNewFilename = $(object).attr("filename");
  429. newNewFilename = decodeURIComponent(newNewFilename);
  430. $("#filename").val(newNewFilename);
  431. return;
  432. }else if (type == "new" && $(object).attr("IsDir") == "true"){
  433. //Selected a folder in new mode. Ignore it
  434. return
  435. }
  436. if (multiSelect){
  437. if (ctrlDown){
  438. //Add this into selection list
  439. $(object).addClass("selected");
  440. }else if (shiftDown){
  441. var start = lastClickedItemID;
  442. var end = $(object).attr("fid");
  443. if (start > end){
  444. start = end;
  445. end = lastClickedItemID;
  446. }
  447. var fileObjects = $(".fileObject");
  448. for (var k = start; k <= end; k++){
  449. $(fileObjects[k]).addClass("selected");
  450. }
  451. }else{
  452. //Reset and add this into selection list
  453. $(".selected").removeClass("selected");
  454. $(object).addClass("selected");
  455. }
  456. }else{
  457. $(".selected").removeClass("selected");
  458. $(object).addClass("selected");
  459. }
  460. //Update last selected id
  461. lastClickedItemID = $(object).attr('fid');
  462. //Update title
  463. var objectCount = $(".selected").length;
  464. var typeName = "object"
  465. if (type == "file"){
  466. typeName = "file";
  467. }else if (type == "folder"){
  468. typeName = "folder";
  469. }
  470. var desc = `${typeName} selected`;
  471. if (objectCount > 1){
  472. desc = `${typeName + "s"} selected`;
  473. }
  474. ao_module_setWindowTitle(`Open (${objectCount} ${desc})`);
  475. }
  476. $(window).on("keydown",function(event){
  477. if (event.which == 17){
  478. ctrlDown = true;
  479. }else if (event.which == 16){
  480. shiftDown = true;
  481. }
  482. });
  483. $(window).on("keyup",function(event){
  484. if (event.which == 17){
  485. ctrlDown = false;
  486. }else if (event.which == 16){
  487. shiftDown = false;
  488. }
  489. });
  490. $(window).on("resize",function(){
  491. updateWindowResize();
  492. });
  493. function updateWindowResize(){
  494. if (window.innerWidth < 560){
  495. $("#sidebar").attr("class", "ts left overlapped sidebar");
  496. $(".pusher").css("margin-left", "0px").css("width","100%");
  497. $("#sidebarToggleBtn").show();
  498. }else{
  499. $("#sidebar").attr("class", "ts left static visible overlapped sidebar");
  500. $(".pusher").css("margin-left", "200px").css("width","calc(100% - 200px)");
  501. $("#sidebarToggleBtn").hide();
  502. }
  503. }
  504. function initAddressBarWidth(){
  505. $("#addressbar").css("width",window.innerWidth - 220 + "px");
  506. }
  507. function chooseThisFile(object){
  508. $(".selected").removeClass('selected');
  509. $(object).addClass('selected');
  510. confirmSelection();
  511. }
  512. function parentDir(){
  513. if (currentDir.substring(currentDir.length - 1) == "/"){
  514. currentDir = currentDir.substring(0, currentDir.length - 1);
  515. }
  516. var tmp = currentDir.split("/");
  517. tmp.pop();
  518. var parentPath = tmp.join("/");
  519. if (parentPath.length == 0){
  520. //Do nothing. Already at root dir
  521. }else{
  522. listDir(parentPath);
  523. }
  524. }
  525. function backDir(){
  526. if (pathHistory.length > 1){
  527. pathHistory.pop();
  528. var targetPath = pathHistory.pop();
  529. listDir(targetPath);
  530. }
  531. }
  532. //Initialize user shortcuts
  533. function initRoots(){
  534. $.get("../../system/file_system/listRoots",function(data){
  535. $("#storagelist").html("");
  536. for (var i =0; i < data.length; i++){
  537. $('#storagelist').append(`<div class="item extrapadding" filepath="${data[i]["RootPath"]}" onclick="openShortcut(this);"><i class="disk outline icon" style="margin-right:8px;"></i> ${data[i]["RootName"]} (${data[i]["RootPath"]})</div>`);
  538. }
  539. });
  540. $.get("../../system/file_system/listRoots?user=true",function(data){
  541. $("#userlist").html("");
  542. for (var i =0; i < data.length; i++){
  543. if (data[i].IsDir == true){
  544. if (data[i]["Filename"].substring(0,1) == "."){
  545. //Do not show hidden files
  546. continue;
  547. }
  548. $('#userlist').append(`<div class="item extrapadding" filepath="${data[i]["Filepath"]}" onclick="openShortcut(this);"><i class="folder icon" style="margin-right:8px;"></i> ${data[i]["Filename"]}</div>`);
  549. }
  550. }
  551. });
  552. }
  553. function openShortcut(object){
  554. var targetdir = $(object).attr("filepath");
  555. targetdir = decodeURIComponent(targetdir);
  556. listDir(targetdir);
  557. if (window.innerWidth < 560){
  558. ts('.sidebar').sidebar('hide');
  559. }
  560. }
  561. </script>
  562. </body>
  563. </html>