Compare commits

...

65 Commits

Author SHA1 Message Date
itdominator d6a3187f2c Merge pull request 'Major refactoring, cleanup' (#3) from develop into master
Reviewed-on: #3
2023-02-10 02:42:49 +00:00
itdominator 93706aeed6 Major refactoring, cleanup 2023-02-09 20:40:58 -06:00
itdominator f517dca01b Merge pull request 'develop' (#2) from develop into master
Reviewed-on: #2
2023-02-07 04:34:26 +00:00
itdominator 3cfd521b60 Moved config items to settings 2023-02-06 22:27:03 -06:00
itdominator 627f30789d Added background manager 2023-02-06 21:36:49 -06:00
itdominator ae0486f020 Updated to Bootstrap 5.3, added more playlist logic, cleanup 2023-02-06 17:49:53 -06:00
itdominator 20800fc750 Added playlist like playing functionality 2023-02-05 17:44:03 -06:00
itdominator bd8e349110 Bootstrap updates 2022-10-08 19:57:45 -05:00
itdominator 0aeb071215 Added simplistic trailer detection 2022-09-05 15:34:18 -05:00
itdominator 0c945c72b0 Updated bootstrap and theme, ref images 2022-06-17 00:27:27 -05:00
itdominator 22c437e8ed addedd rtsp streamer inferastructure, applied fstrings, updated method names to align with standards in project 2022-06-13 21:51:51 -05:00
itdominator b30a8f4b44 added size info, new sort logic 2022-03-18 17:25:01 -05:00
itdominator 978d4de212 Updated video container responsiveness 2021-11-12 01:42:08 -06:00
itdominator 57d52fcddf Added cover art and background art scraper 2021-11-11 23:32:59 -06:00
itdominator a937d1f313 Video cntrl udates 2021-10-14 01:24:48 -05:00
itdominator cec3ee0993 Merge pull request 'Converted to python flask' (#1) from develop into master
Reviewed-on: #1
2021-10-03 19:49:43 +00:00
itdominator ffb0459cfe theme fixes; context menu changes 2021-10-03 14:44:29 -05:00
itdominator be1c12ee86 Updated backgrounds 2021-10-03 03:58:46 -05:00
itdominator 927be36d9f Updated setting pane; added contect menu 2021-10-03 03:54:20 -05:00
itdominator 633cb5b955 Bumped to Bootstrap 5 2021-09-28 17:37:20 -05:00
itdominator f61789ffb8 Updated to latest working structure 2021-09-03 23:51:17 -05:00
itdominator 92c7a76a63 Updated import 2021-07-27 17:50:04 -05:00
itdominator a7d56bae10 Updated view logic 2021-07-27 17:46:56 -05:00
maximstewart 4dafc328a5 Updated open options 2021-02-21 14:20:45 -06:00
maximstewart 86b652e8d7 Updated open options 2021-02-21 14:05:03 -06:00
maximstewart 022046cd3f Updated message timeouts 2021-02-17 21:27:34 -06:00
maximstewart 94017f61ad changed controller setup 2021-02-17 19:28:53 -06:00
maximstewart d3a30067fa Small js changes 2021-02-13 05:33:17 -06:00
maximstewart 73192d136f video keybindings, css changes 2021-02-10 21:16:12 -06:00
maximstewart 649a88cfcd css change 2021-02-10 18:10:02 -06:00
maximstewart 2652810ba1 README update 2021-02-10 13:24:02 -06:00
maximstewart 0633f25691 Updated html 2021-02-10 13:19:39 -06:00
maximstewart 7ac5d7c496 Updated requirements and html 2021-02-10 02:10:37 -06:00
maximstewart e26495882f Font structure updates 2021-02-09 23:30:42 -06:00
maximstewart 4192726cf6 moving fonts, fixing scroll to top 2021-02-09 22:47:51 -06:00
maximstewart 92c94f8ae6 fixing faves icon' 2021-02-09 22:15:32 -06:00
maximstewart 5ad243f475 Updated files view and faves listing. 2021-02-09 21:18:38 -06:00
maximstewart 781d2ece5a Added modal logic, fixed favorites logic 2021-02-09 17:14:32 -06:00
maximstewart e7d588691e Template updates 2021-02-08 19:50:06 -06:00
maximstewart 0ebdc31f19 Event updates, list updates, bacend changes 2021-02-08 18:36:55 -06:00
maximstewart 7fedf2f367 Got thumbnails and files list generating 2021-02-08 14:04:32 -06:00
maximstewart 664c288fea Updated html and css 2021-02-08 02:02:14 -06:00
maximstewart 16ae96b9cf add event handlers 2021-02-08 01:15:38 -06:00
maximstewart 2a45797c82 Quasi functioning lists 2021-02-08 00:30:44 -06:00
maximstewart 340b9a54fd Files listing work' 2021-02-07 23:31:20 -06:00
maximstewart a477654021 Creat react files list 2021-02-07 22:49:38 -06:00
maximstewart 47a03155d5 javascript updates 2021-02-07 20:07:13 -06:00
maximstewart fb429cd1b6 fm fixes 2021-02-07 18:49:29 -06:00
maximstewart e6531f2ce1 Restructured fm settings 2021-02-07 18:44:35 -06:00
maximstewart 6c8317e598 impliminting actual listing structure 2021-02-07 16:52:08 -06:00
maximstewart f6f7cfda58 file manager and config updates, css changes, etc 2021-02-07 00:43:36 -06:00
maximstewart 840ffc8255 Structuring card css 2021-02-06 03:44:11 -06:00
maximstewart 0ba35f8f1c Further file manager work 2021-02-06 02:34:44 -06:00
maximstewart 011a2ec370 Removing pid 2021-02-06 01:41:18 -06:00
maximstewart 786687e0f1 Starting to add file management logic 2021-02-06 01:38:15 -06:00
maximstewart 339c031d28 Updated ignore file 2021-02-05 23:00:32 -06:00
maximstewart e9e47f68a5 Major refactor start 2021-02-05 22:57:46 -06:00
maximstewart a7f9028ce7 Major refactor start 2021-02-05 22:52:46 -06:00
maximstewart 59c2d0860f New player controls, css changes, big fixes 2021-01-25 01:15:46 -06:00
maximstewart c7e76533ec Updated imports 2020-12-21 13:15:00 -06:00
maximstewart a4755dd281 player updates, layout updates, base oidc added 2020-12-21 13:02:34 -06:00
Maxim Stewart d833ec437d Updated background logic 2020-10-25 14:59:15 -05:00
Maxim Stewart 9318b7768a Fixed favorites... 2020-10-11 17:48:06 -05:00
Maxim Stewart fc21329546 Updated README and added back. 2020-10-11 16:42:36 -05:00
Maxim Stewart f65f809fb9 Initial push of flask version. 2020-10-11 16:30:11 -05:00
2061 changed files with 29600 additions and 2068 deletions

143
.gitignore vendored Normal file
View File

@ -0,0 +1,143 @@
*.db
*.pyc
app.pid
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1019 KiB

View File

@ -2,22 +2,17 @@
WebFM is a media and file viewer aspiring to become a full fledged file manager in the browser. WebFM is a media and file viewer aspiring to become a full fledged file manager in the browser.
# Usage # Usage
1. Install php7, php-sqlite3, and ffmpeg on the system this will be on. 1. Install python, sqlite3, and ffmpeg on the system this will be on.
2. Use php -S 0.0.0.0:yourDesiredPort 3. Use ufw or gufw to open the port on your computer to the local network.
3. Use ufw or gufw to open the port on your computer to the network. 4. Use hosts file (or other methods) to redirect webfm.com and ssoapps.com to local app.
4. Place files or start uploading some to the folders. 5. Update client_secrets.json > 'client_secret' field with your Keycloak key. (Current one was local, not public, and has been expired)
5. Double click thumbnails and container outlines to open files. 6. Place files or start uploading some to the folders.
6. Double click the text name to change the file's or folder's name and press enter to set it. 7. Place an image such as a jpg, png, or gif labeled "000.itsExtension" in a directory and the viewer will use it as the background image for that folder/directory.
7. Right-click to get context menu options. 7. Password protect folder based on core/utils/shellfm/windows/Settings.py file settings.
8. Place an image such as a jpg, png, or gif labeled "000.itsExtension" in a directory then the viewer will use it as the background image for that folder/directory. 8. Save paths to favorites list for quick access.
9. Password protect folder based on resources/php/config.php file setting.
10. Save paths to favorites list for quick access.
Notes: Notes:
1. The provided folders except "resources" are optional. You can add and remove them as you please. n/a
2. The media and image pane can be moved by dragging from the transparentish bar that has the close button and other controls.
3. Edit the resources/php/config.php file and put your own programs there.
4. Edit your php.ini file "upload_max_filesize" and "post_max_size" to be higher to upload larger files.
# TO-DO # TO-DO
1. Allow for move and copy. 1. Allow for move and copy.
@ -25,9 +20,8 @@ Notes:
# Images # Images
![1 Home](Images/pic1.png) ![1 Videos List](images/pic1.png)
![2 Images Listed](Images/pic2.png) ![2 Video Playing](images/pic2.png)
![3 Videos Listed](Images/pic3.png) ![3 Images List](images/pic3.png)
![4 Image Open](Images/pic4.png) ![4 Context menu](images/pic4.png)
![5 Image Open And Video Playing](Images/pic5.png) ![5 Settings Pane With Upload And Create Functionality](images/pic5.png)
![6 Alternate Background](Images/pic6.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

40
create_venv.sh Executable file
View File

@ -0,0 +1,40 @@
#!/bin/bash
. CONFIG.sh
# set -o xtrace ## To debug scripts
# set -o errexit ## To exit on error
# set -o errunset ## To exit if a variable is referenced but not set
function main() {
rm -rf venv/
clear
python -m venv venv/
sleep 2
source "./venv/bin/activate"
ANSR="-1"
while [[ $ANSR != "0" ]] && [[ $ANSR != "1" ]] && [[ $ANSR != "2" ]]; do
clear
menu_mesage
read -p "--> : " ANSR
done
case $ANSR in
"1" ) pip install -r linux-requirements.txt;;
"2" ) pip install -r windows-requirements.txt;;
"0" ) exit;;
* ) echo "Don't know how you got here but that's a bad sign...";;
esac
}
function menu_mesage() {
echo "NOTE: Make sure to have Python 3 installed!"
echo -e "\nWhat do you want to do?"
echo -e "\t1) Generate Linux/Mac supported venv. (Installs Repuirements)"
echo -e "\t2) Generate Windows supported venv. (Installs Repuirements)"
echo -e "\t0) EXIT"
}
main $@;

View File

@ -1 +0,0 @@
LOL...Not really!

BIN
images/pic1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
images/pic2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
images/pic3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
images/pic4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

BIN
images/pic5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

View File

@ -1,120 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta content="" charset="utf-8"/>
<title>Web File Manager</title>
<link type="text/css" rel="stylesheet" href="resources/css/base.css"/>
<link type="text/css" rel="stylesheet" href="resources/css/main.css"/>
<link rel="shortcut icon" type="image/png" href="favicon.png"/>
</head>
<body onload="onloadSetBG(); getDir('/')" contextmenu="menu">
<!-- Background -->
<img id="bg" />
<video id="video" src="" controls >
</video>
<!-- Controls -->
<!-- Create the menu -->
<menu type="context" id="menu">
<menuitem label="Home Directory" onclick="clearDirCookie()"></menuitem>
<menuitem label="Show Server Messages" onclick="tgglElmView('serverMsgView')"></menuitem>
<menuitem label="Clear Upload List" onclick="clearDlList()"></menuitem>
<menuitem label="Download" onclick="downloadItem()"></menuitem>
<menuitem label="Delete" onclick="deleteItem()"></menuitem>
</menu>
<!-- Uploader -->
<h2 id="controls">
<button type="button" title="Other Options" onclick="tgglElmView('popOutControls')">&#9881;</button>
<button type="button" title="Refresh" onclick="getDir('./')">&#8635;</button>
<button type="button" title="Back" onclick="getDir('../')">&lArr;</button>
<input type="text" placeholder="Search..." onkeyup="searchPage(this)" name="" value="">
<button type="button" onclick="clearSearch()" title="Clears search..." >Clear Search</button>
<button onclick="getFavesList(); tgglElmView('favesList')">Faves List &#8597;</button>
<button type="button" onclick="lockFolders()" title="Lock unlocked folders..." >Lock Unlocked Folders</button>
<br/>
<button type="button" id="faves" onclick="faveManager(this)" title="Add/Delete from favorites..." >&#9734;</button>
Path:<span id="path"></span>
</h2>
<div id="popOutControls" style="display:none;">
<center>
<form>
<input class="ulFile" type="file" title="files To Upload" name="filesToUpload[]" data-multiple-caption="{count} files selected" multiple />
<input type="button" onclick="uploadFiles()" name="UploadFiles" title="Upload File(s)" value="Upload File(s)" />
<input type="reset" title="Clear" id="CLEARBTTN" value="Clear" style="display:none;">
<input type="text" id="DIRPATHUL" name="DIRPATHUL" value="">
</form>
<br/>
<input type="text" id="NewItem" value=""/>
<input type="button" value="New Dir" onclick="createItem('dir')"/>
<input type="button" value="New File" onclick="createItem('file')"/>
<input type="button" value="Show Server Messages" onclick="tgglElmView('serverMsgView')"/>
<br/>
<input id="MergeType" type="checkbox" onchange="getDir('./')" />
<label for="MergeType">Show seassons in same list.</label>
</center>
</div>
<!-- Dynamic content targets -->
<ul id="favesList" style="display: none;"> </ul>
<ul id="dynUl"></ul>
<!-- Uploader processor -->
<div id="serverMsgView" style="display:none;"> </div>
<!-- Templates -->
<template id="dirTemplate">
<li class="dirStyle" tabindex="1">
<img id="dirID" class="systemIcon" src="" />
<input id="titleID" class="dirTitle" type="text" value="" readonly="true" />
</li>
</template>
<template id="vidTemplate">
<li id="movieID" class="movieStyle" tabindex="1" style="background-image: url('')">
<span class="popOutBttnInner" title="Open In Local Program">&#8765;</span>
<input id="titleID" class="movieTitle" type="text" value="" readonly="true" />
</li>
</template>
<template id="imgTemplate">
<img id="imageID" class="iconImg" src="" alt="">
</template>
<template id="filTemplate">
<li class="fileStyle">
<img id="fileID" class="systemIcon" src="" />
<input id="titleID" class="fileTitle" type="text" value="" readonly="true" />
</li>
</template>
<script type="text/javascript">
if (window.self !== window.top) {
setTimeout(function () {
let elm = document.getElementById("bg");
elm.parentElement.removeChild(elm);
// Stylesheet for iframe views
var link = document.createElement("link");
link.href = "resources/css/iframe.css";
link.type = "text/css";
link.rel = "stylesheet";
document.getElementsByTagName("head")[0].appendChild(link);
}, 500);
}
</script>
<script type="text/javascript" src="resources/js/favorites.js" charset="utf-8"></script>
<script type="text/javascript" src="resources/js/passwordFieldInsert.js" charset="utf-8"></script>
<script type="text/javascript" src="resources/js/cookieHandler.js" charset="utf-8"></script>
<script type="text/javascript" src="resources/js/jsonParser.js" charset="utf-8"></script>
<script type="text/javascript" src="resources/js/ajax.js" charset="utf-8"></script>
<script type="text/javascript" src="resources/js/uiActions.js" charset="utf-8"></script>
<script type="text/javascript" src="resources/js/filesystemActions.js" charset="utf-8"></script>
<script type="text/javascript" src="resources/js/uiEvents.js" charset="utf-8"></script>
</body>
</html>

36
linux-requirements.txt Normal file
View File

@ -0,0 +1,36 @@
bcrypt==4.0.1
certifi==2022.12.7
charset-normalizer==3.0.1
click==7.1.2
dnspython==1.16.0
email-validator==1.1.2
eventlet==0.30.1
Flask==1.1.2
Flask-Bcrypt==0.7.1
Flask-Login==0.5.0
flask-oidc==1.4.0
Flask-SQLAlchemy==2.4.4
Flask-Uploads==0.2.1
Flask-WTF==0.14.3
greenlet==1.0.0
gunicorn==20.0.4
httplib2==0.19.0
idna==3.4
itsdangerous==1.1.0
Jinja2==2.11.3
MarkupSafe==1.1.1
oauth2client==4.1.3
Pillow==9.4.0
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycairo==1.23.0
PyGObject==3.42.2
pyparsing==2.4.7
pyxdg==0.28
requests==2.28.2
rsa==4.7
six==1.15.0
SQLAlchemy==1.3.23
urllib3==1.26.14
Werkzeug==1.0.1
WTForms==2.3.3

View File

@ -1,8 +0,0 @@
html {
margin: 0em;
padding: 0em;
}
ol, ul, li {
list-style: none;
}

View File

@ -1,4 +0,0 @@
#controls, #fullPathHeader, #dynDiv,
.errorStyling, .dirStyle, .movieStyle, .fileStyle {
background-color: rgba(0,0,0,0.2);
}

View File

@ -1,304 +0,0 @@
/* IDs */
#DIRPATHUL {
display: none;
width: 1px;
height: 1px;
}
#video,
#bg {
position: fixed;
top: 0%;
left: 0%;
width: 100%;
height: 100%;
z-index: -999;
}
#video,
#bg img {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: -999;
}
#video {
position: fixed;
display: none;
background-color: rgba(0, 0, 0, 1);
}
#controls, #dynUl,
.errorStyling, .dirStyle, .movieStyle, .fileStyle {
display: block;
width: 100%;
height: auto;
overflow: auto;
padding-bottom: 0.5em;
color: #ffffff;
text-align: center;
font-size: 1.2em;
background-color: rgba(0,0,0,0.64);
}
#favesList {
border-style: solid;
border-color: rgba(0, 0, 0, 0.5);
border-width: 0.2em;
background-color: rgba(7, 150, 159, 0.8);
position: fixed;
font-size: 2em;
overflow-x: auto;
overflow-y: scroll;
padding: 1.5em;
max-height: 632px;
color: #ffffff;
z-index: 888;
}
#favesList > li:hover {
cursor: pointer;
background-color: rgba(92, 199, 35, 0.8);
padding-left: 1em;
padding-right: 1em;
}
#controls {
display: block;
position: fixed;
z-index: 999;
top: 0em;
}
#dynUl {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(18em, 1fr));
grid-column-gap: 1em;
grid-row-gap: 1em;
margin: 5em auto;
width: 85%;
padding: 2em;
}
#imgView, #imgArea, #fileView {
width: 800px;
height: 600px;
}
#imgView, #fileView {
position: fixed;
bottom: 0em;
z-index: 100;
border-style: solid;
border-color: rgb(114,184,199);
}
#fileView {
display: block;
overflow: auto;
}
#fileViewInner {
position: sticky;
display: inline;
width: 100%;
height: 500px;
}
#imgArea {
width: 800px;
height: 600px;
overflow-y: scroll;
}
#imgView {
overflow: hidden;
left: 15em;
}
#NewItem {
background-color: #ffffff;
color: #000000;
text-align: center;
}
#popOutControls {
position: fixed;
top: 15%;
width: 99%;
height: 15em;
padding-top: 6em;
opacity: 0.94;
background: radial-gradient(circle,#3f3f3f,#000000);
color: #ffffff;
text-align: center;
z-index: 999;
}
#serverMsgView {
position: fixed;
bottom: 0em;
height: 5em;
overflow-y: scroll;
width: 100%;
background-color: rgba(0,0,0,0.64);
z-index: 999;
}
#searchField {
text-align: center;
}
#searchField:focus {
height: 2em;
border-style: solid;
border-width: thin;
border-color: rgba(55, 204, 209, 1);
}
/* Classes */
.imgViewImg {
width: inherit;
height: auto;
}
.dirStyle { background-color: rgba(0, 0, 0, 0.56); }
.movieStyle, .fileStyle { background-color: rgba(101, 101, 101, 0.56); }
.movieStyle {
min-height: 6.5em;
overflow: hidden;
background-repeat: no-repeat;
background-size: 100% 100%;
}
.videoInputField {
width: 100%;
margin-top: 5.5em;
background-color: rgba(0, 0, 0, 0.64);
color: rgb(255, 255, 255);
text-align: center;
border-top: 1px solid rgb(255, 255, 255);
border-bottom: 1px solid rgb(255, 255, 255);
text-overflow: ellipsis;
}
.dirStyle:hover, .movieStyle:hover, .fileStyle:hover {
background-color: rgba(0, 141, 166, 0.56);
cursor: pointer;
box-shadow: 0px 0px 15px rgb(114,184,199);
border-radius: 0.5em;
}
.dirStyle:focus, .movieStyle:focus, .fileStyle:focus {
background-color: rgba(0, 139, 35, 0.76);
cursor: pointer;
box-shadow: 0px 0px 25px rgb(114, 199, 120);
border-radius: 0.5em;
}
.dirTitle, .fileTitle, .movieTitle {
white-space: nowrap;
text-overflow: ellipsis;
text-align: center;
overflow: hidden;
border-style: none;
font-size: 75%;
}
.dirTitle, .fileTitle {
width: auto;
background-color: #00000000;
color: #ffffff;
}
.movieTitle {
width: 18em;
background-color: #ffffff00;
color: #ffffff;
}
.thumbnail {
width: 12em;
height: 6.5em;
}
.systemIcon {
width: 2em;
height: auto;
}
.iconImg {
width: 18em;
height: 12em;
margin: 1em;
}
.popOutBttn, .closeBttn {
float: right;
z-index: 2;
width: 4em;
height: 4em;
text-align: center;
vertical-align: middle;
line-height: 4em; /* the same as your div height */
background-color: rgba(0,0,0, 0.85);
color: rgb(255,255,255);
border-style:solid;
border-color: rgb(255,255,255);
}
.popOutBttnInner {
float: right;
z-index: 2;
width: 2em;
height: 2em;
text-align: center;
background-color: rgba(0,0,0, 0.85);
color: rgb(255,255,255);
border-style:solid;
border-color: rgb(255,255,255);
}
.completionBar {
float:left;
clear:left;
height: 0.1em;
background-color: rgba(25, 125, 10, 1.0);
}
/* Hover events */
.dirTitle:hover,
.iconImg:hover,
.closeBttn:hover,
.popOutBttnInner:hover,
.popOutBttn:hover {
cursor: pointer;
}
.popOutBttnInner:hover,
.popOutBttn:hover,
.closeBttn:hover {
background-color: rgba(255,255,255, 0.85);
color: #000000;
border-color: #000000;
}
/* Messages coloring */
.error, .warnning, .success {
float: left;
clear: both;
}
.error { color: rgb(255, 0, 0); }
.warning { color: rgb(255, 168, 0); }
.success { color: rgb(136, 204, 39); }

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -1 +0,0 @@
Place Holder File

View File

@ -1,54 +0,0 @@
// SSE events if supported
if(typeof(EventSource) !== "undefined") {
let source = new EventSource("resources/php/sse.php");
source.onmessage = (event) => {
if (event.data === "updateListing") {
getDir("./");
}
};
} else {
console.log("SSE Not Supported In Browser...");
}
const getFavesList = () => {
doAjax("resources/php/dbController.php", "getTabs=true");
}
const doAjax = async (actionPath, data) => {
let xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
// Send the returned data to further process
if (this.responseText != null) {
handleJSONReturnData(JSON.parse(this.responseText));
} else {
document.getElementById('dynUl').innerHTML =
"<p class=\"error\" style=\"width:100%;text-align:center;\"> "
+ "No content returned. Check the folder path.</p>";
}
}
};
xhttp.open("POST", actionPath, true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.overrideMimeType('application/json'); // Force return to be JSON
xhttp.send(data);
}
const fileUploader = (data) => {
let xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
// Send the returned data to further process
if (this.responseXML != null) {
handleXMLReturnData(this.responseXML);
}
}
};
xhttp.open("POST", "resources/php/filesystemActions.php", true);
xhttp.overrideMimeType('application/xml'); // Force return to be XML
xhttp.send(data);
}

View File

@ -1,15 +0,0 @@
const getCookie = (cname) => {
let decodedCookie = decodeURIComponent(document.cookie);
let name = cname + "=";
let ca = decodedCookie.split(';');
for(let i = 0; i <ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}

View File

@ -1,34 +0,0 @@
const faveManager = (elm) => {
let path = document.getElementById("path").innerHTML;
let data = "";
if (elm.style.backgroundColor != "") {
elm.style.backgroundColor = "";
elm.style.color = "";
data = "deleteLink=true";
} else {
elm.style.backgroundColor = "rgb(255, 255, 255)";
elm.style.color = "rgb(0, 0, 0)";
data = "deleteLink=false";
}
data += "&linkPath=" + path;
doAjax("resources/php/dbController.php", data);
}
// Basically resetting path nodes and setting them up
// to the new path and just doing a refresh
const loadFave = (elm) => {
let path = elm.getAttribute("name");
let parts = path.split("/");
let size = parts.length;
pathNodes = [];
pathNodes.push(parts[0] + "/");
for (let i = 1; i < size - 1; i++) {
pathNodes.push(parts[i] + "/");
}
pathNodes.push(parts[size - 1]);
getDir("./");
}

View File

@ -1,132 +0,0 @@
let binary = null;
let pathNodes = [];
const lockFolders = () => {
const data = "lockFolders=true";
doAjax("resources/php/lockedFolders.php", data);
getDir("./");
}
const getDir = (query) => {
document.getElementById("controls").style.opacity = "1";
document.getElementById("dynUl").style.display = "grid";
document.getElementById("video").src = "#";
document.getElementById("video").style.display = "none";
let formUlPth = document.getElementById("DIRPATHUL");
let mergeType = document.getElementById("MergeType");
let passwd = undefined;
let data = "";
let cookies = "";
let dirCookie = "";
// push or pop to path list
if (query === "/") {
// Process path from cookie and set to array/list
dirCookie = getCookie("dirQuery");
if (dirCookie != "" && dirCookie != "./") {
dirCookie = dirCookie.split("/");
dirCookie.pop(); // account for ending empty slot
let size = dirCookie.length;
for (var i = 0; i < size; i++) {
pathNodes.push(dirCookie[i] + "/");
}
} else {
pathNodes = [];
pathNodes.push("." + query);
}
} else if (query === "../") {
// Only remove while not in root
if (pathNodes.length > 1) {
pathNodes.pop();
}
} else if (query === "./") {
// Do nothing since re-scanning dir
} else {
pathNodes.push(query); // Add path
}
// Create path from array of items
for (pathNode of pathNodes) { data += pathNode; }
try {
passwd = document.getElementById("PASSWD").value;
} catch (e) {
passwd = "";
}
// Setup upload path for form and make a cookie for persistence during browser session....
formUlPth.value = data;
data = "dirQuery=" + encodeURIComponent(data);
document.cookie = data + "; expires=Sun, 31 Dec 2034 12:00:00 UTC";
data +="&mergeType=" + mergeType.checked
+ "&passwd=" + passwd;
doAjax("resources/php/getDirList.php", data);
}
const uploadFiles = async () => {
let toUpload = document.getElementsByName("filesToUpload[]")[0];
let path = document.getElementById("path").innerHTML;
let reader = new FileReader();
let data = new FormData();
let size = toUpload.files.length;
data.append("UploadFiles", "trut");
data.append("DIRPATHUL", path);
// Add files
if (size > 0) {
for (let i = 0; i < size; i++) {
data.append("filesToUpload[]", toUpload.files[i]);
}
fileUploader(data);
}
}
const createItem = (type) => {
let path = document.getElementById("path").innerHTML;
let newItem = document.getElementById("NewItem");
let fullPth = path + newItem.value;
newItem.value = "";
fullPth = encodeURIComponent(fullPth);
doAjax("resources/php/filesystemActions.php",
"createItem=true&item=" + fullPth + "&type=" + type);
}
const deleteItem = () => {
let path = document.getElementById("path").innerHTML;
// Clicked yes to delete and there is an item
if (itemObj != undefined && itemObj != null) {
let fullPth = path + itemObj;
fullPth = encodeURIComponent(fullPth);
let answer = confirm("Are you sure you want to delete: " + fullPth);
if (answer == true) {
doAjax("resources/php/filesystemActions.php",
"deleteItem=true&item=" + fullPth);
console.log("Deleted: " + fullPth);
itemObj = null;
}
}
}
const renameItem = (obj) => {
let path = encodeURIComponent(document.getElementById("path").innerHTML);
let oldName = encodeURIComponent(formerFileName);
let newName = encodeURIComponent(obj.value);
let formData = "renameItem=true&oldName=" + oldName + "&newName=" + newName + "&path=" + path;
console.log("Old name: " + oldName);
console.log("New name: " + newName);
doAjax("resources/php/filesystemActions.php",
formData);
}
const openInLocalProg = (media) => {
doAjax("resources/php/filesystemActions.php",
"media=" + media);
}

View File

@ -1,167 +0,0 @@
const insertArea = document.getElementById('dynUl');
const handleJSONReturnData = (data) => {
if (data.message) {
if (data.message.type == "locked") {
createPassField();
} else {
const text = document.createTextNode(data.message.text)
document.getElementById("serverMsgView").appendChild(text);
}
return ;
}
if (data.list) {
updateHTMLDirList(data);
} else if (data.FAVES_LIST) {
generateFavesList(data.FAVES_LIST);
}
}
const generateFavesList = (data) => {
let listView = document.getElementById("favesList");
listView.innerHTML = "";
data.forEach(fave => {
let liTag = document.createElement("LI");
let parts = (fave.includes("/")) ? fave.split("/") : fave.split("\\");
let txtNode = document.createTextNode(parts[parts.length - 2]);
liTag.setAttribute("name", fave);
liTag.setAttribute("title", fave);
liTag.setAttribute("onclick", "loadFave(this)");
liTag.appendChild(txtNode);
listView.appendChild(liTag);
});
}
const updateHTMLDirList = async (data) => {
var dirTemplate = document.querySelector('#dirTemplate');
var vidTemplate = document.querySelector('#vidTemplate');
var imgTemplate = document.querySelector('#imgTemplate');
var filTemplate = document.querySelector('#filTemplate');
let dirPath = data.PATH_HEAD;
let isInFaves = data.IN_FAVE;
let dirs = (data.list.dirs) ? data.list.dirs : [];
let videos = (data.list.vids) ? data.list.vids : [];
let images = (data.list.imgs) ? data.list.imgs : [];
let files = (data.list.files) ? data.list.files : [];
let i = 0;
let size = 0;
document.getElementById("path").innerHTML = dirPath;
insertArea.innerHTML = "";
// Setup background if there is a 000.* in selection
let bgImgPth = images[0] ? images[0].image : "";
if (bgImgPth.match(/000\.(jpg|png|gif)\b/) != null) {
updateBG(dirPath + bgImgPth);
} else {
updateBG("resources/images/backgrounds/000.jpg");
}
// determin whether to style faves or not
let elm = document.getElementById("faves");
if (isInFaves == "true") {
elm.style.backgroundColor = "rgb(255, 255, 255)";
elm.style.color = "rgb(0, 0, 0)";
} else {
elm.style.backgroundColor = "";
elm.style.color = "";
}
// Insert dirs
let dirClone = document.importNode(dirTemplate.content, true);
let dirImg = "resources/images/icons/folder.png";
let dir = null;
size = dirs.length;
for (; i < size; i++) {
dir = dirs[i].dir;
const clone = dirClone.cloneNode(true);
createElmBlock(clone, dirImg, dir);
}
// Insert videos
let vidClone = document.importNode(vidTemplate.content, true);
let thumbnail = "";
let title = "";
size = videos.length;
for (i = 0; i < size; i++) {
title = videos[i].video.title;
thumbnail = videos[i].video.thumbnail;
const clone = vidClone.cloneNode(true);
createElmBlock(clone, thumbnail, title, true, dirPath);
}
// Insert images
let imgClone = document.importNode(imgTemplate.content, true);
thumbnail = "";
size = images.length;
for (i = 0; i < size; i++) {
thumbnail = images[i].image;
if (thumbnail.match(/000\.(jpg|png|gif)\b/) == null &&
!thumbnail.includes("favicon.png")) {
const clone = imgClone.cloneNode(true);
let imgTag = clone.firstElementChild;
imgTag.src = dirPath + '/' + thumbnail;
imgTag.alt = thumbnail;
insertArea.appendChild(clone);
}
}
// Insert files
let fileClone = document.importNode(filTemplate.content, true);
size = files.length;
for (i = 0; i < size; i++) {
const clone = fileClone.cloneNode(true);
let fileName = files[i].file;
createElmBlock(clone, setFileIconType(fileName), fileName);
}
}
const createElmBlock = (elm, imgSrc, fileName, isVideo = null, path = null) => {
contnrTag = elm.firstElementChild;
let imgTag = null;
let inputTag = elm.querySelector("input");
if (isVideo) {
contnrTag.style = "background-image: url('/resources/images/thumbnails/" + imgSrc + "')";
inputTag.className = "videoInputField";
let fullMedia = path + fileName;
elm.querySelector("span").addEventListener("click", function (eve) {
openInLocalProg(fullMedia);
});
} else {
imgTag = elm.querySelector("img");
imgTag.src = imgSrc;
imgTag.alt = fileName;
}
contnrTag.title = fileName;
inputTag.value = fileName;
inputTag.addEventListener("focusout", function (eve) {
disableEdits(eve.target);
});
insertArea.appendChild(elm);
}
const setFileIconType = (fileName) => {
if (fileName.match(/\.(doc|docx|xls|xlsx|rtf)\b/) != null) {
return "resources/images/icons/doc.png";
} else if (fileName.match(/\.(7z|7zip|zip|tar.gz|tar.xz|gz|rar|jar)\b/) != null) {
return "resources/images/icons/arc.png";
} else if (fileName.match(/\.(pdf)\b/) != null) {
return "resources/images/icons/pdf.png";
} else if (fileName.match(/\.(html)\b/) != null) {
return "resources/images/icons/html.png";
} else if (fileName.match(/\.(txt|conf)\b/) != null) {
return "resources/images/icons/text.png";
} else if (fileName.match(/\.(iso|img)\b/) != null) {
return "resources/images/icons/img.png";
} else if (fileName.match(/\.(sh|batch|exe)\b/) != null) {
return "resources/images/icons/scrip.png";
} else {
return "resources/images/icons/bin.png";
}
}

View File

@ -1,22 +0,0 @@
const createPassField = () => {
let passField = document.createElement("INPUT");
let submitBttn = document.createElement("BUTTON");
passField.id = "PASSWD";
passField.type = "password";
passField.placeholder = "Password...";
submitBttn.innerHTML = "Submit";
insertArea.innerHTML = "";
passField.onkeyup = (eve) => {
if (eve.key == "Enter") {
getDir("./");
}
};
submitBttn.onclick = () => {
getDir("./");
};
insertArea.appendChild(passField);
insertArea.appendChild(submitBttn);
}

View File

@ -1,189 +0,0 @@
let formerFileName = "";
const tgglElmView = (id) => {
let elm = document.getElementById(id);
if (elm.style.display == "none") {
elm.style.display = "block";
} else {
elm.style.display = "none";
}
}
const searchPage = (elm) => {
let query = elm.value.toLowerCase();
let list = document.getElementById("dynUl").querySelectorAll("[title]");
let size = list.length;
for (var i = 0; i < size; i++) {
if (!list[i].title.toLowerCase().includes(query)) {
list[i].style.display = "none";
} else {
list[i].style.display = "";
}
}
}
const clearSearch = () => {
let list = document.getElementById("dynUl").querySelectorAll("[title]");
let size = list.length;
for (var i = 0; i < size; i++) {
list[i].style.display = "";
}
}
const enableEdit = (obj) => {
obj.style.backgroundColor = "#ffffffff";
obj.style.color = '#000000ff';
obj.readOnly = '';
formerFileName = obj.value;
}
const disableEdits = (elm) => {
elm.style.backgroundColor = "";
elm.style.color = '';
elm.value = formerFileName;
elm.readOnly = "true";
}
const showMedia = async (mediaLoc, type) => {
let path = document.getElementById("path").innerHTML;
let tempRef = mediaLoc.toLowerCase();
let fullMedia = path + mediaLoc;
if (type === "video") {
setupVideo(type, fullMedia, tempRef);
} else {
createFloatingPane(type, fullMedia);
}
}
const setupVideo = async (type, fullMedia, tempRef) => {
try {
let video = document.getElementById("video");
video.autoplay = true;
video.poster = "resources/images/loading.gif";
if ((/\.(mkv|avi|flv|mov|m4v|mpg|wmv|mpeg|mp4|mp3|webm|flac|ogg|pdf)$/i).test(tempRef)) {
if ((/\.(mkv|avi|wmv)$/i).test(tempRef)) {
const params = "remuxVideo=true&mediaPth=" + fullMedia;
let response = await fetch("resources/php/filesystemActions.php",
{method: "POST", body: new URLSearchParams(params)});
let xml = new window.DOMParser().parseFromString(await response.text(), "text/xml");
if (xml.getElementsByTagName("REMUX_PATH")[0]) {
fullMedia = xml.getElementsByTagName("REMUX_PATH")[0].innerHTML;
} else {
return ;
}
} else if ((/\.(avi|flv|mov|m4v|mpg|wmv)$/i).test(tempRef)) {
openInLocalProg(fullMedia);
return ;
}
}
// This is questionable in usage since it loads the full video
// before showing; but, seeking doesn't work otherwise...
let response = await fetch(fullMedia, {method: "GET"});
var vidSrc = URL.createObjectURL(await response.blob()); // IE10+
video.src = vidSrc;
document.getElementById("controls").style.opacity = "0";
document.getElementById("video").style.display = "block";
document.getElementById("dynUl").style.display = "none";
} catch (e) {
document.getElementById("controls").style.opacity = "1";
document.getElementById("dynUl").style.display = "grid";
document.getElementById("video").src = "#";
document.getElementById("video").style.display = "none";
console.log(e);
}
}
const createFloatingPane = (type, fullMedia) => {
let iframe = document.createElement("IFRAME");
let outterDiv = document.createElement("DIV");
let popOutDiv = document.createElement("DIV");
let closeDiv = document.createElement("DIV");
let toLocDiv = document.createElement("DIV");
let imgDiv = document.createElement("DIV");
let aTag = document.createElement("A");
let imgTag = document.createElement("IMG");
let closeText = document.createTextNode("X");
closeDiv.className = "closeBttn";
closeDiv.title = "Close";
closeDiv.setAttribute("onclick", "closeContainer(this)");
closeDiv.appendChild(closeText);
aTag.title = "New Tab";
aTag.target = "_blank";
aTag.href = fullMedia;
popOutDiv.className = "popOutBttn";
popOutDiv.innerHTML = "&#8599;";
aTag.appendChild(popOutDiv);
toLocDiv.title = "Open In Local Program";
toLocDiv.className = "popOutBttn";
toLocDiv.innerHTML = "&#8765;";
toLocDiv.setAttribute("onclick", "openInLocalProg('" + fullMedia + "')");
imgDiv.id = "imgArea";
imgTag.className = "imgViewImg";
imgTag.src = fullMedia;
imgDiv.appendChild(imgTag);
iframe.id = "fileViewInner";
iframe.src = fullMedia;
outterDiv.appendChild(closeDiv);
outterDiv.appendChild(aTag);
outterDiv.appendChild(toLocDiv);
if (type === "image") {
outterDiv.id = "imgView";
outterDiv.appendChild(imgDiv);
} else {
outterDiv.id = "fileView";
outterDiv.appendChild(iframe);
}
document.body.appendChild(outterDiv);
dragContainer(outterDiv); // Set for dragging events
}
const closeContainer = (elm) => {
elm.parentElement.parentElement.removeChild(elm.parentElement);
}
const clearDirCookie = () => {
let expireDate = "Thu, 01 Jan 1970 00:00:00 UTC";
document.cookie = "dirQuery=; expires=" + expireDate;
getDir("/");
}
const downloadItem = () => {
let partialPath = document.getElementById("path").innerHTML;
let brTag = document.createElement("BR");
let aTag = document.createElement("A");
let text = document.createTextNode(itemObj);
let fullPath = partialPath + itemObj;
aTag.setAttribute("href", fullPath);
aTag.setAttribute("target", "_blank");
aTag.setAttribute("id", itemObj);
aTag.append(text);
document.getElementById("serverMsgView").append(aTag, brTag);
aTag.click();
}
const clearDlList = () => { document.getElementById("CLEARBTTN").click(); }
const onloadSetBG = () => { updateBG("resources/images/backgrounds/000.jpg"); }
const updateBG = (bgImg) => {
try {
document.getElementById("bg").src = bgImg;
} catch (e) { }
}

View File

@ -1,155 +0,0 @@
let itemObj = undefined;
let interval = undefined;
let cursorX;
let cursorY;
document.getElementById("controls").onmouseover = (eve) => {
let source = document.getElementById("video").src;
let target = eve.target
if (interval)
clearInterval(interval);
if (source !== "#") {
eve.target.style.opacity = "1";
document.getElementById("dynUl").style.display = "grid";
}
}
document.getElementById("video").onmouseover = (eve) => {
interval = setInterval(function () {
elementMouseIsOver = document.elementFromPoint(cursorX, cursorY);
if (elementMouseIsOver.tagName == "BODY" ||
elementMouseIsOver.id == "video") {
let controls = document.getElementById("controls");
controls.style.opacity = "0";
document.getElementById("dynUl").style.display = "none";
clearInterval(interval);
}
}, 2500);
}
// For context menu to have element
document.onclick = (event) => {
let obj = event.target;
let callingID = obj.id;
let classNM = obj.className;
// right-click detect
if (event.which == 3) {
if (callingID == "imageID") {
setSelectedItem(obj.alt);
} else if (callingID == "dirID" || callingID == "fileID" ||
callingID == "movieID") {
let node = obj.parentNode;
setSelectedItem(node.children[1].value);
} else if (classNM == "fileStyle" || classNM == "dirStyle" ||
classNM == "movieStyle") {
setSelectedItem(obj.children[1].value);
}
}
}
// Actions for content
document.ondblclick = (event) => {
let obj = event.target;
let callingID = obj.id;
let classNM = obj.className;
// Left click detect
if (event.which == 1) {
// If clicking on container
if (classNM === "fileStyle" || classNM === "movieStyle" ||
classNM === "dirStyle") {
if (classNM === "dirStyle") {
getDir(obj.children[1].value);
} else if (classNM === "movieStyle") {
showMedia(obj.title, "video");
} else {
showMedia(obj.children[1].value, "file");
}
} else if (callingID === "dirID") { // If clicking on dir icon
let node = obj.parentNode;
getDir(node.children[1].value);
} else if (callingID === "movieID") { // If clicking on movie thumbnail
let node = obj.parentNode;
showMedia(node.children[1].value, "video");
} else if (callingID === "imageID") { // If clicking on image
showMedia(obj.alt, "image");
} else if (callingID === "titleID") { // If clicking on text title
enableEdit(obj);
}
}
}
// Mainly for rename event
document.onkeydown = (event) => {
let obj = event.target;
let callingID = event.target.id;
let keyCodeVal = event.keyCode;
// If keycode == Enter
if (keyCodeVal == 13) {
if (callingID == "titleID") {
renameItem(obj);
}
}
}
const setSelectedItem = (item) => { itemObj = item; }
// Drage event for the poped out image and media container
const dragContainer = (elmnt) => {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
elmnt.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e = e || window.event;
pauseEvent(e);
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
pauseEvent(e);
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
function closeDragElement(e) {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
}
function pauseEvent(e) {
if(e.stopPropagation) e.stopPropagation();
if(e.preventDefault) e.preventDefault();
e.cancelBubble=true;
e.returnValue=false;
return false;
}
}
// Mouse position detection for control show/hide setup
document.onmousemove = function(e){
cursorX = e.pageX;
cursorY = e.pageY;
}
setInterval(checkCursor, 2000);
function checkCursor() {
return "";
}

View File

@ -1,15 +0,0 @@
<?php
$MEDIAPLAYER = "mpv ";
$MPLAYER_WH = " -xy 1600 -geometry 50%:50% ";
$MUSICPLAYER = "/opt/deadbeef/bin/deadbeef";
$IMGVIEWER = "mirage";
$OFFICEPROG = "libreoffice";
$PDFVIEWER = "evince";
$TEXTVIEWER = "leafpad";
$FILEMANAGER = "spacefm";
$LOCKPASSWORD = "1234";
$TMPFOLDERSIZE = 8000; // tmp folder size check for cleanup if above 8GB used.
// NOTE: Split folders with ::::
$LOCKEDFOLDERS = "./dirLockCheck/";
?>

View File

@ -1,10 +0,0 @@
<?php
include_once 'serverMessenger.php';
chdir("../../");
$db = new SQLite3('resources/db/webfm.db');
if($db === false){
$message = "Server: [Error] --> Database connection failed!";
serverMessage("error", $message);
}
?>

View File

@ -1,56 +0,0 @@
<?php
include_once 'connection.php';
include_once 'serverMessenger.php';
function getTabLinks() {
GLOBAL $db;
$res = $db->query('Select * FROM faves');
$GeneratedJSON = array('FAVES_LIST' => array());
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
$GeneratedJSON['FAVES_LIST'][] = $row['link'];
}
echo json_encode($GeneratedJSON);
}
function manageLink($ACTION, $PATH) {
GLOBAL $db;
$ACTION_TYPE = "";
// If action isn't true then we add else we delete or exit.
if ($ACTION == "false") {
$stmt = $db->prepare('INSERT INTO faves VALUES(:link)');
$ACTION_TYPE = "added to";
} elseif ($ACTION == "true") {
$stmt = $db->prepare('DELETE FROM faves WHERE link = :link');
$ACTION_TYPE = "deleted from";
} else {
$message = "Server: [Error] --> Action for adding or deleting isn't set properly!";
serverMessage("error", $message);
return;
}
$stmt->bindValue(":link", $PATH, SQLITE3_TEXT);
$stmt->execute();
$stmt->close();
$message = "Server: [Success] --> Fave link: " .
$PATH . " " . $ACTION_TYPE . " the database!";
serverMessage("success", $message);
}
// Determin action
chdir("../../");
if (isset($_POST['getTabs'])) {
getTabLinks();
} elseif (isset($_POST['deleteLink'],
$_POST['linkPath'])) {
manageLink($_POST['deleteLink'], $_POST['linkPath']);
} else {
$message = "Server: [Error] --> Illegal Access Method!";
serverMessage("error", $message);
}
?>

View File

@ -1,199 +0,0 @@
<?php
session_start();
include_once 'serverMessenger.php';
// Create file or folder
function createItem($FILE, $TYPE) {
$FILE = trim($FILE);
$FILE = preg_replace('/\.*$/','',$FILE); // removing dot . after file extension
if ($TYPE === "dir"){
mkdir($FILE, 0755);
} else if ($TYPE === "file") {
$myfile = fopen($FILE, "w");
fclose($myfile);
} else {
$message = "Server: [Error] --> Failed to create folder or file!";
serverMessage("error", $message);
return;
}
$message = "Server: [Success] --> The file " . $FILE . " has been created.";
serverMessage("success", $message);
$_SESSION["refreshState"] = "updateListing";
}
// File or folder delition
function deleteItem($FILE) {
if (filetype($FILE) == "dir"){
//GLOB_MARK adds a slash to directories returned
$files = glob($FILE . '*', GLOB_MARK);
foreach ($files as $file) {
deleteItem($file);
}
rmdir($FILE);
} else if (filetype($FILE) == "file") {
unlink($FILE);
} else {
$message = "Server: [Error] --> Failed to delete item! Not a folder or file!";
serverMessage("error", $message);
return;
}
$message = "Server: [Success] --> The file(s) has/have been deleted.";
serverMessage("success", $message);
$_SESSION["refreshState"] = "updateListing";
}
// Rename file or folder
function renameItem($OLDFILE, $NEWNAME, $PATH) {
rename($PATH . $OLDFILE, $PATH . $NEWNAME);
$message = "Server: [Success] --> The file " . $OLDFILE . " has been renamed to " . $NEWNAME . " side.";
serverMessage("success", $message);
$_SESSION["refreshState"] = "updateListing";
}
// Uploader
function uploadFiles($targetDir) {
$numberOfFiles = count($_FILES['filesToUpload']['name']);
if ($numberOfFiles === 0) {
$message = "Server: [Error] --> No files were uploaded!";
serverMessage("error", $message);
return;
}
$type = "";
$message = "";
for ($i=0; $i < $numberOfFiles; $i++) {
$uploadOk = 1;
$fileName = $_FILES['filesToUpload']['name'][$i];
$fileTmpName = $_FILES['filesToUpload']['tmp_name'][$i];
// Check if file already exists
$targetFile = $targetDir . $fileName;
if (file_exists($targetFile)) {
if (filetype($targetFile) == "file") {
unlink($targetFile);
$message = "Server: [Warning] --> This file already exists. Overwriting it.";
} else {
$message = "Server: [Warning] --> This file might be a directory. Or, no files were submitted for uploading.";
$uploadOk = 0;
}
}
// Check file size
$fileSize = $_FILES['filesToUpload']['size'][$i];
if ($fileSize > 500000000000) {
$message = "Server: [Warning] --> This file is too large.";
$uploadOk = 0;
}
// Allow certain file formats
// $ext = pathinfo($targetFile,PATHINFO_EXTENSION);
// if(!preg_match('/^.*\.(rar|iso|img|tar|zip|7z|7zip|jpg|jpeg|png|gif|mpeg|mov|flv|avi|mp4|webm|mpg|mkv|m4a|mp3|ogg|docx|doc|odt|txt|pdf|)$/i', strtolower($ext))) {
// $message = "Server: [Warning] --> This file type is not allowed.";
// $uploadOk = 0;
// }
// if everything is ok, try to upload file
if ($uploadOk !== 0) {
if (move_uploaded_file($fileTmpName, $targetFile)) {
$type = "success";
$message = "Server: [Success] --> The file " . $fileName . " has been uploaded.";
$_SESSION["refreshState"] = "updateListing";
}
} else {
$type = "error";
$message .= "\nServer: [Error] --> Your file " . $fileName . " was not uploaded.";
}
}
serverMessage($type, $message);
}
// Local program file access
function openFile($FILE) {
include 'config.php';
$EXTNSN = strtolower(pathinfo($FILE, PATHINFO_EXTENSION));
if (preg_match('(mkv|avi|flv|mov|m4v|mpg|wmv|mpeg|mp4|webm)', $EXTNSN) === 1) {
shell_exec($MEDIAPLAYER . "\"" . $FILE . "\" > /dev/null &");
} else if (preg_match('(png|jpg|jpeg|gif)', $EXTNSN) === 1) {
shell_exec($IMGVIEWER . ' "' . $FILE . '" > /dev/null &');
} else if (preg_match('(psf|mp3|ogg|flac)', $EXTNSN) === 1) {
shell_exec($MUSICPLAYER . ' "' . $FILE . '" > /dev/null &');
} else if (preg_match('(odt|doc|docx|rtf)', $EXTNSN) === 1) {
shell_exec($OFFICEPROG . ' "' . $FILE . '" > /dev/null &');
} else if (preg_match('(txt)', $EXTNSN) === 1) {
shell_exec($TEXTVIEWER . ' "' . $FILE . '" > /dev/null &');
} else if (preg_match('(pdf)', $EXTNSN) === 1) {
shell_exec($PDFVIEWER . ' "' . $FILE . '" > /dev/null &');
}
$message = "Server: [Success] --> The file " . $FILE . " has been opened server side.";
serverMessage("success", $message);
}
function remuxVideo($FILE) {
$FILE = trim($FILE);
$PTH = "resources/tmp/";
$HASHED_NAME = hash('sha256', $FILE) . '.mp4';
$EXTNSN = strtolower(pathinfo($FILE, PATHINFO_EXTENSION));
if (!file_exists($PTH . $HASHED_NAME)) {
$io = popen('/usr/bin/du -sm ' . $PTH, 'r');
$size = fgets($io, 4096);
$size = (int) substr($size, 0, strpos ( $size, "\t" ));
pclose ($io);
include 'config.php';
if ($size > $TMPFOLDERSIZE) {
$files = glob($PTH . '*');
foreach($files as $file){
if(is_file($file))
unlink($file);
}
}
if (preg_match('(mkv)', $EXTNSN) === 1)
$COMMAND = 'ffmpeg -i "' . $FILE . '" -hide_banner -movflags +faststart -codec copy -strict -2 ' . $PTH . $HASHED_NAME;
if (preg_match('(avi)', $EXTNSN) === 1)
$COMMAND = 'ffmpeg -i "' . $FILE . '" -hide_banner -movflags +faststart -c:v libx264 -crf 21 -c:a aac -b:a 192k -ac 2 ' . $PTH . $HASHED_NAME;
if (preg_match('(wmv)', $EXTNSN) === 1)
$COMMAND = 'ffmpeg -i "' . $FILE . '" -hide_banner -movflags +faststart -c:v libx264 -crf 23 -c:a aac -strict -2 -q:a 100 ' . $PTH . $HASHED_NAME;
shell_exec($COMMAND . " 2> resources/vdata.txt");
}
$GeneratedXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
$GeneratedXML .= "<REMUX_PATH>" . $PTH . $HASHED_NAME ."</REMUX_PATH>";
echo $GeneratedXML;
}
chdir("../../");
if (isset($_POST["remuxVideo"], $_POST["mediaPth"])) {
remuxVideo($_POST["mediaPth"]);
} else if (isset($_POST["createItem"],
$_POST["item"],
$_POST["type"])) {
createItem($_POST["item"], $_POST["type"]);
} else if (isset($_POST["deleteItem"], $_POST["item"])) {
deleteItem($_POST["item"]);
} else if (isset($_POST["renameItem"],
$_POST["oldName"],
$_POST["newName"],
$_POST["path"])) {
renameItem($_POST["oldName"], $_POST["newName"], $_POST["path"]);
} else if(isset($_POST["UploadFiles"], $_POST["DIRPATHUL"])) {
uploadFiles($_POST["DIRPATHUL"]);
} else if (isset($_POST["media"])) {
openFile($_POST["media"]);
} else {
$message = "Server: [Error] --> Incorrect access attempt!";
serverMessage("error", $message);
}
?>

View File

@ -1,108 +0,0 @@
<?php
session_start();
include_once 'serverMessenger.php';
// Start of retrieving dir data
function startListing($NEWPATH, $MERGESEASSONS, $PASSWD) {
if (filetype($NEWPATH) == "dir") {
include_once 'lockedFolders.php';
if (checkForLock($NEWPATH, $PASSWD) == false) {
$subPath = ""; // This is used for season scanning as a means of properly getting
// the video src.... It's left blank when not in a sub dir
$GeneratedJSON = array('PATH_HEAD' => $NEWPATH,
'IN_FAVE' => isInDBCheck($NEWPATH),
'list' => array()
);
listDir($GeneratedJSON, $NEWPATH, $MERGESEASSONS, $subPath);
echo json_encode($GeneratedJSON);
} else {
$message = "Server: [Error] --> Folder is locked.";
serverMessage("locked", $message);
}
}
}
function listDir(&$GeneratedJSON, &$NEWPATH, &$MERGESEASSONS, &$subPath) {
if ($MERGESEASSONS !== "true") {
$files = array_diff(scandir($NEWPATH), array('..', '.', 'resources'));
foreach ($files as $fileName) {
$fullPath = $NEWPATH . '/' . $fileName;
// error_log($fullPath, 4);
processItem($GeneratedJSON, $fullPath, $fileName, $subPath);
}
} else {
$files = array_diff(scandir($NEWPATH), array('..', '.', 'resources'));
foreach ($files as $fileName) {
$fullPath = $NEWPATH . $fileName;
// error_log($fullPath, 4);
if (filetype($fullPath) == "dir" && strpos(strtolower($fileName),
'season') !== false) {
$fileName .= "/";
listDir($GeneratedJSON, $fullPath, $MERGESEASSONS, $fileName);
} else {
processItem($GeneratedJSON, $fullPath, $fileName, $subPath);
}
}
}
}
// Assign JSON Markup based on file type
function processItem(&$GeneratedJSON, &$fullPath, &$fileName, $subPath) {
if (preg_match('/^.*\.(mkv|avi|flv|mov|m4v|mpg|wmv|mpeg|mp4|webm)$/i', strtolower($fileName))) {
$NAMEHASH = hash('sha256', $fileName);
if (!file_exists('resources/images/thumbnails/' . $NAMEHASH . '.jpg')) {
shell_exec('resources/ffmpegthumbnailer -t 65% -s 320 -c jpg '
. '-i "' . $subPath . $fullPath . '" '
. '-o resources/images/thumbnails/' . $NAMEHASH . '.jpg'
);
}
$GeneratedJSON['list']['vids'][] = array('video' =>
array('title' => $subPath . $fileName,
'thumbnail' => $NAMEHASH . '.jpg'
)
);
} elseif (preg_match('/^.*\.(png|jpg|gif|jpeg)$/i', strtolower($fileName))) {
$GeneratedJSON['list']['imgs'][] = array('image' => $subPath . $fileName);
} elseif (filetype($fullPath) == "dir") {
$GeneratedJSON['list']['dirs'][] = array('dir' => $fileName . "/");
} else {
$GeneratedJSON['list']['files'][] = array('file' => $subPath . $fileName);
}
}
function isInDBCheck($PATH) {
$db = new SQLite3('resources/db/webfm.db');
if($db === false){
$message = "Server: [Error] --> Database connection failed!";
serverMessage("error", $message);
die("ERROR: Could not connect to db.");
}
$stmt = $db->prepare('SELECT 1 FROM faves WHERE link = :link');
$stmt->bindValue(":link", $PATH, SQLITE3_TEXT);
$result = $stmt->execute() ;
$row = $result->fetchArray() ;
if ($row > 0) {
return "true";
} else {
return "false";
}
}
// Determin action
chdir("../../");
if (isset($_POST['dirQuery'])) {
startListing(trim($_POST['dirQuery']), $_POST['mergeType'], $_POST['passwd']);
} else {
$message = "Server: [Error] --> Illegal Access Method!";
serverMessage("error", $message);
}
?>

View File

@ -1,46 +0,0 @@
<?php
// Check if sub folder is in locked folder
function checkForLock($NEWPATH, $PASSWD) {
include 'config.php';
$LOCKS = explode("::::", $LOCKEDFOLDERS);
$size = sizeof($LOCKS);
if (isset($_SESSION["unlockState"]) && $_SESSION["unlockState"] == true) {
return false;
}
for ($i = 0; $i < $size; $i++) {
if (strpos($NEWPATH, $LOCKS[$i]) !== false) {
if ($PASSWD === $LOCKPASSWORD) {
$_SESSION["unlockState"] = true;
return false;
} else {
return true;
}
}
}
return false;
}
function lockFolders() {
session_start();
include 'serverMessenger.php';
if (isset($_SESSION["unlockState"]) && $_SESSION["unlockState"] == true) {
$_SESSION["unlockState"] = false;
$message = "Server: [Success] --> Folders unlocked!";
serverMessage("success", $message);
} else {
$message = "Server: [Warning] --> Folders aren't unlocked!"
. "\n" . $_SESSION["unlockState"];
serverMessage("warning", $message);
}
}
if (isset($_POST['lockFolders'])) {
lockFolders();
}
?>

View File

@ -1,12 +0,0 @@
<?php
function serverMessage($TYPE, $MESSAGE) {
$GeneratedJSON = array( 'message' =>
array(
'type' => $TYPE,
'text' => $MESSAGE
)
);
echo json_encode($GeneratedJSON);
}
?>

View File

@ -1,16 +0,0 @@
<?php
// Start the session
session_start();
include_once 'config.php';
if (!isset($_SESSION["refreshState"])) {
$_SESSION["refreshState"] = "none";
}
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
echo "data:" . $_SESSION["refreshState"] . "\n\n";
$_SESSION["refreshState"] = "none";
flush();
?>

View File

@ -1,91 +0,0 @@
/*START OF HOMEPAGE BUTTON SETTINGS*/
.homeSection {
float: left;
width: 40%;
height: 14em;
position: relative;
text-align: center;
font-size: 100%;
border-style: solid;
border-width: .2em;
color: rgba(0, 232, 255, .69); /*#00E8FF*/
background: rgba(19, 21, 21, .6);
}
/*END OF HOMEPAGE BUTTON SETTINGS*/
.header {
margin-left: auto;
margin-right: auto;
width: 99%;
}
.section, .lnksNdirs, .header {
display: block;
float: left;
text-align: center;
font-size: 100%;
padding-top: 1em;
margin: 1em;
background: rgba(19, 21, 21, .7);
color: rgba(0, 232, 255, .69); /*#00E8FF*/
/* border-style: solid;
border-width: .2em;
*/
}
.backbutton {
top: 3.55em;
clear: both;
z-index: 2;
position: fixed;
float: left;
width: 5%;
height: 50px;
opacity: .8;
}
iframe {
position: fixed;
bottom: 2em;
left: 25%;
width: 50%;
height: 360px;
background: rgba(0,0,0,.5);
transition: 0s;
transition-delay:2s;
}
iframe:hover {
left: 2%;
width: 99%;
height: 700px;
transition-delay:2s;
}
.lnkStyl {
clear: right;
float: left;
border-style: solid;
}
.imgsStyl {
margin-left: 50%;
opacity: 0.699999988079071044921875;
width: 32.333%;
height: 20em;
}
.imgsStyl:hover {
opacity: 1;
}
#bg {
position: fixed;
top: 0%;
left: 0%;
width: 100%;
height: 100%;
z-index: -1;
}
#bg img {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: -1;
}

View File

@ -1,89 +0,0 @@
body {
width: 600%;
}
/*START OF HOMEPAGE BUTTON SETTINGS*/
.homeSection {
float: left;
width: 40%;
height: 14em;
position: relative;
text-align: center;
font-size: 100%;
border-style: solid;
border-width: .2em;
color: rgba(0, 232, 255, .69); /*#00E8FF*/
background: rgba(19, 21, 21, .6);
}
/*END OF HOMEPAGE BUTTON SETTINGS*/
.header {
margin-left: auto;
margin-right: auto;
width: 99%;
}
.section, .lnksNdirs, .header {
display: block;
clear: right;
float: left;
text-align: center;
font-size: 100%;
padding-top: 1em;
margin: 1em;
background: rgba(19, 21, 21, .7);
color: rgba(0, 232, 255, .69); /*#00E8FF*/
/* border-style: solid;
border-width: .2em;
*/
}
.backbutton {
top: 3.55em;
clear: both;
z-index: 2;
position: fixed;
float: left;
width: 5%;
height: 50px;
opacity: .8;
}
iframe {
position: fixed;
bottom: 2em;
left: 25%;
width: 50%;
height: 360px;
background: rgba(0,0,0,.5)
transition: 0s;
transition-delay:2s;
}
.lnkStyl {
clear: right;
float: left;
border-style: solid;
}
.imgsStyl {
margin-left: 50%;
opacity: 0.699999988079071044921875;
width: 32.333%;
height: 20em;
}
.imgsStyl:hover {
opacity: 1;
}
#bg {
position: fixed;
top: 0%;
left: 0%;
width: 100%;
height: 100%;
z-index: -1;
}
#bg img {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: -1;
}

View File

@ -1,89 +0,0 @@
/*START OF HOMEPAGE BUTTON SETTINGS*/
.homeSection {
float: left;
width: 40%;
height: 14em;
position: relative;
text-align: center;
font-size: 100%;
border-style: solid;
border-width: .2em;
color: rgba(0, 232, 255, .69); /*#00E8FF*/
background: rgba(19, 21, 21, .6);
}
/*END OF HOMEPAGE BUTTON SETTINGS*/
.header {
margin-left: auto;
margin-right: auto;
width: 99%;
}
.section, .lnksNdirs, .header {
position: relative;
text-align: center;
font-size: 100%;
padding-top: 1em;
margin-top: 1em;
background: rgba(19, 21, 21, .7);
color: rgba(0, 232, 255, .69); /*#00E8FF*/
/* border-style: solid;
border-width: .2em;
*/
}
.backbutton {
top: 3.55em;
clear: both;
z-index: 2;
position: fixed;
float: left;
width: 5%;
height: 50px;
opacity: .8;
}
iframe {
clear: both;
z-index: 1;
position: fixed;
bottom: 5%;
width: 40%;
height: 500px;
background: rgba(0,0,0,.5)
transition: 0s;
transition-delay:2s;
}
iframe:hover {
width: 99%;
transition-delay:2s;
}
.lnkStyl {
clear: right;
float: left;
border-style: solid;
}
.imgsStyl {
margin-left: 50%;
opacity: 0.699999988079071044921875;
width: 32.333%;
height: 20em;
}
.imgsStyl:hover {
opacity: 1;
}
#bg {
position: fixed;
top: 0%;
left: 0%;
width: 100%;
height: 100%;
z-index: -1;
}
#bg img {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: -1;
}

View File

@ -1,110 +0,0 @@
/*START OF HOMEPAGE BUTTON SETTINGS*/
.homeSection {
float: left;
width: 40%;
height: 14em;
position: relative;
text-align: center;
font-size: 100%;
border-style: solid;
border-width: .2em;
color: rgba(0, 232, 255, .69); /*#00E8FF*/
background: rgba(19, 21, 21, .6);
}
/*END OF HOMEPAGE BUTTON SETTINGS*/
.lnkStyl {
clear: right;
float: left;
border-style: solid;
}
.imgsStyl {
clear: both;
float: left;
opacity: 0.699999988079071044921875;
width: 20%;
height: 15em;
}
.imgsStyl:hover {
opacity: 1;
}
.header {
margin-left: auto;
margin-right: auto;
width: 100%;
float: left;
}
.lnksNdirs {
width: 20%;
float:left;
}
/* used to format sections of pgs*/
.section, .lnksNdirs, .header {
clear: both;
position: relative;
text-align: center;
padding-top: 1em;
margin-top: 1em;
font-size: 100%;
background: rgba(19, 21, 21, .6);
background: rgba(19, 21, 21, .7);
color: rgba(0, 232, 255, .69); /*#00E8FF*/
/* border-style: solid;
border-width: .2em;
*/
}
.backbutton {
top: 3em;
clear: both;
z-index: 2;
position: fixed;
float: left;
width: 5%;
height: 55px;
opacity: .8;
}
.ifrmbutton2 {
top: 3em;
float: right;
right: .5em;
z-index:2;
position: fixed;
width: 5%;
height: 30px;
opacity: .6;
}
iframe {
margin-left: 2%;
z-index: 2;
position: fixed;
top: 8em;
right: 0.534em;
bottom: 2%;
width: 75%;
height: 525px;
background: rgba(0,0,0,.5)
transition: 0s;
transition-delay:2s;
}
iframe:hover {
height: 775px;
transition-delay:2s;
}
#bg {
position: fixed;
top: 0%;
left: 0%;
width: 100%;
height: 100%;
z-index: -1;
}
#bg img {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: -1;
}

View File

@ -1 +0,0 @@
Place Holder....

94
src/core/__builtins__.py Normal file
View File

@ -0,0 +1,94 @@
# Python imports
import os
import builtins
import threading
import re
import secrets
# Lib imports
from flask import session
# Application imports
from core import app
from core.utils import Logger
# NOTE: Threads WILL NOT die with parent's destruction.
def threaded_wrapper(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
return wrapper
# NOTE: Threads WILL die with parent's destruction.
def daemon_threaded_wrapper(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
def sizeof_fmt_def(num, suffix="B"):
for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]:
if abs(num) < 1024.0:
return f"{num:3.1f} {unit}{suffix}"
num /= 1024.0
return f"{num:.1f} Yi{suffix}"
def _get_file_size(file):
return "4K" if isdir(file) else sizeof_fmt_def(os.path.getsize(file))
# NOTE: Just reminding myself we can add to builtins two different ways...
# __builtins__.update({"event_system": Builtins()})
builtins.app_name = "WebFM"
builtins.threaded = threaded_wrapper
builtins.daemon_threaded = daemon_threaded_wrapper
builtins.sizeof_fmt = sizeof_fmt_def
builtins.get_file_size = _get_file_size
builtins.ROOT_FILE_PTH = os.path.dirname(os.path.realpath(__file__))
builtins.BG_IMGS_PATH = ROOT_FILE_PTH + "/static/imgs/backgrounds/"
builtins.BG_FILE_TYPE = (".webm", ".mp4", ".gif", ".jpg", ".png", ".webp")
builtins.valid_fname_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]{4,20}")
builtins.logger = Logger().get_logger()
# NOTE: Need threads defined befor instantiating
from core.utils.shellfm.windows.controller import WindowController # Get file manager controller
window_controllers = {}
def _get_view():
controller = None
try:
controller = window_controllers[ session["win_controller_id"] ].get_window_by_index(0).get_tab_by_index(0)
except Exception as e:
id = secrets.token_hex(16)
controller = WindowController()
view = controller.create_window().create_tab()
try:
view.ABS_THUMBS_PTH = app.config['ABS_THUMBS_PTH']
except Exception as e:
print("No ABS_THUMBS_PTH set by WebFM...")
try:
view.REMUX_FOLDER = app.config['REMUX_FOLDER']
except Exception as e:
print("No REMUX_FOLDER set by WebFM...")
try:
view.FFMPG_THUMBNLR = app.config['FFMPG_THUMBNLR']
except Exception as e:
print("No FFMPG_THUMBNLR set by WebFM...")
view.logger = logger
session['win_controller_id'] = id
window_controllers.update( {id: controller } )
controller = window_controllers[ session["win_controller_id"] ].get_window_by_index(0).get_tab_by_index(0)
return controller
builtins.get_view = _get_view

45
src/core/__init__.py Normal file
View File

@ -0,0 +1,45 @@
# Python imports
# Lib imports
from flask import Flask
#OIDC Login path
from flask_oidc import OpenIDConnect
# Flask Login Path
from flask_bcrypt import Bcrypt
from flask_login import current_user, login_user, logout_user, LoginManager
app = Flask(__name__)
app.config.from_object("core.config.ProductionConfig")
# app.config.from_object("core.config.DevelopmentConfig")
# Apoplication imports
from .__builtins__ import *
oidc = OpenIDConnect(app)
login_manager = LoginManager(app)
bcrypt = Bcrypt(app)
def oidc_loggedin():
return oidc.user_loggedin
def oidc_isAdmin():
if oidc_loggedin():
isAdmin = oidc.user_getfield("isAdmin")
if isAdmin == "yes" :
return True
return False
app.jinja_env.globals['oidc_loggedin'] = oidc_loggedin
app.jinja_env.globals['oidc_isAdmin'] = oidc_isAdmin
app.jinja_env.globals['TITLE'] = app.config["TITLE"]
from core.models import db, User, Favorites
db.init_app(app)
with app.app_context():
db.create_all()
from core.forms import RegisterForm, LoginForm
from core import routes

View File

@ -0,0 +1,14 @@
{
"web": {
"auth_uri": "https://www.ssoapps.com/auth/realms/apps/protocol/openid-connect/auth",
"client_id": "apps",
"issuer": "https://www.ssoapps.com/auth/realms/apps",
"client_secret": "9028c2ac-d6e0-4d96-86bd-02624b91695d",
"redirect_uris": [
"https%3A%2F%2Fwww.webfm.com%2F"
],
"userinfo_uri": "https://www.ssoapps.com/auth/realms/apps/protocol/openid-connect/userinfo",
"token_uri": "https://www.ssoapps.com/auth/realms/apps/protocol/openid-connect/token",
"token_introspection_uri": "https://www.ssoapps.com/auth/realms/apps/protocol/openid-connect/token/introspect"
}
}

66
src/core/config.py Normal file
View File

@ -0,0 +1,66 @@
# System import
import os, secrets
from datetime import timedelta
# Lib imports
# Apoplication imports
# Configs
APP_NAME = 'WebFM'
ROOT_FILE_PTH = os.path.dirname(os.path.realpath(__file__))
class Config(object):
TITLE = APP_NAME
DEBUG = False
TESTING = False
THREADED = True
SECRET_KEY = "2A#GQafbREoblgMSQYomZSxbaPE6dt#"
# SECRET_KEY = secrets.token_hex(32)
PERMANENT_SESSION_LIFETIME = timedelta(days = 7).total_seconds()
SQLALCHEMY_DATABASE_URI = "sqlite:///static/db/webfm.db"
SQLALCHEMY_TRACK_MODIFICATIONS = False
LOGIN_PATH = "OIDC" # Value can be OIDC or FLASK_LOGIN
OIDC_TOKEN_TYPE_HINT = 'access_token'
APP_REDIRECT_URI = "https%3A%2F%2Fwww.webfm.com%2F" # This path is submitted as the redirect URI in certain code flows
OIDC_CLIENT_SECRETS = f'{ROOT_FILE_PTH}/client_secrets.json'
OIDC_ID_TOKEN_COOKIE_SECURE = True
OIDC_REQUIRE_VERIFIED_EMAIL = False
OIDC_USER_INFO_ENABLED = True
OIDC_VALID_ISSUERS = [
'http://www.ssoapps.com/auth/realms/apps',
'https://www.ssoapps.com/auth/realms/apps'
]
STATIC_FPTH = f"{ROOT_FILE_PTH}/static"
REL_THUMBS_PTH = "static/imgs/thumbnails" # Used for flask thumbnail return
# We are overiding some of the the shellmen view settings with these to make it all work with flask.
# These are passed along to the shellmen view from the Routes file upon the window controller creation.
ABS_THUMBS_PTH = f"{STATIC_FPTH}/imgs/thumbnails" # Used for thumbnail generation
REMUX_FOLDER = f"{STATIC_FPTH}/remuxs" # Remuxed files folder
FFMPG_THUMBNLR = f"{STATIC_FPTH}/ffmpegthumbnailer" # Thumbnail generator binary
class ProductionConfig(Config):
pass
class DevelopmentConfig(Config):
DEBUG = True
USE_RELOADER = True
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_REQUIRE_VERIFIED_EMAIL = False
class TestingConfig(Config):
TESTING = True

24
src/core/forms.py Normal file
View File

@ -0,0 +1,24 @@
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from core import User
class RegisterForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=4, max=24)])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired(), Length(min=8)])
confirm_password = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password', message="Passwords must match!")])
submit = SubmitField("Sign Up")
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError("User exists already! Please use a different name!")
class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=4, max=24)])
password = PasswordField('Password', validators=[DataRequired(), Length(min=8, max=32)])
submit = SubmitField("Login")

41
src/core/models.py Normal file
View File

@ -0,0 +1,41 @@
# System imports
# Lib imports
from flask_sqlalchemy import SQLAlchemy
# App imports
from . import app, login_manager
from flask_login import UserMixin
db = SQLAlchemy(app)
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(db.Model, UserMixin):
username = db.Column(db.String, unique=True, nullable=False)
email = db.Column(db.String, nullable=False)
password = db.Column(db.String, nullable=False)
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
def __repr__(self):
return f"['{self.username}', '{self.email}', '{self.password}', '{self.id}']"
class Favorites(db.Model):
link = db.Column(db.String, nullable=False, unique=True)
id = db.Column(db.Integer, nullable=False, primary_key=True, unique=True, autoincrement=True)
def __repr__(self):
return f"['{self.link}', '{self.id}']"
class Settings(db.Model):
key = db.Column(db.String, nullable=False)
value = db.Column(db.String, nullable=False)
id = db.Column(db.Integer, nullable=False, primary_key=True, unique=True, autoincrement=True)
def __repr__(self):
return f"['{self.key}', '{self.value}', '{self.id}']"

119
src/core/routes/CRUD.py Normal file
View File

@ -0,0 +1,119 @@
# Python imports
import os
import re
# Lib imports
from flask import request
from flask_uploads import ALL
from flask_uploads import configure_uploads
from flask_uploads import UploadSet
# App imports
# Get from __init__
from core import app
from core import db
from core import Favorites
from core import oidc
from core.utils import MessageHandler # Get simple message processor
json_message = MessageHandler()
@app.route('/api/delete/<_hash>', methods=['GET', 'POST'])
def delete_item(_hash = None):
if request.method == 'POST':
msg = "Log in with an Admin privlidged user to delete files!"
if not oidc.user_loggedin:
return json_message.create("danger", msg)
elif oidc.user_loggedin:
isAdmin = oidc.user_getfield("isAdmin")
if isAdmin != "yes" :
return json_message.create("danger", msg)
view = get_view()
folder = view.get_current_directory()
file = view.get_path_part_from_hash(_hash)
fpath = os.path.join(folder, file)
try:
msg = f"[Success] Deleted the file/folder -->: {file} !"
view.delete_file(fpath)
return json_message.create("success", msg)
except Exception as e:
msg = "[Error] Unable to delete the file/folder...."
return json_message.create("danger", msg)
@app.route('/api/create/<_type>', methods=['GET', 'POST'])
def create_item(_type = None):
if request.method == 'POST':
msg = "Log in with an Admin privlidged user to upload files!"
if not oidc.user_loggedin:
return json_message.create("danger", msg)
elif oidc.user_loggedin:
isAdmin = oidc.user_getfield("isAdmin")
if isAdmin != "yes" :
return json_message.create("danger", msg)
TYPE = _type.strip()
if not TYPE in ["dir", "file"]:
msg = "Couldn't handle action type for api create..."
return json_message.create("danger", msg)
FNAME = str(request.values['fname']).strip()
if not re.fullmatch(valid_fname_pat, FNAME):
msg = "A new item name can only contain alphanumeric, -, _, |, [], (), or spaces and must be minimum of 4 and max of 20 characters..."
return json_message.create("danger", msg)
try:
view = get_view()
folder = view.get_current_directory()
new_item = f"{folder}/{FNAME}"
view.create_file(new_item, TYPE)
except Exception as e:
print(repr(e))
msg = "Couldn't create file/folder. An unexpected error occured..."
return json_message.create("danger", msg)
msg = "[Success] created the file/dir..."
return json_message.create("success", msg)
else:
msg = "Can't manage the request type..."
return json_message.create("danger", msg)
@app.route('/api/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'POST' and len(request.files) > 0:
msg = "Log in with an Admin privlidged user to upload files!"
if not oidc.user_loggedin:
return json_message.create("danger", msg)
elif oidc.user_loggedin:
isAdmin = oidc.user_getfield("isAdmin")
if isAdmin != "yes" :
return json_message.create("danger", msg)
view = get_view()
folder = view.get_current_directory()
UPLOADS_PTH = f'{folder}/'
files = UploadSet('files', ALL, default_dest=lambda x: UPLOADS_PTH)
configure_uploads(app, files)
try:
for file in request.files:
files.save(request.files[file])
except Exception as e:
print(repr(e))
msg = "[Error] Failed to upload some or all of the file(s)..."
return json_message.create("danger", msg)
msg = "[Success] Uploaded file(s)..."
return json_message.create("success", msg)
else:
msg = "Can't manage the request type..."
return json_message.create("danger", msg)

View File

@ -0,0 +1,71 @@
# Python imports
# Lib imports
from flask import request
# App imports
from core import app
from core import db
from core import Favorites # Get from __init__
from core.utils import MessageHandler # Get simple message processor
json_message = MessageHandler()
@app.route('/api/list-favorites', methods=['GET', 'POST'])
def listFavorites():
if request.method == 'POST':
list = db.session.query(Favorites).all()
faves = []
for fave in list:
faves.append([fave.link, fave.id])
return json_message.faves_list(faves)
else:
msg = "Can't manage the request type..."
return json_message.create("danger", msg)
@app.route('/api/load-favorite/<_id>', methods=['GET', 'POST'])
def loadFavorite(_id):
if request.method == 'POST':
try:
ID = int(_id)
fave = db.session.query(Favorites).filter_by(id = ID).first()
view = get_view()
view.set_path_with_sub_path(fave.link)
return '{"refresh": "true"}'
except Exception as e:
print(repr(e))
msg = "Incorrect Favorites ID..."
return json_message.create("danger", msg)
else:
msg = "Can't manage the request type..."
return json_message.create("danger", msg)
@app.route('/api/manage-favorites/<_action>', methods=['GET', 'POST'])
def manageFavorites(_action):
if request.method == 'POST':
ACTION = _action.strip()
view = get_view()
sub_path = view.get_current_sub_path()
if ACTION == "add":
fave = Favorites(link = sub_path)
db.session.add(fave)
msg = "Added to Favorites successfully..."
elif ACTION == "delete":
fave = db.session.query(Favorites).filter_by(link = sub_path).first()
db.session.delete(fave)
msg = "Deleted from Favorites successfully..."
else:
msg = "Couldn't handle action for favorites item..."
return json_message.create("danger", msg)
db.session.commit()
return json_message.create("success", msg)
else:
msg = "Can't manage the request type..."
return json_message.create("danger", msg)

92
src/core/routes/Images.py Normal file
View File

@ -0,0 +1,92 @@
# Python imports
import os
import requests
import shutil
# Lib imports
from flask import request
# App imports
# Get from __init__
from core import app
from core.utils import MessageHandler # Get simple message processor
from core.utils.tmdbscraper import scraper # Get media art scraper
json_message = MessageHandler()
tmdb = scraper.get_tmdb_scraper()
@app.route('/api/get-background-poster-trailer', methods=['GET', 'POST'])
def getPosterTrailer():
if request.method == 'GET':
info = {}
view = get_view()
dot_dots = view.get_dot_dots()
sub_path = view.get_current_sub_path()
file = sub_path.split("/")[-1]
trailer = None
if "(" in file and ")" in file:
title = file.split("(")[0].strip()
startIndex = file.index('(') + 1
endIndex = file.index(')')
date = file[startIndex:endIndex]
try:
video_data = tmdb.search(title, date)[0]
video_id = video_data["id"]
background_url = video_data["backdrop_path"]
background_pth = f"{view.get_current_directory()}/000.jpg"
tmdb_videos = tmdb.tmdbapi.get_movie(str(video_id), append_to_response="videos")["videos"]["results"]
for tmdb_video in tmdb_videos:
if "YouTube" in tmdb_video["site"]:
trailer_key = tmdb_video["key"]
trailer = f"https://www.youtube-nocookie.com/embed/{trailer_key}?start=0&autoplay=1";
if not trailer:
raise Exception("No key found. Defering to none...")
except Exception as e:
print("No trailer found...")
trailer = None
if not os.path.isfile(background_pth):
r = requests.get(background_url, stream = True)
if r.status_code == 200:
r.raw.decode_content = True
with open(background_pth,'wb') as f:
shutil.copyfileobj(r.raw, f)
view.load_directory()
print('Cover Background Image sucessfully retreived...')
else:
print('Cover Background Image Couldn\'t be retreived...')
info.update({'trailer': trailer})
info.update({'poster': background_url})
return info
@app.route('/backgrounds', methods=['GET', 'POST'])
def backgrounds():
files = []
data = os.listdir(BG_IMGS_PATH)
for file in data:
if file.lower().endswith(BG_FILE_TYPE):
files.append(file)
return json_message.backgrounds(files)
@app.route('/api/get-thumbnails', methods=['GET', 'POST'])
def getThumbnails():
if request.method == 'GET':
view = get_view()
return json_message.thumbnails( view.get_video_icons() )
else:
msg = "Can't manage the request type..."
return json_message.create("danger", msg)

125
src/core/routes/Routes.py Normal file
View File

@ -0,0 +1,125 @@
# Python imports
import os
# Lib imports
from flask import redirect
from flask import request
from flask import render_template
from flask import send_from_directory
# App imports
# Get from __init__
from core import app
from core import db
from core import Favorites
from core import oidc
from core.utils import MessageHandler # Get simple message processor
json_message = MessageHandler()
@app.route('/', methods=['GET', 'POST'])
def home():
if request.method == 'GET':
view = get_view()
_dot_dots = view.get_dot_dots()
_current_directory = view.get_current_directory()
return render_template('pages/index.html', current_directory = _current_directory, dot_dots = _dot_dots)
return render_template('error.html', title = 'Error!',
message = 'Must use GET request type...')
@app.route('/api/list-files/<_hash>', methods=['GET', 'POST'])
def listFiles(_hash = None):
if request.method == 'POST':
view = get_view()
dot_dots = view.get_dot_dots()
if dot_dots[0][1] == _hash: # Refresh
view.load_directory()
elif dot_dots[1][1] == _hash: # Pop from dir
view.pop_from_path()
msg = "Log in with an Admin privlidged user to view the requested path!"
is_locked = view.is_folder_locked(_hash)
if is_locked and not oidc.user_loggedin:
return json_message.create("danger", msg)
elif is_locked and oidc.user_loggedin:
isAdmin = oidc.user_getfield("isAdmin")
if isAdmin != "yes" :
return json_message.create("danger", msg)
if dot_dots[0][1] != _hash and dot_dots[1][1] != _hash:
path = view.get_path_part_from_hash(_hash)
view.push_to_path(path)
error_msg = view.get_error_message()
if error_msg:
view.unset_error_message()
return json_message.create("danger", error_msg)
sub_path = view.get_current_sub_path()
files = view.get_files_formatted()
fave = db.session.query(Favorites).filter_by(link = sub_path).first()
in_fave = "true" if fave else "false"
files.update({'in_fave': in_fave})
return files
else:
msg = "Can't manage the request type..."
return json_message.create("danger", msg)
@app.route('/api/file-manager-action/<_type>/<_hash>', methods=['GET', 'POST'])
def fileManagerAction(_type, _hash = None):
view = get_view()
if _type == "reset-path" and _hash == "None":
view.set_to_home()
msg = "Returning to home directory..."
return json_message.create("success", msg)
folder = view.get_current_directory()
file = view.get_path_part_from_hash(_hash)
fpath = os.path.join(folder, file)
logger.debug(fpath)
if _type == "files":
logger.debug(f"Downloading:\n\tDirectory: {folder}\n\tFile: {file}")
return send_from_directory(directory=folder, filename=file)
if _type == "remux":
# NOTE: Need to actually implimint a websocket to communicate back to client that remux has completed.
# As is, the remux thread hangs until completion and client tries waiting until server reaches connection timeout.
# I.E....this is stupid but for now works better than nothing
good_result = view.remux_video(_hash, fpath)
if good_result:
return '{"path":"static/remuxs/' + _hash + '.mp4"}'
else:
msg = "Remuxing: Remux failed or took too long; please, refresh the page and try again..."
return json_message.create("success", msg)
if _type == "remux":
stream_target = view.remux_video(_hash, fpath)
# NOTE: Positionally protecting actions further down that are privlidged
# Be aware of ordering!
msg = "Log in with an Admin privlidged user to do this action!"
if not oidc.user_loggedin:
return json_message.create("danger", msg)
elif oidc.user_loggedin:
isAdmin = oidc.user_getfield("isAdmin")
if isAdmin != "yes" :
return json_message.create("danger", msg)
if _type == "run-locally":
msg = "Opened media..."
view.open_file_locally(fpath)
return json_message.create("success", msg)

View File

@ -0,0 +1,9 @@
from . import Images
from . import CRUD
from . import Routes
from . import Favorites
from .pages import Flask_Login
from .pages import Flask_Register
from .pages import OIDC_Login
from .pages import OIDC_Register
from .pages import LoginManager

View File

@ -0,0 +1,37 @@
# Python imports
# Lib imports
from flask import request, render_template, flash, redirect, url_for
from flask_login import current_user, login_user, logout_user
# App imports
from core import app, bcrypt, db, User, LoginForm
from core.utils import MessageHandler # Get simple message processor
msgHandler = MessageHandler()
@app.route('/app-login', methods=['GET', 'POST'])
def app_login():
if current_user.is_authenticated:
return redirect(url_for("home"))
_form = LoginForm()
if _form.validate_on_submit():
user = db.session.query(User).filter(User.username == _form.username.data).first()
if user and bcrypt.check_password_hash(user.password, _form.password.data):
login_user(user, remember=False)
flash("Logged in successfully!", "success")
return redirect(url_for("home"))
flash("Username or password incorrect! Please try again...", "danger")
return render_template('pages/login.html', form = _form)
@app.route('/app-logout')
def app_logout():
logout_user()
flash("Logged out successfully!", "success")
return redirect(url_for("home"))

View File

@ -0,0 +1,30 @@
# Python imports
# Lib imports
from flask import request, render_template, url_for, redirect, flash
# App imports
from core import app, bcrypt, db, current_user, RegisterForm # Get from __init__
from core.models import User
from core.utils import MessageHandler # Get simple message processor
msgHandler = MessageHandler()
@app.route('/app-register', methods=['GET', 'POST'])
def app_register():
if current_user.is_authenticated:
return redirect(url_for("home"))
_form = RegisterForm()
if _form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(_form.password.data).decode("utf-8")
user = User(username = _form.username.data, email = _form.email.data, password = hashed_password)
db.session.add(user)
db.session.commit()
flash("Account created successfully!", "success")
return redirect(url_for("login"))
return render_template('pages/register.html',
form = _form)

View File

@ -0,0 +1,43 @@
# Python imports
# Lib imports
from flask import redirect, url_for, flash
# App imports
from core import app
ROUTE = app.config['LOGIN_PATH']
@app.route('/login', methods=['GET', 'POST'])
def login():
if ROUTE == "OIDC":
return redirect(url_for("oidc_login"))
if ROUTE == "FLASK_LOGIN":
return redirect(url_for("app_login"))
flash("No Login Path Accessable! Please contact an Administrator!", "danger")
return redirect(url_for("home"))
@app.route('/logout')
def logout():
if ROUTE == "OIDC":
return redirect(url_for("oidc_logout"))
if ROUTE == "FLASK_LOGIN":
return redirect(url_for("app_logout"))
flash("No Logout Path Accessable! Please contact an Administrator!", "danger")
return redirect(url_for("home"))
@app.route('/register', methods=['GET', 'POST'])
def register():
if ROUTE == "OIDC":
return redirect(url_for("oidc_register"))
if ROUTE == "FLASK_LOGIN":
return redirect(url_for("app_register"))
flash("No Register Path Accessable! Please contact an Administrator!", "danger")
return redirect(url_for("home"))

View File

@ -0,0 +1,27 @@
# Python imports
# Lib imports
from flask import request, redirect, flash
# App imports
from ... import app, oidc
@app.route('/oidc-login', methods=['GET', 'POST'])
@oidc.require_login
def oidc_login():
print(request)
return redirect("/")
@app.route('/oidc-logout', methods=['GET', 'POST'])
@oidc.require_login
def oidc_logout():
oidc.logout()
flash("Logged out successfully!", "success")
# NOTE: Need to redirect to logout on OIDC server to end session there too.
# If not, we can hit login url again and get same token until it expires.
return redirect( oidc.client_secrets.get('issuer')
+ '/protocol/openid-connect/logout?redirect_uri='
+ app.config['APP_REDIRECT_URI'])

View File

@ -0,0 +1,31 @@
# Python imports
# Lib imports
from flask import request, render_template, url_for, redirect, flash
# App imports
from ... import app, oidc, db # Get from __init__
from ...utils import MessageHandler # Get simple message processor
msgHandler = MessageHandler()
@app.route('/oidc-register', methods=['GET', 'POST'])
def oidc_register():
if oidc.user_loggedin:
return redirect("/home")
_form = RegisterForm()
if _form.validate_on_submit():
# TODO: Create...
# NOTE: Do a requests api here maybe??
# hashed_password = bcrypt.generate_password_hash(_form.password.data).decode("utf-8")
# user = User(username=_form.username.data, password=hashed_password)
# db.session.add(user)
# db.session.commit()
flash("Account created successfully!", "success")
return redirect("/login")
return render_template('pages/register.html', form = _form)

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-123" viewBox="0 0 16 16">
<path d="M2.873 11.297V4.142H1.699L0 5.379v1.137l1.64-1.18h.06v5.961h1.174Zm3.213-5.09v-.063c0-.618.44-1.169 1.196-1.169.676 0 1.174.44 1.174 1.106 0 .624-.42 1.101-.807 1.526L4.99 10.553v.744h4.78v-.99H6.643v-.069L8.41 8.252c.65-.724 1.237-1.332 1.237-2.27C9.646 4.849 8.723 4 7.308 4c-1.573 0-2.36 1.064-2.36 2.15v.057h1.138Zm6.559 1.883h.786c.823 0 1.374.481 1.379 1.179.01.707-.55 1.216-1.421 1.21-.77-.005-1.326-.419-1.379-.953h-1.095c.042 1.053.938 1.918 2.464 1.918 1.478 0 2.642-.839 2.62-2.144-.02-1.143-.922-1.651-1.551-1.714v-.063c.535-.09 1.347-.66 1.326-1.678-.026-1.053-.933-1.855-2.359-1.845-1.5.005-2.317.88-2.348 1.898h1.116c.032-.498.498-.944 1.206-.944.703 0 1.206.435 1.206 1.07.005.64-.504 1.106-1.2 1.106h-.75v.96Z"/>
</svg>

After

Width:  |  Height:  |  Size: 870 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-activity" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M6 2a.5.5 0 0 1 .47.33L10 12.036l1.53-4.208A.5.5 0 0 1 12 7.5h3.5a.5.5 0 0 1 0 1h-3.15l-1.88 5.17a.5.5 0 0 1-.94 0L6 3.964 4.47 8.171A.5.5 0 0 1 4 8.5H.5a.5.5 0 0 1 0-1h3.15l1.88-5.17A.5.5 0 0 1 6 2Z"/>
</svg>

After

Width:  |  Height:  |  Size: 367 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alarm-fill" viewBox="0 0 16 16">
<path d="M6 .5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1H9v1.07a7.001 7.001 0 0 1 3.274 12.474l.601.602a.5.5 0 0 1-.707.708l-.746-.746A6.97 6.97 0 0 1 8 16a6.97 6.97 0 0 1-3.422-.892l-.746.746a.5.5 0 0 1-.707-.708l.602-.602A7.001 7.001 0 0 1 7 2.07V1h-.5A.5.5 0 0 1 6 .5zm2.5 5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5zM.86 5.387A2.5 2.5 0 1 1 4.387 1.86 8.035 8.035 0 0 0 .86 5.387zM11.613 1.86a2.5 2.5 0 1 1 3.527 3.527 8.035 8.035 0 0 0-3.527-3.527z"/>
</svg>

After

Width:  |  Height:  |  Size: 626 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alarm" viewBox="0 0 16 16">
<path d="M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9V5.5z"/>
<path d="M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1h-3zm1.038 3.018a6.093 6.093 0 0 1 .924 0 6 6 0 1 1-.924 0zM0 3.5c0 .753.333 1.429.86 1.887A8.035 8.035 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5zM13.5 1c-.753 0-1.429.333-1.887.86a8.035 8.035 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1z"/>
</svg>

After

Width:  |  Height:  |  Size: 711 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-bottom" viewBox="0 0 16 16">
<rect width="4" height="12" x="6" y="1" rx="1"/>
<path d="M1.5 14a.5.5 0 0 0 0 1v-1zm13 1a.5.5 0 0 0 0-1v1zm-13 0h13v-1h-13v1z"/>
</svg>

After

Width:  |  Height:  |  Size: 271 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-center" viewBox="0 0 16 16">
<path d="M8 1a.5.5 0 0 1 .5.5V6h-1V1.5A.5.5 0 0 1 8 1zm0 14a.5.5 0 0 1-.5-.5V10h1v4.5a.5.5 0 0 1-.5.5zM2 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7z"/>
</svg>

After

Width:  |  Height:  |  Size: 315 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-end" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M14.5 1a.5.5 0 0 0-.5.5v13a.5.5 0 0 0 1 0v-13a.5.5 0 0 0-.5-.5z"/>
<path d="M13 7a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7z"/>
</svg>

After

Width:  |  Height:  |  Size: 318 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-middle" viewBox="0 0 16 16">
<path d="M6 13a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1v10zM1 8a.5.5 0 0 0 .5.5H6v-1H1.5A.5.5 0 0 0 1 8zm14 0a.5.5 0 0 1-.5.5H10v-1h4.5a.5.5 0 0 1 .5.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 316 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-start" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1.5 1a.5.5 0 0 1 .5.5v13a.5.5 0 0 1-1 0v-13a.5.5 0 0 1 .5-.5z"/>
<path d="M3 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7z"/>
</svg>

After

Width:  |  Height:  |  Size: 318 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-top" viewBox="0 0 16 16">
<rect width="4" height="12" rx="1" transform="matrix(1 0 0 -1 6 15)"/>
<path d="M1.5 2a.5.5 0 0 1 0-1v1zm13-1a.5.5 0 0 1 0 1V1zm-13 0h13v1h-13V1z"/>
</svg>

After

Width:  |  Height:  |  Size: 287 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alt" viewBox="0 0 16 16">
<path d="M1 13.5a.5.5 0 0 0 .5.5h3.797a.5.5 0 0 0 .439-.26L11 3h3.5a.5.5 0 0 0 0-1h-3.797a.5.5 0 0 0-.439.26L5 13H1.5a.5.5 0 0 0-.5.5zm10 0a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 0-1h-3a.5.5 0 0 0-.5.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 326 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app-indicator" viewBox="0 0 16 16">
<path d="M5.5 2A3.5 3.5 0 0 0 2 5.5v5A3.5 3.5 0 0 0 5.5 14h5a3.5 3.5 0 0 0 3.5-3.5V8a.5.5 0 0 1 1 0v2.5a4.5 4.5 0 0 1-4.5 4.5h-5A4.5 4.5 0 0 1 1 10.5v-5A4.5 4.5 0 0 1 5.5 1H8a.5.5 0 0 1 0 1H5.5z"/>
<path d="M16 3a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 387 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app" viewBox="0 0 16 16">
<path d="M11 2a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3h6zM5 1a4 4 0 0 0-4 4v6a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4H5z"/>
</svg>

After

Width:  |  Height:  |  Size: 282 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-apple" viewBox="0 0 16 16">
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516.024.034 1.52.087 2.475-1.258.955-1.345.762-2.391.728-2.43zm3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422.212-2.189 1.675-2.789 1.698-2.854.023-.065-.597-.79-1.254-1.157a3.692 3.692 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56.244.729.625 1.924 1.273 2.796.576.984 1.34 1.667 1.659 1.899.319.232 1.219.386 1.843.067.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758.347-.79.505-1.217.473-1.282z"/>
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516.024.034 1.52.087 2.475-1.258.955-1.345.762-2.391.728-2.43zm3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422.212-2.189 1.675-2.789 1.698-2.854.023-.065-.597-.79-1.254-1.157a3.692 3.692 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56.244.729.625 1.924 1.273 2.796.576.984 1.34 1.667 1.659 1.899.319.232 1.219.386 1.843.067.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758.347-.79.505-1.217.473-1.282z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-archive-fill" viewBox="0 0 16 16">
<path d="M12.643 15C13.979 15 15 13.845 15 12.5V5H1v7.5C1 13.845 2.021 15 3.357 15h9.286zM5.5 7h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1 0-1zM.8 1a.8.8 0 0 0-.8.8V3a.8.8 0 0 0 .8.8h14.4A.8.8 0 0 0 16 3V1.8a.8.8 0 0 0-.8-.8H.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 359 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-archive" viewBox="0 0 16 16">
<path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1V2zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5H2zm13-3H1v2h14V2zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 401 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-down" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4.854 14.854a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L4 13.293V3.5A2.5 2.5 0 0 1 6.5 1h8a.5.5 0 0 1 0 1h-8A1.5 1.5 0 0 0 5 3.5v9.793l3.146-3.147a.5.5 0 0 1 .708.708l-4 4z"/>
</svg>

After

Width:  |  Height:  |  Size: 350 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-left" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1.146 4.854a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H12.5A2.5 2.5 0 0 1 15 6.5v8a.5.5 0 0 1-1 0v-8A1.5 1.5 0 0 0 12.5 5H2.707l3.147 3.146a.5.5 0 1 1-.708.708l-4-4z"/>
</svg>

After

Width:  |  Height:  |  Size: 349 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M14.854 4.854a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 4H3.5A2.5 2.5 0 0 0 1 6.5v8a.5.5 0 0 0 1 0v-8A1.5 1.5 0 0 1 3.5 5h9.793l-3.147 3.146a.5.5 0 0 0 .708.708l4-4z"/>
</svg>

After

Width:  |  Height:  |  Size: 350 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-up" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4.854 1.146a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L4 2.707V12.5A2.5 2.5 0 0 0 6.5 15h8a.5.5 0 0 0 0-1h-8A1.5 1.5 0 0 1 5 12.5V2.707l3.146 3.147a.5.5 0 1 0 .708-.708l-4-4z"/>
</svg>

After

Width:  |  Height:  |  Size: 349 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-bar-down" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 3.5a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13a.5.5 0 0 1-.5-.5zM8 6a.5.5 0 0 1 .5.5v5.793l2.146-2.147a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-3-3a.5.5 0 0 1 .708-.708L7.5 12.293V6.5A.5.5 0 0 1 8 6z"/>
</svg>

After

Width:  |  Height:  |  Size: 375 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-bar-left" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M12.5 15a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5zM10 8a.5.5 0 0 1-.5.5H3.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L3.707 7.5H9.5a.5.5 0 0 1 .5.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 375 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-bar-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M6 8a.5.5 0 0 0 .5.5h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L12.293 7.5H6.5A.5.5 0 0 0 6 8zm-2.5 7a.5.5 0 0 1-.5-.5v-13a.5.5 0 0 1 1 0v13a.5.5 0 0 1-.5.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 375 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-bar-up" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 10a.5.5 0 0 0 .5-.5V3.707l2.146 2.147a.5.5 0 0 0 .708-.708l-3-3a.5.5 0 0 0-.708 0l-3 3a.5.5 0 1 0 .708.708L7.5 3.707V9.5a.5.5 0 0 0 .5.5zm-7 2.5a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13a.5.5 0 0 1-.5-.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 376 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
</svg>

After

Width:  |  Height:  |  Size: 352 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z"/>
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z"/>
</svg>

After

Width:  |  Height:  |  Size: 359 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-circle-fill" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.5 4.5a.5.5 0 0 0-1 0v5.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V4.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 321 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-circle" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8.5 4.5a.5.5 0 0 0-1 0v5.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V4.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 370 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-left-circle-fill" viewBox="0 0 16 16">
<path d="M16 8A8 8 0 1 0 0 8a8 8 0 0 0 16 0zm-5.904-2.803a.5.5 0 1 1 .707.707L6.707 10h2.768a.5.5 0 0 1 0 1H5.5a.5.5 0 0 1-.5-.5V6.525a.5.5 0 0 1 1 0v2.768l4.096-4.096z"/>
</svg>

After

Width:  |  Height:  |  Size: 326 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-left-circle" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-5.904-2.854a.5.5 0 1 1 .707.708L6.707 9.95h2.768a.5.5 0 1 1 0 1H5.5a.5.5 0 0 1-.5-.5V6.475a.5.5 0 1 1 1 0v2.768l4.096-4.097z"/>
</svg>

After

Width:  |  Height:  |  Size: 377 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-left-square-fill" viewBox="0 0 16 16">
<path d="M2 16a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2zm8.096-10.803L6 9.293V6.525a.5.5 0 0 0-1 0V10.5a.5.5 0 0 0 .5.5h3.975a.5.5 0 0 0 0-1H6.707l4.096-4.096a.5.5 0 1 0-.707-.707z"/>
</svg>

After

Width:  |  Height:  |  Size: 363 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-left-square" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M15 2a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V2zM0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm10.096 3.146a.5.5 0 1 1 .707.708L6.707 9.95h2.768a.5.5 0 1 1 0 1H5.5a.5.5 0 0 1-.5-.5V6.475a.5.5 0 1 1 1 0v2.768l4.096-4.097z"/>
</svg>

After

Width:  |  Height:  |  Size: 451 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-left" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M2 13.5a.5.5 0 0 0 .5.5h6a.5.5 0 0 0 0-1H3.707L13.854 2.854a.5.5 0 0 0-.708-.708L3 12.293V7.5a.5.5 0 0 0-1 0v6z"/>
</svg>

After

Width:  |  Height:  |  Size: 286 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-right-circle-fill" viewBox="0 0 16 16">
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm5.904-2.803a.5.5 0 1 0-.707.707L9.293 10H6.525a.5.5 0 0 0 0 1H10.5a.5.5 0 0 0 .5-.5V6.525a.5.5 0 0 0-1 0v2.768L5.904 5.197z"/>
</svg>

After

Width:  |  Height:  |  Size: 326 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-right-circle" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.854 5.146a.5.5 0 1 0-.708.708L9.243 9.95H6.475a.5.5 0 1 0 0 1h3.975a.5.5 0 0 0 .5-.5V6.475a.5.5 0 1 0-1 0v2.768L5.854 5.146z"/>
</svg>

After

Width:  |  Height:  |  Size: 379 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-down-right-square-fill" viewBox="0 0 16 16">
<path d="M14 16a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12zM5.904 5.197 10 9.293V6.525a.5.5 0 0 1 1 0V10.5a.5.5 0 0 1-.5.5H6.525a.5.5 0 0 1 0-1h2.768L5.197 5.904a.5.5 0 0 1 .707-.707z"/>
</svg>

After

Width:  |  Height:  |  Size: 365 B

Some files were not shown because too many files have changed in this diff Show More