v1.0.1 release |

- infinite scroll pagination added
- custom port fixed
- some code clean up
- fix admin page route (band-aid)
- add on hover tooltip for truncated item names
- add convertToPaginatedArray util function
- fix console errors due to having js files on pages i wasnt needed on
| nice
pull/15/head
Facinorous-420 2 years ago
parent e95fd10f0e
commit 5049465a78

@ -1,6 +1,6 @@
{
"name": "dick",
"version": "1.0.0",
"version": "1.0.1",
"description": "A frontend for ASS",
"main": "./dist/dashboard.js",
"repository": {

@ -2,7 +2,7 @@ import { Response } from "express"
import { parseAuthFile, parseDataFile } from "./utils/assJSONStructure"
import { RenderOptions } from "./typings/Pager"
import { ASS_DOMAIN, ASS_SECURE, STAFF_IDS } from "./constants"
import { convertTimestamp, formatSize } from "./utils/utils"
import { convertTimestamp, convertToPaginatedArray, formatSize } from "./utils/utils"
import { ASSUser, ASSItem } from "./typings/ASSTypes"
import { IExtendedRequest } from "./typings/express-ext"
@ -87,7 +87,8 @@ export class Pager {
// I feel like this could be done better, but I created an object filled with useful variables for the user data to be rendered on the pages
const usersDataObj = {
data: usersData,
data: convertToPaginatedArray(usersData,50),
totalFiles: usersData.length,
allImages: usersData.filter(item=> item.type.includes('image')),
allVideos: usersData.filter(item=> item.type.includes('video')),
allAudio: usersData.filter(item=> item.type.includes('audio')),
@ -105,7 +106,7 @@ export class Pager {
if (options.params.userID) {
const targetData = data.filter((item: ASSItem ) => (item: { owner: string }) => item.owner == options.params.userID)
targetDataObj = {
data: targetData,
data: convertToPaginatedArray(targetData,50),
allImages: targetData.filter(item=> item.type.includes('image')),
allVideos: targetData.filter(item=> item.type.includes('video')),
allAudio: targetData.filter(item=> item.type.includes('audio')),
@ -118,7 +119,7 @@ export class Pager {
}
}
*/
const baseData = {
assDomain: ASS_DOMAIN,
assSecure: ASS_SECURE,

@ -18,7 +18,7 @@ import { PORT } from "./constants"
const app = express()
// Setting up express
app.set("port", process.env.PORT || 3000)
app.set("port", PORT || 3000)
app.set("trust proxy", true)
app.set("views", path.join(__dirname, "../views"))
app.set("view engine", "ejs")

@ -43,4 +43,10 @@
height: 800px;
width: 800px;
}
.name-tooltip {
@apply invisible;
}
.has-name-tooltip:hover .name-tooltip {
@apply visible z-50;
}
}

@ -1,205 +1,196 @@
// DOM Ready Function
function ready(fn) {
if (document.readyState != 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
if (document.readyState != 'loading') {
fn()
} else {
document.addEventListener('DOMContentLoaded', fn)
}
}
}
// DOM Ready Called
ready(function () {
const openFile = document.getElementById('openFile');
const showFile = document.getElementById('showFile');
const traMove = document.getElementsByClassName('traMove');
const tabsProfile = document.getElementsByClassName('tabsProfile');
const tabsProfileContent = document.getElementsByClassName('tabsProfileContent');
const dropdownNavBtn = document.getElementById('dropdownNavBtn');
const dropdownNav = document.getElementById('dropdownNav');
const dropdownProfileBtn = document.getElementsByClassName('dropdownProfileBtn');
const dropdownProfile = document.getElementsByClassName('dropdownProfile');
const dropdownSearchBtn = document.getElementsByClassName('dropdownSearchBtn');
const dropdownSearch = document.getElementsByClassName('dropdownSearch');
const dropdownFileBtn = document.getElementsByClassName('dropdownFileBtn');
// Open File Manager Event
/*
function fireFileManager(){
openFile.addEventListener('click', () => {
showFile.click();
});
}
fireFileManager();
*/
// Dropdown Navbar Event
function fireDropdownNav(){
dropdownNavBtn.addEventListener('click', () => {
if (dropdownNav.classList.contains('-translate-y-full')) {
dropdownNav.classList.remove('-translate-y-full');
dropdownNav.classList.add('translate-y-0', 'ease-linear');
// When the event get fired, every 'traMove' id attibute will move down
Array.prototype.forEach.call(traMove, (e) => {
e.classList.remove('-translate-y-32');
e.classList.add('translate-y-0', 'ease-linear');
});
} else {
dropdownNav.classList.add('-translate-y-full',);
dropdownNav.classList.remove('translate-y-0', 'ease-linear');
// Then if you hide the event, every 'traMove' id attribute will back to normal
Array.prototype.forEach.call(traMove, (e) => {
e.classList.add('-translate-y-32');
e.classList.remove('translate-y-0', 'ease-linear');
});
}
});
}
fireDropdownNav();
// Dropdown Profile Event
function fireDropdownProfile() {
Array.prototype.forEach.call(dropdownProfileBtn, function (e, index) {
e.addEventListener('click', () => {
var timer;
if (dropdownProfile[index].classList.contains('opacity-0')) {
window.clearTimeout(timer);
dropdownProfile[index].classList.remove('opacity-0', 'translate-y-6', 'invisible');
dropdownProfile[index].classList.add('translate-y-0', 'opacity-100');
} else {
dropdownProfile[index].classList.add('opacity-0', 'translate-y-6');
dropdownProfile[index].classList.remove('translate-y-0', 'opacity-100');
//Set timer to hide the dropdown
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownProfile attribute
timer = window.setTimeout( () => {
dropdownProfile[index].classList.add('invisible');
}, 250);
}
});
// Click outside event
window.addEventListener('click', (eve) => {
if (!dropdownProfileBtn[index].contains(eve.target) && !dropdownProfile[index].contains(eve.target)) {
dropdownProfile[index].classList.add('opacity-0', 'translate-y-6');
dropdownProfile[index].classList.remove('translate-y-0', 'opacity-100');
// Same as above
timer = window.setTimeout( () => {
dropdownProfile[index].classList.add('invisible');
}, 250);
}
});
});
}
fireDropdownProfile();
// Dropdown Search Event
function fireDropdownSearch() {
Array.prototype.forEach.call(dropdownSearchBtn, (e, index) => {
e.addEventListener('click', () => {
var timer;
if (dropdownSearch[index].classList.contains('opacity-0')) {
window.clearTimeout(timer);
dropdownSearch[index].classList.remove('opacity-0', 'translate-y-6', 'invisible');
dropdownSearch[index].classList.add('translate-y-0', 'opacity-100');
} else {
dropdownSearch[index].classList.add('opacity-0', 'translate-y-6');
dropdownSearch[index].classList.remove('translate-y-0', 'opacity-100');
//Set timer to hide the dropdown
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownSearch attribute
timer = window.setTimeout( () => {
dropdownSearch[index].classList.add('invisible');
}, 250);
}
});
// Click outside event
window.addEventListener('click', (eve) => {
if (!dropdownSearchBtn[index].contains(eve.target) && !dropdownSearch[index].contains(eve.target)) {
dropdownSearch[index].classList.add('opacity-0', 'translate-y-6');
dropdownSearch[index].classList.remove('translate-y-0', 'opacity-100');
// Same as above
timer = window.setTimeout( () => {
dropdownSearch[index].classList.add('invisible');
}, 250);
}
});
});
}
fireDropdownSearch();
// Dropdown File Event
function fireDropdownFile() {
Array.prototype.forEach.call(dropdownFileBtn, (e, index) => {
const findSibling = e.parentElement.children[1];
e.addEventListener('click', () => {
var timer;
if (findSibling.classList.contains('opacity-0')) {
window.clearTimeout(timer);
findSibling.classList.remove('opacity-0', 'translate-y-6', 'invisible');
findSibling.classList.add('translate-y-0', 'opacity-100');
} else {
findSibling.classList.add('opacity-0', 'translate-y-6');
findSibling.classList.remove('translate-y-0', 'opacity-100');
//Set timer to hide the dropdown
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownFile attribute
timer = window.setTimeout( () => {
findSibling.classList.add('invisible');
}, 250);
}
});
// Click outside event
window.addEventListener('click', (eve) => {
if (!dropdownFileBtn[index].contains(eve.target) && !findSibling.contains(eve.target)) {
findSibling.classList.add('opacity-0', 'translate-y-6');
findSibling.classList.remove('translate-y-0', 'opacity-100');
// Same as above
timer = window.setTimeout( () => {
findSibling.classList.add('invisible');
}, 250);
}
});
});
}
fireDropdownFile();
// Tabs Profile Event
function fireTabsProfile(){
//File Manager Tab = 0
tabsProfile[0].addEventListener('click', () => {
if (tabsProfile[1].classList.contains('border-b-2', 'border-purple-400', 'font-semibold')) {
tabsProfile[1].classList.remove('border-b-2', 'border-purple-400', 'font-semibold');
tabsProfile[0].classList.add('border-b-2', 'border-purple-400', 'font-semibold');
tabsProfileContent[1].classList.add('hidden');
tabsProfileContent[0].classList.remove('hidden');
}
});
//Config Gen Tab = 1
tabsProfile[1].addEventListener('click', () => {
if (tabsProfile[0].classList.contains('border-b-2', 'border-purple-400', 'font-semibold')) {
tabsProfile[0].classList.remove('border-b-2', 'border-purple-400', 'font-semibold');
tabsProfile[1].classList.add('border-b-2', 'border-purple-400', 'font-semibold');
tabsProfileContent[0].classList.add('hidden');
tabsProfileContent[1].classList.remove('hidden');
}
});
}
fireTabsProfile();
});
// DOM Ready Called
ready(function () {
const openFile = document.getElementById('openFile')
const showFile = document.getElementById('showFile')
const traMove = document.getElementsByClassName('traMove')
const tabsProfile = document.getElementsByClassName('tabsProfile')
const tabsProfileContent = document.getElementsByClassName('tabsProfileContent')
const dropdownNavBtn = document.getElementById('dropdownNavBtn')
const dropdownNav = document.getElementById('dropdownNav')
const dropdownProfileBtn = document.getElementsByClassName('dropdownProfileBtn')
const dropdownProfile = document.getElementsByClassName('dropdownProfile')
const dropdownSearchBtn = document.getElementsByClassName('dropdownSearchBtn')
const dropdownSearch = document.getElementsByClassName('dropdownSearch')
const dropdownFileBtn = document.getElementsByClassName('dropdownFileBtn')
// Open File Manager Event
/*
function fireFileManager(){
openFile.addEventListener('click', () => {
showFile.click();
});
}
fireFileManager();
*/
// Dropdown Navbar Event
function fireDropdownNav(){
dropdownNavBtn.addEventListener('click', () => {
if (dropdownNav.classList.contains('-translate-y-full')) {
dropdownNav.classList.remove('-translate-y-full')
dropdownNav.classList.add('translate-y-0', 'ease-linear')
// When the event get fired, every 'traMove' id attibute will move down
Array.prototype.forEach.call(traMove, (e) => {
e.classList.remove('-translate-y-32')
e.classList.add('translate-y-0', 'ease-linear')
})
} else {
dropdownNav.classList.add('-translate-y-full',)
dropdownNav.classList.remove('translate-y-0', 'ease-linear')
// Then if you hide the event, every 'traMove' id attribute will back to normal
Array.prototype.forEach.call(traMove, (e) => {
e.classList.add('-translate-y-32')
e.classList.remove('translate-y-0', 'ease-linear')
})
}
})
}
fireDropdownNav()
// Dropdown Profile Event
function fireDropdownProfile() {
Array.prototype.forEach.call(dropdownProfileBtn, function (e, index) {
e.addEventListener('click', () => {
var timer
if (dropdownProfile[index].classList.contains('opacity-0')) {
window.clearTimeout(timer)
dropdownProfile[index].classList.remove('opacity-0', 'translate-y-6', 'invisible')
dropdownProfile[index].classList.add('translate-y-0', 'opacity-100')
} else {
dropdownProfile[index].classList.add('opacity-0', 'translate-y-6')
dropdownProfile[index].classList.remove('translate-y-0', 'opacity-100')
//Set timer to hide the dropdown
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownProfile attribute
timer = window.setTimeout( () => {
dropdownProfile[index].classList.add('invisible')
}, 250)
}
})
// Click outside event
window.addEventListener('click', (eve) => {
if (!dropdownProfileBtn[index].contains(eve.target) && !dropdownProfile[index].contains(eve.target)) {
dropdownProfile[index].classList.add('opacity-0', 'translate-y-6')
dropdownProfile[index].classList.remove('translate-y-0', 'opacity-100')
// Same as above
timer = window.setTimeout( () => {
dropdownProfile[index].classList.add('invisible')
}, 250)
}
})
})
}
fireDropdownProfile()
// Dropdown Search Event
function fireDropdownSearch() {
Array.prototype.forEach.call(dropdownSearchBtn, (e, index) => {
e.addEventListener('click', () => {
var timer
if (dropdownSearch[index].classList.contains('opacity-0')) {
window.clearTimeout(timer)
dropdownSearch[index].classList.remove('opacity-0', 'translate-y-6', 'invisible')
dropdownSearch[index].classList.add('translate-y-0', 'opacity-100')
} else {
dropdownSearch[index].classList.add('opacity-0', 'translate-y-6')
dropdownSearch[index].classList.remove('translate-y-0', 'opacity-100')
//Set timer to hide the dropdown
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownSearch attribute
timer = window.setTimeout( () => {
dropdownSearch[index].classList.add('invisible')
}, 250)
}
})
// Click outside event
window.addEventListener('click', (eve) => {
if (!dropdownSearchBtn[index].contains(eve.target) && !dropdownSearch[index].contains(eve.target)) {
dropdownSearch[index].classList.add('opacity-0', 'translate-y-6')
dropdownSearch[index].classList.remove('translate-y-0', 'opacity-100')
// Same as above
timer = window.setTimeout( () => {
dropdownSearch[index].classList.add('invisible')
}, 250)
}
})
})
}
fireDropdownSearch()
// Dropdown File Event
function fireDropdownFile() {
Array.prototype.forEach.call(dropdownFileBtn, (e, index) => {
const findSibling = e.parentElement.children[1]
e.addEventListener('click', () => {
var timer
if (findSibling.classList.contains('opacity-0')) {
window.clearTimeout(timer)
findSibling.classList.remove('opacity-0', 'translate-y-6', 'invisible')
findSibling.classList.add('translate-y-0', 'opacity-100')
} else {
findSibling.classList.add('opacity-0', 'translate-y-6')
findSibling.classList.remove('translate-y-0', 'opacity-100')
//Set timer to hide the dropdown
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownFile attribute
timer = window.setTimeout( () => {
findSibling.classList.add('invisible')
}, 250)
}
})
// Click outside event
window.addEventListener('click', (eve) => {
if (!dropdownFileBtn[index].contains(eve.target) && !findSibling.contains(eve.target)) {
findSibling.classList.add('opacity-0', 'translate-y-6')
findSibling.classList.remove('translate-y-0', 'opacity-100')
// Same as above
timer = window.setTimeout( () => {
findSibling.classList.add('invisible')
}, 250)
}
})
})
}
fireDropdownFile()
// Tabs Profile Event
function fireTabsProfile(){
//File Manager Tab = 0
tabsProfile[0].addEventListener('click', () => {
if (tabsProfile[1].classList.contains('border-b-2', 'border-purple-400', 'font-semibold')) {
tabsProfile[1].classList.remove('border-b-2', 'border-purple-400', 'font-semibold')
tabsProfile[0].classList.add('border-b-2', 'border-purple-400', 'font-semibold')
tabsProfileContent[1].classList.add('hidden')
tabsProfileContent[0].classList.remove('hidden')
}
});
//Config Gen Tab = 1
tabsProfile[1].addEventListener('click', () => {
if (tabsProfile[0].classList.contains('border-b-2', 'border-purple-400', 'font-semibold')) {
tabsProfile[0].classList.remove('border-b-2', 'border-purple-400', 'font-semibold')
tabsProfile[1].classList.add('border-b-2', 'border-purple-400', 'font-semibold')
tabsProfileContent[0].classList.add('hidden')
tabsProfileContent[1].classList.remove('hidden')
}
})
}
fireTabsProfile()
})

@ -16,6 +16,7 @@ export const userRoutes = (app: Router) => {
)
// External Viewing Of Other User Profiles (Admin Only)
/* Currently fucks up the admin route :)
app.get(
"/:userID",
authCheck,
@ -33,4 +34,5 @@ export const userRoutes = (app: Router) => {
})
}
)
*/
}

@ -63,15 +63,12 @@ export const isObjectEmpty = (obj: object) => {
* @param decimals The amount of decimals you wish to add to the converted value
*/
export const formatSize = (kb: number, decimals = 2) => {
if (kb === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(kb) / Math.log(k));
return parseFloat((kb / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
if (kb === 0) return '0 Bytes'
const k = 1024
const dm = decimals < 0 ? 0 : decimals
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(kb) / Math.log(k))
return parseFloat((kb / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
}
/**
@ -80,4 +77,17 @@ export const formatSize = (kb: number, decimals = 2) => {
*/
export const convertTimestamp = (unixTimestamp: number) => {
return new Date(unixTimestamp)
}
/**
*
* @param data Original array of many objects
* @param itemsPerPage The amount of objects you wish you wish to have in each array in the new array
*/
export const convertToPaginatedArray = (data: Array<any>, itemsPerPage: number) => {
let paginatedArray: Array<any> = []
for (let i=0; i < data.length; i += itemsPerPage) {
paginatedArray.push(data.slice(i,i + itemsPerPage))
}
return paginatedArray
}

@ -11,30 +11,30 @@ module.exports = {
extend: {
colors: {
darktheme: {
primary: '#1b253b',
secondary: '#232d45',
secondaryHover: '#222f4d',
tertiary: '#28334e',
tertiaryHover: '#2d3c5d'
primary: '#1b253b', //bg-primary
secondary: '#232d45', //bg-secondary
secondaryHover: '#222f4d', //bg-secondary-hover
tertiary: '#28334e', //bg-tertiary
tertiaryHover: '#2d3c5d' //bg-tertiary-hover
},
darkthemeText: {
primary: colors.white,
secondary: colors.slate[400],
accentPrimary: colors.purple[400],
accentSecondary: colors.purple[800]
primary: colors.white, //text-color-primary
secondary: colors.slate[400], //text-color-secondary
accentPrimary: colors.purple[400], //text-color-accent
accentSecondary: colors.purple[800] //text-color-accentsecondary
},
lighttheme: {
primary: '#2b910e',
secondary: '#f1f5f9',
secondaryHover: '#294ab3',
tertiary: colors.white,
tertiaryHover: '#eef1f6'
primary: '#2b910e', //bg-primary
secondary: '#f1f5f9', //bg-secondary
secondaryHover: '#294ab3', //bg-secondary-hover
tertiary: colors.white, //bg-tertiary
tertiaryHover: '#eef1f6' //bg-tertiary-hover
},
lightthemeText: {
primary: colors.slate[800],
secondary: colors.slate[400],
accentPrimary: colors.purple[600],
accentSecondary: colors.purple[800]
primary: colors.slate[800], //text-color-primary
secondary: colors.slate[400], //text-color-secondary
accentPrimary: colors.purple[600], //text-color-accent
accentSecondary: colors.purple[800] //text-color-accentsecondary
}
},
maxHeight: {

@ -36,7 +36,7 @@
</p>
</div>
<div class="text-color-primary sm:text-lg md:text-3xl font-medium leading-8 mt-6">
<%= usersDataObj.data.length %>
<%= usersDataObj.totalFiles %>
</div>
<div class="sm:text-sm md:text-base text-base text-slate-500 mt-1">
Total Files
@ -148,6 +148,4 @@
<div class="tabsProfileContent hidden">
<%- include("./tabs/configgen") %>
</div>
</div>
</div>

@ -5,117 +5,19 @@
<!--This is where we will place the upper menu when we are ready for that-->
<!-- Files -->
<div class="flex flex-row flex-wrap justify-center lg:justify-start gap-3 pt-3 lg:gap-4">
<% if (usersDataObj.data.length == 0) { %>
<p class="text-orange-700 dark:text-orange-400 pl-5">Looks like you have no files!<br/>
Upload some to see some action here.</p>
<% } else { %>
<% usersDataObj.data.forEach(item => { %>
<div class="bg-tertiary h-auto w-36 cursor-pointer rounded-md p-3 transition duration-300 ease-in-out hover:scale-105 hover:shadow-xl sm:w-44 md:w-[139px] lg:w-40">
<!-- File Header -->
<div class="flex flex-row flex-nowrap items-center justify-between">
<!--We will add this and style it when we add multi-delete, for now eggplant
<input class="accent-blue-700 rounded-md h-3 w-4 md:h-4 md:w-5 lg:h-4 lg:w-4" type="checkbox" />
-->
<span>🍆</span>
<div class="relative">
<!-- File Dropdown Button -->
<button class="dropdownFileBtn relative text-gray-400">
<svg class="relative h-3 w-3 md:h-4 md:w-4 lg:h-4 lg:w-4" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<circle class="relative" cx="12" cy="12" r="1"></circle>
<circle class="relative" cx="12" cy="5" r="1"></circle>
<circle class="relative" cx="12" cy="19" r="1"></circle>
</svg>
</button>
<!-- End File Dropdown Button -->
<!-- File Dropdown -->
<div class="dropdownFile duration-250 bg-tertiary invisible absolute top-6 right-0 z-10 h-auto w-36 translate-y-6 rounded-lg opacity-0 shadow-lg transition ease-linear">
<!-- File Dropdown Menu-->
<div class="grid grid-cols-1 px-3 py-2 space-y-2 shadow-xl">
<button onClick="copyLink<%= `('${item.id}')` %>" data-tooltip-target=<%= `${item.id}-copy` %> data-tooltip-trigger="click" class="tooltip dropdown-item w-full rounded-md p-2 text-color-primary hover:bg-tertiary-hover">
<i data-lucide="link" class="w-4 h-4 mr-2 inline"></i>
Copy Link
</button>
<div id=<%= `${item.id}-copy` %> role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
Link copied to clipboard!
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<button onClick="deleteItem<%= `('${item.id}', '${item.deleteId}')` %>" data-tooltip-target=<%= `${item.id}-delete` %> data-tooltip-trigger="click"
class="tooltip dropdown-item w-full rounded-md p-2 text-color-primary hover:bg-tertiary-hover">
<i data-lucide="trash" class="w-4 h-4 mr-2 inline"></i>
Delete
</button>
<div id=<%= `${item.id}-delete` %> role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
Item is being deleted, page will refresh when finished.
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
<!-- End File Dropdown Menu-->
</div>
<!-- File Dropdown -->
</div>
</div>
<!-- End File Header -->
<div class="space-y-3">
<% if (item.type == 'image') { %>
<a target="_blank" href=<%= assSecure ? `https://${assDomain}/${item.id}` : `http://${assDomain}/${item.id}` %>>
<div class="mt-2 flex flex-wrap items-center justify-center">
<img class="h-20 w-auto rounded-md object-cover object-center md:h-24 lg:h-24"
src=<%= assSecure ? `https://${assDomain}/${item.id}/direct` : `http://${assDomain}/${item.id}/direct`%> alt=<%= item.originalName %> />
</div>
</a>
<% } else { %>
<a target="_blank" href=<%= assSecure ? `https://${assDomain}/${item.id}` : `http://${assDomain}/${item.id}` %>>
<div class="relatiive mt-2 flex flex-wrap items-center justify-center">
<svg class="relative h-20 w-auto fill-gray-400 md:h-24 lg:h-24"
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="0" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.5 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V7.5L14.5 2z"> </path>
<polyline points="14 2 14 8 20 8"></polyline>
</svg>
<div class="absolute text-sm font-medium text-color-primary">
<%= item.fileExtension %>
</div>
</div>
</a>
<% } %>
<div class="text-center">
<a target="_blank" href=<%= assSecure ? `https://${assDomain}/${item.id}` : `http://${assDomain}/${item.id}` %> class="text-base font-medium text-color-primary md:text-lg lg:text-lg">
<p class="truncate hover:overflow-visible"><%= item.originalName %></p>
</a>
<div class="text-xs text-gray-400 md:text-sm lg:text-sm">
<%= item.timestamp.toISOString().split('T')[0] %>
</div>
<div class="text-xs text-gray-400">
<% const k = 1024 %>
<% const dm = 2 %>
<% const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] %>
<% const i = Math.floor(Math.log(item.size) / Math.log(k)) %>
<% const convertedSize = parseFloat((item.size / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; item.size %>
<%= convertedSize %>
</div>
</div>
</div>
</div>
<% }) %>
<% } %>
</div>
<% if (usersDataObj.data.length == 0) { %>
<p class="text-orange-700 dark:text-orange-400 pl-5">Looks like you have no files!<br/>
Upload some to see some action here.</p>
<% } else { %>
<div id="appendItems" class=" flex flex-row flex-wrap justify-center gap-3 pt-3 lg:gap-4"></div>
<div id="loading" class="pt-4 flex flex-nowrap justify-center"></div>
<% } %>
<!-- End Files -->
</div>
<!--This is where we will place the pagination when we are ready for that-->
<iframe src="" id="HiddenFrame" class="hidden w-0 h-0"></iframe>
<!-- Copy Link & Delete Item Button Logic -->
<script>
function copyLink(itemId) {
const itemLink = <%= assSecure %> ? `https://<%= assDomain %>/${itemId}` : `http://<%= assDomain %>/${itemId}`
@ -129,7 +31,10 @@
setTimeout(function() {
document.getElementById("HiddenFrame").src=''
document.location.reload()
}, 250);
}, 250)
}
</script>
</script>
<% if (usersDataObj.data.length > 0) { %>
<%- include("./filemanager/infinite-scroll/logic-js") %>
<% } %>

@ -0,0 +1,74 @@
<div class="bg-tertiary h-auto w-36 cursor-pointer rounded-md p-3 transition duration-300 ease-in-out hover:scale-105 hover:shadow-xl sm:w-44 md:w-[139px] lg:w-40">
<!-- File Header -->
<div class="flex flex-row flex-nowrap items-center justify-between">
<!--We will add this and style it when we add multi-delete, for now eggplant
<input class="accent-blue-700 rounded-md h-3 w-4 md:h-4 md:w-5 lg:h-4 lg:w-4" type="checkbox" />
-->
<span>🍆</span>
<div class="relative">
<!-- File Dropdown Button -->
<button class="dropdownFileBtn relative text-gray-400">
<svg class="relative h-3 w-3 md:h-4 md:w-4 lg:h-4 lg:w-4" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<circle class="relative" cx="12" cy="12" r="1"></circle>
<circle class="relative" cx="12" cy="5" r="1"></circle>
<circle class="relative" cx="12" cy="19" r="1"></circle>
</svg>
</button>
<!-- End File Dropdown Button -->
<!-- File Dropdown -->
<div class="dropdownFile duration-250 bg-tertiary invisible absolute top-6 right-0 z-10 h-auto w-36 translate-y-6 rounded-lg opacity-0 shadow-lg transition ease-linear">
<!-- File Dropdown Menu-->
<div class="grid grid-cols-1 px-3 py-2 space-y-2 shadow-xl">
<button onClick="copyLink('${id}')" data-tooltip-target="${id}-copy" data-tooltip-trigger="click" class="tooltip dropdown-item w-full rounded-md p-2 text-color-primary hover:bg-tertiary-hover">
<i data-lucide="link" class="w-4 h-4 mr-2 inline"></i>
Copy Link
</button>
<div id="${id}-copy" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
Link copied to clipboard!
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<button onClick="deleteItem('${id}', '${deleteId}')" data-tooltip-target="${id}-delete" data-tooltip-trigger="click"
class="tooltip dropdown-item w-full rounded-md p-2 text-color-primary hover:bg-tertiary-hover">
<i data-lucide="trash" class="w-4 h-4 mr-2 inline"></i>
Delete
</button>
<div id="${id}-delete" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
Item is being deleted, page will refresh when finished.
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
<!-- End File Dropdown Menu-->
</div>
<!-- File Dropdown -->
</div>
</div>
<!-- End File Header -->
<div class="space-y-3">
<a target="_blank" href='${itemURL}'>
<div class="mt-2 flex flex-wrap items-center justify-center">
<img class="h-20 w-auto rounded-md object-cover object-center md:h-24 lg:h-24"
src='${itemURL}/direct' alt=${originalName} />
</div>
</a>
<div class="has-name-tooltip text-center">
<a target="_blank" href='${itemURL}' class="text-base font-medium text-color-primary hover:text-color-accent md:text-lg lg:text-lg">
<span class='name-tooltip rounded shadow-lg p-2 bg-primary text-color-primary -mt-8'>${originalName}</span>
<p class="truncate">${originalName}</p>
</a>
<div class="text-xs text-gray-400 md:text-sm lg:text-sm">
${timeStamp}
</div>
<div class="text-xs text-gray-400">
${size}
</div>
</div>
</div>
</div>

@ -0,0 +1,12 @@
<div class="flex-1">
<div class="flex justify-center flex-col items-center space-y-2">
<svg class="animate-spin text-purple-400 w-10 h-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 2v6h-6"></path>
<path d="M3 12a9 9 0 0 1 15-6.7L21 8"></path>
<path d="M3 22v-6h6"></path>
<path d="M21 12a9 9 0 0 1-15 6.7L3 16"></path>
</svg>
<span class="font-semibold text-white">Loading more files...</span>
</div>
</div>

@ -0,0 +1,184 @@
<script>
function ready(fn) {
if (document.readyState != 'loading') {
fn()
} else {
document.addEventListener('DOMContentLoaded', fn)
}
}
ready(function () {
// Set the default number of current pagination
let defaultNumber = 0
// Transfer ASS system variables from EJS to front end
const assSecure = <%- assSecure %>;
const assDomain = '<%- assDomain %>'
// Variables used in converting size
const k = 1024
const dm = 2
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
// Data (I dont like this being here ngl)
const userData = <%- JSON.stringify(usersDataObj.data) %>;
// When pagination reach the end, its still add more items with undefined array, remove it
const currentPaginationRaw = []
const currentPagination = currentPaginationRaw.filter(Boolean)
function appendHtml(items, card, type) {
items.insertAdjacentHTML(type, card)
}
function showData() {
// Check if the current pahe has no data
if (currentPagination.length == 0) {
// Push the first userData page to currentPagination
currentPagination.push(userData[0])
}
// Loops the first userData page when page loaded
userData[0].forEach(item => {
const items = document.getElementById('appendItems')
const i = Math.floor(Math.log(item.size) / Math.log(k))
let card = ``
// Variables used in card items
const { originalName, fileExtension, id, deleteId } = item
const size = parseFloat((item.size / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
const timeStamp = new Date(item.timestamp).toISOString().split('T')[0]
const itemURL = assSecure ? `https://${assDomain}/${item.id}` : `http://${assDomain}/${item.id}`
// The card items
if (item.type == 'image') {
card = `<%- include("./image-item") %>`
} else {
card = `<%- include("./other-item") %>`
}
appendHtml(items, card, 'beforeend')
})
}
showData()
// Once the user reaches the end of the currentPagination content (bottom of page)
document.addEventListener('scroll', () => {
const { scrollTop, scrollHeight, clientHeight } = document.documentElement
// Refresh icon with spin animation
const spinLoading = `<%- include("./loading") %>`
// No more items to load text
const endLoop = `<%- include("./no-more-data") %>`
// Loading items animation
const loading = document.getElementById('loading')
// Add class invisible to hide loading animation at first load
loading.classList.add('invisible')
// Check if the users scroll distance has reached the bottom of page
if (clientHeight + scrollTop >= scrollHeight - 2) {
// Show the hidden loading animation
if (loading.classList.contains('invisible')) {
loading.classList.remove('invisible')
}
// Check if the userData length is greater than current pagination data, if so still need to load more pages
if (userData.length > currentPagination.length) {
// Show spin lodaing
loading.innerHTML = spinLoading
// Set timer to load more data
setTimeout(appendData, 2000)
// else its end of the pagination
} else {
// Show no items icon
loading.innerHTML = endLoop
}
}
})
// This will append more cards to the page when we reach the bottom if there is more to load
function appendData() {
// Check if currentPagination is greater than the default number.
if (defaultNumber < currentPagination.length) {
defaultNumber++
const morePage = defaultNumber
if (morePage != userData.length) {
// Push the next page from userData into current paginationData
currentPagination.push(userData[morePage])
// Loops through the new pagination data
userData[morePage].forEach(item => {
const items = document.getElementById('appendItems')
const i = Math.floor(Math.log(item.size) / Math.log(k))
let card = ``
// Variables used in card items
const { originalName, fileExtension, id, deleteId } = item
const size = parseFloat((item.size / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
const timeStamp = new Date(item.timestamp).toISOString().split('T')[0]
const itemURL = assSecure ? `https://${assDomain}/${item.id}` : `http://${assDomain}/${item.id}`
// The card items
if (item.type == 'image') {
card = `<%- include("./image-item") %>`
} else {
card = `<%- include("./other-item") %>`
}
appendHtml(items, card, 'beforeend')
})
// We trigger the onClick events for the drop down buttons on the appended cards
triggerAppearedHtml()
}
}
}
// Store the current items
let currentItems = []
function triggerAppearedHtml() {
// Grab the dropdown buttons in all the page for the item cards
const trigger = document.querySelectorAll('.dropdownFileBtn')
let totalLengthCurrentPagination = 0
for (let index = 0; index < currentPagination.length; index++) {
// Get the length of every current pagination
const lengthEveryItems = currentPagination[index].length
// Store it into totalLengthCurrentPagination
totalLengthCurrentPagination += lengthEveryItems
}
// Get the last items from currentItems
const lastItemAppeared = currentItems.slice(-1)[0]
// If there is no lastItemAppeared the pagination has not fired yet, so set it with the first [0] current pagination length,
// Which is the first page in the first reload, else the pagination has fired then it will choose lastItemAppeared
const firstLoop = !lastItemAppeared ? currentPagination[0].length : lastItemAppeared
// Setting this into a smaller variable name to reduce line size & be more readable in the loop below
const lastLoop = totalLengthCurrentPagination
let Items = []
// Finally loop through the newest items
for (let index = firstLoop; index < lastLoop; index++) {
Items.push(index)
// Add the click event listener to the buttons
trigger[index].addEventListener('click', () => {
let timer
const findSibling = trigger[index].nextElementSibling
if (findSibling.classList.contains('opacity-0')) {
window.clearTimeout(timer)
findSibling.classList.remove('opacity-0', 'translate-y-6', 'invisible')
findSibling.classList.add('translate-y-0', 'opacity-100')
} else {
findSibling.classList.add('opacity-0', 'translate-y-6')
findSibling.classList.remove('translate-y-0', 'opacity-100')
//Set timer to hide the dropdown
//the value of timer '250' must be same as the tailwind class 'duration-250' in the class dropdownFile attribute
timer = window.setTimeout(() => {
findSibling.classList.add('invisible')
}, 250)
}
// Click outside event
window.addEventListener('click', (eve) => {
if (!trigger[index].contains(eve.target) && !findSibling.contains(eve.target)) {
findSibling.classList.add('opacity-0', 'translate-y-6')
findSibling.classList.remove('translate-y-0', 'opacity-100')
// Same as above
timer = window.setTimeout(() => {
findSibling.classList.add('invisible')
}, 250)
}
})
})
}
// Slice Items to get the current items, and push it to currentItems
currentItems.push(Items.slice(-1)[0] + 1)
}
})
</script>

@ -0,0 +1,11 @@
<div class="flex-1">
<div class="flex justify-center flex-col items-center space-y-2">
<svg class="text-purple-400 w-10 h-10" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M16 16s-1.5-2-4-2-4 2-4 2"></path>
<line x1="9" y1="9" x2="9.01" y2="9"></line>
<line x1="15" y1="9" x2="15.01" y2="9"></line>
</svg>
<span class="font-semibold text-white">You have no more files to load!</span>
</div>
</div>

@ -0,0 +1,81 @@
<div class="bg-tertiary h-auto w-36 cursor-pointer rounded-md p-3 transition duration-300 ease-in-out hover:scale-105 hover:shadow-xl sm:w-44 md:w-[139px] lg:w-40">
<!-- File Header -->
<div class="flex flex-row flex-nowrap items-center justify-between">
<!--We will add this and style it when we add multi-delete, for now eggplant
<input class="accent-blue-700 rounded-md h-3 w-4 md:h-4 md:w-5 lg:h-4 lg:w-4" type="checkbox" />
-->
<span>🍆</span>
<div class="relative">
<!-- File Dropdown Button -->
<button class="dropdownFileBtn relative text-gray-400">
<svg class="relative h-3 w-3 md:h-4 md:w-4 lg:h-4 lg:w-4" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<circle class="relative" cx="12" cy="12" r="1"></circle>
<circle class="relative" cx="12" cy="5" r="1"></circle>
<circle class="relative" cx="12" cy="19" r="1"></circle>
</svg>
</button>
<!-- End File Dropdown Button -->
<!-- File Dropdown -->
<div class="dropdownFile duration-250 bg-tertiary invisible absolute top-6 right-0 z-10 h-auto w-36 translate-y-6 rounded-lg opacity-0 shadow-lg transition ease-linear">
<!-- File Dropdown Menu-->
<div class="grid grid-cols-1 px-3 py-2 space-y-2 shadow-xl">
<button onClick="copyLink('${id}')" data-tooltip-target="${id}-copy" data-tooltip-trigger="click" class="tooltip dropdown-item w-full rounded-md p-2 text-color-primary hover:bg-tertiary-hover">
<i data-lucide="link" class="w-4 h-4 mr-2 inline"></i>
Copy Link
</button>
<div id="${id}-copy" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
Link copied to clipboard!
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<button onClick="deleteItem('${id}', '${deleteId}')" data-tooltip-target="${id}-delete" data-tooltip-trigger="click"
class="tooltip dropdown-item w-full rounded-md p-2 text-color-primary hover:bg-tertiary-hover">
<i data-lucide="trash" class="w-4 h-4 mr-2 inline"></i>
Delete
</button>
<div id="${id}-delete" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
Item is being deleted, page will refresh when finished.
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
<!-- End File Dropdown Menu-->
</div>
<!-- File Dropdown -->
</div>
</div>
<!-- End File Header -->
<div class="space-y-3">
<a target="_blank" href='${itemURL}'>
<div class="relatiive mt-2 flex flex-wrap items-center justify-center">
<svg class="relative h-20 w-auto fill-gray-400 md:h-24 lg:h-24"
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="0" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.5 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V7.5L14.5 2z"> </path>
<polyline points="14 2 14 8 20 8"></polyline>
</svg>
<div class="absolute text-sm font-medium text-color-primary">
${fileExtension}
</div>
</div>
</a>
<div class="has-name-tooltip text-center">
<a target="_blank" href='${itemURL}' class="text-base font-medium text-color-primary hover:text-color-accent hober:bg-secondary md:text-lg lg:text-lg">
<span class='name-tooltip rounded shadow-lg p-2 bg-primary text-color-primary -mt-8'>${originalName}</span>
<p class="truncate">${originalName}</p>
</a>
<div class="text-xs text-gray-400 md:text-sm lg:text-sm">
${timeStamp}
</div>
<div class="text-xs text-gray-400">
${size}
</div>
</div>
</div>
</div>

@ -1,40 +0,0 @@
<div class="mt-8 flex flex-nowrap justify-end">
<div class="basis-full px-0 md:basis-2/3 md:px-12 lg:basis-3/4 lg:px-12">
<div
class="flex flex-row flex-wrap items-center justify-center space-x-3 text-sm text-color-primary md:justify-start md:text-base lg:justify-start lg:text-base">
<button>
<svg class="h-auto w-4 md:w-6 lg:w-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="11 17 6 12 11 7"></polyline>
<polyline points="18 17 13 12 18 7"></polyline>
</svg>
</button>
<button>
<svg class="h-auto w-4 md:w-6 lg:w-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</button>
<button class="bg-tertiary rounded-mdpy-2 px-4">1</button>
<button class="py-2 px-4">2</button>
<button class="py-2 px-4">3</button>
<button>
<svg class="h-auto w-4 md:w-6 lg:w-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</button>
<button>
<svg class="h-auto w-4 md:w-6 lg:w-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="13 17 18 12 13 7"></polyline>
<polyline points="6 17 11 12 6 7"></polyline>
</svg>
</button>
</div>
</div>
</div>

@ -15,5 +15,4 @@
</div>
</section>
<script src="/js/app.js"></script>
<script src="/js/theme/theme-switcher.js"></script>
<script src="/js/app.js"></script>

@ -1,3 +1,10 @@
<script src="/js/app.js"></script>
<script src="/js/components.js"></script>
<script src="/js/theme/theme-switcher.js"></script>
<% if(path=='/' ){ %>
<script src="/js/app.js"></script>
<script src="/js/components.js"></script>
<script src="/js/theme/theme-switcher.js"></script>
<% } %>
<% if(path=='/admin' ){ %>
<script src="/js/app.js"></script>
<script src="/js/theme/theme-switcher.js"></script>
<% } %>

@ -1,3 +1,4 @@
<!DOCTYPE html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

@ -6,5 +6,4 @@
<%//We will enable this when we add the server logic to toggle registration if(path == '/register'){ %> <%-// include("../pages/public/register") %> <%// } %>
<!-- /Page Content -->
</div>
</section>
<%- include("./partials/footer") %>
</section>

@ -18,6 +18,8 @@
</div>
</section>
</main>
<%- include("./partials/footer") %>
</body>
</html>
Loading…
Cancel
Save