file_selector.html 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  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. }else{
  256. $("#sidebarPadder").css("height", "46px");
  257. }
  258. updateFileListTopLocation();
  259. }
  260. function hideFolderNameInput(){
  261. $("#newFolderInput").hide();
  262. $("#sidebarPadder").css("height", "46px");
  263. $(".fileListWrapper").css("padding-top", "0px");
  264. }
  265. function createFolder(){
  266. var folderName = $("#foldername").val();
  267. if (folderName == ""){
  268. folderName = "New Folder"
  269. $("#foldername").val("New Folder");
  270. }
  271. folderName = folderName.replace(/[<>:"/\\|?*]/g, "_");
  272. //Check if folder exists
  273. var nameAlreadyExists = false;
  274. currentFileList.forEach(fileObject => {
  275. if (fileObject.IsDir && fileObject.Filename == folderName){
  276. nameAlreadyExists = true;
  277. }
  278. });
  279. if (nameAlreadyExists){
  280. alert("Folder already exists")
  281. return
  282. }
  283. //Create the new folder request
  284. requestCSRFToken(function(token){
  285. $.ajax({
  286. url: "../../system/file_system/newItem",
  287. data: {type: "folder", src: currentDir, filename: folderName, csrft: token},
  288. success: function(data){
  289. if (data.error !== undefined){
  290. alert(data.error);
  291. }else{
  292. refresh()
  293. }
  294. hideFolderNameInput();
  295. }
  296. });
  297. });
  298. }
  299. function loadSelectorInfoFromHash(){
  300. if (window.location.hash.length == 0){
  301. return {
  302. root: "user:/",
  303. type: "file",
  304. allowMultiple: false
  305. }
  306. }else{
  307. try{
  308. var selectInfo = JSON.parse(decodeURIComponent(window.location.hash.substring(1)));
  309. return selectInfo;
  310. }catch{
  311. //Error parsing the input. Use default settings
  312. return {
  313. root: "user:/",
  314. type: "file",
  315. allowMultiple: false
  316. }
  317. }
  318. }
  319. }
  320. function listDir(dir){
  321. currentDir = dir;
  322. pathHistory.push(currentDir);
  323. $("#addressbar").val(currentDir);
  324. ao_module_setWindowTitle(`Open`);
  325. $.get("../../system/file_system/listDir?dir=" + encodeURIComponent(dir),function(data){
  326. $("#folderList").html("");
  327. $("#fileList").html("");
  328. if (data === null){
  329. $("#folderList").hide();
  330. $("#fileList").hide();
  331. return;
  332. }else{
  333. $("#folderList").show();
  334. $("#fileList").show();
  335. }
  336. if (data.error !== undefined){
  337. //Load the index instead
  338. listDir("user:/");
  339. }else{
  340. currentFileList = data;
  341. var folders = [];
  342. var files = [];
  343. for (var i =0; i < data.length; i++){
  344. if (data[i].IsDir == true){
  345. folders.push(data[i]);
  346. }else{
  347. if (fileOptions.filter != undefined){
  348. var fileExt = data[i].Filename.split(".").pop();
  349. for (var j = 0; j < fileOptions.filter.length; j++){
  350. if (fileOptions.filter[j] == fileExt){
  351. files.push(data[i]);
  352. break;
  353. }
  354. }
  355. }else{
  356. files.push(data[i]);
  357. }
  358. }
  359. }
  360. //Append folder first then files
  361. var count = 0;
  362. for (var i =0; i < folders.length; i++){
  363. var filename = folders[i].Filename;
  364. var filepath = folders[i].Filepath;
  365. var ext = filename.split(".").pop();
  366. var icon = ao_module_utils.getIconFromExt(ext);
  367. var isDir = folders[i].IsDir;
  368. if (isDir == true){
  369. icon = "folder";
  370. }
  371. var fileSize = folders[i].Displaysize;
  372. $("#folderList").append(`<div class="fileObject item" fid="${count}" ondblclick="openFolder(event,this);" onclick="selectThis(this,event);" filepath="${encodeURIComponent(filepath)}" filename="${encodeURIComponent(filename)}" isDir="${isDir}">
  373. <span class="fileInfo"><i class="${icon} icon" style="margin-right:4px;"></i> ${filename}</span>
  374. </div>`);
  375. count++;
  376. }
  377. if (folders.length == 0){
  378. $("#folderList").hide();
  379. }
  380. for (var i =0; i < files.length; i++){
  381. var filename = files[i].Filename;
  382. var filepath = files[i].Filepath;
  383. var ext = filename.split(".").pop();
  384. var icon = ao_module_utils.getIconFromExt(ext);
  385. var isDir = files[i].IsDir;
  386. if (isDir == true){
  387. icon = "folder";
  388. }
  389. var fileSize = files[i].Displaysize;
  390. $("#fileList").append(`<div class="fileObject item" fid="${count}" ondblclick="chooseThisFile(this);" onclick="selectThis(this,event);" filepath="${encodeURIComponent(filepath)}" filename="${encodeURIComponent(filename)}" isDir="${isDir}">
  391. <span class="fileInfo"><i class="${icon} icon" style="margin-right:4px;margin-top: 2px;"></i> ${filename}</span>
  392. </div>`);
  393. count++;
  394. }
  395. if (files.length == 0){
  396. $("#fileList").hide();
  397. }
  398. $('.pusher').scrollTop(0);
  399. }
  400. });
  401. }
  402. function requestCSRFToken(callback){
  403. $.ajax({
  404. url: "../../system/csrf/new",
  405. success: function(token){
  406. callback(token);
  407. }
  408. })
  409. }
  410. //Open folder
  411. function openFolder(event, object){
  412. event.preventDefault();
  413. var filepath = $(object).attr("filepath");
  414. filepath = decodeURIComponent(filepath);
  415. listDir(filepath);
  416. }
  417. function selectThis(object,event){
  418. //event.preventDefault();
  419. //event.stopImmediatePropagation();
  420. //Check if this object is in suitable selection type
  421. if ($(object).attr("IsDir") == "true" && type == "file"){
  422. return;
  423. }else if ($(object).attr("IsDir") == "false" && type == "folder"){
  424. return;
  425. }else if (type == "new" && $(object).attr("IsDir") == "false"){
  426. //Use this filename as the newfile name (aka overwrite mode)
  427. var newNewFilename = $(object).attr("filename");
  428. newNewFilename = decodeURIComponent(newNewFilename);
  429. $("#filename").val(newNewFilename);
  430. return;
  431. }else if (type == "new" && $(object).attr("IsDir") == "true"){
  432. //Selected a folder in new mode. Ignore it
  433. return
  434. }
  435. if (multiSelect){
  436. if (ctrlDown){
  437. //Add this into selection list
  438. $(object).addClass("selected");
  439. }else if (shiftDown){
  440. var start = lastClickedItemID;
  441. var end = $(object).attr("fid");
  442. if (start > end){
  443. start = end;
  444. end = lastClickedItemID;
  445. }
  446. var fileObjects = $(".fileObject");
  447. for (var k = start; k <= end; k++){
  448. $(fileObjects[k]).addClass("selected");
  449. }
  450. }else{
  451. //Reset and add this into selection list
  452. $(".selected").removeClass("selected");
  453. $(object).addClass("selected");
  454. }
  455. }else{
  456. $(".selected").removeClass("selected");
  457. $(object).addClass("selected");
  458. }
  459. //Update last selected id
  460. lastClickedItemID = $(object).attr('fid');
  461. //Update title
  462. var objectCount = $(".selected").length;
  463. var typeName = "object"
  464. if (type == "file"){
  465. typeName = "file";
  466. }else if (type == "folder"){
  467. typeName = "folder";
  468. }
  469. var desc = `${typeName} selected`;
  470. if (objectCount > 1){
  471. desc = `${typeName + "s"} selected`;
  472. }
  473. ao_module_setWindowTitle(`Open (${objectCount} ${desc})`);
  474. }
  475. $(window).on("keydown",function(event){
  476. if (event.which == 17){
  477. ctrlDown = true;
  478. }else if (event.which == 16){
  479. shiftDown = true;
  480. }
  481. });
  482. $(window).on("keyup",function(event){
  483. if (event.which == 17){
  484. ctrlDown = false;
  485. }else if (event.which == 16){
  486. shiftDown = false;
  487. }
  488. });
  489. $(window).on("resize",function(){
  490. updateWindowResize();
  491. });
  492. function updateWindowResize(){
  493. if (window.innerWidth < 560){
  494. //Mobile mode
  495. $("#sidebar").attr("class", "ts left overlapped sidebar");
  496. $(".pusher").css("margin-left", "0px").css("width","100%");
  497. $("#sidebar").css("margin-top", "30px");
  498. $("#sidebarToggleBtn").show();
  499. $("#addressbar").parent().css({
  500. "width": "100%",
  501. "margin-top": "4px"
  502. });
  503. $("#newfilenameInput").find(".input").css({
  504. "width": "100%",
  505. "margin-top": "4px"
  506. });
  507. $("#newFolderInput").find(".input").css({
  508. "width": "100%",
  509. "margin-top": "4px"
  510. });
  511. }else{
  512. $("#sidebar").attr("class", "ts left static visible overlapped sidebar");
  513. $(".pusher").css("margin-left", "200px").css("width","calc(100% - 200px)");
  514. $("#sidebarToggleBtn").hide();
  515. $("#sidebar").css("margin-top", "0px");
  516. $("#addressbar").parent().css({
  517. "width": "calc(100% - 180px)",
  518. "margin-top": "0px"
  519. });
  520. $("#newfilenameInput").find(".input").css({
  521. "width": "calc(100% - 180px)",
  522. "margin-top": "0px"
  523. });
  524. $("#newFolderInput").find(".input").css({
  525. "width": "calc(100% - 180px)",
  526. "margin-top": "8px"
  527. });
  528. }
  529. updateFileListTopLocation();
  530. }
  531. function updateFileListTopLocation(){
  532. $(".fileListWrapper").css("padding-top", $(".navi").height() - 38 + "px");
  533. }
  534. function initAddressBarWidth(){
  535. $("#addressbar").css("width",window.innerWidth - 220 + "px");
  536. }
  537. function chooseThisFile(object){
  538. $(".selected").removeClass('selected');
  539. $(object).addClass('selected');
  540. confirmSelection();
  541. }
  542. function parentDir(){
  543. if (currentDir.substring(currentDir.length - 1) == "/"){
  544. currentDir = currentDir.substring(0, currentDir.length - 1);
  545. }
  546. var tmp = currentDir.split("/");
  547. tmp.pop();
  548. var parentPath = tmp.join("/");
  549. if (parentPath.length == 0){
  550. //Do nothing. Already at root dir
  551. }else{
  552. listDir(parentPath);
  553. }
  554. }
  555. function backDir(){
  556. if (pathHistory.length > 1){
  557. pathHistory.pop();
  558. var targetPath = pathHistory.pop();
  559. listDir(targetPath);
  560. }
  561. }
  562. //Initialize user shortcuts
  563. function initRoots(){
  564. $.get("../../system/file_system/listRoots",function(data){
  565. $("#storagelist").html("");
  566. for (var i =0; i < data.length; i++){
  567. $('#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>`);
  568. }
  569. });
  570. $.get("../../system/file_system/listRoots?user=true",function(data){
  571. $("#userlist").html("");
  572. for (var i =0; i < data.length; i++){
  573. if (data[i].IsDir == true){
  574. if (data[i]["Filename"].substring(0,1) == "."){
  575. //Do not show hidden files
  576. continue;
  577. }
  578. $('#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>`);
  579. }
  580. }
  581. });
  582. }
  583. function openShortcut(object){
  584. var targetdir = $(object).attr("filepath");
  585. targetdir = decodeURIComponent(targetdir);
  586. listDir(targetdir);
  587. if (window.innerWidth < 560){
  588. ts('.sidebar').sidebar('hide');
  589. }
  590. }
  591. </script>
  592. </body>
  593. </html>