netdiskfinder.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. /**
  2. * @fileOverview
  3. *
  4. * 网盘的目录访问组件
  5. *
  6. * @author: techird
  7. * @copyright: Baidu FEX, 2014
  8. */
  9. KityMinder.registerUI('widget/netdiskfinder', function(minder) {
  10. var eve = minder.getUI('eve');
  11. var notice = minder.getUI('widget/notice');
  12. var recycleReady = null;
  13. var base = '/apps/kityminder';
  14. var recyclePath = base + '/.recycle';
  15. var moveConfirm = true;
  16. var instances = [];
  17. var Finder = eve.setup({});
  18. Finder.BASE_PATH = base + '/';
  19. Finder.RECYCLE_PATH = recyclePath + '/';
  20. Finder.on('mv', function(from, to, source) {
  21. instances.forEach(function(instance) {
  22. if (source != instance) instance.refresh();
  23. });
  24. });
  25. /**
  26. * 生成一个网盘的目录访问组件
  27. *
  28. * @param {JQueryObject} $container 容器
  29. * @param {function} listFilter 一个函数,检查一个文件是否应该被列出
  30. */
  31. function generate($container, listFilter) {
  32. var finder = eve.setup({});
  33. instances.push(finder);
  34. var currentPath = base;
  35. var $finder = $('<div class="netdisk-finder"></div>').appendTo($container);
  36. /* 顶部工具栏 */
  37. var $headbar = $('<div class="head"></div>').appendTo($finder);
  38. /* 控制按钮 */
  39. var $control = $('<div class="control"></div>').appendTo($headbar);
  40. var $mkdir = $('<a></a>')
  41. .text(minder.getLang('ui.mkdir'))
  42. .attr('title', minder.getLang('ui.mkdir'))
  43. .addClass('button mkdir')
  44. .appendTo($control)
  45. .click(mkdir);
  46. var $recycle = $('<a></a>')
  47. .text(minder.getLang('ui.recycle'))
  48. .attr('title', minder.getLang('ui.recycle'))
  49. .addClass('button recycle dir')
  50. .data('file', {
  51. path: recyclePath,
  52. filename: minder.getLang('ui.recycle')
  53. })
  54. .appendTo($control)
  55. .click(recycle);
  56. var $recycleClear = $('<a></a>')
  57. .text(minder.getLang('ui.recycle_clear'))
  58. .attr('title', minder.getLang('ui.recycle_clear'))
  59. .addClass('button recycle-clear')
  60. .appendTo($control)
  61. .click(clearRecycle);
  62. /* 路径导航 */
  63. var $nav = $('<div class="nav"></div>').appendTo($headbar);
  64. /* 显示当前目录文件列表 */
  65. var $list = $('<ul class="file-list"></ul>')
  66. .appendTo($finder);
  67. var selected = null;
  68. minder.on('uiready', function() {
  69. var $user = minder.getUI('topbar/user');
  70. $user.requireLogin($container);
  71. fio.user.on('login', function() {
  72. list();
  73. });
  74. });
  75. handleClick();
  76. handleDrag();
  77. handleNav();
  78. handleRename();
  79. function handleRename() {
  80. $list.delegate('.file-list-item a.rename-button', 'click', function(e) {
  81. var $li = $(e.target).closest('li');
  82. $li.find('span.filename').remove();
  83. rename($li);
  84. $li.addClass('renaming');
  85. e.stopPropagation();
  86. });
  87. function rename($li) {
  88. rename.onprogress = true;
  89. var file = $li.data('file');
  90. var $input = $('<input>')
  91. .attr('type', 'text')
  92. .addClass('new-dir-name fui-widget fui-selectable')
  93. .val(file.filename)
  94. .appendTo($li);
  95. $li.removeAttr('draggable');
  96. $input.on('keydown', function (e) {
  97. if (e.keyCode == 13) return confirm();
  98. if (e.keyCode == 27) {
  99. e.stopPropagation();
  100. return cancel();
  101. }
  102. }).on('blur', cancel);
  103. $input.on('dragstart mousedown mouseup click dblclick', function(e) {
  104. e.stopPropagation();
  105. });
  106. setTimeout(function() {
  107. $input[0].select();
  108. });
  109. function reset(filename) {
  110. $input.remove();
  111. $li.find('.icon').after('<span class="filename">' + filename + '</span>');
  112. $li.removeClass('renaming');
  113. $li.attr('draggable', true);
  114. }
  115. function cancel() {
  116. reset(file.filename);
  117. }
  118. function confirm() {
  119. var newFilename = $input.val();
  120. var newPath = file.parentPath + newFilename;
  121. if (file.filename == newFilename) return cancel();
  122. if (fio.file.anlysisPath(newFilename).extension != file.extension) {
  123. $input.addClass('invalid-name');
  124. setTimeout(function () {
  125. $input.removeClass('invalid-name');
  126. }, 500);
  127. return $input.select();
  128. }
  129. $container.addClass('loading');
  130. mv(file.path, newPath).then(function () {
  131. var oldPath = file.path;
  132. file.filename = newFilename;
  133. file.path = newPath;
  134. reset(newFilename);
  135. Finder.fire('mv', oldPath, newPath, finder);
  136. notice.info(minder.getLang('ui.rename_success', newFilename));
  137. })['catch'](function(e) {
  138. notice.error('err_rename', e);
  139. cancel();
  140. }).then(function() {
  141. $container.removeClass('loading');
  142. });
  143. }
  144. }
  145. }
  146. function handleClick() {
  147. /* 点击目录中的项目时打开项目 */
  148. $list.delegate('.file-list-item', 'dblclick', function(e) {
  149. if (currentPath == recyclePath + '/') return;
  150. if (mkdir.onprogress) return mkdir.onprogress.select();
  151. var $file = $(e.target).closest('li'),
  152. file = $file.data('file');
  153. if (file) open(file);
  154. });
  155. $list.delegate('.file-list-item', 'mousedown', function(e) {
  156. if (mkdir.onprogress) return mkdir.onprogress.select();
  157. var $file = $(e.target).closest('li'),
  158. file = $file.data('file');
  159. if (!file) return;
  160. select(file && file.path);
  161. });
  162. }
  163. function handleNav() {
  164. /* 点击导航处,切换路径 */
  165. $nav.delegate('a', 'click', function(e) {
  166. if (mkdir.onprogress) return mkdir.onprogress.select();
  167. if ($(e.target).hasClass('dir-back')) {
  168. var parts = currentPath.split('/');
  169. parts.pop(); // 有一个无效部分
  170. parts.pop();
  171. return list(parts.join('/'));
  172. }
  173. list($(e.target).data('path'));
  174. });
  175. }
  176. function handleDrag() {
  177. var fileItemSelector = '.file-list-item';
  178. var dirSelector = '.dir';
  179. var $dragging = null;
  180. $list.delegate(fileItemSelector, 'dragstart', itemDragStart)
  181. .delegate(fileItemSelector, 'dragend', itemDragEnd)
  182. .delegate(dirSelector, 'dragover', dragOver)
  183. .delegate(dirSelector, 'dragenter', dirDragEnter)
  184. .delegate(dirSelector, 'dragleave', dirDragLeave)
  185. .delegate(dirSelector, 'drop', dirDrop);
  186. $headbar.delegate(dirSelector, 'dragover', dragOver)
  187. .delegate(dirSelector, 'dragenter', dirDragEnter)
  188. .delegate(dirSelector, 'dragleave', dirDragLeave)
  189. .delegate(dirSelector, 'drop', dirDrop);
  190. $list.delegate(fileItemSelector + ' input', 'dragstart', function(e) {
  191. e.stopPropagation();
  192. e.preventDefault();
  193. });
  194. function itemDragStart(e) {
  195. var $target = $(e.target);
  196. if (!$target.hasClass('file-list-item')) {
  197. return;
  198. }
  199. // e.originalEvent.dataTransfer.effectAllowed = "move";
  200. // e.originalEvent.dataTransfer.dropEffect = 'move';
  201. try {
  202. var dataType = kity.Browser.ie && kity.Browser.version == 10 ? 'text' : 'text/plain';
  203. e.originalEvent.dataTransfer.setData(dataType, 'FEX');
  204. e.originalEvent.dataTransfer.setDragImage($target.find('.icon').get(0), 12, 12);
  205. } catch (ignore) {}
  206. $dragging = $target.addClass('dragging');
  207. $finder.addClass('drop-mode');
  208. }
  209. function itemDragEnd(e) {
  210. $(e.target).removeClass('dragging');
  211. e.originalEvent.dataTransfer.dropEffect = 'move';
  212. e.preventDefault();
  213. $finder.removeClass('drop-mode');
  214. }
  215. function dragOver(e) {
  216. if ($(e.target).hasClass('filename')) e.preventDefault();
  217. }
  218. function dirDragEnter(e) {
  219. var $target = $(e.target).closest('.dir');
  220. $target.addClass('drag-enter');
  221. if (e.target != $target[0]) $target.addClass('enter-child');
  222. }
  223. function dirDragLeave(e) {
  224. if ($(e.target).hasClass('dir')) {
  225. if ($(e.target).hasClass('enter-child')) {
  226. return $(e.target).removeClass('enter-child');
  227. }
  228. $(e.target).removeClass('drag-enter');
  229. }
  230. }
  231. function dirDrop(e) {
  232. e.preventDefault();
  233. var $target = $(e.target).closest('.dir').removeClass('drag-enter');
  234. if (!$target.hasClass('dir')) return;
  235. var source = $dragging.data('file');
  236. var destination = $target.data('file');
  237. var destinationPath = destination.path + '/' + source.filename;
  238. var sourcePath = source.path;
  239. if (destinationPath.indexOf(sourcePath) === 0) return;
  240. if (!moveConfirm || window.confirm(minder.getLang('ui.move_file_confirm', source.filename, destination.filename))) {
  241. $container.addClass('loading');
  242. recycleReady.then(doMove);
  243. moveConfirm = false;
  244. }
  245. function doMove() {
  246. mv(sourcePath, destinationPath).then(function() {
  247. $dragging.remove();
  248. Finder.fire('mv', sourcePath, destinationPath, finder);
  249. notice.info(minder.getLang('ui.move_success', source.filename, destination.filename));
  250. })['catch'](function(e) {
  251. notice.error('err_move_file', e);
  252. }).then(function() {
  253. $container.removeClass('loading');
  254. });
  255. }
  256. }
  257. }
  258. function recycle() {
  259. list(recyclePath);
  260. }
  261. function createRecycleBin() {
  262. return fio.file.mkdir({
  263. path: recyclePath
  264. });
  265. }
  266. function clearRecycle() {
  267. if (!window.confirm(minder.getLang('ui.recycle_clear_confirm'))) return;
  268. $container.addClass('loading');
  269. fio.file['delete']({
  270. path: recyclePath
  271. }).then(function() {
  272. return recycleReady = createRecycleBin();
  273. }).then(function() {
  274. renderList([]);
  275. $container.removeClass('loading');
  276. });
  277. }
  278. function mv(source, destination) {
  279. return fio.file.move({
  280. path: source,
  281. newPath: destination,
  282. ondup: destination.indexOf(recyclePath) === 0 ? fio.file.DUP_RENAME : fio.file.DUP_FAIL
  283. });
  284. }
  285. function mkdir() {
  286. if (mkdir.onprogress) {
  287. return mkdir.onprogress.select();
  288. }
  289. var $li = $('<li>').addClass('file-list-item dir').prependTo($list);
  290. $li.append('<span class="icon"></span>');
  291. var $input = $('<input>')
  292. .attr('type', 'text')
  293. .addClass('new-dir-name fui-widget fui-selectable')
  294. .val(minder.getLang('ui.newdir'))
  295. .appendTo($li);
  296. mkdir.onprogress = $input[0];
  297. $input[0].select();
  298. $input.on('keydown', function(e) {
  299. if (e.keyCode == 13) confirm();
  300. if (e.keyCode == 27) {
  301. cancel();
  302. e.stopPropagation();
  303. }
  304. }).on('blur', confirm);
  305. function cancel() {
  306. $li.remove();
  307. mkdir.onprogress = false;
  308. }
  309. function confirm() {
  310. var name = $input.val();
  311. if (name) {
  312. $container.addClass('loading');
  313. fio.file.mkdir({
  314. path: currentPath + name
  315. }).then(function() {
  316. return new Promise(function(resolve) {
  317. setTimeout(function() {
  318. resolve(refresh());
  319. }, 200);
  320. });
  321. }, function(e) {
  322. if (e.detail && e.detail.error_code == 31061) {
  323. e.message = '已存在同名目录';
  324. }
  325. var notice = minder.getUI('widget/notice');
  326. notice.error('err_mkdir', e);
  327. $li.remove();
  328. }).then(function() {
  329. $container.removeClass('loading');
  330. mkdir.onprogress = false;
  331. });
  332. }
  333. }
  334. }
  335. /**
  336. * 返回数值的符号:
  337. * 正数 => 1
  338. * 0 => 0
  339. * 负数 => -1
  340. */
  341. function sign(num) {
  342. return num > 0 ? 1 : (num < 0 ? -1 : 0);
  343. }
  344. /**
  345. * 打开选中的文件或目录
  346. *
  347. * @param {fio.file.File} file
  348. */
  349. function open(file) {
  350. if (file.isDir) return list(file.path);
  351. finder.fire('fileclick', file);
  352. }
  353. function fadeOutList(x) {
  354. return new Promise(function(resolve, reject) {
  355. $list.transit({
  356. x: x,
  357. opacity: 0
  358. }, 100, resolve);
  359. });
  360. }
  361. function fadeInList() {
  362. return new Promise(function(resolve) {
  363. $list.css({
  364. x: -parseInt($list.css('x'))
  365. }).stop().transit({
  366. x: 0,
  367. opacity: 1
  368. }, 100, resolve);
  369. });
  370. }
  371. function refresh() {
  372. return list(currentPath, true);
  373. }
  374. /**
  375. * 列出指定目录的文件
  376. */
  377. function list(path, noAnimate) {
  378. path = path || base;
  379. var listPromise = fio.file.list({
  380. path: path
  381. });
  382. var transitPromise = noAnimate ? Promise.resolve() : fadeOutList(-100 * sign(path.length - currentPath.length));
  383. currentPath = path.charAt(path.length - 1) == '/' ? path : path + '/';
  384. updateNav();
  385. function checkRecycleBin(files) {
  386. if (!recycleReady && path == base) {
  387. for (var i = 0; i < files.length; i++) {
  388. if (files[i].path == recyclePath) {
  389. recycleReady = Promise.resolve(true);
  390. }
  391. break;
  392. }
  393. recycleReady = recycleReady || createRecycleBin();
  394. }
  395. }
  396. return Promise.all([listPromise, transitPromise]).then(function(values) {
  397. var files = values[0];
  398. checkRecycleBin(files);
  399. return renderList(files);
  400. }, function(error) {
  401. var notice = minder.getUI('widget/notice');
  402. notice.error('err_ls', error);
  403. });
  404. }
  405. function renderFileList(files) {
  406. $list.empty();
  407. if (!files.length) {
  408. $list.append('<li class="empty" disabled="disabled">' + minder.getLang('ui.emptydir') + '</li>');
  409. } else {
  410. files.forEach(function(file) {
  411. if (!file.isDir && (!listFilter || !listFilter(file))) return;
  412. if (file.path == recyclePath) return;
  413. $('<li></li>')
  414. .append('<span class="icon"></span>')
  415. .append('<span class="filename">' + file.filename + '</span>')
  416. .append('<a class="rename-button" title="' + minder.getLang('ui.rename') + '">"' + minder.getLang('ui.rename') + '"</a>')
  417. .addClass('file-list-item')
  418. .addClass(file.isDir ? 'dir' : 'file')
  419. .data('file', file)
  420. .attr('draggable', true)
  421. .appendTo($list);
  422. });
  423. }
  424. }
  425. finder._renderFileList = renderFileList;
  426. function renderList(files) {
  427. files.sort(function(a, b) {
  428. if (a.isDir > b.isDir) {
  429. return -1;
  430. } else if (a.isDir == b.isDir) {
  431. return a.createTime > b.createTime ? -1 : 1;
  432. } else return 1;
  433. });
  434. renderFileList(files);
  435. // 通知其他 finder 更新
  436. instances.forEach(function(instance) {
  437. if (instance == finder) return;
  438. if (instance.pwd() == currentPath)
  439. instance._renderFileList(files);
  440. });
  441. fadeInList();
  442. checkSelect();
  443. finder.fire('cd', currentPath);
  444. }
  445. function updateNav() {
  446. $nav.empty();
  447. if (currentPath != base && currentPath != base + '/') {
  448. $nav.append('<a class="dir-back">Back</a>');
  449. } else {
  450. $nav.append('<span class="my-document"></span>');
  451. }
  452. var path = currentPath.substr(base.length);
  453. var parts = path.split('/');
  454. var processPath = '';
  455. function pathButton(part) {
  456. processPath += part + '/';
  457. var $a = $('<a></a>').addClass('dir');
  458. if (part == base) {
  459. $a.text(minder.getLang('ui.mydocument'));
  460. } else if (part == '.recycle') {
  461. $a.text(minder.getLang('ui.recycle'));
  462. $finder.addClass('recycle-bin');
  463. } else {
  464. $a.text(part);
  465. }
  466. return $a.data('path', processPath).data('file', {
  467. path: processPath.substr(0, processPath.length - 1),
  468. filename: part == base ? minder.getLang('ui.mydocument') : part
  469. });
  470. }
  471. $finder.removeClass('recycle-bin');
  472. $nav.append(pathButton(base));
  473. parts.forEach(function(part) {
  474. if (!part) return;
  475. $nav.append('<span class="spliter"></span>');
  476. $nav.append(pathButton(part));
  477. });
  478. }
  479. function select(path) {
  480. selected = path;
  481. return checkSelect();
  482. }
  483. function checkSelect() {
  484. var hasSelect = false;
  485. $list.find('.file-list-item').removeClass('selected').each(function() {
  486. var file = $(this).data('file');
  487. if (file && file.path == selected) {
  488. $(this).addClass('selected');
  489. hasSelect = true;
  490. $list[0].focus();
  491. finder.fire('select', file, this);
  492. }
  493. });
  494. if (!hasSelect) selected = false;
  495. return hasSelect;
  496. }
  497. function pwd() {
  498. return currentPath;
  499. }
  500. finder.list = list;
  501. finder.select = select;
  502. finder.pwd = pwd;
  503. finder.refresh = refresh;
  504. return finder;
  505. }
  506. Finder.generate = generate;
  507. return Finder;
  508. });