Skip to content

Commit

Permalink
Fixed env vars and added better performance
Browse files Browse the repository at this point in the history
  • Loading branch information
SamTV12345 committed Nov 12, 2024
1 parent bc00783 commit 3e6ea97
Show file tree
Hide file tree
Showing 8 changed files with 338 additions and 196 deletions.
250 changes: 79 additions & 171 deletions app/plugins/page.tsx
Original file line number Diff line number Diff line change
@@ -1,127 +1,89 @@
'use client'

import {useUIStore} from "../../src/store/store";
import {useEffect, useMemo, useState} from "react";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faSearch} from "@fortawesome/free-solid-svg-icons";
import {PluginCom} from "../../src/components/Plugin";
import {useEffect} from "react";
import axios, {AxiosResponse} from "axios";
import {PluginMappedResponseVal, PluginResponse, ServerStats} from "../../src/store/Plugin";
import Link from "next/link";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from "@/components/ui/select"
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"
import { Checkbox } from "@/components/ui/checkbox"
import {PluginResponse, ServerStats} from "../../src/store/Plugin";
import {PluginViewerHeader} from "../../src/pagesToDisplay/PluginViewerHeader";
import {PluginsList} from "../../src/pagesToDisplay/PluginsList";
import {PluginsFooter} from "../../src/pagesToDisplay/PluginsFooter";

const IMAGE_REGEX = /\b(https?:\/\/[\S]+?(?:png|jpe?g|gif))\b/;

const productionURL = "https://static.etherpad.org"

export default function PluginViewer() {
const setPlugin = useUIStore(state => state.setPlugins)
const plugins = useUIStore(state => state.plugins)
const pluginSearchTerm = useUIStore(state => state.pluginSearchTerm)
const setPluginSearchTerm = useUIStore(state => state.setPluginSearchTerm)
const [officalOnly, setOfficalOnly] = useState<boolean>(false)
const totalDownloads = useMemo(()=>{
if (!plugins) return 0
return Object.values(plugins).reduce((acc, val) => acc + val.downloads, 0)
},[plugins])

const [sortKey, setSortKey] = useState<string>('newest')
const [downloadPercentage, setDownloadAveragePercentage] = useState<number>(0)
const totalCount = useMemo(()=>{
if (!plugins) return 0
return Object.keys(plugins).length
}, [plugins])
const filteredPlugins = useMemo(()=>{
if (!plugins) return plugins
let highestDownload = 0
function performSearch() {
let lastModified: number = 0;
fetch(productionURL+'/plugins.viewer.json')
.then(response => {
lastModified = Date.parse(response.headers.get('last-modified')!)
return response.json() as Promise<PluginResponse>
}).then((result) => {
let list = Object.values(result);
let keywordsTmp: { [key: string]: number } = {};
let downloadMaxCount = 0;
let downloadCount = 0;

let regex = /\b(https?:\/\/[\S]+?(?:png|jpe?g|gif))\b/;

list.forEach(function (plugin, index) {
if (plugin.keywords) {
plugin.keywords.forEach(function (key) {
if (keywordsTmp[key]) {
keywordsTmp[key]++
} else {
keywordsTmp[key] = 1
}
})
}

const entry: PluginMappedResponseVal[] = Object.entries(plugins).filter((plugin) => {
if (officalOnly && plugin[1].official == false) {
return false
}
if (plugin[1].keywords && pluginSearchTerm) {
for (let i = 0; i < plugin[1].keywords.length; i++) {
let keyword = plugin[1].keywords[i];
if (keyword.toUpperCase().indexOf(pluginSearchTerm) > -1) {
return true;
downloadCount += plugin.downloads || 0;
if (plugin.downloads > downloadMaxCount) {
downloadMaxCount = plugin.downloads;
}

if (plugin.readme) {
let results = plugin.readme.match(regex);
if (results) {
results.forEach(function (item, i) {
results[i] = item.replace('http://', 'https://');
})
list[index].images = results.filter((e, pos) => pos === results.indexOf(e));
}
}
}
})

if (plugin[1].downloads > highestDownload) {
highestDownload = plugin[1].downloads
let tmp: { [key: string]: number } = {};
for (let key in keywordsTmp) {
let count = keywordsTmp[key]
if (count > 1) {
tmp[key] = count
}
}

return true
}).map(plugin=> {
return {
...plugin[1],
name: plugin[0],
image: plugin[1].readme.match(IMAGE_REGEX)?.[0]
} satisfies PluginMappedResponseVal
useUIStore.getState().setPluginData(
{
downloadCount,
downloadMaxCount,
list,
downloadAverageCount: downloadCount / list.length,
lastModified,
keywords: [],
searchKeyword: '',
sortKey: 'newest',
filterOfficial: false
})
})

setDownloadAveragePercentage(highestDownload)
entry.sort(function (a, b) {
if (sortKey === 'newest') {
if (a.time === undefined) {
return 1;
} else if (b.time === undefined) {
return -1;
}
return a.time < b.time ? 1 : -1;
} else if (sortKey === 'updated') {
if (a.modified === undefined) {
return 1;
} else if (b.modified === undefined) {
return -1;
}
return a.modified < b.modified ? 1 : -1;
} else {
return a.downloads < b.downloads ? 1 : -1;
}})

const chunkSize = 30;
const chunkedArray = []
for (let i = 0; i < entry.length; i += chunkSize) {
const chunk = entry.slice(i, i + chunkSize);
chunkedArray.push(chunk)
}


return chunkedArray
}, [plugins, officalOnly, pluginSearchTerm])
const [currentPage, setCurrentPage] = useState<number>(0)
const pluginsToDisplay = useMemo(()=>{
if (!filteredPlugins) return []
return filteredPlugins[currentPage]
}, [currentPage, filteredPlugins])


function performSearch() {
axios.get(process.env!.NEXT_PUBLIC_API_URL!+'/plugins.viewer.json')
.then((data: AxiosResponse<PluginResponse>) => {
setPlugin(data.data)
})
.catch(error => {
console.log("error", error);
});
}

function performStatSearch() {
axios.get(process.env!.NEXT_PUBLIC_API_URL!+'/server-stats.json')
.then((data: AxiosResponse<ServerStats>)=>{
useUIStore.setState({serverStats:data.data})
axios.get(productionURL + '/server-stats.json')
.then((data: AxiosResponse<ServerStats>) => {
useUIStore.setState({serverStats: data.data})
})
}

Expand All @@ -132,74 +94,20 @@ export default function PluginViewer() {

return (
<div className="flex items-center flex-col bg-gray-800">
<style jsx global>{`
#root {
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
}
html {
background-color: rgb(31 41 55 / 1);
}
`}</style>
<div className="w-full md:w-3/4">
<h1 className="text-4xl font-bold text-white text-left w-full">PluginViewer</h1>
<div className="text-white text-2xl">
This page lists all available plugins for etherpad hosted on npm. <div
className=""><span className="text-primary">{totalDownloads}</span> downloads
of {totalCount} plugins in the last month. </div>
For more information about Etherpad visit <Link href="https://etherpad.org"
target="_blank">https://etherpad.org</Link>
</div>
<h2 className="text-3xl text-primary">Plugins ({totalCount})</h2>
<div className="flex gap-5 border-t-[1px] border-b-[1px] border-gray-600 pt-2 pb-2">
<span>
<Checkbox className="self-center text-white" checked={officalOnly} onCheckedChange={() => setOfficalOnly(!officalOnly)} id="c1"/>
<label className="text-white ml-2 mt-auto text-2xl" htmlFor="c1">
Only official plugins
</label>
</span>
<span className="text-white mt-auto text-2xl">Sort by:</span>
<Select onValueChange={(v: string)=>{
setSortKey(v)
}}>
<SelectTrigger className="bg-gray-700 text-white border-[1px] w-1/2 pt-1 pl-2">{sortKey}</SelectTrigger>
<SelectContent className="bg-gray-700 text-white">
<SelectItem className="bg-gray-700" onClick={() => {
setSortKey('created')
}} value={"created"}>Created</SelectItem>
<SelectItem className="bg-gray-700" value="updated">Updated</SelectItem>
<SelectItem className="bg-gray-700" value="newest">Newest</SelectItem>
</SelectContent>
</Select>
</div>
<div className="mt-5 mb-5 relative flex self-center w-full">
<input className="w-full rounded border-[1px] pt-2 pb-2 pl-8 pr-1 bg-gray-700 text-white"
placeholder="Search for plugins to install" value={pluginSearchTerm}
onChange={v => setPluginSearchTerm(v.target.value)}/>
<FontAwesomeIcon icon={faSearch} className="absolute left-2 mt-[0.7rem] text-white"/>
</div>
<PluginViewerHeader/>

<div className="grid grid-cols-1 gap-3 w-full mb-5">
{
pluginsToDisplay.map((plugin, i) => {
return <PluginCom key={i} index={i} plugins={plugin} averageDownload={downloadPercentage}/>
})
}
<Pagination>
<PaginationContent className="text-white">
<PaginationItem>
<PaginationPrevious onClick={()=>{
if (currentPage === 0) return
setCurrentPage(currentPage-1)
}} />
</PaginationItem>
{
filteredPlugins && filteredPlugins.map((_, i) => {
return <PaginationItem key={i}>
<PaginationLink isActive={currentPage === i} onClick={()=>{
setCurrentPage(i)
}}>{i+1}</PaginationLink>
</PaginationItem>
})
}
<PaginationItem>
<PaginationNext className="text-white" aria-disabled={filteredPlugins && filteredPlugins.length -1 == currentPage} onClick={()=>{
if (currentPage === filteredPlugins!.length -1) return
setCurrentPage(currentPage+1)
}} />
</PaginationItem>
</PaginationContent>
</Pagination>
<PluginsList/>
<PluginsFooter/>
</div>
</div>
</div>
Expand Down
39 changes: 29 additions & 10 deletions src/components/Plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import sanitizeHtml from 'sanitize-html'
import * as marked from 'marked'
import {PluginAuthorComp} from "./PluginAuthorComp.tsx";
import {Chip} from "./Chip.tsx";
import {useUIStore} from "@/store/store.ts";
type PluginProps = {
plugins: PluginResponseVal,
index: number,
averageDownload: number
index: number
}
TimeAgo.addDefaultLocale(en)
const timeago = new TimeAgo('en-US')
Expand All @@ -19,16 +19,36 @@ const formatTime = (isoDate: string) => {
return timeago.format(new Date(isoDate))
}

export const PluginCom: FC<PluginProps> = ({plugins, averageDownload}) => {

export const PluginCom: FC<PluginProps> = ({plugins}) => {
const downloadAverage = useUIStore(state => state.pluginData)
const renderMarkdown = (text: string) => {
const unsafeHtml = marked.parse(text) as string
const sanitizedHtml = sanitizeHtml(unsafeHtml)
return {__html: sanitizedHtml}
}
const popularityScore = useMemo(() => (plugins.downloads / (averageDownload)), [plugins.downloads, averageDownload])

return <div className="text-gray-400 border-[1px] p-2 rounded">
const downloadStatsStyle = useMemo(()=>{
if (!downloadAverage) return 'rgb(100, 100, 100) 50%'

let downloadPercentage: number;
if (plugins.downloads < downloadAverage?.downloadAverageCount) {
downloadPercentage = plugins.downloads / downloadAverage.downloadAverageCount * 50;
} else {
downloadPercentage = 50 + (plugins.downloads / downloadAverage.downloadMaxCount * 50);
}

let downloadStatsStyle = 'rgb(100, 100, 100) ' + downloadPercentage + '%';
if (downloadPercentage > 50) {
downloadStatsStyle = 'rgb(0, 200, 0) ' + downloadPercentage + '%';
} else if (downloadPercentage > 15) {
downloadStatsStyle = 'orange ' + downloadPercentage + '%';
}
return downloadStatsStyle
}, [])



return <div className="text-gray-400 border-[1px] p-2 rounded-2xl bg-gray-900 ">
<div className="flex">
<div className="text-3xl text-primary flex gap-3">
<a target={"_blank"} href={'https://www.npmjs.org/package/' + plugins.name}>{plugins.name}</a>
Expand All @@ -39,19 +59,18 @@ export const PluginCom: FC<PluginProps> = ({plugins, averageDownload}) => {
<div className="w-10 flex items-center mr-2">
<div className="w-10 border-[1px] border-white"
title={(plugins.downloads) + " downloads"}>
<div style={{width: popularityScore * 100 + "%"}}
<div style={{background: "linear-gradient(to right, " + downloadStatsStyle + ", lightgrey 1%)"}}
className="bg-primary w-10 h-5 self-center"></div>
</div>
</div>
</div>
<div>{plugins.description}</div>
<div className="flex gap-10">
{plugins.image &&
<img alt={"Image of " + plugins.name} className="w-60 object-contain" src={plugins.image}/>}
{plugins.images && plugins.images.map((img, i) => <img src={img} className="w-60 object-contain" key={plugins.name + i} alt={"Image of " + plugins.name}/>)}
{plugins.readme && <div className="w-full line-clamp-5 readme-of-plugin"
dangerouslySetInnerHTML={renderMarkdown(plugins.readme)}></div>}
</div>
<div className="mt-5 flex">
<div className="mt-5 flex mb-2">
<PluginAuthorComp name={plugins.author.name} email={plugins.author.email}/>
<span className="flex-grow"></span>
<span className="mr-5">License: {plugins.license ? plugins.license : '--'}</span>
Expand Down
Loading

0 comments on commit 3e6ea97

Please sign in to comment.