file_selector.html 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  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 class="ts icon tiny button" onclick="backDir();"><i class="arrow left icon"></i></button>
  73. <button class="ts icon tiny button" onclick="parentDir();"><i class="arrow up icon"></i></button>
  74. <button class="ts icon tiny button" onclick="refresh();"><i class="refresh icon"></i></button>
  75. <div class="ts action fluid tiny input" style="width: calc(100% - 180px); float: right;">
  76. <input id="addressbar" type="text" placeholder="" onchange="updatePath();">
  77. <button class="ts positive icon button" onclick="confirmSelection();"><i class="checkmark icon"></i></button>
  78. </div>
  79. <div id="newfilenameInput" style="width:100%; margin-top:12px;" align="right">
  80. <div class="ts fluid tiny input" style="width: calc(100% - 180px); float: right;">
  81. <input id="filename" type="text" placeholder="New Filename">
  82. </div>
  83. </div>
  84. </div>
  85. <div id="sidebar" class="ts left static visible overlapped sidebar" style="background-color:#f5f5f5 !important;z-index:90 !important; width:200px;">
  86. <div id="sidebarPadder" style="height:46px;"></div>
  87. <details class="ts accordion" open>
  88. <summary>
  89. <i class="dropdown icon"></i> User
  90. </summary>
  91. <div class="content" >
  92. <div class="ts list" id="userlist">
  93. </div>
  94. </div>
  95. </details>
  96. <details class="ts accordion" open>
  97. <summary>
  98. <i class="dropdown icon"></i> Storage
  99. </summary>
  100. <div class="content">
  101. <div class="ts list" id="storagelist">
  102. </div>
  103. </div>
  104. </details>
  105. </div>
  106. <div class="pusher">
  107. <br>
  108. <div class="fileListWrapper" style="min-height:300px; width:100%;">
  109. <div id="folderList" class="ts segmented basic fluid list whiteTheme">
  110. <div class="fileObject">
  111. <span class="fileInfo"><i class="loading spinner icon" style="margin-right:12px;"></i> Loading</span>
  112. </div>
  113. </div>
  114. <div id="fileList" class="ts segmented fluid list whiteTheme">
  115. </div>
  116. </div>
  117. <br><br>
  118. </div>
  119. <div id="waitloader" class="ts active dimmer" style="display:none; z-index:999;">
  120. <div id="waitloadertext" class="ts indeterminate text loader">Waiting Response</div>
  121. </div>
  122. <script>
  123. var multiSelect = false;
  124. var type = "file";
  125. var currentDir = "user:/";
  126. var pathHistory = [];
  127. var ctrlDown = false;
  128. var shiftDown = false;
  129. var lastClickedItemID = 0;
  130. var listenerUUID = "";
  131. var fileOptions = {};
  132. initSelectorObject();
  133. initRoots();
  134. updateWindowResize();
  135. function initSelectorObject(){
  136. var initInfo = loadSelectorInfoFromHash();
  137. //Load the initiation directory
  138. listDir(initInfo.root);
  139. //init global var
  140. type = initInfo.type;
  141. multiSelect = initInfo.allowMultiple;
  142. listenerUUID = initInfo.listenerUUID;
  143. if (initInfo.options != undefined){
  144. fileOptions = JSON.parse(JSON.stringify(initInfo.options));
  145. }
  146. //Load options and parse the UI
  147. if (type == "new"){
  148. //Resize the top bar
  149. $("#sidebarPadder").css("height", "90px");
  150. $(".fileListWrapper").css("padding-top", "50px");
  151. if (typeof(fileOptions.defaultName) != "undefined"){
  152. $("#filename").val(fileOptions.defaultName);
  153. }else{
  154. $("#filename").val("newfile.txt");
  155. }
  156. }else{
  157. $("#newfilenameInput").hide();
  158. }
  159. }
  160. function cancelSelection(){
  161. localStorage.setItem(listenerUUID, JSON.stringify("&&selection_canceled&&"));
  162. }
  163. function confirmSelection(){
  164. var files = [];
  165. $(".selected.fileObject").each(function(){
  166. var filename = decodeURIComponent($(this).attr('filename'));
  167. var filepath = decodeURIComponent($(this).attr('filepath'));
  168. files.push({
  169. filename: filename,
  170. filepath: filepath
  171. });
  172. });
  173. //Check if currentdir end with "/". If not, append it
  174. if(currentDir.substr(currentDir.length - 1, 1) != "/"){
  175. currentDir = currentDir + "/";
  176. }
  177. //Check for special cases
  178. if (files.length == 0 && type == "folder"){
  179. //Select the current path as target instead
  180. var currentPathname = currentDir.split("/");
  181. currentPathname.pop();
  182. currentPathname = currentPathname.pop();
  183. if (currentPathname == ""){
  184. currentPathname = currentDir;
  185. }
  186. files.push({
  187. filename: currentPathname,
  188. filepath: currentDir
  189. });
  190. }else if (files.length == 0 && type == "new"){
  191. //Push this new file into the return structure
  192. var newFilename = $("#filename").val();
  193. files.push({
  194. filename: newFilename,
  195. filepath: currentDir + newFilename
  196. });
  197. }
  198. if (ao_module_virtualDesktop){
  199. if (!ao_module_parentCallback(files)){
  200. //Parent callback not exists
  201. alert("Selection Failed. Is parent window alive?")
  202. }else{
  203. parent.closeFwProcess(ao_module_windowID);
  204. }
  205. }else{
  206. if (listenerUUID == ""){
  207. alert("Invalid listener UUID. Please re-open your file selector.")
  208. return;
  209. }
  210. var selectedFilesInJSON = JSON.stringify(files);
  211. localStorage.setItem(listenerUUID, selectedFilesInJSON);
  212. $("#waitloader").show();
  213. $(".pusher").css("overflow","hidden");
  214. setTimeout(function(){
  215. $("#waitloadertext").html("<i class='remove icon'></i> System is not responding. <br>Please close this window and retry.");
  216. },10000)
  217. }
  218. }
  219. //Handle on window close function, cancel current selection
  220. window.onbeforeunload = function(){
  221. cancelSelection();
  222. }
  223. //Overwrite the ao_module close function
  224. function ao_module_close(){
  225. if (!ao_module_virtualDesktop){
  226. return;
  227. }
  228. if (!ao_module_parentCallback(files)){
  229. alert("Selection Failed. Is parent window alive?")
  230. }else{
  231. parent.closeFwProcess(ao_module_windowID);
  232. }
  233. }
  234. function updatePath(){
  235. var newDir = $("#addressbar").val();
  236. listDir(newDir);
  237. }
  238. function refresh(){
  239. $("#fileList").html("");
  240. $("#folderList").html("");
  241. listDir(currentDir);
  242. }
  243. function loadSelectorInfoFromHash(){
  244. if (window.location.hash.length == 0){
  245. return {
  246. root: "user:/",
  247. type: "file",
  248. allowMultiple: false
  249. }
  250. }else{
  251. try{
  252. var selectInfo = JSON.parse(decodeURIComponent(window.location.hash.substring(1)));
  253. return selectInfo;
  254. }catch{
  255. //Error parsing the input. Use default settings
  256. return {
  257. root: "user:/",
  258. type: "file",
  259. allowMultiple: false
  260. }
  261. }
  262. }
  263. }
  264. function listDir(dir){
  265. currentDir = dir;
  266. pathHistory.push(currentDir);
  267. $("#addressbar").val(currentDir);
  268. ao_module_setWindowTitle(`Open`);
  269. $.get("../../system/file_system/listDir?dir=" + encodeURIComponent(dir),function(data){
  270. $("#folderList").html("");
  271. $("#fileList").html("");
  272. if (data === null){
  273. $("#folderList").hide();
  274. $("#fileList").hide();
  275. return;
  276. }else{
  277. $("#folderList").show();
  278. $("#fileList").show();
  279. }
  280. if (data.error !== undefined){
  281. //Load the index instead
  282. listDir("user:/");
  283. }else{
  284. var folders = [];
  285. var files = [];
  286. for (var i =0; i < data.length; i++){
  287. if (data[i].IsDir == true){
  288. folders.push(data[i]);
  289. }else{
  290. if (fileOptions.filter != undefined){
  291. var fileExt = data[i].Filename.split(".").pop();
  292. for (var j = 0; j < fileOptions.filter.length; j++){
  293. if (fileOptions.filter[j] == fileExt){
  294. files.push(data[i]);
  295. break;
  296. }
  297. }
  298. }else{
  299. files.push(data[i]);
  300. }
  301. }
  302. }
  303. //Append folder first then files
  304. var count = 0;
  305. for (var i =0; i < folders.length; i++){
  306. var filename = folders[i].Filename;
  307. var filepath = folders[i].Filepath;
  308. var ext = filename.split(".").pop();
  309. var icon = ao_module_utils.getIconFromExt(ext);
  310. var isDir = folders[i].IsDir;
  311. if (isDir == true){
  312. icon = "folder";
  313. }
  314. var fileSize = folders[i].Displaysize;
  315. $("#folderList").append(`<div class="fileObject item" fid="${count}" ondblclick="openFolder(event,this);" onclick="selectThis(this,event);" filepath="${encodeURIComponent(filepath)}" filename="${encodeURIComponent(filename)}" isDir="${isDir}">
  316. <span class="fileInfo" ><i class="${icon} icon" style="margin-right:12px;"></i> ${filename}</span>
  317. </div>`);
  318. count++;
  319. }
  320. if (folders.length == 0){
  321. $("#folderList").hide();
  322. }
  323. for (var i =0; i < files.length; i++){
  324. var filename = files[i].Filename;
  325. var filepath = files[i].Filepath;
  326. var ext = filename.split(".").pop();
  327. var icon = ao_module_utils.getIconFromExt(ext);
  328. var isDir = files[i].IsDir;
  329. if (isDir == true){
  330. icon = "folder";
  331. }
  332. var fileSize = files[i].Displaysize;
  333. $("#fileList").append(`<div class="fileObject item" fid="${count}" ondblclick="chooseThisFile(this);" onclick="selectThis(this,event);" filepath="${encodeURIComponent(filepath)}" filename="${encodeURIComponent(filename)}" isDir="${isDir}">
  334. <span class="fileInfo"><i class="${icon} icon" style="margin-right:12px;"></i> ${filename}</span>
  335. </div>`);
  336. count++;
  337. }
  338. if (files.length == 0){
  339. $("#fileList").hide();
  340. }
  341. $('.pusher').scrollTop(0);
  342. }
  343. });
  344. }
  345. //Open folder
  346. function openFolder(event, object){
  347. event.preventDefault();
  348. var filepath = $(object).attr("filepath");
  349. filepath = decodeURIComponent(filepath);
  350. listDir(filepath);
  351. }
  352. function selectThis(object,event){
  353. //event.preventDefault();
  354. //event.stopImmediatePropagation();
  355. //Check if this object is in suitable selection type
  356. if ($(object).attr("IsDir") == "true" && type == "file"){
  357. return;
  358. }else if ($(object).attr("IsDir") == "false" && type == "folder"){
  359. return;
  360. }else if (type == "new" && $(object).attr("IsDir") == "false"){
  361. //Use this filename as the newfile name (aka overwrite mode)
  362. var newNewFilename = $(object).attr("filename");
  363. newNewFilename = decodeURIComponent(newNewFilename);
  364. $("#filename").val(newNewFilename);
  365. return;
  366. }else if (type == "new" && $(object).attr("IsDir") == "true"){
  367. //Selected a folder in new mode. Ignore it
  368. return
  369. }
  370. if (multiSelect){
  371. if (ctrlDown){
  372. //Add this into selection list
  373. $(object).addClass("selected");
  374. }else if (shiftDown){
  375. var start = lastClickedItemID;
  376. var end = $(object).attr("fid");
  377. if (start > end){
  378. start = end;
  379. end = lastClickedItemID;
  380. }
  381. var fileObjects = $(".fileObject");
  382. for (var k = start; k <= end; k++){
  383. $(fileObjects[k]).addClass("selected");
  384. }
  385. }else{
  386. //Reset and add this into selection list
  387. $(".selected").removeClass("selected");
  388. $(object).addClass("selected");
  389. }
  390. }else{
  391. $(".selected").removeClass("selected");
  392. $(object).addClass("selected");
  393. }
  394. //Update last selected id
  395. lastClickedItemID = $(object).attr('fid');
  396. //Update title
  397. var objectCount = $(".selected").length;
  398. var typeName = "object"
  399. if (type == "file"){
  400. typeName = "file";
  401. }else if (type == "folder"){
  402. typeName = "folder";
  403. }
  404. var desc = `${typeName} selected`;
  405. if (objectCount > 1){
  406. desc = `${typeName + "s"} selected`;
  407. }
  408. ao_module_setWindowTitle(`Open (${objectCount} ${desc})`);
  409. }
  410. $(window).on("keydown",function(event){
  411. if (event.which == 17){
  412. ctrlDown = true;
  413. }else if (event.which == 16){
  414. shiftDown = true;
  415. }
  416. });
  417. $(window).on("keyup",function(event){
  418. if (event.which == 17){
  419. ctrlDown = false;
  420. }else if (event.which == 16){
  421. shiftDown = false;
  422. }
  423. });
  424. $(window).on("resize",function(){
  425. updateWindowResize();
  426. });
  427. function updateWindowResize(){
  428. if (window.innerWidth < 560){
  429. $("#sidebar").attr("class", "ts left overlapped sidebar");
  430. $(".pusher").css("margin-left", "0px").css("width","100%");
  431. $("#sidebarToggleBtn").show();
  432. }else{
  433. $("#sidebar").attr("class", "ts left static visible overlapped sidebar");
  434. $(".pusher").css("margin-left", "200px").css("width","calc(100% - 200px)");
  435. $("#sidebarToggleBtn").hide();
  436. }
  437. }
  438. function initAddressBarWidth(){
  439. $("#addressbar").css("width",window.innerWidth - 220 + "px");
  440. }
  441. function chooseThisFile(object){
  442. $(".selected").removeClass('selected');
  443. $(object).addClass('selected');
  444. confirmSelection();
  445. }
  446. function parentDir(){
  447. if (currentDir.substring(currentDir.length - 1) == "/"){
  448. currentDir = currentDir.substring(0, currentDir.length - 1);
  449. }
  450. var tmp = currentDir.split("/");
  451. tmp.pop();
  452. var parentPath = tmp.join("/");
  453. if (parentPath.length == 0){
  454. //Do nothing. Already at root dir
  455. }else{
  456. listDir(parentPath);
  457. }
  458. }
  459. function backDir(){
  460. if (pathHistory.length > 1){
  461. pathHistory.pop();
  462. var targetPath = pathHistory.pop();
  463. listDir(targetPath);
  464. }
  465. }
  466. //Initialize user shortcuts
  467. function initRoots(){
  468. $.get("../../system/file_system/listRoots",function(data){
  469. $("#storagelist").html("");
  470. for (var i =0; i < data.length; i++){
  471. $('#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>`);
  472. }
  473. });
  474. $.get("../../system/file_system/listRoots?user=true",function(data){
  475. $("#userlist").html("");
  476. for (var i =0; i < data.length; i++){
  477. if (data[i].IsDir == true){
  478. if (data[i]["Filename"].substring(0,1) == "."){
  479. //Do not show hidden files
  480. continue;
  481. }
  482. $('#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>`);
  483. }
  484. }
  485. });
  486. }
  487. function openShortcut(object){
  488. var targetdir = $(object).attr("filepath");
  489. targetdir = decodeURIComponent(targetdir);
  490. listDir(targetdir);
  491. if (window.innerWidth < 560){
  492. ts('.sidebar').sidebar('hide');
  493. }
  494. }
  495. </script>
  496. </body>
  497. </html>