From b99f63700f0faab6d2cde3b989b25456b5c96561 Mon Sep 17 00:00:00 2001 From: Jinkun Jang Date: Wed, 13 Mar 2013 02:12:56 +0900 Subject: [PATCH 2/4] Tizen 2.1 base --- AUTHORS | 6 + LICENSE.Flora | 206 ++++++++++++++ NOTICE.Flora | 4 + config.xml | 15 ++ css/style.css | 166 ++++++++++++ icon.png | Bin 0 -> 17581 bytes images/00_winset_Back.png | Bin 0 -> 3225 bytes images/etc.png | Bin 0 -> 1292 bytes images/folder.png | Bin 0 -> 743 bytes images/img.png | Bin 0 -> 1454 bytes images/music.png | Bin 0 -> 1621 bytes images/pdf.png | Bin 0 -> 4794 bytes images/ppt.png | Bin 0 -> 5417 bytes images/text.png | Bin 0 -> 4023 bytes images/video.png | Bin 0 -> 2040 bytes index.html | 16 ++ js/app.clipboard.js | 116 ++++++++ js/app.config.js | 30 +++ js/app.helpers.js | 121 +++++++++ js/app.js | 244 +++++++++++++++++ js/app.model.js | 222 +++++++++++++++ js/app.systemIO.js | 247 +++++++++++++++++ js/app.ui.js | 623 +++++++++++++++++++++++++++++++++++++++++++ js/app.ui.templateManager.js | 110 ++++++++ js/main.js | 80 ++++++ templates/emptyFolder.tpl | 1 + templates/fileRow.tpl | 5 + templates/folderRow.tpl | 6 + templates/levelUpRow.tpl | 3 + templates/main.tpl | 64 +++++ 30 files changed, 2285 insertions(+) create mode 100644 AUTHORS create mode 100644 LICENSE.Flora create mode 100644 NOTICE.Flora create mode 100644 config.xml create mode 100644 css/style.css create mode 100755 icon.png create mode 100644 images/00_winset_Back.png create mode 100755 images/etc.png create mode 100755 images/folder.png create mode 100755 images/img.png create mode 100755 images/music.png create mode 100755 images/pdf.png create mode 100755 images/ppt.png create mode 100755 images/text.png create mode 100755 images/video.png create mode 100644 index.html create mode 100644 js/app.clipboard.js create mode 100644 js/app.config.js create mode 100644 js/app.helpers.js create mode 100644 js/app.js create mode 100644 js/app.model.js create mode 100644 js/app.systemIO.js create mode 100644 js/app.ui.js create mode 100644 js/app.ui.templateManager.js create mode 100644 js/main.js create mode 100644 templates/emptyFolder.tpl create mode 100644 templates/fileRow.tpl create mode 100644 templates/folderRow.tpl create mode 100644 templates/levelUpRow.tpl create mode 100644 templates/main.tpl diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..a447a9f --- /dev/null +++ b/AUTHORS @@ -0,0 +1,6 @@ +Pawel Sierszen +Piotr Wronski +Dariusz Paziewski +Tomasz Lukawski +Tomasz Paciorek +Aniela Rudy-Gawecka diff --git a/LICENSE.Flora b/LICENSE.Flora new file mode 100644 index 0000000..9c95663 --- /dev/null +++ b/LICENSE.Flora @@ -0,0 +1,206 @@ +Flora License + +Version 1.0, May, 2012 + +http://floralicense.org/license/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and +all other entities that control, are controlled by, or are +under common control with that entity. For the purposes of +this definition, "control" means (i) the power, direct or indirect, +to cause the direction or management of such entity, +whether by contract or otherwise, or (ii) ownership of fifty percent (50%) +or more of the outstanding shares, or (iii) beneficial ownership of +such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation source, +and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, +made available under the License, as indicated by a copyright notice +that is included in or attached to the work (an example is provided +in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, +that is based on (or derived from) the Work and for which the editorial +revisions, annotations, elaborations, or other modifications represent, +as a whole, an original work of authorship. For the purposes of this License, +Derivative Works shall not include works that remain separable from, +or merely link (or bind by name) to the interfaces of, the Work and +Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original +version of the Work and any modifications or additions to that Work or +Derivative Works thereof, that is intentionally submitted to Licensor +for inclusion in the Work by the copyright owner or by an individual or +Legal Entity authorized to submit on behalf of the copyright owner. +For the purposes of this definition, "submitted" means any form of +electronic, verbal, or written communication sent to the Licensor or +its representatives, including but not limited to communication on +electronic mailing lists, source code control systems, and issue +tracking systems that are managed by, or on behalf of, the Licensor +for the purpose of discussing and improving the Work, but excluding +communication that is conspicuously marked or otherwise designated +in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +"Tizen Certified Platform" shall mean a software platform that complies +with the standards set forth in the Compatibility Definition Document +and passes the Compatibility Test Suite as defined from time to time +by the Tizen Technical Steering Group and certified by the Tizen +Association or its designated agent. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work +solely as incorporated into a Tizen Certified Platform, where such +license applies only to those patent claims licensable by such +Contributor that are necessarily infringed by their Contribution(s) +alone or by combination of their Contribution(s) with the Work solely +as incorporated into a Tizen Certified Platform to which such +Contribution(s) was submitted. If You institute patent litigation +against any entity (including a cross-claim or counterclaim +in a lawsuit) alleging that the Work or a Contribution incorporated +within the Work constitutes direct or contributory patent infringement, +then any patent licenses granted to You under this License for that +Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof pursuant to the copyright license +above, in any medium, with or without modifications, and in Source or +Object form, provided that You meet the following conditions: + + 1. You must give any other recipients of the Work or Derivative Works + a copy of this License; and + 2. You must cause any modified files to carry prominent notices stating + that You changed the files; and + 3. You must retain, in the Source form of any Derivative Works that + You distribute, all copyright, patent, trademark, and attribution + notices from the Source form of the Work, excluding those notices + that do not pertain to any part of the Derivative Works; and + 4. If the Work includes a "NOTICE" text file as part of its distribution, + then any Derivative Works that You distribute must include a readable + copy of the attribution notices contained within such NOTICE file, + excluding those notices that do not pertain to any part of + the Derivative Works, in at least one of the following places: + within a NOTICE text file distributed as part of the Derivative Works; + within the Source form or documentation, if provided along with the + Derivative Works; or, within a display generated by the Derivative Works, + if and wherever such third-party notices normally appear. + The contents of the NOTICE file are for informational purposes only + and do not modify the License. + +You may add Your own attribution notices within Derivative Works +that You distribute, alongside or as an addendum to the NOTICE text +from the Work, provided that such additional attribution notices +cannot be construed as modifying the License. You may add Your own +copyright statement to Your modifications and may provide additional or +different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works +as a whole, provided Your use, reproduction, and distribution of +the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Flora License to your work + +To apply the Flora License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Flora License, Version 1.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://floralicense.org/license/ + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/NOTICE.Flora b/NOTICE.Flora new file mode 100644 index 0000000..fdb699a --- /dev/null +++ b/NOTICE.Flora @@ -0,0 +1,4 @@ +Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved. +Except as noted, this software is licensed under Flora License, Version 1. +Please, see the LICENSE file for Flora License terms and conditions. + diff --git a/config.xml b/config.xml new file mode 100644 index 0000000..3bc4bb5 --- /dev/null +++ b/config.xml @@ -0,0 +1,15 @@ + + + + + FileManager + + + + + + + + + + diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..f81279e --- /dev/null +++ b/css/style.css @@ -0,0 +1,166 @@ +* { + margin: 0px; + padding: 0px; +} + +body { + overflow: hidden; +} + +#fileList { + margin: 0; +} + +#mainTitle { + width: 260px; +} + +#fileList > li { + padding-top: 0.3rem; + padding-bottom: 0.3rem; + border-top: solid 1px #ddd; +} + +#fileList > li > span.nodename { + display: inline-block; + position: absolute; + line-height: 32px; + white-space: nowrap; + text-overflow: ellipsis; + width: 75%; + overflow: hidden; + margin-top: 5px; +} + +#fileList > li.gradientBackground > span.nodename { + color: #fff !important; +} + +#fileList > li.file img { + width: 32px; + height: 32px; +} + +#fileList > li.folder img { + margin-top: 0.1rem; +} + +#fileList > li.levelUp { + padding-left: 47px !important; + height: 32px; +} + +.selectAll { + padding-left: 10px; + display: inline-block; +} + +.selectAll span.ui-icon { + top: 40% !important; +} + +.selectAll span.ui-btn-text { + padding-left: 1.5rem !important; +} + +#navbar { + height: 16px; + padding: 2px 10px; + font-size: 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + background-color: #EEE; + border-top: solid 1px #DDD; + direction: rtl; + text-align: left; +} + +.ui-pathDiv { + position: absolute; + top: 49px; + left: 0px; + right: 0px; + bottom: 0px; +} + +#pathDiv { + padding: 2px 0px 0px 5px; + border: 0px; +} + +#pathDiv .ui-li-text-main { + font-size: 18px; +} + +#morePopup td.text { + padding: 5px; +} + +.ui-header.ui-bar-s .ui-btn.standard { + width: 100%; + height: 100%; +} + +.ui-li-1line-bigicon1.ui-li.ui-li-static.ui-body-s.ui-li-has-thumb { + padding-left: 0.7rem; + padding-right: 0rem; +} + +.my-ui-checkbox { + display: inline-block; + margin-top: 0rem; + margin-right: 0rem; + position: relative !important; + top: -0.7rem; + left: -1.5rem; +} + +ul.ui-listview > li.ui-li-1line-bigicon1 img.ui-li-bigicon { + display: inline-block; + margin-top: 0rem; + margin-right: 0.7rem; + position: relative; + left: 5px; +} + +.ui-checkbox .ui-btn.ui-btn-icon-left .ui-btn-inner { + line-height: 1.1rem; + padding: 0 0 0 0rem; + width: 30px; +} + +.ui-checkbox .ui-btn.ui-btn-icon-left .ui-btn-inner.ui-btn-hastxt { + width: 100%; +} + +.ui-btn-corner-all { + -webkit-border-radius: 0px; + bordert-radius: 0px; +} + +.ui-content.ui-scrollview-clip > div.ui-scrollview-view { + padding: 0px; +} + +input.ui-input-text.new_folder { + width: 100%; + height: 50px; + padding: 0 0 0 .4em; +} + +.gradientBackground { + background: -webkit-linear-gradient(top, #5A99BA 0%, #205473 100%) !important; /* from tizen-white */ +} + +.hidden { + display: none !important; +} + +.vhidden { + visibility: hidden !important; +} + +.ui-tabbar a { + color: #999 !important; +} \ No newline at end of file diff --git a/icon.png b/icon.png new file mode 100755 index 0000000000000000000000000000000000000000..983c883493a0ae561b9592d54ea8204042b4b6fb GIT binary patch literal 17581 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 LNkvXXu0mjfzQ*#c literal 0 HcmV?d00001 diff --git a/images/etc.png b/images/etc.png new file mode 100755 index 0000000000000000000000000000000000000000..26748d83cfa4fee28a125d4dcdb01a77f901102d GIT binary patch literal 1292 zcmXX_4NMbf82%72CqH@+lifgL83s7xUn4kb2m%%b4_HAR0;?NfOL0M?fWN^EDrmWZ zWy)=3p!|qJnXozNCQ%1o4yIBDC@|4AgDu@C!HGx=0^Z&axP15A^Zq^W`@Ly(ikMxl zP99DOA=k(Vc^rKG@k`hPzPdj46rm+iim1Keq9}p~9@z^~YFXr{RHO$)$wQGcEqZ`2 zM8N}`sKO!Ah!50)iOYvD(k11x5lrAf2%K~`Rs$rJha|eo*lEGZfHK>Bv=3)W?u*nt zTm^_vE9N-Iin$atZ8(j*g*||9u-Pa|O0EGy4mRgUf+|UeNYr8v{m=>~Ob8qvBI?W{ zslZba6^njk5NQ}=9tnd8f`G6G@Zee?rMNH3w=O6HqL55z0b?%+;`v*#DFxXoyctk{ zTT?JMz}}`zTLu3pb>7NWTBm;N}w{d$UhOcP_W1W$zXLO zqNG-k7zMPCCH!czN+fg^M3As9VxKk?7FZ;p^}7*4P^AKyFGxEqWHKuoctI#w>L^PV z33Q`CDp@j}C3S+NLZE8}B7voEK`)lb70@X&sbXo9nM~JG1_)`1QjyFRNR?LFYDU>+ zszg8vt<+?ujRI_dOxB`=aw1vVU4~>Z=aH1EF`5q$TK__edCE*w+NGh29$nazH~aews|Vu zxibu}UIr>4$unIMlfGO%u-nZ{Y!SEWtQCIWOHiadbnnRn@~SZ zzbWIHsVMV+#~Qb4pmUw4?^#r5wr;s?%kufF?mbX z_B@|X&$@$!Jubt4cI-<2X=LICpRY+AZD>kUTQ8I;KR&+cYa;1Ed(g0YP4P>g{{Fe5 zrOOZJHoshPC_ZV%wR$4*PVJ*xeuf}NMQ^3terhZ-qDej?4avE$=(v%Byl#d~uMHVH zDErPme&hKQuL8f~k9|?_)T9hQmth)r(&=xSTw{+Y2i=~;4_-a$zjttr-IJloAcvNz z*MqCFU*GwVyxqup4d&>>8=ZUYxESw$NhWRkhLq9I(Q4I~UK)qgO|ot6l-zrDXK0V6 zbGogU>YR4z*}t34v&NmiRHM6WajbJK-nQqhwr2U|0|kC56jeyQ`Oz;W$U$Rt6MC|| z-?BCD?_60u&Z)dKoJti~mYOhVXEPet<+aw?l8UVjwwaw(zU~QaE3VjQ_4r3SY}gw8 zZuHj^kGs~_U))9)=2adpJeK&uqu^==GIaV` z*J;mo^`-0S#87qmr-AOx(axKjcQ_jbOFe)!9*clvqyM!=M3Q?<0t&rNN_|Ch+{7?P)00001b5ch_0Itp) z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2i*Y| z4FVjzHBMOo000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0006)Nkl)yf6okK#$FQXemh5sL&XS|#I#jWS6*nMNELczk$H9v1NHg=A#f<+TB$knX zrpi?s$un+s_vqBmivu8m}@DuHOc(Dg`tA zu=W-oQ{Fs9L(=wdxivvs^M8=|=5bhu?Sa*rT4jFi~U->YAbMrnHNAy0={u z-sWGkcyWSKbFk<5Ubqar)n78O(dhwgNvR^G&IeaEzok(A#5f;dX5bffZXs25UckUsq literal 0 HcmV?d00001 diff --git a/images/img.png b/images/img.png new file mode 100755 index 0000000000000000000000000000000000000000..4dd3be3b1bbeef29bb56a19a9de7184919ceec6e GIT binary patch literal 1454 zcmV;f1yTBmP)<>OB zFU*^7e)%%*4YO+*WB7k!45LO-s3=qvDhd^aib6#pA1*BNBRoJg5OzhO=mL&*l+sQb z#wC|<1%Tu_i|Xd$t(aOzO8O-Nj&XHN{$T-A4 z-cR}bCgfV2-qlMIKtZhf1IkzP>Gv5@KEy#oPwQNy z^9Rc3G=T=-I>s!c00=x|(l7R{YE3m9UP8F){VF9ok7%Ps<%B0LKps9N2;nQe9 ztGnGB0{#t5<~9?7ja(a8UUMNfGdgSHk9b9=wJ3n`K*Ezy?Iq>_dx5byGHaV~WH9D) zE>W3b0=pfVbO@H-Pa=L=Ra8%rD?Jyy3%L;-vI^BiX%zSbC;?DF&l`)0e{K;@!Tml-M}?|O)DtuQC6<+ZT9FASm-`y!O{k`e2H>FbC1t>o`EnP) z5{%i0g9ZW6Dm{4^A2^!A-BC4l6Zmr19H8Axg~T;$g&-M(9GQnnRYo+RA@=v>Zkau# zqmz-2-}!w;dHw6unO}San3^p*e0Vzjeo$8u~ZR zgM{1w_Ia(4dkm!o$s#n36tR7qiGQ#GkJvyvunrf!vr10}7p`v(&R)KUP`PD0o#O-a zuWdvwcH9CMV$2P%LbfZR_%c)|bdnN*kkdt?=K=nKG3yus!9|-disq`)JU|Ko2&g}R z28`*zD?0r;(;CwF6^s>kXE@6U2pwaUz&2p4xF@4Z32r@cA3fbG`@yr2Xwpy_g^;vs zz|LFd2up#t$wbA(7lUi}SH!n9Jv;h41_Q!al*Q%ODq%K^fUkf+DCPcjR+6%~03YIe}qJp-f+uV_JzZ3lQT z>-zC|eGh?+#sN`5@aug`|Z+wUb_;3P4R?ja)bp zA|H%71w6+nqITj3R*^#DF%SW`FyHg<%{{?s_`yt7DGoo15BRt)5oQG!og-{Lm1pQIyItZOZ0iD1L7;`6rne)IQ^@4HYdOP-S3`z_MX`T z!uUlVZVWdBK|Ep?$B5wn9LEoJf$`0$=N$;*Tp?T{7L1IHKm`sQvy6esfDs1FgkvTG zGeCyXgyCc)mk5VQkbbB@Ag3nGFv`&Hfd3gEcfrii2BQJf6X3%sq)6%{D?v5{l1U($ z^uTBtTZKR}X~Th%bdo2I)(_wA!vpJt%%y~WR6YU}2~0m$8@v;EA$k~Lwy{XSEBepW z!SI2i7C6#EhTG+6M($`0IMBc)C2-`VBXqbLhdLTV4(K04cpHw}gJ#$isKdyia9|`A zEkKq0qtZ}HD3=mt09pgkh$vAaN}LpxD1p)d z^a@8Ri5_SaK%)l)0km46$v~wNG(&;rY8)&_5kwEP9E24|iJ~JcC4f4#X<^qSs8j>= z5{gI<401F#1I_gf9tnppP_|VRzYMvyBPGSGQkl&vvkeBD%8b)naGEVlMa9ke>Y+L@ zFR!oj;*g7qzl~WtuTBftYP@un8xtjXwHS^ES%`&dtFhqOAzSQ0(XlAyo`00;MiM@G~ zP7^f19abKEd$6uMh-N9&m1}}?=fkh6F*YVjykm8r#gh5ySH4X)w(x}aJ<-XaHh1E| zhjILF)AFLWgc(i7gu-vEC{-hVp`bq%y%>HbfP3F)VQoDR^fwvyU)zwX~c^?N^s*YGC7UVU9iSLHQ@1%LldbWMlVika)9)BDG* zjJ{vAC*9gh;T&%- z%qLiSRi~CUrUw|!^{+eJW+9Tu)Dh+B+q{i~fC}CfE)sLr_w0yBY|m=lSy_LC9atgk z(cw8+N`C(KtjN;wlV46}xkXa6tt}B)@0I}ZbH(9_f#QBw@N5txom#V z#&s2szc8QiXGX{QpNUR_}rkcNY@4q(qs#pL3 literal 0 HcmV?d00001 diff --git a/images/pdf.png b/images/pdf.png new file mode 100755 index 0000000000000000000000000000000000000000..2480d818341a660d16797b59ef59121484cf9aa0 GIT binary patch literal 4794 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 Wcsr)1CMZz=0000h7 literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 0000000..3b69ba6 --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + file manager + + + + + + + +
+ + \ No newline at end of file diff --git a/js/app.clipboard.js b/js/app.clipboard.js new file mode 100644 index 0000000..a944925 --- /dev/null +++ b/js/app.clipboard.js @@ -0,0 +1,116 @@ +/*jslint browser: true, devel: true */ +/*global $*/ + +/** + * @class Config + */ +function Clipboard() { + 'use strict'; + this.mode = this.INACTIVE_MODE; +} + +(function () { // strict mode wrapper + 'use strict'; + Clipboard.prototype = { + /** + * Clipboard mode for copying + */ + COPY_MODE_ID: 0, + + /** + * Clipboard mode for moving + */ + MOVE_MODE_ID: 1, + + /** + * Clipbboard inactive mode + */ + INACTIVE_MODE: -1, + + /** + * Clipboard data + */ + data: [], + + /** + * Clipboard mode: [copy | move | inactive] + */ + mode: undefined, + + /** + * Returns all paths in clipboard + * @returns {array} + */ + get: function Clipboard_get() { + return this.data; + }, + + /** + * Add new path to clipboard + * @param {array} full path + * @returns {number} current length of clipboard objects + */ + add: function Clipboard_add(paths) { + var len = paths.length, + i; + + // clear clipboard + this.clear(); + for (i = 0; i < len; i += 1) { + if (this.has(paths[i]) === false) { + console.log('Adding file ' + paths[i] + ' to clipboard'); + this.data.push(paths[i]); + } + } + + return this.data.length; + }, + + /** + * Checks if specified path is already in clipboard + * @param {string} full path + * @returns {boolean} + */ + has: function Clipboard_has(path) { + console.log('Clipboard_has', path); + return $.inArray(path, this.data) === -1 ? false : true; + }, + + /** + * Clears all clipboard data and reset clipboard mode + */ + clear: function Clipboard_clear() { + console.log('Clipboard_clear'); + this.data = []; + this.mode = this.INACTIVE_MODE; + }, + + /** + * Sets clipboard mode + * @param {number} mode + * @returns {boolean} + */ + setMode: function Clipboard_setMode(mode) { + if ($.inArray(mode, [this.MOVE_MODE_ID, this.COPY_MODE_ID]) === false) { + console.error('Incorrect clipboard mode'); + return false; + } + this.mode = mode; + return true; + }, + + /** + * @returns {number} mode Clipboard mode + */ + getMode: function Clipboard_getMode() { + return this.mode; + }, + + /** + * @returns {boolean} + */ + isEmpty: function Clipboard_isEmpty() { + return this.data.length === 0; + } + }; +}()); diff --git a/js/app.config.js b/js/app.config.js new file mode 100644 index 0000000..32b92f5 --- /dev/null +++ b/js/app.config.js @@ -0,0 +1,30 @@ +/*jslint devel: true */ + +/** + * @class Config + */ +function Config() { + 'use strict'; +} + +(function () { // strict mode wrapper + 'use strict'; + Config.prototype = { + + properties: { + 'templateDir': 'templates', + 'templateExtension': '.tpl' + }, + + /** + * Returns config value + */ + get: function (value, defaultValue) { + + if (this.properties.hasOwnProperty(value)) { + return this.properties[value]; + } + return defaultValue; + } + }; +}()); diff --git a/js/app.helpers.js b/js/app.helpers.js new file mode 100644 index 0000000..43687e1 --- /dev/null +++ b/js/app.helpers.js @@ -0,0 +1,121 @@ +/*jslint devel: true */ +/*global $ */ + +/** + * @class Helpers + */ +function Helpers() { + 'use strict'; +} + +(function () { // strict mode wrapper + 'use strict'; + Helpers.prototype = { + + /** + * Capitalise the first letter + * + * @param {string} text + * @returns {string} + */ + UCFirst: function Helpers_UCFirst(text) { + return text.charAt(0).toUpperCase() + text.slice(1); + }, + + /** + * @param {string} fileName + * @returns {string} extension for specified file name + */ + getFileExtension: function Helpers_getFileExtension(fileName) { + console.log('Helpers_getFileExtension', fileName); + var splittedFileName = fileName.split('.'), + ext = ''; + + if (splittedFileName.length > 1) { + ext = '.' + splittedFileName.pop(); + } + return ext; + }, + + /** + * Return icon filename for the given extension. + * For example, for '.mp3' returns 'music.png' + * + * @param {string} ext + * @return {string} + */ + resolveFileIcon: function Helpers_resolveFileIcon(ext) { + console.log('Helpers_resolveFileIcon', ext); + switch (ext) { + case '.jpg': + return 'img.png'; + case '.png': + return 'img.png'; + case '.gif': + return 'img.png'; + case '.pdf': + return 'pdf.png'; + case '.mp3': + return 'music.png'; + case '.mp4': + return 'video.png'; + case '.ppt': + return 'ppt.png'; + case '.txt': + return 'text.png'; + case '.doc': + return 'text.png'; + case '.xls': + return 'text.png'; + case '.directory': + return 'folder.png'; + default: + return 'etc.png'; + } + }, + + /** + * Resolve file extension to MIME type + * + * @param {string} ext File extension + * @returns {string} + */ + resolveMimeType: function Helpers_resolveMimeType(ext) { + console.log('Helpers_resolveMimeType', ext); + var mime = ''; + + if (ext === '.jpg' || ext === '.png' || ext === '.gif') { + mime = 'image/*'; + } else if (ext === '.mp4' || ext === '.ogv') { + mime = 'video/*'; + } else if (ext === '.mp3') { + mime = 'audio/*'; + } else if (ext === '.txt' || ext === '.doc' || ext === '.html' || ext === '.ppt' || ext === '.xls' || ext === '.pdf') { + mime = 'text/*'; + } + + return mime; + }, + + /** + * Returns thumbnail URI for specified file + * @param {string} fileName + * @param {File} node + * @returns {string} + */ + getThumbnailURI: function Helpers_getThumbnailURI(fileName, node) { + console.log('Helpers_getThumbnailURI', fileName, node); + var ext = this.getFileExtension(fileName), + thumbnailURI = ''; + + if (!node.thumbnailURIs) { + console.log('getFileListItem: get icon'); + thumbnailURI = 'images/' + this.resolveFileIcon(ext); + } else if (node.thumbnailURIs[0] && $.inArray(ext, ['.mp4', '.jpg', '.png', '.gif'])) { + thumbnailURI = node.thumbnailURIs[0]; + } + + return thumbnailURI; + } + }; +}()); \ No newline at end of file diff --git a/js/app.js b/js/app.js new file mode 100644 index 0000000..08bd63a --- /dev/null +++ b/js/app.js @@ -0,0 +1,244 @@ +/*jslint devel: true */ +/*global tizen, $, app, Ui, Model, Helpers, Config, Clipboard*/ + +var App = null; + +(function () { // strict mode wrapper + 'use strict'; + + /** + * Creates a new application object + * + * @class Application + * @constructor + */ + App = function App() { + }; + + App.prototype = { + /** + * @type Array + */ + requires: ['js/app.config.js', 'js/app.model.js', 'js/app.ui.js', 'js/app.ui.templateManager.js', 'js/app.systemIO.js', 'js/app.helpers.js', 'js/app.clipboard.js'], + + /** + * @type Model + */ + model: null, + + /** + * @type Ui + */ + ui: null, + + /** + * @type Config + */ + config: null, + + /** + * @type SystemIO + */ + systemIO: null, + + /** + * @type Helpers + */ + helpers: null, + + /** + * @type {string} + */ + currentPath: '', + + /** + * + */ + currentDirHandle: null, + + /** + * @type {Clipboard} + */ + clipboard: null, + + /** + * Initialization + */ + init: function App_init() { + console.log('App_init'); + this.config = new Config(); + this.model = new Model(); + this.ui = new Ui(); + this.helpers = new Helpers(); + this.clipboard = new Clipboard(); + + this.model.loadInternalStorages(this.initUi.bind(this)); + this.addEvents(); + }, + + /** + * UI initialization + */ + initUi: function App_initUi() { + console.log('App_initUi'); + this.ui.init(this.model.getInternalStorages()); + }, + + /** + * Add pages events + */ + addEvents: function App_addEvents() { + console.log('App_addEvents'); + var self = this; + document.addEventListener('webkitvisibilitychange', function() { self.refreshCurrentPage(); } ); + }, + + /** + * Displays media storages + */ + displayStorages: function App_displayStorages() { + this.ui.displayStorages(this.model.getInternalStorages()); + }, + + /** + * Displays specified folder + * @param {string} path + */ + displayFolder: function App_displayFolder(path) { + console.log('App_displayFolder', path); + var self = this; + + // get folder data and push into rendering method + this.model.getFolderData(path, function (dir, nodes) { + // on success + console.log('App_displayFolder success', dir, nodes); + + // update current path + self.currentPath = path; + + // update current dir handle + self.currentDirHandle = dir; + + // display folder UI + self.ui.displayFolder(path, nodes); + }); + }, + + /** + * Opens specified file + * @params {string} uri File URI + */ + openFile: function App_openFile(uri, fullUri) { + console.log('App_openFile', uri); + var ext = this.helpers.getFileExtension(uri), + mime = this.helpers.resolveMimeType(ext); + + if (mime !== '') { + this.model.openFile(fullUri, mime); + } else { + console.error('Unsupported mime type', mime); + } + }, + + /** + * Displays parent location + */ + goLevelUp: function App_goLevelUp() { + // split current path and get proper path for parent location + var newPath = this.currentPath.split('/').slice(0, -1).join('/'); + + if (newPath !== '') { + this.displayFolder(newPath); + } else { + this.displayStorages(); + } + }, + + /** + * creates new dir in currently viewed dir + * @param {string} dirName + */ + createDir: function App_createDir(dirName) { + console.log('App_createDir', dirName); + + if (this.currentDirPath !== '') { + try { + this.currentDirHandle.createDirectory(dirName); + } catch (e) { + console.error('App_createDir error', e); + } + this.refreshCurrentPage(); + } else { + alert("you can't create new nodes in main view"); + } + }, + + /** + * Triggers refresh current page + */ + refreshCurrentPage: function App_refreshCurrentPage() { + console.log('App_refreshCurrentPage'); + if (this.currentPath !== '') { + this.displayFolder(this.currentPath); + } else { + this.displayStorages(); + } + }, + + /** + * Deletes nodes with specified paths + * @param {string[]} nodePaths + */ + deleteNodes: function App_deleteNodes(nodes) { + console.log('App_deleteNodes', nodes); + this.model.deleteNodes(nodes, this.currentDirHandle, this.ui.removeNodeFromList.bind(this.ui)); + }, + + /** + * @param {string[]} filepaths + * @param {number} clipboard mode + */ + saveToClipboard: function App_saveToClipboard(paths, mode) { + this.clipboard.add(paths); + this.clipboard.setMode(mode); + this.ui.refreshPasteActionBtn(this.clipboard.isEmpty()); + }, + + /** + * Paste nodes from clipboard to current dir + */ + pasteClipboard: function App_pasteClipboard() { + console.log('App_pasteClipboard'); + var clipboardData = this.clipboard.get(); + + if (clipboardData.length === 0) { + alert('Clipboard is empty'); + return false; + } + + if (this.clipboard.getMode() === this.clipboard.COPY_MODE_ID) { + this.model.copyNodes(this.currentDirHandle, clipboardData, this.currentPath, this.onPasteClipboardSuccess.bind(this)); + } else { + this.model.moveNodes(this.currentDirHandle, clipboardData, this.currentPath, this.onPasteClipboardSuccess.bind(this)); + } + + this.ui.refreshPasteActionBtn(this.clipboard.isEmpty()); + }, + + /** + * Handler for paste clipboard success + */ + onPasteClipboardSuccess: function App_onPasteClipboardSuccess() { + console.log('App_onPasteClipboardSuccess'); + this.clipboard.clear(); + this.refreshCurrentPage(); + }, + + /** + * App exit + */ + exit: function App_exit() { + tizen.application.getCurrentApplication().exit(); + } + }; +}()); diff --git a/js/app.model.js b/js/app.model.js new file mode 100644 index 0000000..fa1223d --- /dev/null +++ b/js/app.model.js @@ -0,0 +1,222 @@ +/*jslint devel: true */ +/*global tizen, SystemIO */ + +/** + * @class Model + */ +function Model() { + 'use strict'; + this.init(); +} + +(function () { // strict mode wrapper + 'use strict'; + Model.prototype = { + + /** + * @type SystemIO + */ + systemIO: null, + + /** + * @type Array + */ + storages: [], + + /** + * @type MediaSource + */ + mediaSource: null, + + /** + * API module initialisation + */ + init: function Model_init() { + console.log('Model_init'); + this.systemIO = new SystemIO(); + //this.mediaSource = tizen.mediacontent.getLocalMediaSource(); + }, + + /** + * @returns {FileSystemStorage[]} storages + */ + getInternalStorages: function Model_getInternalStorages() { + console.log('Model_getInternalStorages'); + return this.storages; + }, + + /** + * Saves storages + * @param {function} callback + */ + loadInternalStorages: function Model_loadInternalStorages(onSuccess) { + console.log('Model_loadInternalStorages', onSuccess); + var self = this; + + this.systemIO.getStorages('INTERNAL', function (storages) { + self.storages = storages; + if (typeof onSuccess === 'function') { + console.log('Storages loaded successfully'); + onSuccess(); + } + }); + }, + + /** + * Returns folder data + * @param {string} node Node path + * @param {function} onSuccess Success callback + */ + getFolderData: function Model_getFolderData(path, onSuccess, onError) { + console.log('Model_getFolderData', path); + + var onOpenSuccess = function (dir) { + dir.listFiles( + function (files) { + console.log('Model_getFolderData listFiles success', files); + onSuccess(dir, files); + }, + function (e) { + console.error('Model_getFolderData listFiles error', e); + } + ); + }, + onOpenError = function (e) { + console.error('Model_getFolderData openDir error', e); + }; + + this.systemIO.openDir(path, onOpenSuccess, onOpenError); + }, + + /** + * Launch a service associated with 'ext' to launch the 'uri' + * @param {string} ext + * @param {string} uri + * @returns {ApplicationSevice} + */ + openFile: function Model_openFile(fullUri, mime) { + var serviceReplyCB = { + onsuccess: function (reply) { + var num = 0; + for (num = 0; num < reply.data.length; num += 1) { + console.log('reply.data[' + num + '].key = ' + reply.data[num].key); + console.log('reply.data[' + num + '].value = ' + reply.data[num].value); + } + }, + onfailure: function () { + console.log('Launch service failed'); + } + }; + + try { + tizen.application.launchAppControl(new tizen.ApplicationControl( + 'http://tizen.org/appcontrol/operation/view', + fullUri, + mime + ), + null, + function () { }, + function (e) { + alert('launch sevice failed. reason :' + e.message); + }, + serviceReplyCB + ); + } catch (e) { + console.error('openFile error:', e); + } + }, + + /** + * @param {File[]} nodes Collection of node objects + * @param {File} dir Directory handle + * @param {function} onSuccess + * @param {function} onError + */ + deleteNodes: function Model_deleteNodes(nodes, dir, onSuccess, onError) { + console.log('Model_deleteNodes', nodes, dir); + var len = nodes.length, + onDeleteNodeSuccess = function (nodeId, isDir) { + console.log((isDir ? 'Folder' : 'File') + nodeId + ' deleted successfully'); + if (typeof onSuccess === 'function') { + onSuccess(nodeId); + } + }, + onDeleteNodeError = function (e) { + console.log('Folder delete error', e); + if (typeof onError === 'function') { + onError(); + } + }, + i; + + for (i = 0; i < len; i = i + 1) { + if (nodes[i].folder) { + dir.deleteDirectory( + nodes[i].uri, + true, + onDeleteNodeSuccess.bind(this, nodes[i].id, true), + onDeleteNodeError + ); + } else { + dir.deleteFile( + nodes[i].uri, + onDeleteNodeSuccess.bind(this, nodes[i].id, false), + onDeleteNodeError + ); + } + } + }, + + /** + * Copy specified files to destination path + * Overwrites existing files + * + * @param {File} dir Directory handle + * @param {string[]} paths Array with absolute virtual file paths + * @param {string} destinationPath + * @param {function} onSuccess callback + */ + copyNodes: function Model_copyNodes(dir, paths, destinationPath, onSuccess) { + console.log('Model_copyNodes', dir, paths, destinationPath); + var len = paths.length, + copied = 0, + onCopyNodeSuccess = function () { + copied += 1; + if (copied === len) { + onSuccess(); + } + }, + i; + + for (i = 0; i < len; i = i + 1) { + dir.copyTo(paths[i], destinationPath, true, onCopyNodeSuccess); + } + }, + + /** + * Move specified files to destination path + * Overwrites existing files + * + * @param {File} dir Directory handle + * @param {string[]} paths Array with absolute virtual file paths + * @param {string} destinationPath + * @param {function} onSuccess callback + */ + moveNodes: function Model_moveNodes(dir, paths, destinationPath, onSuccess) { + console.log('Model_moveNodes', dir, paths, destinationPath); + var len = paths.length, + moved = 0, + onMoveNodeSuccess = function () { + moved += 1; + if (moved === len) { + onSuccess(); + } + }, + i; + + for (i = 0; i < len; i = i + 1) { + dir.moveTo(paths[i], destinationPath, true, onMoveNodeSuccess); + } + } + }; +}()); diff --git a/js/app.systemIO.js b/js/app.systemIO.js new file mode 100644 index 0000000..41c6c96 --- /dev/null +++ b/js/app.systemIO.js @@ -0,0 +1,247 @@ +/*jslint devel: true */ +/*global tizen, localStorage */ + +/** + * @class SystemIO + */ +function SystemIO() { + 'use strict'; +} + +(function () { // strict mode wrapper + 'use strict'; + SystemIO.prototype = { + /** + * Creates new empty file in specified location + * + * @param {File} directoryHandle + * @param {string} fileName + */ + createFile: function SystemIO_createFile(directoryHandle, fileName) { + console.log('SystemIO_createFile', directoryHandle, fileName); + + try { + return directoryHandle.createFile(fileName); + } catch (e) { + console.error('SystemIO_createFile error:', e); + return false; + } + }, + + /** + * Writes content to file stream + * + * @param {File} file handler + * @param {string} file content + * @param {function} on success callback + * @param {string} content encoding + */ + writeFile: function SystemIO_writeFile(fileHandle, fileContent, onSuccess, onError, contentEncoding) { + console.log('SystemIO_writeFile', fileHandle, fileContent.length, contentEncoding); + onError = onError || function () {}; + + fileHandle.openStream('w', function (fileStream) { + console.log('SystemIO_writeFile:_onOpenStreamSuccess', fileStream); + if (contentEncoding === 'base64') { + fileStream.writeBase64(fileContent); + } else { + fileStream.write(fileContent); + } + + fileStream.close(); + + // launch onSuccess callback + if (typeof onSuccess === 'function') { + onSuccess(); + } + }, onError, 'UTF-8'); + }, + + /** + * Opens specified location + * + * @param {string} directory path + * @param {function} on success callback + * @param {function} on error callback + * @param {string} mode + */ + openDir: function SystemIO_openDir(directoryPath, onSuccess, onError, openMode) { + console.log('SystemIO_openDir', directoryPath, openMode); + openMode = openMode || 'rw'; + onSuccess = onSuccess || function () {}; + + try { + tizen.filesystem.resolve(directoryPath, onSuccess, onError, openMode); + } catch (e) { + console.log('SystemIO_openDir error:' + e.message); + } + }, + + /** + * Parse specified filepath and returns data parts + * + * @param {string} filePath + * @returns {array} + */ + getPathData: function SystemIO_getPathData(filePath) { + console.log('SystemIO_getPathData', filePath); + var path = { + originalPath: filePath, + fileName: '', + dirName: '' + }, + splittedPath = filePath.split('/'); + + path.fileName = splittedPath.pop(); + path.dirName = splittedPath.join('/') || '/'; + + return path; + }, + + /** + * Save specified content to file + * + * @param {string} file path + * @param {string} file content + * @param {string} file encoding + */ + saveFileContent: function SystemIO_saveFileContent(filePath, fileContent, onSaveSuccess, fileEncoding) { + console.log('SystemIO_saveFileContent', filePath, fileContent.length, fileEncoding); + var pathData = this.getPathData(filePath), + self = this, + fileHandle; + + function onOpenDirSuccess(dir) { + console.log('SystemIO_saveFileContent:_onOpenDirSuccess', dir); + // create new file + fileHandle = self.createFile(dir, pathData.fileName); + if (fileHandle !== false) { + // save data into this file + self.writeFile(fileHandle, fileContent, onSaveSuccess, false, fileEncoding); + } + } + + // open directory + this.openDir(pathData.dirName, onOpenDirSuccess); + }, + + /** + * Deletes node with specified path + * + * @param {string} node path + * @param {function} success callback + */ + deleteNode: function SystemIO_deleteNode(nodePath, onSuccess) { + console.log('SystemIO_deleteNode', nodePath); + var pathData = this.getPathData(nodePath), + self = this; + + function onDeleteSuccess() { + console.log('SystemIO_deleteNode:_onDeleteSuccess'); + onSuccess(); + } + + function onDeleteError(e) { + console.log('SystemIO_deleteNode:_onDeleteError', e); + } + + function onOpenDirSuccess(dir) { + console.log('SystemIO_deleteNode:_onOpenDirSuccess', dir); + var onListFiles = function (files) { + console.log('SystemIO_deleteNode:_onListFiles', files); + if (files.length > 0) { + // file exists; + if (files[0].isDirectory) { + self.deleteDir(dir, files[0].fullPath, onDeleteSuccess, onDeleteError); + } else { + self.deleteFile(dir, files[0].fullPath, onDeleteSuccess, onDeleteError); + } + } else { + onDeleteSuccess(); + } + }; + + // check file exists; + dir.listFiles(onListFiles, function (e) { + console.error(e); + }, { + name: pathData.fileName + }); + } + + this.openDir(pathData.dirName, onOpenDirSuccess, function (e) { + console.error('openDir error:' + e.message); + }); + }, + + /** + * Deletes specified file + * + * @param {File} dir + * @param {string} file path + * @param {function} delete success callback + * @param {function} delete error callback + */ + deleteFile: function SystemIO_deleteFile(dir, filePath, onDeleteSuccess, onDeleteError) { + console.log('SystemIO_deleteFile', filePath); + try { + dir.deleteFile(filePath, onDeleteSuccess, onDeleteError); + } catch (e) { + console.error('SystemIO_deleteFile error:' + e.message); + return false; + } + }, + + /** + * Deletes specified directory + * + * @param {File} dir + * @param {string} dir path + * @param {function} delete success callback + * @param {function} delete error callback + * @returns {boolean} + */ + deleteDir: function SystemIO_deleteDir(dir, dirPath, onDeleteSuccess, onDeleteError) { + console.log('SystemIO_deleteDir', dir, dirPath); + try { + dir.deleteDirectory(dirPath, false, onDeleteSuccess, onDeleteError); + } catch (e) { + console.error('SystemIO_deleteDir error:' + e.message); + return false; + } + + return true; + }, + + /** + * @param {string} storage type + */ + getStorages: function SystemIO_getStorages(type, onSuccess) { + console.log('SystemIO_getStorages', type); + try { + tizen.filesystem.listStorages(function (storages) { + console.log('...listStorages success', storages); + var tmp = [], + len = storages.length, + i; + + if (type !== undefined) { + for (i = 0; i < len; i += 1) { + if (storages[i].type === 0 || storages[i].type === type) { + tmp.push(storages[i]); + } + } + } else { + tmp = storages; + } + + if (typeof onSuccess === 'function') { + onSuccess(tmp); + } + }); + } catch (e) { + console.error('SystemIO_getStorages error:', e); + } + } + }; +}()); \ No newline at end of file diff --git a/js/app.ui.js b/js/app.ui.js new file mode 100644 index 0000000..f931c02 --- /dev/null +++ b/js/app.ui.js @@ -0,0 +1,623 @@ +/*jslint devel: true */ +/*global $, app, TemplateManager, Helpers */ + +/** + * @class Ui + */ +function Ui() { + 'use strict'; +} + +(function () { // strict mode wrapper + 'use strict'; + Ui.prototype = { + /** + * UI edit mode + * @type {boolean} + */ + editMode: false, + + /** + * @type {bool} block taps until the page change is completed + */ + nodeTapBlock: false, + + /** + * @type {TemplateManager} + */ + templateManager: null, + + /** + * @type Helpers + */ + helpers: null, + + /** + * @const {number} + */ + PATH_DIV_HEIGHT: 20, + + /** + * @const {number} + */ + SELECT_ALL_HEIGHT: 32, + + /** + * @const {number} header height, set on domReady + */ + HEADER_HEIGHT: 53, + + /** + * name of row gradient class + */ + CSS_GRADIENT_CLASS: 'gradientBackground', + + /** + * Standard tabbar actions + * @type {number} + */ + STD_TABBAR_EDIT_ACTION: 0, + STD_TABBAR_MORE_ACTION: 1, + STD_TABBAR_EXIT_ACTION: 2, + + /** + * Edit tabbar actions + * @type {number} + */ + EDIT_TABBAR_DELETE_ACTION: 0, + EDIT_TABBAR_MOVE_ACTION: 1, + EDIT_TABBAR_COPY_ACTION: 2, + EDIT_TABBAR_CANCEL_ACTION: 3, + + /** + * UI Initialization + */ + init: function Ui_init(storages) { + console.log('Ui_init', storages); + this.templateManager = new TemplateManager(); + this.helpers = new Helpers(); + // Disable text selection + $.mobile.tizen.disableSelection(document); + $(document).ready(this.initDom.bind(this, storages)); + }, + + initDom: function Ui_initDom(storages) { + console.log('Ui_initDom', storages); + var self = this; + + this.templateManager.loadToCache(['main', 'fileRow', 'folderRow', 'levelUpRow', 'emptyFolder'], function () { + $('#main').append($(self.templateManager.get('main')).children()).trigger('pagecreate'); + self.addEvents(); + self.displayStorages(storages); + }); + }, + + /** + * Add UI events + */ + addEvents: function Ui_addEvents() { + console.log('Ui_addEvents'); + var self = this; + // touch events for all nodes + $('ul#fileList') + .on('tap', 'li.levelUp', function () { + if (self.editMode === true) { + self.handleCancelEditAction(); + } + app.goLevelUp(); + }) + .on('tap', 'li.node', function (e) { + self.handleNodeClick($(this), true); + }) + .on('change', 'input[type=checkbox]', function (e) { + self.handleNodeClick($(this).closest('li.node'), false); + }) + .on('touchstart', 'li', function (event) { + $(this).addClass(self.CSS_GRADIENT_CLASS); + }) + .on('touchend touchmove', 'li', function (event) { + $(this).removeClass(self.CSS_GRADIENT_CLASS); + }); + + $('.selectAll input').on('change', this.handleSelectAllChange.bind(this)); + + // navbar + $('#navbar').on('tap', 'span', function () { + var uri = $(this).attr('uri'); + if (uri === 'home') { + app.displayStorages(); + } else { + app.displayFolder(uri); + } + }); + + // level up + $('#levelUpBtn').on('tap', app.goLevelUp.bind(app)); + + $('#homeBtn').on('tap', app.displayStorages.bind(app)); + + // edit action + $('#editActionBtn').on('tap', this.handleEditAction.bind(this)); + + // delete action + $('#deleteActionBtn').on('tap', this.handleDeleteAction.bind(this)); + + // cancel edit + $('#cancelActionBtn').on('tap', this.handleCancelEditAction.bind(this)); + + // copy action + $('#copyActionBtn').on('tap', this.handleCopyAction.bind(this)); + + // move action + $('#moveActionBtn').on('tap', this.handleMoveAction.bind(this)); + + // paste action + $('a#pasteActionBtn').on('tap', app.pasteClipboard.bind(app)); + + // exit + $('.ui-myExit').on('tap', app.exit); + + // add folder popup actions + $('#addFolderPopup').on("popupafterclose", function () { + // clear input value + $('#newFolderName').val('New folder'); + }); + + $('#newFolderName').on('tap', function () { + if ($(this).attr('value') === 'New folder') { + $(this).attr('value', ''); + } + }); + + $('#cancelNewFolder').on('tap', function () { + $('#addFolderPopup').popup('close'); + $('#morePopup').popupwindow('close'); + }); + + $('#saveNewFolder').on('tap', function () { + var folderName = $('#newFolderName').val().trim(); + $('#addFolderPopup').popup('close'); + $('#morePopup').popupwindow('close'); + + if (folderName !== '') { + app.createDir(folderName); + } else { + alert("Wrong name of folder"); + } + }); + + $('#newFolderActionBtn, #pasteActionBtn').on('tap', function (e) { + setTimeout(function () { + $('#morePopup').popupwindow('close'); + }, 700); + }); + + }, + + /** + * Handler for node click + * @param {File} node + * @param {boolean} toggleCheckbox + */ + handleNodeClick: function Ui_handleNodeClick(node, toggleCheckbox) { + console.log('Ui_handleNodeClick', node); + if (this.editMode === true) { + //if edit mode is on toggle checkbox state + if (toggleCheckbox === true) { + this.toggleCheckBoxState(node); // select the checkbox + } + + this.refreshSelectAllStatus(); + + if ($('ul#fileList input:checkbox:checked').length > 0) { + this.enableControlBarButtons($('.editTabbar'), [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); + } else { + this.disableControlBarButtons($('.editTabbar'), [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); + } + } else if (node.hasClass('folder')) { + // otherwise display folder + app.displayFolder(node.attr('uri')); + } else { + // file + app.openFile(node.attr('uri'), node.attr('fullUri')); + } + }, + + /** + * Handler for edit action + */ + handleEditAction: function Ui_handleEditAction() { + console.log('Ui_handleEditAction'); + this.editMode = true; + $('.standardTabbar').hide(); + $('div.editTabbar').show(); + this.disableControlBarButtons($('div.editTabbar'), [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); + this.showEditCheckBoxes(); + }, + + /** + * Handler for cancel edit action + */ + handleCancelEditAction: function Ui_handleCancelEditAction() { + console.log('Ui_handleCancelEditAction'); + $('div.editTabbar').hide(); + $('.standardTabbar').show(); + this.hideEditCheckBoxes(); + }, + + /** + * Handler for delete action + */ + handleDeleteAction: function Ui_handleDeleteAction() { + console.log('Ui_handleDeleteAction'); + var nodesToDelete = [], + $rowElement; + + $('ul#fileList input:checkbox:checked').each(function (index) { + $rowElement = $(this).closest('li'); + nodesToDelete.push({ + id: $rowElement.attr('id'), + uri: $rowElement.attr('uri'), + name: $rowElement.attr('label'), + folder: $rowElement.hasClass('folder') + }); + }); + + if (nodesToDelete.length > 0 && confirm('Selected nodes will be deleted. Are you sure?')) { + app.deleteNodes(nodesToDelete); + } + }, + + /** + * Handler for copy action + */ + handleCopyAction: function Ui_handleCopyAction() { + console.log('Ui_handleCopyAction'); + var paths = []; + if (this.editMode === true) { + $('ul#fileList input:checkbox:checked').each(function (index) { + paths.push($(this).closest('li').attr('uri')); + }); + app.saveToClipboard(paths, app.clipboard.COPY_MODE_ID); + } + }, + + /** + * Handler for move action + */ + handleMoveAction: function Ui_handleMoveAction() { + console.log('Ui_handleMoveAction'); + var paths = []; + if (this.editMode === true) { + $('ul#fileList input:checkbox:checked').each(function (index) { + paths.push($(this).closest('li').attr('uri')); + }); + app.saveToClipboard(paths, app.clipboard.MOVE_MODE_ID); + } + }, + + /** + * Handler for paste action + */ + handlePasteAction: function Ui_handlePasteAction() { + console.log('Ui_handlePasteAction'); + }, + + /** + * @param {FileSystemStorage[]} nodes Storage elements + */ + displayStorages: function Ui_displayStorages(nodes) { + console.log('Ui_displayStorages', nodes, nodes.length); + var len = nodes.length, + listElements = [], + nodeName, + listTemplate = '', + i; + + this.updateNavbar(''); + + for (i = 0; i < len; i = i + 1) { + nodeName = nodes[i].label.trim(); + if (nodeName !== '' && (nodes[i].type === 0 || nodes[i].type === 'INTERNAL') && nodeName.indexOf('wgt-') === -1) { + listElements.push(this.templateManager.get('folderRow', { + id: i, + name: nodeName, + uri: nodeName, + fullUri: nodeName + })); + } + } + + $('#levelUpBtn').addClass('vhidden'); + $('#homeBtn').addClass('vhidden'); + + $('#editActionBtn').addClass('vhidden'); + $('#moreActionBtn').addClass('vhidden'); + $('h1#mainTitle').html('Media'); + + // update file list + $('#fileList').empty(); + listTemplate = listElements.join(''); + $(listTemplate).appendTo('#fileList'); + // reset scrollview position + $('#main .ui-scrollview-view').css('-webkit-transform','none'); + $('#fileList') + .trigger("refresh"); + + this.resetDefaultCheckBoxLabelEvents(); + this.hideSelectAllArea(); + this.handleCancelEditAction(); + }, + + /** + * renders node list for folder + * @param {File[]} nodes + */ + displayFolder: function Ui_displayFolder(folderName, nodes) { + console.log('Ui_displayFolder', nodes, nodes.length); + var len = nodes.length, + listElements = [this.templateManager.get('levelUpRow')], + nodeName, + i; + + // update title + this.updateTitle(folderName); + // update navbar + this.updateNavbar(folderName); + + // render nodes + for (i = 0; i < len; i = i + 1) { + nodeName = nodes[i].name.trim(); + console.log('node:', nodeName); + if (nodeName !== '') { + if (nodes[i].isDirectory) { + // folder + listElements.push(this.templateManager.get('folderRow', { + id: i, + name: nodeName, + uri: nodes[i].fullPath, + fullUri: nodes[i].toURI() + })); + } else { + // file + listElements.push(this.templateManager.get('fileRow', { + id: i, + name: nodeName, + uri: nodes[i].fullPath, + fullUri: nodes[i].toURI(), + thumbnailURI: this.helpers.getThumbnailURI(nodeName, nodes[i]) + })); + } + } + } + + if (listElements.length === 1) { + // set content for empty folder + listElements.push(this.templateManager.get('emptyFolder')); + } + + $('#levelUpBtn').removeClass('vhidden'); + $('#homeBtn').removeClass('vhidden'); + $('#editActionBtn').removeClass('vhidden'); + $('#moreActionBtn').removeClass('vhidden'); + this.hideSelectAllArea(); + + // update file list + $('#fileList').html(listElements.join('')) + .trigger('refresh') + .trigger('create'); + }, + + /** + * Toggle a checkbox associated with a given list element + * @param {jQuery} listElement + */ + toggleCheckBoxState: function Ui_toggleCheckBoxState(listElement) { + console.log('Ui_toggleCheckBoxState', listElement); + var checkboxInput = null, + checkboxesInputChecked = null, + editTabbar = null; + + checkboxInput = listElement.find('form > div.ui-checkbox input'); + checkboxInput + .attr('checked', !checkboxInput.attr('checked')) + .data('checkboxradio').refresh(); + }, + + /** + * Shows item checkboxes and topbar with select all option + */ + showEditCheckBoxes: function Ui_showEditCheckBoxes() { + console.log('Ui_showEditCheckBoxes'); + var self = this; + + this.showSelectAllArea(); + + $('ul#fileList > li').animate({paddingLeft: '2rem'}, 500, 'swing', function () { + self.editMode = true; + $('.my-ui-checkbox').removeClass('hidden'); + }); + }, + + /** + * Hides item checkboxes and topbar with select all option + * All checkboxes are auto uncheked + */ + hideEditCheckBoxes: function Ui_hideEditCheckBoxes() { + console.log('Ui_hideEditCheckBoxes'); + var self = this; + + this.hideSelectAllArea(); // hide select all option topbar + + $('ul#fileList > li').animate({paddingLeft: '0'}, 200, 'swing', function () { + self.editMode = false; + $('.my-ui-checkbox').addClass('hidden'); + $.mobile.activePage.page('refresh'); + }); + + // uncheck all checkboxes + $('ul#fileList input[type=checkbox]').each(function (index) { + $(this).attr('checked', false); + //$(this).data('checkboxradio').refresh(); // element undefined + }); + + //uncheck select all input + $('.ui-header .selectAll .ui-checkbox input') + .attr('checked', false) + .data('checkboxradio') + .refresh(); + }, + + /** + * Shows topbar with select all option + */ + showSelectAllArea: function Ui_showSelectAllArea() { + console.log('showSelectAllArea'); + $('.selectAll').show(); + $.mobile.activePage.page('refresh'); + }, + + /** + * Hides topbar with select all option + */ + hideSelectAllArea: function Ui_hideSelectAllArea() { + console.log('hideSelectAllArea'); + $('.selectAll').hide(); + $.mobile.activePage.page('refresh'); + }, + + /** + * Enable specified options for tabbar + * @param {object} tabbar + * @param {array} options to enable + */ + enableControlBarButtons: function Ui_enableControlBarButtons(tabbar, enableOptions) { + console.log('Ui_enableControlBarButtons', tabbar, enableOptions); + var i = 0, + len = enableOptions.length; + + for (i = 0; i < len; i += 1) { + tabbar.tabbar('enable', enableOptions[i]); + } + }, + + /** + * Disable specified options for tabbar + * @param {object} controlbar + * @param {array} options to enable + */ + disableControlBarButtons: function Ui_disableControlBarButtons(tabbar, disableOptions) { + console.log('Ui_disableControlBarButtons', tabbar, disableOptions); + var i = 0, + len = disableOptions.length; + + for (i = 0; i < len; i += 1) { + tabbar.tabbar('disable', disableOptions[i]); + } + }, + + /** + * @param {string} path + */ + updateTitle: function Ui_updateTitle(path) { + console.log('Ui_updateTitle', path); + var regexp = new RegExp('([^\/])+$', 'g'), + match = path.match(regexp), + lastDir = match[0] || '(dir)'; + $('h1#mainTitle').html(lastDir); + }, + + /** + * @param {string} path + */ + updateNavbar: function Ui_updateNavbar(path) { + console.log('Ui_updateNavbar', path); + var html = ['Media'], + splitted, + len, + i; + + if (typeof path === 'string' && path !== '') { + splitted = path.split('/'); + len = splitted.length; + + for (i = 0; i < len; i = i + 1) { + html.push('' + splitted[i] + ''); + } + } + $('#navbar').html(html.join(' > ')); + }, + + handleSelectAllChange: function Ui_handleSelectAllChange() { + console.log('Ui_handleSelectAllChange'); + var $selectAllInput = $('.ui-header .selectAll .ui-checkbox input'); + $selectAllInput.data('checkboxradio').refresh(); + + if ($selectAllInput.is(':checked')) { + this.enableControlBarButtons($('.editTabbar'), [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); + + // check all checkboxes + $('ul#fileList input[type=checkbox]').each(function (index) { + $(this).attr('checked', true); + $(this).data('checkboxradio').refresh(); + }); + } else { + this.disableControlBarButtons($('.editTabbar'), [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); + + $('ul#fileList input[type=checkbox]').each(function (index) { + $(this).attr('checked', false); + $(this).data('checkboxradio').refresh(); + }); + } + }, + + /** + * + */ + refreshSelectAllStatus: function Ui_refreshSelectAllStatus() { + console.log('Ui_refreshSelectAllStatus'); + var $selectAllInput = $('.ui-header .selectAll .ui-checkbox input'); + // update status of select all checkbox + if ($('ul#fileList input:checkbox:not(:checked)').length === 0) { + // all nodes checked + $selectAllInput.attr('checked', true).data('checkboxradio').refresh(); + } else { + // some node is not checked + $selectAllInput.attr('checked', false).data('checkboxradio').refresh(); + } + }, + + /** + * Unbinds default events for checkbox labels + */ + resetDefaultCheckBoxLabelEvents: function Ui_resetDefaultCheckBoxLabelEvents() { + console.log('Ui_resetDefaultCheckBoxLabelEvents'); + $('div.ui-checkbox > label') + .unbind('vmousedown') + .unbind('vmouseup') + .unbind('vmouseover') + .unbind('vclick'); + }, + + /** + * Remove html node element from list + * @param {string} node id + */ + removeNodeFromList: function Ui_removeNodeFromList(nodeId) { + console.log('Ui_removeNodeFromList', nodeId); + $('ul#fileList > li#' + nodeId).remove(); + }, + + /** + * Enable/Disable + */ + refreshPasteActionBtn: function Ui_refreshPasteActionBtn(clipboardEmpty) { + console.log('Ui_refreshPasteActionBtn', clipboardEmpty); + if (clipboardEmpty === true) { + $('#pasteActionBtnRow').addClass('hidden'); + } else { + $('#pasteActionBtnRow').removeClass('hidden'); + } + } + }; +}()); diff --git a/js/app.ui.templateManager.js b/js/app.ui.templateManager.js new file mode 100644 index 0000000..bfe2b73 --- /dev/null +++ b/js/app.ui.templateManager.js @@ -0,0 +1,110 @@ +/*jslint devel: true*/ +/*global $, app */ +/** + * @class TemplateManager + */ +function TemplateManager() { + 'use strict'; + this.init(); +} + +(function () { // strict mode wrapper + 'use strict'; + TemplateManager.prototype = { + + /** + * Template cache + */ + cache: {}, + + /** + * UI module initialisation + */ + init: function init() { + + }, + + /** + * Returns template html (from cache) + */ + get: function TemplateManager_get(tplName, tplParams) { + console.log('TemplateManager_get:' + tplName); + + if (this.cache[tplName] !== undefined) { + return this.getCompleted(this.cache[tplName], tplParams); + } + return ''; + }, + + /** + * Load templates to cache + */ + loadToCache: function TemplateManager_loadToCache(tplNames, onSuccess) { + var self = this, + cachedTemplates = 0, + tplName, + tplPath; + + if ($.isArray(tplNames)) { + + // for each template + $.each(tplNames, function (index, fileName) { + + // cache template html + if (self.cache[fileName] === undefined) { + tplName = [fileName, app.config.get('templateExtension')].join(''); + tplPath = [app.config.get('templateDir'), tplName].join('/'); + + $.ajax({ + url: tplPath, + cache: true, + dataType: 'html', + async: true, + success: function (data) { + // increase counter + cachedTemplates += 1; + + // save to cache + self.cache[fileName] = data; + console.log('Cached template: ' + fileName); + + // if all templates are cached launch callback + if (cachedTemplates >= tplNames.length && typeof onSuccess === 'function') { + onSuccess(); + } + }, + error: function (jqXHR, textStatus, errorThrown) { + alert(errorThrown); + } + }); + } else { + // template is already cached + cachedTemplates += 1; + // if all templates are cached launch callback + if (cachedTemplates >= tplNames.length && typeof onSuccess === 'function') { + onSuccess(); + } + } + }); + + } + }, + + /** + * Returns template completed by specified params + */ + getCompleted: function TemplateManager_getCompleted(tplHtml, tplParams) { + var tplParam, replaceRegExp; + + for (tplParam in tplParams) { + if (tplParams.hasOwnProperty(tplParam)) { + replaceRegExp = new RegExp(['%', tplParam, '%'].join(''), 'g'); + tplHtml = tplHtml.replace(replaceRegExp, tplParams[tplParam]); + } + } + + return tplHtml; + } + }; + +}()); \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..e3440f4 --- /dev/null +++ b/js/main.js @@ -0,0 +1,80 @@ +/* + * Copyright 2012 Samsung Electronics Co., Ltd + * + * Licensed under the Flora License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://floralicense.org/license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*jslint devel: true */ +/*global $, tizen, App */ + +/** + * This file acts as a loader for the application and its dependencies + * + * First, the 'app.js' script is loaded . + * Then, scripts defined in 'app.requires' are loaded. + * Finally, the app is initialised - the app is instantiated ('app = new App()') + * and 'app.init()' is called. + */ + + +var app = null; + +(function () { // strict mode wrapper + 'use strict'; + + ({ + /** + * Loader init - load the App constructor + */ + init: function init() { + var self = this; + $.getScript('js/app.js') + .done(function () { + console.log('Loaded app.js'); + // once the app is loaded, create the app object + // and load the libraries + app = new App(); + self.loadLibs(); + }) + .fail(this.onGetScriptError); + }, + + /** + * Load dependencies + */ + loadLibs: function loadLibs() { + var loadedLibs = 0; + if ($.isArray(app.requires)) { + $.each(app.requires, function (index, filename) { + $.getScript(filename) + .done(function () { + loadedLibs += 1; + console.log('Loaded subscript: ' + filename + ' : ' + loadedLibs); + if (loadedLibs >= app.requires.length) { + // All dependencies are loaded - initialise the app + app.init(); + } + }) + .fail(this.onGetScriptError); + }); + } + }, + + /** + * Handle ajax errors + */ + onGetScriptError: function onGetScriptError(e, jqxhr, setting, exception) { + alert('An error occurred: ' + e.message); + } + }).init(); // run the loader +}()); \ No newline at end of file diff --git a/templates/emptyFolder.tpl b/templates/emptyFolder.tpl new file mode 100644 index 0000000..da09356 --- /dev/null +++ b/templates/emptyFolder.tpl @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/templates/fileRow.tpl b/templates/fileRow.tpl new file mode 100644 index 0000000..47e30f2 --- /dev/null +++ b/templates/fileRow.tpl @@ -0,0 +1,5 @@ +
  • + + + %name% +
  • \ No newline at end of file diff --git a/templates/folderRow.tpl b/templates/folderRow.tpl new file mode 100644 index 0000000..c4e91e3 --- /dev/null +++ b/templates/folderRow.tpl @@ -0,0 +1,6 @@ +
  • + + + %name% + +
  • \ No newline at end of file diff --git a/templates/levelUpRow.tpl b/templates/levelUpRow.tpl new file mode 100644 index 0000000..ddbc7cf --- /dev/null +++ b/templates/levelUpRow.tpl @@ -0,0 +1,3 @@ +
  • + .. +
  • \ No newline at end of file diff --git a/templates/main.tpl b/templates/main.tpl new file mode 100644 index 0000000..1069bb3 --- /dev/null +++ b/templates/main.tpl @@ -0,0 +1,64 @@ +
    +
    +

    + Home + Up + + +
    + +
    +
      +
      + +
      +
      +
      + +
      + +
      + + + + + + + +
      + New folder +
      + Paste to folder +
      +
      +
      +

      Add new folder

      +

      + +

      +

      + + +

      +
      +
      +
      +
      \ No newline at end of file -- 2.7.4 From c791b2d702880f2e329252806e02232f38ccfab2 Mon Sep 17 00:00:00 2001 From: "gs86.lee" Date: Fri, 12 Apr 2013 15:29:33 +0900 Subject: [PATCH 3/4] [FileManager]update FileManager(tizen_2.1) Change-Id: I37f107514b4cd217ddde7e1e643dfff082fd8b6e --- NOTICE | 4 + config.xml | 5 +- css/style.css | 26 +++++ index.html | 10 +- js/app.clipboard.js | 11 +-- js/app.config.js | 2 - js/app.helpers.js | 88 +++++++++++++++-- js/app.js | 60 +++++++----- js/app.model.js | 162 +++++++++++++++++++------------ js/app.systemIO.js | 76 +++++++++------ js/app.ui.js | 224 ++++++++++++++++++++++++++++++------------- js/app.ui.templateManager.js | 15 +-- js/main.js | 4 +- templates/main.tpl | 22 +++-- 14 files changed, 481 insertions(+), 228 deletions(-) create mode 100644 NOTICE diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..85044e4 --- /dev/null +++ b/NOTICE @@ -0,0 +1,4 @@ +Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved. +Except as noted, this software is licensed under Flora License, Version 1. +Please, see the LICENSE.Flora file for Flora License terms and conditions. + diff --git a/config.xml b/config.xml index 3bc4bb5..f18a5d3 100644 --- a/config.xml +++ b/config.xml @@ -1,9 +1,8 @@ - + FileManager - @@ -11,5 +10,5 @@ - + diff --git a/css/style.css b/css/style.css index f81279e..2be9239 100644 --- a/css/style.css +++ b/css/style.css @@ -13,6 +13,7 @@ body { #mainTitle { width: 260px; + text-transform: uppercase; } #fileList > li { @@ -102,6 +103,11 @@ body { height: 100%; } +#homeBtn, +#levelUpBtn { + top:20px; +} + .ui-li-1line-bigicon1.ui-li.ui-li-static.ui-body-s.ui-li-has-thumb { padding-left: 0.7rem; padding-right: 0rem; @@ -163,4 +169,24 @@ input.ui-input-text.new_folder { .ui-tabbar a { color: #999 !important; +} + +#newFolderName { + margin:0.4em; +} + +.ui-header.ui-bar-s > .ui-btn { + width: 2rem; +} +/** workaround to prevent hiding footer caused by broken softkeyboardupdate event**/ +[data-role="footer"] { + display: block !important; +} + +.ui-ctxpopup .horizontal a.ui-link { + display: block; +} + +.ui-footer .ui-btn-text { + text-transform: uppercase; } \ No newline at end of file diff --git a/index.html b/index.html index 3b69ba6..008c256 100644 --- a/index.html +++ b/index.html @@ -1,15 +1,17 @@ + - file manager - - - + File Manager + + + +
      diff --git a/js/app.clipboard.js b/js/app.clipboard.js index a944925..6dd0c4a 100644 --- a/js/app.clipboard.js +++ b/js/app.clipboard.js @@ -1,4 +1,4 @@ -/*jslint browser: true, devel: true */ +/*jslint devel: true*/ /*global $*/ /** @@ -47,7 +47,7 @@ function Clipboard() { /** * Add new path to clipboard - * @param {array} full path + * @param {array} paths array of full paths * @returns {number} current length of clipboard objects */ add: function Clipboard_add(paths) { @@ -58,7 +58,6 @@ function Clipboard() { this.clear(); for (i = 0; i < len; i += 1) { if (this.has(paths[i]) === false) { - console.log('Adding file ' + paths[i] + ' to clipboard'); this.data.push(paths[i]); } } @@ -68,19 +67,17 @@ function Clipboard() { /** * Checks if specified path is already in clipboard - * @param {string} full path + * @param {string} path full path * @returns {boolean} */ has: function Clipboard_has(path) { - console.log('Clipboard_has', path); return $.inArray(path, this.data) === -1 ? false : true; }, /** - * Clears all clipboard data and reset clipboard mode + * Clears all clipboard data and resets clipboard mode */ clear: function Clipboard_clear() { - console.log('Clipboard_clear'); this.data = []; this.mode = this.INACTIVE_MODE; }, diff --git a/js/app.config.js b/js/app.config.js index 32b92f5..e0332c3 100644 --- a/js/app.config.js +++ b/js/app.config.js @@ -1,5 +1,3 @@ -/*jslint devel: true */ - /** * @class Config */ diff --git a/js/app.helpers.js b/js/app.helpers.js index 43687e1..5cc3311 100644 --- a/js/app.helpers.js +++ b/js/app.helpers.js @@ -1,4 +1,4 @@ -/*jslint devel: true */ +/*jslint devel: true*/ /*global $ */ /** @@ -24,10 +24,21 @@ function Helpers() { /** * @param {string} fileName + * @returns {string} file name without extension + */ + getFileName: function Helpers_getFileName(fileName) { + var fileNameLen = fileName.indexOf('.'); + if (fileNameLen !== -1) { + fileName = fileName.slice(0, fileNameLen); + } + return fileName; + }, + + /** + * @param {string} fileName * @returns {string} extension for specified file name */ getFileExtension: function Helpers_getFileExtension(fileName) { - console.log('Helpers_getFileExtension', fileName); var splittedFileName = fileName.split('.'), ext = ''; @@ -45,7 +56,9 @@ function Helpers() { * @return {string} */ resolveFileIcon: function Helpers_resolveFileIcon(ext) { - console.log('Helpers_resolveFileIcon', ext); + + ext = ext.toLowerCase(); + switch (ext) { case '.jpg': return 'img.png'; @@ -57,6 +70,8 @@ function Helpers() { return 'pdf.png'; case '.mp3': return 'music.png'; + case '.avi': + return 'video.png'; case '.mp4': return 'video.png'; case '.ppt': @@ -81,15 +96,16 @@ function Helpers() { * @returns {string} */ resolveMimeType: function Helpers_resolveMimeType(ext) { - console.log('Helpers_resolveMimeType', ext); var mime = ''; + ext = ext.toLowerCase(); + if (ext === '.jpg' || ext === '.png' || ext === '.gif') { mime = 'image/*'; - } else if (ext === '.mp4' || ext === '.ogv') { + } else if (ext === '.mp4' || ext === '.ogv' || ext === '.avi') { mime = 'video/*'; } else if (ext === '.mp3') { - mime = 'audio/*'; + mime = 'audio/mp3'; } else if (ext === '.txt' || ext === '.doc' || ext === '.html' || ext === '.ppt' || ext === '.xls' || ext === '.pdf') { mime = 'text/*'; } @@ -104,18 +120,72 @@ function Helpers() { * @returns {string} */ getThumbnailURI: function Helpers_getThumbnailURI(fileName, node) { - console.log('Helpers_getThumbnailURI', fileName, node); var ext = this.getFileExtension(fileName), thumbnailURI = ''; if (!node.thumbnailURIs) { - console.log('getFileListItem: get icon'); thumbnailURI = 'images/' + this.resolveFileIcon(ext); } else if (node.thumbnailURIs[0] && $.inArray(ext, ['.mp4', '.jpg', '.png', '.gif'])) { thumbnailURI = node.thumbnailURIs[0]; } return thumbnailURI; + }, + + /** + * File name automatic number increase for copy files + */ + getCopyFileName: function (sourceName, filesList) { + var ext = this.getFileExtension(sourceName), + fileName = this.getFileName(sourceName), + copyFileName = sourceName, i = 1; + while ($.inArray(copyFileName, filesList) !== -1) { + i+=1; + copyFileName = fileName+'('+i+')'+ext; + } + return copyFileName; } }; -}()); \ No newline at end of file +}()); + +(function($) { + 'use strict'; + + function height(t, el) { + return t.height() > el.height(); + } + function width(t, el) { + return t.width() > el.width(); + } + + $.fn.ellipsis = function () + { + return this.each(function () { + var el, text, multiline, t, func; + el = $(this); + if(el.css("overflow") === "hidden") { + text = el.html(); + multiline = el.hasClass('multiline'); + t = $(this.cloneNode(true)) + .hide() + .css('position', 'absolute') + .css('overflow', 'visible') + .width(multiline ? el.width() : 'auto') + .height(multiline ? 'auto' : el.height()) + ; + + el.after(t); + + func = multiline ? height : width; + + while (text.length > 0 && func(t, el)) { + text = text.substr(0, text.length - 1); + t.html(text + "..."); + } + + el.html(t.html()); + t.remove(); + } + }); + }; +}(jQuery)); diff --git a/js/app.js b/js/app.js index 08bd63a..36062d7 100644 --- a/js/app.js +++ b/js/app.js @@ -1,4 +1,4 @@ -/*jslint devel: true */ +/*jslint devel: true*/ /*global tizen, $, app, Ui, Model, Helpers, Config, Clipboard*/ var App = null; @@ -65,14 +65,13 @@ var App = null; * Initialization */ init: function App_init() { - console.log('App_init'); this.config = new Config(); this.model = new Model(); this.ui = new Ui(); this.helpers = new Helpers(); this.clipboard = new Clipboard(); - this.model.loadInternalStorages(this.initUi.bind(this)); + this.initUi(); this.addEvents(); }, @@ -80,7 +79,6 @@ var App = null; * UI initialization */ initUi: function App_initUi() { - console.log('App_initUi'); this.ui.init(this.model.getInternalStorages()); }, @@ -88,30 +86,36 @@ var App = null; * Add pages events */ addEvents: function App_addEvents() { - console.log('App_addEvents'); var self = this; - document.addEventListener('webkitvisibilitychange', function() { self.refreshCurrentPage(); } ); + document.addEventListener('webkitvisibilitychange', function () { self.refreshCurrentPage(); }); + // workaround: page refresh for on/off keyboard + window.addEventListener('softkeyboardchange',function () { + $.mobile.activePage.page('refresh'); + }); }, /** * Displays media storages */ displayStorages: function App_displayStorages() { + this.currentPath = ''; + this.ui.scrollContentTo(0); this.ui.displayStorages(this.model.getInternalStorages()); }, /** * Displays specified folder * @param {string} path + * @param {bool} [refresh=false] */ - displayFolder: function App_displayFolder(path) { - console.log('App_displayFolder', path); + displayFolder: function App_displayFolder(path, refresh) { var self = this; + refresh = refresh || false; + // get folder data and push into rendering method this.model.getFolderData(path, function (dir, nodes) { // on success - console.log('App_displayFolder success', dir, nodes); // update current path self.currentPath = path; @@ -120,7 +124,10 @@ var App = null; self.currentDirHandle = dir; // display folder UI - self.ui.displayFolder(path, nodes); + if (refresh === undefined) { + self.ui.scrollContentTo(0); + } + self.ui.displayFolder(path, nodes, refresh); }); }, @@ -129,14 +136,13 @@ var App = null; * @params {string} uri File URI */ openFile: function App_openFile(uri, fullUri) { - console.log('App_openFile', uri); var ext = this.helpers.getFileExtension(uri), mime = this.helpers.resolveMimeType(ext); if (mime !== '') { this.model.openFile(fullUri, mime); } else { - console.error('Unsupported mime type', mime); + console.error('Unsupported mime type for extension ' + ext); } }, @@ -159,17 +165,16 @@ var App = null; * @param {string} dirName */ createDir: function App_createDir(dirName) { - console.log('App_createDir', dirName); if (this.currentDirPath !== '') { try { this.currentDirHandle.createDirectory(dirName); } catch (e) { - console.error('App_createDir error', e); + alert(e.message); } this.refreshCurrentPage(); } else { - alert("you can't create new nodes in main view"); + alert("You can't create new nodes in the main view"); } }, @@ -177,9 +182,8 @@ var App = null; * Triggers refresh current page */ refreshCurrentPage: function App_refreshCurrentPage() { - console.log('App_refreshCurrentPage'); if (this.currentPath !== '') { - this.displayFolder(this.currentPath); + this.displayFolder(this.currentPath, true); } else { this.displayStorages(); } @@ -187,20 +191,26 @@ var App = null; /** * Deletes nodes with specified paths - * @param {string[]} nodePaths + * @param {string[]} nodes nodePaths */ deleteNodes: function App_deleteNodes(nodes) { - console.log('App_deleteNodes', nodes); this.model.deleteNodes(nodes, this.currentDirHandle, this.ui.removeNodeFromList.bind(this.ui)); }, /** - * @param {string[]} filepaths - * @param {number} clipboard mode + * @param {string[]} paths filepaths + * @param {number} mode clipboard mode */ saveToClipboard: function App_saveToClipboard(paths, mode) { - this.clipboard.add(paths); - this.clipboard.setMode(mode); + var clipboardLength = this.clipboard.add(paths); + + if (clipboardLength > 0) { + this.clipboard.setMode(mode); + alert('Data saved in clipboard'); + } else { + alert('Error occured. Data has not been saved in clipboard'); + } + this.ui.refreshPasteActionBtn(this.clipboard.isEmpty()); }, @@ -208,7 +218,6 @@ var App = null; * Paste nodes from clipboard to current dir */ pasteClipboard: function App_pasteClipboard() { - console.log('App_pasteClipboard'); var clipboardData = this.clipboard.get(); if (clipboardData.length === 0) { @@ -223,13 +232,14 @@ var App = null; } this.ui.refreshPasteActionBtn(this.clipboard.isEmpty()); + + return true; }, /** * Handler for paste clipboard success */ onPasteClipboardSuccess: function App_onPasteClipboardSuccess() { - console.log('App_onPasteClipboardSuccess'); this.clipboard.clear(); this.refreshCurrentPage(); }, diff --git a/js/app.model.js b/js/app.model.js index fa1223d..7983b0f 100644 --- a/js/app.model.js +++ b/js/app.model.js @@ -1,5 +1,5 @@ -/*jslint devel: true */ -/*global tizen, SystemIO */ +/*jslint devel: true*/ +/*global tizen, SystemIO, $ */ /** * @class Model @@ -14,66 +14,61 @@ function Model() { Model.prototype = { /** - * @type SystemIO + * file open unlock flag + * @type {boolean} */ - systemIO: null, + openFileUnLocked: true, /** - * @type Array + * @type SystemIO */ - storages: [], + systemIO: null, /** - * @type MediaSource + * @type Array */ - mediaSource: null, + storages: [{label: 'root', type: 'INTERNAL'}], /** * API module initialisation */ init: function Model_init() { - console.log('Model_init'); this.systemIO = new SystemIO(); - //this.mediaSource = tizen.mediacontent.getLocalMediaSource(); }, /** * @returns {FileSystemStorage[]} storages */ getInternalStorages: function Model_getInternalStorages() { - console.log('Model_getInternalStorages'); return this.storages; }, /** * Saves storages - * @param {function} callback + * @param {function} onSuccess callback */ loadInternalStorages: function Model_loadInternalStorages(onSuccess) { - console.log('Model_loadInternalStorages', onSuccess); var self = this; this.systemIO.getStorages('INTERNAL', function (storages) { self.storages = storages; if (typeof onSuccess === 'function') { - console.log('Storages loaded successfully'); onSuccess(); } - }); + }, 'internal0'); }, /** * Returns folder data - * @param {string} node Node path + * @param {string} path Node path * @param {function} onSuccess Success callback + * @param {function} onError Error callback */ getFolderData: function Model_getFolderData(path, onSuccess, onError) { - console.log('Model_getFolderData', path); var onOpenSuccess = function (dir) { dir.listFiles( function (files) { - console.log('Model_getFolderData listFiles success', files); onSuccess(dir, files); }, function (e) { @@ -89,40 +84,44 @@ function Model() { }, /** - * Launch a service associated with 'ext' to launch the 'uri' - * @param {string} ext - * @param {string} uri - * @returns {ApplicationSevice} + * Launch a service to open the file + * @param {string} fullUri ext + * @param {string} mime uri */ openFile: function Model_openFile(fullUri, mime) { - var serviceReplyCB = { - onsuccess: function (reply) { - var num = 0; - for (num = 0; num < reply.data.length; num += 1) { - console.log('reply.data[' + num + '].key = ' + reply.data[num].key); - console.log('reply.data[' + num + '].value = ' + reply.data[num].value); + if (this.openFileUnLocked) { + var self = this, serviceReplyCB = { + onsuccess: function (reply) { + self.openFileUnLocked = true; + }, + onfailure: function () { + self.openFileUnLocked = true; + console.error('Launch service failed'); } - }, - onfailure: function () { - console.log('Launch service failed'); + }; + this.openFileUnLocked = false; + try { + tizen.application.launchAppControl(new tizen.ApplicationControl( + 'http://tizen.org/appcontrol/operation/view', + fullUri, + mime + ), + null, + function () { + setTimeout(function () { + self.openFileUnLocked = true; + }, 500); + }, + function (e) { + self.openFileUnLocked = true; + alert('launch sevice failed. reason :' + e.message); + }, + serviceReplyCB + ); + } catch (e) { + self.openFileUnLocked = true; + console.error('openFile error:', e); } - }; - - try { - tizen.application.launchAppControl(new tizen.ApplicationControl( - 'http://tizen.org/appcontrol/operation/view', - fullUri, - mime - ), - null, - function () { }, - function (e) { - alert('launch sevice failed. reason :' + e.message); - }, - serviceReplyCB - ); - } catch (e) { - console.error('openFile error:', e); } }, @@ -133,16 +132,14 @@ function Model() { * @param {function} onError */ deleteNodes: function Model_deleteNodes(nodes, dir, onSuccess, onError) { - console.log('Model_deleteNodes', nodes, dir); var len = nodes.length, onDeleteNodeSuccess = function (nodeId, isDir) { - console.log((isDir ? 'Folder' : 'File') + nodeId + ' deleted successfully'); if (typeof onSuccess === 'function') { onSuccess(nodeId); } }, onDeleteNodeError = function (e) { - console.log('Folder delete error', e); + console.error('Folder delete error', e); if (typeof onError === 'function') { onError(); } @@ -177,7 +174,6 @@ function Model() { * @param {function} onSuccess callback */ copyNodes: function Model_copyNodes(dir, paths, destinationPath, onSuccess) { - console.log('Model_copyNodes', dir, paths, destinationPath); var len = paths.length, copied = 0, onCopyNodeSuccess = function () { @@ -186,11 +182,33 @@ function Model() { onSuccess(); } }, - i; + onCopyNodeFailure = function () { + alert('Copying error'); + }, + i, + sourceName, + decision; - for (i = 0; i < len; i = i + 1) { - dir.copyTo(paths[i], destinationPath, true, onCopyNodeSuccess); - } + this.systemIO.getFilesList(dir, function (filesList) { + for (i = 0; i < len; i = i + 1) { + if (destinationPath.indexOf(paths[i]) !== -1) { + alert('Copying error'); + return; + } + } + + for (i = 0; i < len; i = i + 1) { + decision = true; + sourceName = paths[i].split('/').pop(); + sourceName = app.helpers.getCopyFileName(sourceName, filesList); + + try { + dir.copyTo(paths[i], destinationPath + '/' + sourceName, true, onCopyNodeSuccess, onCopyNodeFailure); + } catch (e) { + console.error(e); + } + } + }); }, /** @@ -203,7 +221,6 @@ function Model() { * @param {function} onSuccess callback */ moveNodes: function Model_moveNodes(dir, paths, destinationPath, onSuccess) { - console.log('Model_moveNodes', dir, paths, destinationPath); var len = paths.length, moved = 0, onMoveNodeSuccess = function () { @@ -212,11 +229,34 @@ function Model() { onSuccess(); } }, - i; + onMoveNodeFailure = function () { + alert('Moving error'); + }, + i, + sourceName, + decision; - for (i = 0; i < len; i = i + 1) { - dir.moveTo(paths[i], destinationPath, true, onMoveNodeSuccess); - } + this.systemIO.getFilesList(dir, function (filesList) { + for (i = 0; i < len; i = i + 1) { + if (destinationPath.indexOf(paths[i]) !== -1) { + alert('Moving error'); + return; + } + } + + for (i = 0; i < len; i = i + 1) { + decision = true; + sourceName = paths[i].split('/').pop(); + + if ($.inArray(sourceName, filesList) !== -1) { + decision = confirm('A file with (' + sourceName + ') name already exists.\nDo you want to overwrite it?'); + } + + if (decision) { + dir.moveTo(paths[i], destinationPath + '/' + sourceName, true, onMoveNodeSuccess, onMoveNodeFailure); + } + } + }); } }; }()); diff --git a/js/app.systemIO.js b/js/app.systemIO.js index 41c6c96..4c70377 100644 --- a/js/app.systemIO.js +++ b/js/app.systemIO.js @@ -1,4 +1,4 @@ -/*jslint devel: true */ +/*jslint devel: true*/ /*global tizen, localStorage */ /** @@ -18,12 +18,11 @@ function SystemIO() { * @param {string} fileName */ createFile: function SystemIO_createFile(directoryHandle, fileName) { - console.log('SystemIO_createFile', directoryHandle, fileName); try { return directoryHandle.createFile(fileName); } catch (e) { - console.error('SystemIO_createFile error:', e); + console.error('SystemIO_createFile error:' + e.message); return false; } }, @@ -31,17 +30,16 @@ function SystemIO() { /** * Writes content to file stream * - * @param {File} file handler - * @param {string} file content - * @param {function} on success callback + * @param {File} fileHandle file handler + * @param {string} fileContent file content + * @param {function} onSuccess on success callback + * @param {function} onError on error callback * @param {string} content encoding */ writeFile: function SystemIO_writeFile(fileHandle, fileContent, onSuccess, onError, contentEncoding) { - console.log('SystemIO_writeFile', fileHandle, fileContent.length, contentEncoding); onError = onError || function () {}; fileHandle.openStream('w', function (fileStream) { - console.log('SystemIO_writeFile:_onOpenStreamSuccess', fileStream); if (contentEncoding === 'base64') { fileStream.writeBase64(fileContent); } else { @@ -66,14 +64,12 @@ function SystemIO() { * @param {string} mode */ openDir: function SystemIO_openDir(directoryPath, onSuccess, onError, openMode) { - console.log('SystemIO_openDir', directoryPath, openMode); openMode = openMode || 'rw'; onSuccess = onSuccess || function () {}; try { tizen.filesystem.resolve(directoryPath, onSuccess, onError, openMode); } catch (e) { - console.log('SystemIO_openDir error:' + e.message); } }, @@ -84,7 +80,6 @@ function SystemIO() { * @returns {array} */ getPathData: function SystemIO_getPathData(filePath) { - console.log('SystemIO_getPathData', filePath); var path = { originalPath: filePath, fileName: '', @@ -106,13 +101,11 @@ function SystemIO() { * @param {string} file encoding */ saveFileContent: function SystemIO_saveFileContent(filePath, fileContent, onSaveSuccess, fileEncoding) { - console.log('SystemIO_saveFileContent', filePath, fileContent.length, fileEncoding); var pathData = this.getPathData(filePath), self = this, fileHandle; function onOpenDirSuccess(dir) { - console.log('SystemIO_saveFileContent:_onOpenDirSuccess', dir); // create new file fileHandle = self.createFile(dir, pathData.fileName); if (fileHandle !== false) { @@ -132,23 +125,19 @@ function SystemIO() { * @param {function} success callback */ deleteNode: function SystemIO_deleteNode(nodePath, onSuccess) { - console.log('SystemIO_deleteNode', nodePath); var pathData = this.getPathData(nodePath), self = this; function onDeleteSuccess() { - console.log('SystemIO_deleteNode:_onDeleteSuccess'); onSuccess(); } function onDeleteError(e) { - console.log('SystemIO_deleteNode:_onDeleteError', e); + console.error('SystemIO_deleteNode:_onDeleteError', e); } function onOpenDirSuccess(dir) { - console.log('SystemIO_deleteNode:_onOpenDirSuccess', dir); var onListFiles = function (files) { - console.log('SystemIO_deleteNode:_onListFiles', files); if (files.length > 0) { // file exists; if (files[0].isDirectory) { @@ -183,11 +172,10 @@ function SystemIO() { * @param {function} delete error callback */ deleteFile: function SystemIO_deleteFile(dir, filePath, onDeleteSuccess, onDeleteError) { - console.log('SystemIO_deleteFile', filePath); try { dir.deleteFile(filePath, onDeleteSuccess, onDeleteError); } catch (e) { - console.error('SystemIO_deleteFile error:' + e.message); + console.error('SystemIO_deleteFile error: ' + e.message); return false; } }, @@ -196,13 +184,12 @@ function SystemIO() { * Deletes specified directory * * @param {File} dir - * @param {string} dir path - * @param {function} delete success callback - * @param {function} delete error callback + * @param {string} dirPath dir path + * @param {function} onDeleteSuccess delete success callback + * @param {function} onDeleteError delete error callback * @returns {boolean} */ deleteDir: function SystemIO_deleteDir(dir, dirPath, onDeleteSuccess, onDeleteError) { - console.log('SystemIO_deleteDir', dir, dirPath); try { dir.deleteDirectory(dirPath, false, onDeleteSuccess, onDeleteError); } catch (e) { @@ -214,21 +201,23 @@ function SystemIO() { }, /** - * @param {string} storage type + * @param {string} type storage type + * @param {function} onSuccess on success callback + * @param {string} excluded Excluded storage */ - getStorages: function SystemIO_getStorages(type, onSuccess) { - console.log('SystemIO_getStorages', type); + getStorages: function SystemIO_getStorages(type, onSuccess, excluded) { try { tizen.filesystem.listStorages(function (storages) { - console.log('...listStorages success', storages); var tmp = [], len = storages.length, i; if (type !== undefined) { for (i = 0; i < len; i += 1) { - if (storages[i].type === 0 || storages[i].type === type) { - tmp.push(storages[i]); + if (storages[i].label !== excluded) { + if (storages[i].type === 0 || storages[i].type === type) { + tmp.push(storages[i]); + } } } } else { @@ -240,7 +229,32 @@ function SystemIO() { } }); } catch (e) { - console.error('SystemIO_getStorages error:', e); + console.error('SystemIO_getStorages error:' + e.message); + } + }, + + getFilesList: function SystemIO_getFilesList(dir, onSuccess) { + try { + dir.listFiles( + function (files) { + var tmp = [], + len = files.length, + i; + + for (i = 0; i < len; i += 1) { + tmp.push(files[i].name); + } + + if (typeof onSuccess === 'function') { + onSuccess(tmp); + } + }, + function (e) { + console.error('SystemIO_getFilesList dir.listFiles() error:', e); + } + ); + } catch (e) { + console.error('SystemIO_getFilesList error:', e.message); } } }; diff --git a/js/app.ui.js b/js/app.ui.js index f931c02..cb7ced7 100644 --- a/js/app.ui.js +++ b/js/app.ui.js @@ -12,6 +12,12 @@ function Ui() { 'use strict'; Ui.prototype = { /** + * root mode + * @type {boolean} + */ + root: true, + + /** * UI edit mode * @type {boolean} */ @@ -69,11 +75,13 @@ function Ui() { EDIT_TABBAR_COPY_ACTION: 2, EDIT_TABBAR_CANCEL_ACTION: 3, + currentHeaderHeight: null, + currentScrollPosition: null, + /** * UI Initialization */ init: function Ui_init(storages) { - console.log('Ui_init', storages); this.templateManager = new TemplateManager(); this.helpers = new Helpers(); // Disable text selection @@ -82,7 +90,6 @@ function Ui() { }, initDom: function Ui_initDom(storages) { - console.log('Ui_initDom', storages); var self = this; this.templateManager.loadToCache(['main', 'fileRow', 'folderRow', 'levelUpRow', 'emptyFolder'], function () { @@ -96,7 +103,6 @@ function Ui() { * Add UI events */ addEvents: function Ui_addEvents() { - console.log('Ui_addEvents'); var self = this; // touch events for all nodes $('ul#fileList') @@ -132,7 +138,12 @@ function Ui() { }); // level up - $('#levelUpBtn').on('tap', app.goLevelUp.bind(app)); + $('#levelUpBtn').on('tap', function () { + if (self.editMode === true) { + self.handleCancelEditAction(); + } + app.goLevelUp(); + }); $('#homeBtn').on('tap', app.displayStorages.bind(app)); @@ -169,20 +180,14 @@ function Ui() { } }); - $('#cancelNewFolder').on('tap', function () { - $('#addFolderPopup').popup('close'); - $('#morePopup').popupwindow('close'); - }); - - $('#saveNewFolder').on('tap', function () { + $('#saveNewFolder').on('click', function () { var folderName = $('#newFolderName').val().trim(); - $('#addFolderPopup').popup('close'); - $('#morePopup').popupwindow('close'); - - if (folderName !== '') { - app.createDir(folderName); + if (folderName === '') { + alert("Empty folder name"); + } else if (folderName.match(/[\*\.\/\\\?\"\'\:<>|]/)) { + alert("The following special characters are not allowed: *./\\?:<>|'\""); } else { - alert("Wrong name of folder"); + app.createDir(folderName); } }); @@ -192,6 +197,8 @@ function Ui() { }, 700); }); + /* workaround for UIFW & webkit scroll*/ + //$('.ui-page').css('min-height', 0); }, /** @@ -200,20 +207,17 @@ function Ui() { * @param {boolean} toggleCheckbox */ handleNodeClick: function Ui_handleNodeClick(node, toggleCheckbox) { - console.log('Ui_handleNodeClick', node); - if (this.editMode === true) { + if (this.root) { + app.model.loadInternalStorages(function(){app.displayStorages()}); + this.root = false; + } else if (this.editMode === true) { //if edit mode is on toggle checkbox state if (toggleCheckbox === true) { this.toggleCheckBoxState(node); // select the checkbox } this.refreshSelectAllStatus(); - - if ($('ul#fileList input:checkbox:checked').length > 0) { - this.enableControlBarButtons($('.editTabbar'), [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); - } else { - this.disableControlBarButtons($('.editTabbar'), [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); - } + this.refreshEditMenu(); } else if (node.hasClass('folder')) { // otherwise display folder app.displayFolder(node.attr('uri')); @@ -227,7 +231,6 @@ function Ui() { * Handler for edit action */ handleEditAction: function Ui_handleEditAction() { - console.log('Ui_handleEditAction'); this.editMode = true; $('.standardTabbar').hide(); $('div.editTabbar').show(); @@ -239,7 +242,7 @@ function Ui() { * Handler for cancel edit action */ handleCancelEditAction: function Ui_handleCancelEditAction() { - console.log('Ui_handleCancelEditAction'); + this.editMode = false; $('div.editTabbar').hide(); $('.standardTabbar').show(); this.hideEditCheckBoxes(); @@ -249,7 +252,6 @@ function Ui() { * Handler for delete action */ handleDeleteAction: function Ui_handleDeleteAction() { - console.log('Ui_handleDeleteAction'); var nodesToDelete = [], $rowElement; @@ -265,6 +267,9 @@ function Ui() { if (nodesToDelete.length > 0 && confirm('Selected nodes will be deleted. Are you sure?')) { app.deleteNodes(nodesToDelete); + this.scrollContentTo(0); + $('ul#fileList input:checkbox:checked').remove(); + this.refreshEditMenu(); } }, @@ -272,7 +277,6 @@ function Ui() { * Handler for copy action */ handleCopyAction: function Ui_handleCopyAction() { - console.log('Ui_handleCopyAction'); var paths = []; if (this.editMode === true) { $('ul#fileList input:checkbox:checked').each(function (index) { @@ -286,7 +290,6 @@ function Ui() { * Handler for move action */ handleMoveAction: function Ui_handleMoveAction() { - console.log('Ui_handleMoveAction'); var paths = []; if (this.editMode === true) { $('ul#fileList input:checkbox:checked').each(function (index) { @@ -300,20 +303,24 @@ function Ui() { * Handler for paste action */ handlePasteAction: function Ui_handlePasteAction() { - console.log('Ui_handlePasteAction'); + }, + + /** + * Scrolls content to the specified position + */ + scrollContentTo: function scrollContentTo(value) { + $('#main [data-role="content"]').scrollview('scrollTo', 0, value); }, /** * @param {FileSystemStorage[]} nodes Storage elements */ displayStorages: function Ui_displayStorages(nodes) { - console.log('Ui_displayStorages', nodes, nodes.length); var len = nodes.length, listElements = [], nodeName, listTemplate = '', i; - this.updateNavbar(''); for (i = 0; i < len; i = i + 1) { @@ -340,9 +347,8 @@ function Ui() { listTemplate = listElements.join(''); $(listTemplate).appendTo('#fileList'); // reset scrollview position - $('#main .ui-scrollview-view').css('-webkit-transform','none'); - $('#fileList') - .trigger("refresh"); + //$('#main .ui-scrollview-view').css('-webkit-transform', 'none'); + $('#fileList').listview("refresh"); this.resetDefaultCheckBoxLabelEvents(); this.hideSelectAllArea(); @@ -350,25 +356,50 @@ function Ui() { }, /** + * File comparison function using their names (case insensitive) + * + * @param {File} x + * @param {File} y + * @returns {Number} + */ + fileComparison: function fileComparison(x, y) { + var a = x.name.toLowerCase(), + b = y.name.toLowerCase(); + + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + return 0; + }, + + /** * renders node list for folder + * @param {string} folderName * @param {File[]} nodes + * @param {bool} [refresh=false] */ - displayFolder: function Ui_displayFolder(folderName, nodes) { - console.log('Ui_displayFolder', nodes, nodes.length); + displayFolder: function Ui_displayFolder(folderName, nodes, refresh) { var len = nodes.length, listElements = [this.templateManager.get('levelUpRow')], nodeName, + checkedRows = [], i; + refresh = refresh || false; + // update title this.updateTitle(folderName); // update navbar this.updateNavbar(folderName); + nodes.sort(this.fileComparison); + // render nodes for (i = 0; i < len; i = i + 1) { nodeName = nodes[i].name.trim(); - console.log('node:', nodeName); if (nodeName !== '') { if (nodes[i].isDirectory) { // folder @@ -396,16 +427,47 @@ function Ui() { listElements.push(this.templateManager.get('emptyFolder')); } + // scroll to top of list + this.scrollContentTo(0); + $('#levelUpBtn').removeClass('vhidden'); $('#homeBtn').removeClass('vhidden'); $('#editActionBtn').removeClass('vhidden'); $('#moreActionBtn').removeClass('vhidden'); - this.hideSelectAllArea(); + + if (refresh === true && this.editMode === true) { + $.each($('#fileList .ui-checkbox input:checked'), function () { + checkedRows.push($(this).closest('li').attr('id')); + }); + } // update file list $('#fileList').html(listElements.join('')) .trigger('refresh') .trigger('create'); + // fixing ellisis for Bengali language + $(".ui-li-text-main.nodename").ellipsis(); + $("#mainTitle").ellipsis(); + $("#navbar").ellipsis(); + + if (this.editMode === true) { + $('.selectAll').show(); + $('ul#fileList > li').css('paddingLeft', '2rem'); + $('.my-ui-checkbox').removeClass('hidden'); + + if (refresh === true) { + // restore checked checkboxes + for (i = 0; i < checkedRows.length; i += 1) { + $('#' + checkedRows[i] + ' input:checkbox') + .attr('checked', 'checked') + .data('checkboxradio').refresh(); + } + } + } else { + $('.selectAll').hide(); + $('ul#fileList > li').css('paddingLeft', '0'); + $('.my-ui-checkbox').addClass('hidden'); + } }, /** @@ -413,10 +475,8 @@ function Ui() { * @param {jQuery} listElement */ toggleCheckBoxState: function Ui_toggleCheckBoxState(listElement) { - console.log('Ui_toggleCheckBoxState', listElement); - var checkboxInput = null, - checkboxesInputChecked = null, - editTabbar = null; + + var checkboxInput = null; checkboxInput = listElement.find('form > div.ui-checkbox input'); checkboxInput @@ -428,7 +488,6 @@ function Ui() { * Shows item checkboxes and topbar with select all option */ showEditCheckBoxes: function Ui_showEditCheckBoxes() { - console.log('Ui_showEditCheckBoxes'); var self = this; this.showSelectAllArea(); @@ -444,21 +503,24 @@ function Ui() { * All checkboxes are auto uncheked */ hideEditCheckBoxes: function Ui_hideEditCheckBoxes() { - console.log('Ui_hideEditCheckBoxes'); var self = this; this.hideSelectAllArea(); // hide select all option topbar $('ul#fileList > li').animate({paddingLeft: '0'}, 200, 'swing', function () { - self.editMode = false; $('.my-ui-checkbox').addClass('hidden'); $.mobile.activePage.page('refresh'); }); // uncheck all checkboxes $('ul#fileList input[type=checkbox]').each(function (index) { + var checkboxradio = $(this).data('checkboxradio'); + $(this).attr('checked', false); - //$(this).data('checkboxradio').refresh(); // element undefined + + if (checkboxradio) { + checkboxradio.refresh(); + } }); //uncheck select all input @@ -469,30 +531,50 @@ function Ui() { }, /** + * Save current header and content height + */ + saveHeights: function Ui_saveHeights() { + this.currentHeaderHeight = $('#main div[data-role="header"]').height(); + this.currentScrollPosition = $('#main div[data-role="content"]').scrollview('getScrollPosition').y; + }, + + /** + * Changes content scroll position after showing/hiding selectAllArea + */ + changeContentScrollPosition: function Ui_changeContentScrollPosition() { + var diff; + if (this.currentScrollPosition !== 0) { + diff = $('#main div[data-role="header"]').height() - this.currentHeaderHeight; + $('#main div[data-role="content"]').scrollview('scrollTo', 0, -(this.currentScrollPosition + diff)); + } + }, + + /** * Shows topbar with select all option */ showSelectAllArea: function Ui_showSelectAllArea() { - console.log('showSelectAllArea'); + this.saveHeights(); $('.selectAll').show(); $.mobile.activePage.page('refresh'); + this.changeContentScrollPosition(); }, /** * Hides topbar with select all option */ hideSelectAllArea: function Ui_hideSelectAllArea() { - console.log('hideSelectAllArea'); + this.saveHeights(); $('.selectAll').hide(); $.mobile.activePage.page('refresh'); + this.changeContentScrollPosition(); }, /** * Enable specified options for tabbar * @param {object} tabbar - * @param {array} options to enable + * @param {array} enableOptions options to enable */ enableControlBarButtons: function Ui_enableControlBarButtons(tabbar, enableOptions) { - console.log('Ui_enableControlBarButtons', tabbar, enableOptions); var i = 0, len = enableOptions.length; @@ -503,11 +585,10 @@ function Ui() { /** * Disable specified options for tabbar - * @param {object} controlbar - * @param {array} options to enable + * @param {object} tabbar controlbar + * @param {array} disableOptions options to enable */ disableControlBarButtons: function Ui_disableControlBarButtons(tabbar, disableOptions) { - console.log('Ui_disableControlBarButtons', tabbar, disableOptions); var i = 0, len = disableOptions.length; @@ -520,7 +601,6 @@ function Ui() { * @param {string} path */ updateTitle: function Ui_updateTitle(path) { - console.log('Ui_updateTitle', path); var regexp = new RegExp('([^\/])+$', 'g'), match = path.match(regexp), lastDir = match[0] || '(dir)'; @@ -531,7 +611,6 @@ function Ui() { * @param {string} path */ updateNavbar: function Ui_updateNavbar(path) { - console.log('Ui_updateNavbar', path); var html = ['Media'], splitted, len, @@ -549,25 +628,24 @@ function Ui() { }, handleSelectAllChange: function Ui_handleSelectAllChange() { - console.log('Ui_handleSelectAllChange'); var $selectAllInput = $('.ui-header .selectAll .ui-checkbox input'); $selectAllInput.data('checkboxradio').refresh(); if ($selectAllInput.is(':checked')) { - this.enableControlBarButtons($('.editTabbar'), [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); - // check all checkboxes $('ul#fileList input[type=checkbox]').each(function (index) { $(this).attr('checked', true); $(this).data('checkboxradio').refresh(); }); - } else { - this.disableControlBarButtons($('.editTabbar'), [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); + this.enableControlBarButtons($('.editTabbar'), [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); + } else { $('ul#fileList input[type=checkbox]').each(function (index) { $(this).attr('checked', false); $(this).data('checkboxradio').refresh(); }); + + this.disableControlBarButtons($('.editTabbar'), [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); } }, @@ -575,7 +653,6 @@ function Ui() { * */ refreshSelectAllStatus: function Ui_refreshSelectAllStatus() { - console.log('Ui_refreshSelectAllStatus'); var $selectAllInput = $('.ui-header .selectAll .ui-checkbox input'); // update status of select all checkbox if ($('ul#fileList input:checkbox:not(:checked)').length === 0) { @@ -588,10 +665,22 @@ function Ui() { }, /** + * Refresh activity of edit menu + */ + refreshEditMenu: function () { + if ($('ul#fileList input:checkbox:checked').length > 0) { + this.enableControlBarButtons($('.editTabbar'), + [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); + } else { + this.disableControlBarButtons($('.editTabbar'), + [this.EDIT_TABBAR_DELETE_ACTION, this.EDIT_TABBAR_COPY_ACTION, this.EDIT_TABBAR_MOVE_ACTION]); + } + }, + + /** * Unbinds default events for checkbox labels */ resetDefaultCheckBoxLabelEvents: function Ui_resetDefaultCheckBoxLabelEvents() { - console.log('Ui_resetDefaultCheckBoxLabelEvents'); $('div.ui-checkbox > label') .unbind('vmousedown') .unbind('vmouseup') @@ -601,18 +690,21 @@ function Ui() { /** * Remove html node element from list - * @param {string} node id + * @param {string} nodeId node id */ removeNodeFromList: function Ui_removeNodeFromList(nodeId) { - console.log('Ui_removeNodeFromList', nodeId); $('ul#fileList > li#' + nodeId).remove(); + + // hide select All checkbox if removed all elements; + if ($('ul#fileList > li.node').length === 0) { + this.hideSelectAllArea(); + } }, /** * Enable/Disable */ refreshPasteActionBtn: function Ui_refreshPasteActionBtn(clipboardEmpty) { - console.log('Ui_refreshPasteActionBtn', clipboardEmpty); if (clipboardEmpty === true) { $('#pasteActionBtnRow').addClass('hidden'); } else { diff --git a/js/app.ui.templateManager.js b/js/app.ui.templateManager.js index bfe2b73..68c6678 100644 --- a/js/app.ui.templateManager.js +++ b/js/app.ui.templateManager.js @@ -1,5 +1,4 @@ -/*jslint devel: true*/ -/*global $, app */ +/*global tizen, $, app */ /** * @class TemplateManager */ @@ -21,15 +20,14 @@ function TemplateManager() { * UI module initialisation */ init: function init() { - }, /** * Returns template html (from cache) + * @param {string} tplName + * @param {string} tplParams */ get: function TemplateManager_get(tplName, tplParams) { - console.log('TemplateManager_get:' + tplName); - if (this.cache[tplName] !== undefined) { return this.getCompleted(this.cache[tplName], tplParams); } @@ -38,6 +36,8 @@ function TemplateManager() { /** * Load templates to cache + * @param {string} tplNames + * @param {function} onSuccess */ loadToCache: function TemplateManager_loadToCache(tplNames, onSuccess) { var self = this, @@ -66,7 +66,6 @@ function TemplateManager() { // save to cache self.cache[fileName] = data; - console.log('Cached template: ' + fileName); // if all templates are cached launch callback if (cachedTemplates >= tplNames.length && typeof onSuccess === 'function') { @@ -74,7 +73,7 @@ function TemplateManager() { } }, error: function (jqXHR, textStatus, errorThrown) { - alert(errorThrown); + console.error('templateManagerError: ' + errorThrown); } }); } else { @@ -92,6 +91,8 @@ function TemplateManager() { /** * Returns template completed by specified params + * @param {string} tplHtml + * @param {string} tplParams */ getCompleted: function TemplateManager_getCompleted(tplHtml, tplParams) { var tplParam, replaceRegExp; diff --git a/js/main.js b/js/main.js index e3440f4..45d59e6 100644 --- a/js/main.js +++ b/js/main.js @@ -14,7 +14,7 @@ * limitations under the License. */ -/*jslint devel: true */ +/*jslint devel: true*/ /*global $, tizen, App */ /** @@ -40,7 +40,6 @@ var app = null; var self = this; $.getScript('js/app.js') .done(function () { - console.log('Loaded app.js'); // once the app is loaded, create the app object // and load the libraries app = new App(); @@ -59,7 +58,6 @@ var app = null; $.getScript(filename) .done(function () { loadedLibs += 1; - console.log('Loaded subscript: ' + filename + ' : ' + loadedLibs); if (loadedLibs >= app.requires.length) { // All dependencies are loaded - initialise the app app.init(); diff --git a/templates/main.tpl b/templates/main.tpl index 1069bb3..f6871ca 100644 --- a/templates/main.tpl +++ b/templates/main.tpl @@ -14,6 +14,18 @@
        +
        +
        +

        Add new folder

        +

        +
        + +
        +
        + Save + Cancel +
        +
        @@ -49,16 +61,6 @@
        -
        -

        Add new folder

        -

        - -

        -

        - - -

        -
        \ No newline at end of file -- 2.7.4 From 17d5711e35b572dad88ea54bb4031e9ca647e334 Mon Sep 17 00:00:00 2001 From: "gs86.lee" Date: Tue, 23 Apr 2013 17:47:50 +0900 Subject: [PATCH 4/4] [FileManager]update FileManager(tizen_2.1) Change-Id: I5546eb273b4efb4d444af80de72e150ea2383500 --- LICENSE.Flora | 6 +++--- NOTICE.Flora | 4 ---- config.xml | 27 ++++++++++++++++----------- icon.png | Bin 17581 -> 13996 bytes js/app.helpers.js | 2 +- js/app.model.js | 2 +- 6 files changed, 21 insertions(+), 20 deletions(-) delete mode 100644 NOTICE.Flora mode change 100755 => 100644 icon.png diff --git a/LICENSE.Flora b/LICENSE.Flora index 9c95663..fd90a36 100644 --- a/LICENSE.Flora +++ b/LICENSE.Flora @@ -1,6 +1,6 @@ Flora License -Version 1.0, May, 2012 +Version 1.0, April, 2013 http://floralicense.org/license/ @@ -67,8 +67,8 @@ on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. "Tizen Certified Platform" shall mean a software platform that complies -with the standards set forth in the Compatibility Definition Document -and passes the Compatibility Test Suite as defined from time to time +with the standards set forth in the Tizen Compliance Specification +and passes the Tizen Compliance Tests as defined from time to time by the Tizen Technical Steering Group and certified by the Tizen Association or its designated agent. diff --git a/NOTICE.Flora b/NOTICE.Flora deleted file mode 100644 index fdb699a..0000000 --- a/NOTICE.Flora +++ /dev/null @@ -1,4 +0,0 @@ -Copyright (c) 2012 Samsung Electronics Co., Ltd. All rights reserved. -Except as noted, this software is licensed under Flora License, Version 1. -Please, see the LICENSE file for Flora License terms and conditions. - diff --git a/config.xml b/config.xml index f18a5d3..f8b4411 100644 --- a/config.xml +++ b/config.xml @@ -1,14 +1,19 @@ - - - + + + FileManager - - - - - - - - + + + + + + + + diff --git a/icon.png b/icon.png old mode 100755 new mode 100644 index 983c883493a0ae561b9592d54ea8204042b4b6fb..33fbb63277c58e177c08d250aeff66b259da2e98 GIT binary patch literal 13996 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 SLHsBH0000Gn)_u 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