feat ContextMenu: Rename and Delete
32
src/img/ Share.svg
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="30px" height="33px" viewBox="0 0 30 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.3.3 (12072) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Share</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-1">
|
||||
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14 0" in="shadowBlurOuter1" type="matrix" result="shadowMatrixOuter1"></feColorMatrix>
|
||||
<feMerge>
|
||||
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="Components" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Toolbar" sketch:type="MSArtboardGroup" transform="translate(-257.000000, -9.000000)">
|
||||
<g sketch:type="MSLayerGroup">
|
||||
<rect id="Box" stroke="#CDCDCD" fill="#F0F0F0" filter="url(#filter-1)" sketch:type="MSShapeGroup" x="0" y="0" width="360" height="50"></rect>
|
||||
<g id="Buttons" transform="translate(26.000000, 10.000000)" sketch:type="MSShapeGroup">
|
||||
<g id="Share" transform="translate(234.000000, 1.000000)">
|
||||
<rect id="Rectangle-19" stroke="#63B0CD" stroke-width="4" x="1" y="5" width="22" height="22" rx="3"></rect>
|
||||
<g id="Arrow" transform="translate(3.970456, 0.882233)" stroke="#F0F0F0" stroke-width="2.5" fill="#63B0CD">
|
||||
<path d="M20.2530929,5.25326679 C20.3438814,5.03976384 20.3987852,4.80672423 20.4099227,4.56134515 C20.432031,4.07426178 20.2779908,3.61997718 20.0040956,3.26013822 L20.0040956,3.26013822 C19.9211454,3.11025176 19.8084527,2.97343991 19.666954,2.85885655 L16.8349944,0.565580823 C16.2473288,0.0896986089 15.3888974,0.175692837 14.9102322,0.766795217 C14.5280707,1.23872535 14.51148,1.88774371 14.8254507,2.37170039 C8.0442336,2.34435705 5.13629361,3.46855439 3.00866682,7.10849311 C2.68821987,7.65671298 2.40532468,8.22634036 2.15810417,8.81484569 C0.984261485,11.6091635 0.691790704,14.485364 0.877899405,16.9746675 C0.895731743,17.2131846 0.914705989,17.391546 0.929731873,17.5029256 C1.07740823,18.5975788 2.08451526,19.3652551 3.17916839,19.2175787 C4.27382152,19.0699024 5.04149788,18.0627954 4.89382152,16.9681422 C4.88912564,16.933334 4.87851724,16.833613 4.86676693,16.6764463 C4.72372596,14.7631968 4.95552945,12.4836072 5.84592458,10.3640328 C6.02652213,9.93412255 6.23155977,9.52126628 6.46199346,9.1270409 C7.68123763,7.04116095 9.53426863,6.32342019 14.9076101,6.36745059 C14.6234537,6.83929221 14.6426924,7.45589796 15.0015672,7.91523679 C15.4698434,8.514603 16.3266432,8.61556582 16.9225246,8.15001227 L19.4222418,6.19701916 C19.5803173,6.10431474 19.7247752,5.9904338 19.8515556,5.85908383 C20.0438195,5.69160959 20.1787056,5.48100042 20.2530929,5.25326679 Z" id="Shape-Copy"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
14
src/img/Directory.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="36px" height="32px" viewBox="0 0 36 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.3.3 (12072) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Directory</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Components" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Directory" sketch:type="MSArtboardGroup" transform="translate(-14.000000, -14.000000)" fill="#5EBEC2">
|
||||
<g sketch:type="MSLayerGroup" transform="translate(14.000000, 14.000000)" id="Rectangle-26">
|
||||
<path d="M31.5861302,4.8857172 L31.5861302,4.01206383 C31.5861302,1.79308927 29.7957552,0.00332028593 27.5872158,0.00332028593 L4.63272197,0.00332028593 C2.43172245,0.00332028593 0.633807607,1.7980959 0.633807607,4.01206383 L0.633807607,22.5765087 L2.99385717,8.43023526 C3.32044476,6.47265038 5.37422383,4.88571429 7.58945402,4.88571429 L31.5809386,4.88571429 C31.5826695,4.88571429 31.5844001,4.88571526 31.5861302,4.8857172 L31.5861302,4.8857172 Z M0.633839725,29.5203448 C0.634442717,29.6731046 0.643531019,29.8238105 0.660655651,29.9720175 L0.660655651,29.7103842 C0.660655651,29.6569617 0.661490198,29.6037345 0.663146965,29.5507151 C0.653298862,29.5406529 0.643529589,29.5305292 0.633839725,29.5203448 L0.633839725,29.5203448 Z M8.58945402,5.88571429 L32.5809386,5.88571429 C34.7924291,5.88571429 36.320372,7.47308677 35.9938572,9.43023526 L32.8913391,28.0269076 C32.5647516,29.9844925 30.5109725,31.5714286 28.2957423,31.5714286 L4.3042577,31.5714286 C2.09276724,31.5714286 0.564824355,29.9840561 0.891339142,28.0269076 L3.99385717,9.43023526 C4.32044476,7.47265038 6.37422383,5.88571429 8.58945402,5.88571429 Z" sketch:type="MSShapeGroup"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
14
src/img/File.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="30px" height="36px" viewBox="0 0 30 36" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.3.3 (12072) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>File</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Components" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="File" sketch:type="MSArtboardGroup" transform="translate(-17.000000, -16.000000)" fill="#5EBEC2">
|
||||
<g sketch:type="MSLayerGroup" transform="translate(17.000000, 16.000000)" id="Rectangle-28">
|
||||
<path d="M0,3.99017859 C0,1.7864638 1.78679466,0 3.9992748,0 L26.0007252,0 C28.2094637,0 30,1.7852456 30,3.99017859 L30,32.0098214 C30,34.2135362 28.2132053,36 26.0007252,36 L3.9992748,36 C1.79053632,36 0,34.2147544 0,32.0098214 L0,3.99017859 L0,3.99017859 Z M6.36363636,9.9 C6.36363636,9.40294373 6.75685482,9 7.26797379,9 L22.7320262,9 C23.231478,9 23.6363636,9.39947834 23.6363636,9.9 C23.6363636,10.3970563 23.2431452,10.8 22.7320262,10.8 L7.26797379,10.8 C6.76852202,10.8 6.36363636,10.4005217 6.36363636,9.9 L6.36363636,9.9 Z M6.36363636,15.93 C6.36363636,15.4329437 6.75685482,15.03 7.26797379,15.03 L22.7320262,15.03 C23.231478,15.03 23.6363636,15.4294783 23.6363636,15.93 C23.6363636,16.4270563 23.2431452,16.83 22.7320262,16.83 L7.26797379,16.83 C6.76852202,16.83 6.36363636,16.4305217 6.36363636,15.93 L6.36363636,15.93 Z M6.36363636,21.6 C6.36363636,21.1029437 6.75685482,20.7 7.26797379,20.7 L22.7320262,20.7 C23.231478,20.7 23.6363636,21.0994783 23.6363636,21.6 C23.6363636,22.0970563 23.2431452,22.5 22.7320262,22.5 L7.26797379,22.5 C6.76852202,22.5 6.36363636,22.1005217 6.36363636,21.6 L6.36363636,21.6 Z M6.36363636,27 C6.36363636,26.5029437 6.75685482,26.1 7.26797379,26.1 L22.7320262,26.1 C23.231478,26.1 23.6363636,26.4994783 23.6363636,27 C23.6363636,27.4970563 23.2431452,27.9 22.7320262,27.9 L7.26797379,27.9 C6.76852202,27.9 6.36363636,27.5005217 6.36363636,27 L6.36363636,27 Z" sketch:type="MSShapeGroup"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
27
src/img/More.svg
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="6px" height="24px" viewBox="0 0 6 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.3.3 (12072) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>More</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-1">
|
||||
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||
<feGaussianBlur stdDeviation="2" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.14 0" in="shadowBlurOuter1" type="matrix" result="shadowMatrixOuter1"></feColorMatrix>
|
||||
<feMerge>
|
||||
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="Components" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Toolbar" sketch:type="MSArtboardGroup" transform="translate(-336.000000, -14.000000)">
|
||||
<g sketch:type="MSLayerGroup">
|
||||
<rect id="Box" stroke="#CDCDCD" fill="#F0F0F0" filter="url(#filter-1)" sketch:type="MSShapeGroup" x="0" y="0" width="360" height="50"></rect>
|
||||
<g id="Buttons" transform="translate(26.000000, 10.000000)" fill="#63B0CD" sketch:type="MSShapeGroup">
|
||||
<path d="M313,10 C314.656854,10 316,8.65685425 316,7 C316,5.34314575 314.656854,4 313,4 C311.343146,4 310,5.34314575 310,7 C310,8.65685425 311.343146,10 313,10 Z M313,19 C314.656854,19 316,17.6568542 316,16 C316,14.3431458 314.656854,13 313,13 C311.343146,13 310,14.3431458 310,16 C310,17.6568542 311.343146,19 313,19 Z M313,28 C314.656854,28 316,26.6568542 316,25 C316,23.3431458 314.656854,22 313,22 C311.343146,22 310,23.3431458 310,25 C310,26.6568542 311.343146,28 313,28 Z" id="More"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
16
src/img/Plus.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.3.3 (12072) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Plus</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Components" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Toolbar" sketch:type="MSArtboardGroup" transform="translate(-26.000000, -14.000000)" fill="#63B0CD">
|
||||
<g sketch:type="MSLayerGroup" id="Buttons">
|
||||
<g transform="translate(26.000000, 10.000000)" sketch:type="MSShapeGroup">
|
||||
<path d="M15,13.5 L15,6.50050165 C15,5.12537944 13.8807119,4 12.5,4 C11.1096621,4 10,5.11951272 10,6.50050165 L10,13.5 L2.50050165,13.5 C1.12537944,13.5 0,14.6192881 0,16 C0,17.3903379 1.11951272,18.5 2.50050165,18.5 L10,18.5 L10,25.4994984 C10,26.8746206 11.1192881,28 12.5,28 C13.8903379,28 15,26.8804873 15,25.4994984 L15,18.5 L21.4994984,18.5 C22.8746206,18.5 24,17.3807119 24,16 C24,14.6096621 22.8804873,13.5 21.4994984,13.5 L15,13.5 Z" id="Plus"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
20
src/img/Refresh.svg
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="26px" height="28px" viewBox="0 0 26 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.3.3 (12072) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Refresh</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Components" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Toolbar" sketch:type="MSArtboardGroup" transform="translate(-180.000000, -11.000000)">
|
||||
<g sketch:type="MSLayerGroup" id="Buttons">
|
||||
<g transform="translate(26.000000, 10.000000)" sketch:type="MSShapeGroup">
|
||||
<g id="Refresh" transform="translate(155.000000, 0.000000)">
|
||||
<path d="M10.5474605,1.30277122 C11.1919885,0.780842749 12.1334814,0.875157759 12.6584622,1.52345507 L12.6584622,1.52345507 C13.1798083,2.16726386 13.0808307,3.1115672 12.4354217,3.6342091 L9.32944225,6.14938166 C8.68491427,6.67131013 7.74342133,6.57699512 7.21844052,5.92869781 L7.21844052,5.92869781 C6.69709444,5.28488901 6.79607209,4.34058567 7.44148108,3.81794378 L10.5474605,1.30277122 L10.5474605,1.30277122 Z" id="Rectangle-14" fill="#63B0CD"></path>
|
||||
<path d="M9.26782395,4.9280266 C8.54245587,4.5259485 7.63164396,4.78231919 7.22721443,5.51192937 L7.22721443,5.51192937 C6.82558499,6.23648806 7.0870355,7.14925802 7.81339509,7.55188572 L11.3089429,9.48949949 C12.0343109,9.89157758 12.9451229,9.63520689 13.3495524,8.90559671 L13.3495524,8.90559671 C13.7511818,8.18103802 13.4897313,7.26826806 12.7633717,6.86564036 L9.26782395,4.9280266 L9.26782395,4.9280266 Z" id="Rectangle-14-Copy" fill="#63B0CD"></path>
|
||||
<path d="M2.19892823,20.9938955 C4.95698056,26.406878 11.580913,28.5591241 16.9938955,25.8010718 C22.406878,23.0430194 24.5591241,16.419087 21.8010718,11.0061045 C19.0430194,5.59312203 12.419087,3.44087591 7.0061045,6.19892823 C1.59312203,8.95698056 -0.559124091,15.580913 2.19892823,20.9938955 L2.19892823,20.9938955 Z" id="Oval-13" stroke="#63B0CD" stroke-width="4" stroke-linecap="round" stroke-dasharray="50,6"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
22
src/img/Share.svg
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="25px" height="27px" viewBox="0 0 25 27" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.3.3 (12072) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Share</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Components" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Toolbar" sketch:type="MSArtboardGroup" transform="translate(-260.000000, -12.000000)">
|
||||
<g sketch:type="MSLayerGroup" id="Buttons">
|
||||
<g transform="translate(26.000000, 10.000000)" sketch:type="MSShapeGroup">
|
||||
<g id="Share" transform="translate(234.000000, 3.000000)">
|
||||
<path d="M2.25,6.24976556 L2.25,6.24976556 L2.25,21.7502344 C2.25,22.8576463 3.14314782,23.75 4.24976556,23.75 L19.7502344,23.75 C20.8576463,23.75 21.75,22.8568522 21.75,21.7502344 L21.75,6.24976556 C21.75,5.14235373 20.8568522,4.25 19.7502344,4.25 L4.24976556,4.25 C3.14235373,4.25 2.25,5.14314782 2.25,6.24976556 L2.25,6.24976556 Z M0.25,6.24976556 C0.25,4.04075604 2.0356077,2.25 4.24976556,2.25 L19.7502344,2.25 C21.959244,2.25 23.75,4.0356077 23.75,6.24976556 L23.75,21.7502344 C23.75,23.959244 21.9643923,25.75 19.7502344,25.75 L4.24976556,25.75 C2.04075604,25.75 0.25,23.9643923 0.25,21.7502344 L0.25,6.24976556 L0.25,6.24976556 Z" id="Shape" fill="#63B0CD"></path>
|
||||
<rect id="Rectangle-19" stroke="#63B0CD" x="1" y="3" width="22" height="22" rx="4"></rect>
|
||||
<g id="Arrow" transform="translate(2.970456, 0.882233)" stroke="#F0F0F0" stroke-width="1.5" fill="#63B0CD">
|
||||
<path d="M20.2530929,5.25326679 C20.3438814,5.03976384 20.3987852,4.80672423 20.4099227,4.56134515 C20.432031,4.07426178 20.2779908,3.61997718 20.0040956,3.26013822 L20.0040956,3.26013822 C19.9211454,3.11025176 19.8084527,2.97343991 19.666954,2.85885655 L16.8349944,0.565580823 C16.2473288,0.0896986089 15.3888974,0.175692837 14.9102322,0.766795217 C14.5280707,1.23872535 14.51148,1.88774371 14.8254507,2.37170039 C8.0442336,2.34435705 5.13629361,3.46855439 3.00866682,7.10849311 C2.68821987,7.65671298 2.40532468,8.22634036 2.15810417,8.81484569 C0.984261485,11.6091635 0.691790704,14.485364 0.877899405,16.9746675 C0.895731743,17.2131846 0.914705989,17.391546 0.929731873,17.5029256 C1.07740823,18.5975788 2.08451526,19.3652551 3.17916839,19.2175787 C4.27382152,19.0699024 5.04149788,18.0627954 4.89382152,16.9681422 C4.88912564,16.933334 4.87851724,16.833613 4.86676693,16.6764463 C4.72372596,14.7631968 4.95552945,12.4836072 5.84592458,10.3640328 C6.02652213,9.93412255 6.23155977,9.52126628 6.46199346,9.1270409 C7.68123763,7.04116095 9.53426863,6.32342019 14.9076101,6.36745059 C14.6234537,6.83929221 14.6426924,7.45589796 15.0015672,7.91523679 C15.4698434,8.514603 16.3266432,8.61556582 16.9225246,8.15001227 L19.4222418,6.19701916 C19.5803173,6.10431474 19.7247752,5.9904338 19.8515556,5.85908383 C20.0438195,5.69160959 20.1787056,5.48100042 20.2530929,5.25326679 Z" id="Shape-Copy"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
16
src/img/View.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
|
||||
<!-- Generator: Sketch 3.3.3 (12072) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>View</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Components" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
|
||||
<g id="Toolbar" sketch:type="MSArtboardGroup" transform="translate(-100.000000, -14.000000)" fill="#63B0CD">
|
||||
<g sketch:type="MSLayerGroup" id="Buttons">
|
||||
<g transform="translate(26.000000, 10.000000)" sketch:type="MSShapeGroup">
|
||||
<path d="M77,10 C78.6568542,10 80,8.65685425 80,7 C80,5.34314575 78.6568542,4 77,4 C75.3431458,4 74,5.34314575 74,7 C74,8.65685425 75.3431458,10 77,10 Z M77,19 C78.6568542,19 80,17.6568542 80,16 C80,14.3431458 78.6568542,13 77,13 C75.3431458,13 74,14.3431458 74,16 C74,17.6568542 75.3431458,19 77,19 Z M86,19 C87.6568542,19 89,17.6568542 89,16 C89,14.3431458 87.6568542,13 86,13 C84.3431458,13 83,14.3431458 83,16 C83,17.6568542 84.3431458,19 86,19 Z M86,28 C87.6568542,28 89,26.6568542 89,25 C89,23.3431458 87.6568542,22 86,22 C84.3431458,22 83,23.3431458 83,25 C83,26.6568542 84.3431458,28 86,28 Z M77,28 C78.6568542,28 80,26.6568542 80,25 C80,23.3431458 78.6568542,22 77,22 C75.3431458,22 74,23.3431458 74,25 C74,26.6568542 75.3431458,28 77,28 Z M86,10 C87.6568542,10 89,8.65685425 89,7 C89,5.34314575 87.6568542,4 86,4 C84.3431458,4 83,5.34314575 83,7 C83,8.65685425 84.3431458,10 86,10 Z M95,19 C96.6568542,19 98,17.6568542 98,16 C98,14.3431458 96.6568542,13 95,13 C93.3431458,13 92,14.3431458 92,16 C92,17.6568542 93.3431458,19 95,19 Z M95,28 C96.6568542,28 98,26.6568542 98,25 C98,23.3431458 96.6568542,22 95,22 C93.3431458,22 92,23.3431458 92,25 C92,26.6568542 93.3431458,28 95,28 Z M95,10 C96.6568542,10 98,8.65685425 98,7 C98,5.34314575 96.6568542,4 95,4 C93.3431458,4 92,5.34314575 92,7 C92,8.65685425 93.3431458,10 95,10 Z" id="View"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
@ -4,8 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>Hawk</title>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<link rel='stylesheet' href='style.css' />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id='wrapper'></div>
|
||||
|
||||
<script src='main.js'></script>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { CHANGE_DIRECTORY } from 'actions/types';
|
||||
|
||||
export default function changedir(dir) {
|
||||
if (dir === 'sdcard') dir = '';
|
||||
return {
|
||||
type: CHANGE_DIRECTORY,
|
||||
dir
|
||||
|
32
src/js/actions/dialog.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { DIALOG } from 'actions/types';
|
||||
|
||||
export function show(id) {
|
||||
return {
|
||||
type: DIALOG,
|
||||
active: true,
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
export function hide(id) {
|
||||
return {
|
||||
type: DIALOG,
|
||||
active: false,
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
export function toggle(id) {
|
||||
return {
|
||||
type: DIALOG,
|
||||
active: 'toggle',
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
export function hideAll() {
|
||||
return {
|
||||
type: DIALOG,
|
||||
active: false
|
||||
}
|
||||
}
|
35
src/js/actions/file.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { CREATE_FILE, SHARE_FILE, RENAME_FILE, ACTIVE_FILE, DELETE_FILE } from 'actions/types';
|
||||
|
||||
export function create(path, name) {
|
||||
return {
|
||||
type: CREATE_FILE,
|
||||
path, name
|
||||
}
|
||||
}
|
||||
|
||||
export function share() {
|
||||
return {
|
||||
type: SHARE_FILE
|
||||
}
|
||||
}
|
||||
|
||||
export function rename(file, name) {
|
||||
return {
|
||||
type: RENAME_FILE,
|
||||
file, name
|
||||
}
|
||||
}
|
||||
|
||||
export function active(file) {
|
||||
return {
|
||||
type: ACTIVE_FILE,
|
||||
file
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteFile(file) {
|
||||
return {
|
||||
type: DELETE_FILE,
|
||||
file
|
||||
}
|
||||
}
|
29
src/js/actions/files-view.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { LIST_FILES, FILES_VIEW, REFRESH } from 'actions/types';
|
||||
import store from 'store';
|
||||
|
||||
export function refresh() {
|
||||
return {
|
||||
type: REFRESH
|
||||
}
|
||||
}
|
||||
|
||||
export function toggle(state) {
|
||||
return {
|
||||
type: FILES_VIEW,
|
||||
view: 'toggle'
|
||||
}
|
||||
}
|
||||
|
||||
export function details(state) {
|
||||
return {
|
||||
type: FILES_VIEW,
|
||||
view: 'details'
|
||||
}
|
||||
}
|
||||
|
||||
export function list(state) {
|
||||
return {
|
||||
type: FILES_VIEW,
|
||||
view: 'list'
|
||||
}
|
||||
}
|
32
src/js/actions/menu.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { MENU } from 'actions/types';
|
||||
|
||||
export function show(id, x, y) {
|
||||
return {
|
||||
type: MENU,
|
||||
active: true,
|
||||
id, x, y
|
||||
}
|
||||
}
|
||||
|
||||
export function hide(id) {
|
||||
return {
|
||||
type: MENU,
|
||||
active: false,
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
export function toggle(id, x, y) {
|
||||
return {
|
||||
type: MENU,
|
||||
active: 'toggle',
|
||||
id, x, y
|
||||
}
|
||||
}
|
||||
|
||||
export function hideAll() {
|
||||
return {
|
||||
type: MENU,
|
||||
active: false
|
||||
}
|
||||
}
|
22
src/js/actions/navigation.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { NAVIGATION, TOGGLE } from 'actions/types';
|
||||
|
||||
export function show() {
|
||||
return {
|
||||
type: NAVIGATION,
|
||||
active: true
|
||||
}
|
||||
}
|
||||
|
||||
export function hide() {
|
||||
return {
|
||||
type: NAVIGATION,
|
||||
active: false
|
||||
}
|
||||
}
|
||||
|
||||
export function toggle() {
|
||||
return {
|
||||
type: NAVIGATION,
|
||||
active: TOGGLE
|
||||
}
|
||||
}
|
@ -1,9 +1,26 @@
|
||||
const TYPES = {
|
||||
CHANGE_DIRECTORY: Symbol(),
|
||||
LIST_FILES: Symbol(),
|
||||
SORT: Symbol(),
|
||||
SEARCH: Symbol(),
|
||||
REFRESH: Symbol()
|
||||
CHANGE_DIRECTORY: Symbol('CHANGE_DIRECTORY'),
|
||||
|
||||
LIST_FILES: Symbol('LIST_FILES'),
|
||||
FILES_VIEW: Symbol('FILES_VIEW'),
|
||||
|
||||
NAVIGATION: Symbol('NAVIGATION'),
|
||||
TOGGLE: Symbol('TOGGLE'),
|
||||
REFRESH: Symbol('REFRESH'),
|
||||
SORT: Symbol('SORT'),
|
||||
|
||||
NEW_FILE: Symbol('NEW_FILE'),
|
||||
CREATE_FILE: Symbol('CREATE_FILE'),
|
||||
SHARE_FILE: Symbol('SHARE_FILE'),
|
||||
RENAME_FILE: Symbol('RENAME_FILE'),
|
||||
ACTIVE_FILE: Symbol('ACTIVE_FILE'),
|
||||
DELETE_FILE: Symbol('DELETE_FILE'),
|
||||
|
||||
MENU: Symbol('MENU'),
|
||||
|
||||
DIALOG: Symbol('DEBUG'),
|
||||
|
||||
SEARCH: Symbol('SEARCH')
|
||||
};
|
||||
|
||||
export default TYPES;
|
||||
|
@ -1,13 +1,86 @@
|
||||
export async function directory(dir = '/') {
|
||||
let storage = navigator.getDeviceStorage('sdcard');
|
||||
let root = await storage.getRoot();
|
||||
import { type } from 'utils';
|
||||
|
||||
if (dir === '/' || !dir) return root;
|
||||
let SD_CACHE;
|
||||
export function sdcard() {
|
||||
if (SD_CACHE) return SD_CACHE;
|
||||
|
||||
return await root.get(dir);
|
||||
SD_CACHE = navigator.getDeviceStorage('sdcard');
|
||||
return SD_CACHE;
|
||||
}
|
||||
|
||||
let ROOT_CACHE;
|
||||
export async function root() {
|
||||
if (ROOT_CACHE) return ROOT_CACHE;
|
||||
|
||||
ROOT_CACHE = await sdcard().getRoot();
|
||||
return ROOT_CACHE;
|
||||
}
|
||||
|
||||
export async function getFile(dir = '/') {
|
||||
let parent = await root();
|
||||
|
||||
if (dir === '/' || !dir) return root();
|
||||
|
||||
return await parent.get(dir);
|
||||
}
|
||||
|
||||
export async function children(dir) {
|
||||
let parent = await directory(dir);
|
||||
let parent = await getFile(dir);
|
||||
return await parent.getFilesAndDirectories();
|
||||
}
|
||||
|
||||
export async function readFile(path) {
|
||||
let file = await getFile(path);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.onabort = reject;
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
export async function createFile(...args) {
|
||||
let parent = await root();
|
||||
|
||||
return await parent.createFile(...args);
|
||||
}
|
||||
|
||||
export async function createDirectory(...args) {
|
||||
let parent = await root();
|
||||
|
||||
return await parent.createDirectory(...args);
|
||||
}
|
||||
|
||||
export async function rename(file, newName) {
|
||||
console.log(file);
|
||||
let path = (file.path || '').slice(1); // remove starting slash
|
||||
let oldPath = (path + file.name);
|
||||
let newPath = path + newName;
|
||||
|
||||
let target = await getFile(oldPath);
|
||||
|
||||
if (type(target) === 'Directory') {
|
||||
await createDirectory(newPath);
|
||||
let childs = await target.getFilesAndDirectories();
|
||||
|
||||
for (let child of childs) {
|
||||
await rename(child, newPath + '/' + child.name);
|
||||
}
|
||||
|
||||
target.delete();
|
||||
return;
|
||||
} else {
|
||||
let content = await readFile(fullpath);
|
||||
|
||||
let blob = new Blob([content], {type: target.type});
|
||||
|
||||
sdcard().delete(fullpath);
|
||||
|
||||
sdcard().addNamed(blob, path + newName);
|
||||
}
|
||||
}
|
||||
|
53
src/js/components/breadcrumb.js
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import changedir from 'actions/changedir';
|
||||
import { bind } from 'store';
|
||||
|
||||
@connect(props)
|
||||
export default class Breadcrumb extends Component {
|
||||
render() {
|
||||
let directories = this.props.cwd.split('/');
|
||||
directories.unshift('sdcard');
|
||||
|
||||
let els = directories.map((dir, index, arr) => {
|
||||
let path = arr.slice(1, index + 1).join('/');
|
||||
let slash = index > 0 ? '/' : '';
|
||||
|
||||
return (
|
||||
<span key={index} onClick={bind(changedir(path))}>
|
||||
<i>{slash}</i>{dir}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
let lastDirectories = this.props.lwd.split('/');
|
||||
if (lastDirectories.length > directories.length - 1) {
|
||||
lastDirectories.splice(0, directories.length - 1);
|
||||
let history = lastDirectories.map((dir, index, arr) => {
|
||||
let current = directories.slice(1).concat(arr.slice(0, index + 1));
|
||||
let path = current.join('/');
|
||||
|
||||
return (
|
||||
<span key={directories.length + index} className='history' onClick={bind(changedir(path))}>
|
||||
<i>/</i>{dir}
|
||||
</span>
|
||||
)
|
||||
});
|
||||
|
||||
els = els.concat(history);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='breadcrumb'>
|
||||
{els}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function props(state) {
|
||||
return {
|
||||
lwd: state.get('lwd'), // last working directory
|
||||
cwd: state.get('cwd')
|
||||
}
|
||||
}
|
28
src/js/components/dialog.js
Normal file
@ -0,0 +1,28 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export default class Dialog extends Component {
|
||||
render() {
|
||||
let conditionalInput = this.props.input ? <input ref='input' /> : '';
|
||||
let buttons = this.props.buttons.map((button, i) => {
|
||||
return <button className={button.className + ' btn'} key={i}
|
||||
onClick={button.action.bind(this)}>
|
||||
{button.text}
|
||||
</button>;
|
||||
});
|
||||
|
||||
let className = this.props.active ? 'dialog active' : 'dialog';
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<p className='regular-medium'>{this.props.title}</p>
|
||||
<p className='light-medium'>{this.props.description}</p>
|
||||
|
||||
{conditionalInput}
|
||||
|
||||
<div className='foot'>
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
39
src/js/components/directory.js
Normal file
@ -0,0 +1,39 @@
|
||||
import React, { Component } from 'react';
|
||||
import changedir from 'actions/changedir';
|
||||
import { show } from 'actions/menu';
|
||||
import { active } from 'actions/file';
|
||||
import { MENU_WIDTH } from './menu';
|
||||
import store from 'store';
|
||||
|
||||
const MENU_TOP_SPACE = 20;
|
||||
|
||||
export default class Directory extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className='directory' ref='container'
|
||||
onClick={this.peek.bind(this)}
|
||||
onContextMenu={this.contextMenu.bind(this)}>
|
||||
<i></i>
|
||||
<p>{this.props.name}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
peek() {
|
||||
let file = store.getState().get('files')[this.props.index];
|
||||
|
||||
store.dispatch(changedir(file.path.slice(1) + file.name));
|
||||
}
|
||||
|
||||
contextMenu(e) {
|
||||
e.preventDefault();
|
||||
|
||||
let rect = React.findDOMNode(this.refs.container).getBoundingClientRect();
|
||||
let {x, y, width, height} = rect;
|
||||
|
||||
let left = x + width / 2 - MENU_WIDTH / 2,
|
||||
top = y + height / 2 + MENU_TOP_SPACE;
|
||||
store.dispatch(show('directoryMenu', left, top));
|
||||
store.dispatch(active(this.props.index));
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import File from './file';
|
||||
import Directory from './directory';
|
||||
|
||||
@connect(props)
|
||||
export default class FileList extends Component {
|
||||
@ -9,14 +10,18 @@ export default class FileList extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
let { cwd, files } = this.props;
|
||||
let { files } = this.props;
|
||||
|
||||
let els = files.map((file, index) => {
|
||||
return <File key={index} index={index} name={file.name} />;
|
||||
if (fileType(file) === 'File') {
|
||||
return <File key={index} index={index} name={file.name} />;
|
||||
} else {
|
||||
return <Directory key={index} index={index} name={file.name} />
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div><strong>cwd: {cwd}</strong>
|
||||
<div className='file-list'>
|
||||
{els}
|
||||
</div>
|
||||
);
|
||||
@ -25,7 +30,6 @@ export default class FileList extends Component {
|
||||
|
||||
function props(state) {
|
||||
return {
|
||||
cwd: state.get('cwd'),
|
||||
files: state.get('files')
|
||||
}
|
||||
}
|
||||
@ -36,3 +40,7 @@ async function getFiles(dir) {
|
||||
|
||||
return await root.getFilesAndDirectories();
|
||||
}
|
||||
|
||||
function fileType(file) {
|
||||
return Object.prototype.toString.call(file).slice(8, -1);
|
||||
}
|
||||
|
@ -1,20 +1,35 @@
|
||||
import React, { Component } from 'react';
|
||||
import { show } from 'actions/menu';
|
||||
import { active } from 'actions/file';
|
||||
import { MENU_WIDTH } from './menu';
|
||||
import store from 'store';
|
||||
import changedir from 'actions/changedir';
|
||||
|
||||
const MENU_TOP_SPACE = 20;
|
||||
|
||||
export default class File extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div onClick={this.peekInside.bind(this)}>
|
||||
<p>{this.props.index}. {this.props.name}</p>
|
||||
<div className='file' ref='container'
|
||||
onContextMenu={this.contextMenu.bind(this)}>
|
||||
<i></i>
|
||||
<p>{this.props.name}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
peekInside() {
|
||||
let file = store.getState().get('files')[this.props.index];
|
||||
contextMenu(e) {
|
||||
e.preventDefault();
|
||||
|
||||
console.log(file);
|
||||
store.dispatch(changedir(file.path.slice(1) + file.name));
|
||||
let rect = React.findDOMNode(this.refs.container).getBoundingClientRect();
|
||||
let {x, y, width, height} = rect;
|
||||
|
||||
let left = x + width / 2 - MENU_WIDTH / 2,
|
||||
top = y + height / 2 + MENU_TOP_SPACE;
|
||||
store.dispatch(show('fileMenu', left, top));
|
||||
store.dispatch(active(this.props.index));
|
||||
}
|
||||
}
|
||||
|
18
src/js/components/header.js
Normal file
@ -0,0 +1,18 @@
|
||||
import React, { Component } from 'react';
|
||||
import { toggle } from 'actions/navigation';
|
||||
import store from 'store';
|
||||
|
||||
export default class Header extends Component {
|
||||
render() {
|
||||
return (
|
||||
<header>
|
||||
<button className='drawer' onClick={this.toggleNavigation.bind(this)}></button>
|
||||
<h1 className='regular-medium'>Hawk</h1>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
toggleNavigation() {
|
||||
store.dispatch(toggle());
|
||||
}
|
||||
}
|
21
src/js/components/menu.js
Normal file
@ -0,0 +1,21 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export const MENU_WIDTH = 245;
|
||||
|
||||
export default class Menu extends Component {
|
||||
render() {
|
||||
let { items, active, style } = this.props;
|
||||
items = items || [];
|
||||
|
||||
let els = items.map((item, index) => {
|
||||
return <li key={index} onClick={item.action.bind(this)}>{item.name}</li>
|
||||
});
|
||||
let className = 'menu ' + (active ? 'active' : '');
|
||||
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<ul>{els}</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
43
src/js/components/navigation.js
Normal file
@ -0,0 +1,43 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { hide } from 'actions/navigation';
|
||||
|
||||
@connect(props)
|
||||
export default class Navigation extends Component {
|
||||
render() {
|
||||
return (
|
||||
<nav className={this.props.active ? 'active' : ''}>
|
||||
<i onClick={this.hide.bind(this)} />
|
||||
|
||||
<p>Filter</p>
|
||||
<ul>
|
||||
<li>Picture</li>
|
||||
<li>Video</li>
|
||||
<li>Audio</li>
|
||||
</ul>
|
||||
|
||||
<p>Tools</p>
|
||||
<ul>
|
||||
<li>FTP Browser</li>
|
||||
</ul>
|
||||
|
||||
<p>Preferences</p>
|
||||
<ul>
|
||||
<li>Show Hidden Files <input type='checkbox' /></li>
|
||||
<li>Show Directories First <input type='checkbox' /></li>
|
||||
<li>Advanced Preferences</li>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.props.dispatch(hide());
|
||||
}
|
||||
}
|
||||
|
||||
function props(store) {
|
||||
return {
|
||||
active: store.get('navigation')
|
||||
}
|
||||
}
|
@ -1,18 +1,46 @@
|
||||
import React, { Component } from 'react'
|
||||
import FileList from 'components/file-list';
|
||||
import Navigation from 'components/navigation';
|
||||
import Header from 'components/header';
|
||||
import Breadcrumb from 'components/breadcrumb';
|
||||
import Toolbar from 'components/toolbar';
|
||||
import Menu from 'components/menu';
|
||||
import Dialog from 'components/dialog';
|
||||
import { connect } from 'react-redux';
|
||||
import { hideAll } from 'actions/menu';
|
||||
|
||||
import changedir from 'actions/changedir';
|
||||
import store from 'store';
|
||||
|
||||
window.store = store;
|
||||
window.changedir = changedir;
|
||||
|
||||
let FileMenu = connect(state => state.get('fileMenu'))(Menu);
|
||||
let DirectoryMenu = connect(state => state.get('directoryMenu'))(Menu);
|
||||
|
||||
let RenameDialog = connect(state => state.get('renameDialog'))(Dialog);
|
||||
|
||||
export default class Root extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
Hawk!
|
||||
<div onTouchStart={this.touchStart.bind(this)}>
|
||||
<Header />
|
||||
<Breadcrumb />
|
||||
<Navigation />
|
||||
<FileList />
|
||||
<Toolbar />
|
||||
|
||||
<FileMenu />
|
||||
<DirectoryMenu />
|
||||
|
||||
<RenameDialog />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
touchStart(e) {
|
||||
if (!e.target.closest('.menu')) {
|
||||
store.dispatch(hideAll());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
26
src/js/components/toolbar.js
Normal file
@ -0,0 +1,26 @@
|
||||
import React, { Component } from 'react';
|
||||
import { create, share } from 'actions/file';
|
||||
import { toggle as toggleView, refresh } from 'actions/files-view';
|
||||
import { bind } from 'store';
|
||||
|
||||
export default class Toolbar extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className='toolbar'>
|
||||
<button className='icon-plus' onClick={this.newFile} />
|
||||
<button className='icon-view' onClick={bind(toggleView())} />
|
||||
<button className='icon-refresh' onClick={bind(refresh())} />
|
||||
<button className='icon-share' onClick={bind(share())} />
|
||||
<button className='icon-more' onClick={this.showMore} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
showMore() {
|
||||
|
||||
}
|
||||
|
||||
newFile() {
|
||||
|
||||
}
|
||||
}
|
48
src/js/dialogs.js
Normal file
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { hide, hideAll } from 'actions/dialog';
|
||||
import { rename, deleteFile } from 'actions/file';
|
||||
import store, { bind } from 'store';
|
||||
|
||||
export default {
|
||||
renameDialog: {
|
||||
title: 'Rename',
|
||||
description: 'Enter your desired new name',
|
||||
input: true,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
action: bind(hideAll())
|
||||
},
|
||||
{
|
||||
text: 'Rename',
|
||||
action() {
|
||||
let input = React.findDOMNode(this.refs.input);
|
||||
|
||||
let activeFile = store.getState().get('activeFile');
|
||||
this.props.dispatch(rename(activeFile, input.value))
|
||||
this.props.dispatch(hideAll());
|
||||
},
|
||||
className: 'success'
|
||||
}
|
||||
]
|
||||
},
|
||||
deleteDialog: {
|
||||
title: 'Delete',
|
||||
description: 'Are you sure you want to remove @activeFile.name?',
|
||||
buttons: [
|
||||
{
|
||||
text: 'No',
|
||||
action: bind(hideAll())
|
||||
},
|
||||
{
|
||||
text: 'Yes',
|
||||
action() {
|
||||
let activeFile = store.getState().get('activeFile');
|
||||
this.props.dispatch(deleteFile(activeFile));
|
||||
this.props.dispatch(hideAll());
|
||||
},
|
||||
className: 'success'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
27
src/js/menus.js
Normal file
@ -0,0 +1,27 @@
|
||||
import { hideAll } from 'actions/menu';
|
||||
import { show } from 'actions/dialog';
|
||||
import store from 'store';
|
||||
|
||||
const entryMenu = {
|
||||
items: [
|
||||
{
|
||||
name: 'Rename',
|
||||
action() {
|
||||
store.dispatch(hideAll());
|
||||
store.dispatch(show('renameDialog'));
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
action() {
|
||||
store.dispatch(hideAll());
|
||||
store.dispatch(show('deleteDialog'))
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export default {
|
||||
fileMenu: Object.assign({}, entryMenu),
|
||||
directoryMenu: Object.assign({}, entryMenu)
|
||||
}
|
9
src/js/reducers/active-file.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { ACTIVE_FILE } from 'actions/types';
|
||||
|
||||
export default function(state = -1, action) {
|
||||
if (action.type === ACTIVE_FILE) {
|
||||
return action.file;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
@ -1,10 +1,23 @@
|
||||
import Immutable from 'immutable';
|
||||
import cwd from './cwd';
|
||||
import lwd from './lwd';
|
||||
import files from './files';
|
||||
import navigation from './navigation';
|
||||
import activeFile from './active-file';
|
||||
import menu from './menu';
|
||||
import dialog from './dialog';
|
||||
|
||||
export default function(state = new Immutable.Map(), action) {
|
||||
console.log('action', action);
|
||||
return new Immutable.Map({
|
||||
lwd: lwd(state, action), // last working directory
|
||||
cwd: cwd(state.get('cwd'), action),
|
||||
files: files(state.get('files'), action)
|
||||
files: files(state.get('files'), action),
|
||||
activeFile: activeFile(state.get('activeFile'), action),
|
||||
navigation: navigation(state.get('navigation'), action),
|
||||
fileMenu: menu(state, action, 'fileMenu'),
|
||||
directoryMenu: menu(state, action, 'directoryMenu'),
|
||||
renameDialog: dialog(state, action, 'renameDialog'),
|
||||
deleteDialog: dialog(state, action, 'deleteDialog')
|
||||
});
|
||||
}
|
||||
|
@ -1,16 +1,22 @@
|
||||
import { CHANGE_DIRECTORY } from 'actions/types';
|
||||
import { CHANGE_DIRECTORY, REFRESH } from 'actions/types';
|
||||
import listFiles from 'actions/list-files';
|
||||
import { children } from 'api/files';
|
||||
import store from 'store';
|
||||
|
||||
export default function(state = '/', action) {
|
||||
switch (action.type) {
|
||||
case CHANGE_DIRECTORY:
|
||||
children(action.dir).then(files => {
|
||||
store.dispatch(listFiles(files));
|
||||
});
|
||||
return action.dir;
|
||||
default:
|
||||
return state;
|
||||
export default function(state = '', action) {
|
||||
if (action.type === CHANGE_DIRECTORY) {
|
||||
children(action.dir).then(files => {
|
||||
store.dispatch(listFiles(files));
|
||||
});
|
||||
return action.dir;
|
||||
}
|
||||
|
||||
if (action.type === REFRESH) {
|
||||
children(state).then(files => {
|
||||
store.dispatch(listFiles(files));
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
22
src/js/reducers/dialog.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { DIALOG } from 'actions/types';
|
||||
import Immutable from 'immutable';
|
||||
|
||||
export default function(state = new Immutable.Map({}), action, id) {
|
||||
if (action.type === DIALOG) {
|
||||
// action applied to all dialogs
|
||||
if (!action.id) {
|
||||
return Object.assign({}, state.get(id), {active: action.active});
|
||||
}
|
||||
|
||||
if (action.id !== id) return state.get(id);
|
||||
|
||||
let target = state.get(action.id);
|
||||
let active = action.active === 'toggle' ? !target.get('active') : action.active;
|
||||
|
||||
let style = Object.assign({}, state.style, {left: action.x, top: action.y});
|
||||
|
||||
return Object.assign({}, target, { style, active });
|
||||
} else {
|
||||
return state.get(id);
|
||||
}
|
||||
}
|
@ -1,10 +1,29 @@
|
||||
import { LIST_FILES } from 'actions/types';
|
||||
import { LIST_FILES, RENAME_FILE, DELETE_FILE } from 'actions/types';
|
||||
import { refresh } from 'actions/files-view';
|
||||
import { rename, sdcard } from 'api/files';
|
||||
|
||||
export default function(state = [], action) {
|
||||
switch (action.type) {
|
||||
case LIST_FILES:
|
||||
return action.files;
|
||||
default:
|
||||
return state;
|
||||
if (action.type === LIST_FILES) {
|
||||
return action.files;
|
||||
}
|
||||
|
||||
|
||||
if (action.type === RENAME_FILE) {
|
||||
let file = state[action.file];
|
||||
|
||||
rename(file, action.name).then(refresh);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
if (action.type === DELETE_FILE) {
|
||||
let file = state[action.file];
|
||||
|
||||
sdcard().delete((file.path || '') + '/' + file.name);
|
||||
let copy = state.slice(0);
|
||||
copy.splice(action.file, 1);
|
||||
return copy;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
8
src/js/reducers/lwd.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { CHANGE_DIRECTORY } from 'actions/types';
|
||||
|
||||
export default function(state = '', action) {
|
||||
if (action.type === CHANGE_DIRECTORY) {
|
||||
return state.get('cwd');
|
||||
}
|
||||
return state.get('lwd');
|
||||
}
|
22
src/js/reducers/menu.js
Normal file
@ -0,0 +1,22 @@
|
||||
import { MENU } from 'actions/types';
|
||||
import Immutable from 'immutable';
|
||||
|
||||
export default function(state = new Immutable.Map({}), action, id) {
|
||||
if (action.type === MENU) {
|
||||
// action applied to all menus
|
||||
if (!action.id) {
|
||||
return Object.assign({}, state.get(id), {active: action.active});
|
||||
}
|
||||
|
||||
if (action.id !== id) return state.get(id);
|
||||
|
||||
let target = state.get(action.id);
|
||||
let active = action.active === 'toggle' ? !target.get('active') : action.active;
|
||||
|
||||
let style = Object.assign({}, state.style, {left: action.x, top: action.y});
|
||||
|
||||
return Object.assign({}, target, { style, active });
|
||||
} else {
|
||||
return state.get(id);
|
||||
}
|
||||
}
|
9
src/js/reducers/navigation.js
Normal file
@ -0,0 +1,9 @@
|
||||
import { NAVIGATION, TOGGLE } from 'actions/types';
|
||||
|
||||
export default function(state = false, action) {
|
||||
if (action.type === NAVIGATION) {
|
||||
return action.active === TOGGLE ? !state : action.active;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
@ -2,13 +2,19 @@ import { createStore } from 'redux';
|
||||
import reducers from 'reducers/all';
|
||||
import changedir from 'actions/changedir';
|
||||
import Immutable from 'immutable';
|
||||
import menus from './menus';
|
||||
import dialogs from './dialogs';
|
||||
|
||||
const DEFAULT = new Immutable.Map({
|
||||
dir: '/',
|
||||
const DEFAULT = new Immutable.Map(Object.assign({
|
||||
dir: '',
|
||||
files: []
|
||||
});
|
||||
}, dialogs, menus));
|
||||
|
||||
let store = createStore(reducers, DEFAULT);
|
||||
store.dispatch(changedir(DEFAULT.dir));
|
||||
store.dispatch(changedir(DEFAULT.get('dir')));
|
||||
|
||||
export function bind(action) {
|
||||
return () => store.dispatch(action);
|
||||
}
|
||||
|
||||
export default store;
|
||||
|
3
src/js/utils.js
Normal file
@ -0,0 +1,3 @@
|
||||
export function type(obj) {
|
||||
return Object.prototype.toString.call(obj).slice(8, -1);
|
||||
}
|
8
src/less/components/all.less
Normal file
@ -0,0 +1,8 @@
|
||||
@import 'entries';
|
||||
@import 'header';
|
||||
@import 'menu';
|
||||
@import 'navigation';
|
||||
@import 'toolbar';
|
||||
@import 'breadcrumb';
|
||||
@import 'file-list';
|
||||
@import 'dialog';
|
26
src/less/components/breadcrumb.less
Normal file
@ -0,0 +1,26 @@
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
|
||||
width: 100vw;
|
||||
height: 3.5rem;
|
||||
|
||||
padding: 8px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
.light-medium;
|
||||
|
||||
background: @light-gray;
|
||||
|
||||
border-bottom: 1px solid @dark-transparent;
|
||||
|
||||
i {
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
span.history {
|
||||
color: @overlay;
|
||||
}
|
||||
}
|
62
src/less/components/dialog.less
Normal file
@ -0,0 +1,62 @@
|
||||
.dialog {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
|
||||
width: 335px;
|
||||
height: auto;
|
||||
|
||||
padding: 1.5rem 1.7rem;
|
||||
|
||||
background: white;
|
||||
|
||||
.shadow-bottom;
|
||||
|
||||
z-index: 3;
|
||||
|
||||
transition: opacity 0.5s ease;
|
||||
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
p:first-of-type {
|
||||
margin: 0 0 2.5rem;
|
||||
}
|
||||
p {
|
||||
margin: 0 0 2rem;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.foot {
|
||||
display: flex;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
|
||||
margin: 0 8px;
|
||||
|
||||
&:first-of-type {
|
||||
margin-left: 0;
|
||||
}
|
||||
&:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
src/less/components/entries.less
Normal file
@ -0,0 +1,26 @@
|
||||
.file, .directory {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
|
||||
padding: 1.4rem;
|
||||
|
||||
width: 100%;
|
||||
.light-big;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
border-bottom: 1px solid @dark-separator;
|
||||
|
||||
i {
|
||||
margin-right: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.directory i {
|
||||
.icon-directory;
|
||||
}
|
||||
|
||||
.file i {
|
||||
.icon-file;
|
||||
}
|
6
src/less/components/file-list.less
Normal file
@ -0,0 +1,6 @@
|
||||
.file-list {
|
||||
height: ~'calc(100vh - 13.5rem)';
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
44
src/less/components/header.less
Normal file
@ -0,0 +1,44 @@
|
||||
header {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 5rem;
|
||||
|
||||
background: @dark;
|
||||
color: white;
|
||||
|
||||
.shadow;
|
||||
|
||||
h1 {
|
||||
margin-left: -3rem;
|
||||
}
|
||||
|
||||
button {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
width: 8rem;
|
||||
height: 2rem;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
|
||||
width: 2rem;
|
||||
height: 4px;
|
||||
|
||||
margin-top: -9px;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
background: @overlay;
|
||||
|
||||
box-shadow: 0 7px 0 @overlay,
|
||||
0 14px 0 @overlay;
|
||||
}
|
||||
}
|
||||
}
|
43
src/less/components/menu.less
Normal file
@ -0,0 +1,43 @@
|
||||
.menu {
|
||||
width: 24.5rem;
|
||||
|
||||
position: fixed;
|
||||
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
background: white;
|
||||
border-radius: @radius;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 0.5s ease;
|
||||
|
||||
.shadow-bottom;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 0;
|
||||
|
||||
padding: 1.3rem 8px;
|
||||
|
||||
border-bottom: 1px solid @dark-separator;
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
77
src/less/components/navigation.less
Normal file
@ -0,0 +1,77 @@
|
||||
nav {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
position: fixed;
|
||||
left: -70vw;
|
||||
top: 0;
|
||||
|
||||
width: 70vw;
|
||||
height: 100vh;
|
||||
|
||||
background: @dark;
|
||||
color: white;
|
||||
|
||||
box-shadow: 1px 0 5px @dark-transparent;
|
||||
z-index: 6;
|
||||
|
||||
transition: left 0.5s ease;
|
||||
|
||||
&.active {
|
||||
left: 0;
|
||||
|
||||
i {
|
||||
pointer-events: all;
|
||||
opacity: 0.99;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin-left: 1.6rem;
|
||||
|
||||
.regular-medium;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
.light-medium;
|
||||
|
||||
padding: 1rem 0 1rem 3rem;
|
||||
|
||||
border-bottom: 1px solid @bright-separator;
|
||||
|
||||
&:first-of-type {
|
||||
padding-top: 0;
|
||||
}
|
||||
&:last-of-type {
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
display: block;
|
||||
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
background: rgba(0, 0, 0, 0.55);
|
||||
|
||||
opacity: 0;
|
||||
|
||||
z-index: -1;
|
||||
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
}
|
14
src/less/components/toolbar.less
Normal file
@ -0,0 +1,14 @@
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
align-self: flex-end;
|
||||
|
||||
width: 100vw;
|
||||
height: 5rem;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
background: @light-gray;
|
||||
}
|
52
src/less/icons.less
Normal file
@ -0,0 +1,52 @@
|
||||
.icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon-directory {
|
||||
.icon;
|
||||
background: url(/img/Directory.svg) no-repeat;
|
||||
width: 36px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.icon-file {
|
||||
.icon;
|
||||
background: url(/img/File.svg) no-repeat;
|
||||
width: 30px;
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.icon-plus {
|
||||
.icon;
|
||||
background: url(/img/Plus.svg) no-repeat;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.icon-view {
|
||||
.icon;
|
||||
background: url(/img/View.svg) no-repeat;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.icon-refresh {
|
||||
.icon;
|
||||
background: url(/img/Refresh.svg) no-repeat;
|
||||
width: 26px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.icon-share {
|
||||
.icon;
|
||||
background: url(/img/Share.svg) no-repeat;
|
||||
width: 25px;
|
||||
height: 27px;
|
||||
}
|
||||
|
||||
.icon-more {
|
||||
.icon;
|
||||
background: url(/img/More.svg) no-repeat;
|
||||
width: 6px;
|
||||
height: 24px;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
@import 'variables';
|
||||
@import 'icons';
|
||||
@import 'styles/all';
|
||||
@import 'components/all';
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
|
||||
font-family: Fira Sans;
|
||||
font-weight: regular;
|
||||
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 10px;
|
||||
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 1.6rem;
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-flow: column;
|
||||
}
|
||||
|
4
src/less/styles/all.less
Normal file
@ -0,0 +1,4 @@
|
||||
@import 'texts';
|
||||
@import 'shadows';
|
||||
@import 'buttons';
|
||||
@import 'forms';
|
21
src/less/styles/buttons.less
Normal file
@ -0,0 +1,21 @@
|
||||
button {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 6px 3rem;
|
||||
|
||||
border: 1px solid @gray;
|
||||
|
||||
border-radius: @radius;
|
||||
|
||||
.light-medium;
|
||||
|
||||
background: @light-gray;
|
||||
color: @dark;
|
||||
|
||||
&.success {
|
||||
background: @success;
|
||||
}
|
||||
}
|
15
src/less/styles/forms.less
Normal file
@ -0,0 +1,15 @@
|
||||
input {
|
||||
border: 1px solid @gray;
|
||||
|
||||
background: @light-gray;
|
||||
|
||||
border-radius: 4px;
|
||||
|
||||
height: 32px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
padding: 5px 1rem;
|
||||
|
||||
.light-medium;
|
||||
}
|
7
src/less/styles/shadows.less
Normal file
@ -0,0 +1,7 @@
|
||||
.shadow-bottom {
|
||||
box-shadow: 0 1px 2px @dark-transparent;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
box-shadow: 0 0 4px @dark-transparent;
|
||||
}
|
31
src/less/styles/texts.less
Normal file
@ -0,0 +1,31 @@
|
||||
.regular-medium {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.regular-medium {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.light-medium, .light-big, .light-small {
|
||||
font-weight: @light;
|
||||
}
|
||||
|
||||
.light-medium {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.light-big {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.light-small {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.thin-small {
|
||||
font-weight: @thin;
|
||||
}
|
||||
|
||||
.thin-small {
|
||||
font-size: 1.4rem;
|
||||
}
|
@ -1 +1,23 @@
|
||||
@
|
||||
@dark: #39393A;
|
||||
@green: #5EBEC2;
|
||||
@overlay: #9B9B93;
|
||||
@light-gray: #F8F8F8;
|
||||
@gray: #F0F0F0;
|
||||
@background: #FAFAFA;
|
||||
@blue: #63B0CD;
|
||||
|
||||
@success: #B8E986;
|
||||
|
||||
@bright-separator: rgba(255, 255, 255, 0.1);
|
||||
@dark-separator: rgba(0, 0, 0, 0.1);
|
||||
|
||||
@dark-transparent: rgba(0, 0, 0, 0.2);
|
||||
@bright-transparent: rgba(0, 0, 0, 0.2);
|
||||
|
||||
|
||||
// Font Weights
|
||||
@regular: normal;
|
||||
@light: 200;
|
||||
@thin: 100;
|
||||
|
||||
@radius: 4px;
|
||||
|
@ -15,10 +15,10 @@
|
||||
},
|
||||
"type": "privileged",
|
||||
"permissions": {
|
||||
"device-storage:sdcard": {"access": "readwrite"},
|
||||
"device-storage:videos": {"access": "readwrite"},
|
||||
"device-storage:pictures": {"access": "readwrite"},
|
||||
"device-storage:music": {"access": "readwrite"},
|
||||
"device-storage:sdcard": {"access": "readwrite"},
|
||||
"device-storage:apps": {"access": "readwrite"},
|
||||
"webapps-manage": {}
|
||||
},
|
||||
|