Skip to content

Commit

Permalink
feat: Extensions/Projects page on the Gate Website (#424)
Browse files Browse the repository at this point in the history
* wip - extensions page

wip, still testing pages worker

* fix issue while building with yarn

* fix yarn version hopefully

* move functions to where it's supposed to be

had functions in the wrong folder i believe

* Potential fix for duplicate repos

* Auto change title based on search mode

Automatically adjusts page title to Extensions or Minekube Libraries based on the current search mode being used.

* feat: show cached data from browser if api unreachable

Added the ability to show cached data in the browsers localstorage if the API is down/unable to be reached. If no cached data is found message shown is "Error reaching the API. To see updated results, please try again later. " If cached data is found, it is shown and a message saying "Warning: Showing locally cached results. To see updated results, please try again later. " is shown above the results.

* fix: localstorage build fix

Forgot to check for browser env when using localstorage since it breaks yarn build.

* fix: cache message showing when API is still reachable

cached message was showing due to us using cached data on second refresh to reduce api calls, we should only show the cached data message if we are using it as fallback due to api being unreachable

* real fix: cache fallback

* fix: this is annoying me, hopefully real fix this time

* fix: cache & message

i wish I could test this without commiting lol

* real fix: hopefully.

* fix: remove stuff

* Update Extensions.vue

* final fix: remove not needed message

* chore: remove yarn state

* fit hero actions to one line on wide screen

---------

Co-authored-by: robinbraemer <[email protected]>
  • Loading branch information
dilllxd and robinbraemer authored Nov 16, 2024
1 parent c94333d commit 81a1528
Show file tree
Hide file tree
Showing 11 changed files with 1,519 additions and 1,116 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

/.web/docs/.vitepress/cache
1 change: 1 addition & 0 deletions .web/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
1 change: 1 addition & 0 deletions .web/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export default defineConfig({
{text: 'Developer Guide', link: '/developers/'},
{text: 'Config', link: '/guide/config/'},
{text: 'Downloads', link: '/guide/install/'},
{text: 'Extensions', link: '/extensions'},
{
text: 'Blog',
link: 'https://connect.minekube.com/blog',
Expand Down
202 changes: 202 additions & 0 deletions .web/docs/.vitepress/theme/components/Extensions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
<template>
<div class="VPDoc px-[32px] py-[48px]">
<div class="flex flex-col gap-2 relative mx-auto max-w-[948px]">
<h1 class="text-vp-c-text-1 text-3xl font-semibold mb-4">
{{ searchMode === 'extensions' ? 'Extensions' : 'Projects using Minekube Libraries' }}
</h1>
<p class="text-vp-c-text-3 font-normal text-md mb-4">
<span v-if="searchMode === 'extensions'">
Here you can find useful extensions that can improve your Gate proxy!
<br />
To add your own extension, simply add the <code class="font-bold mx-1">gate-extension</code> topic to your repository on GitHub.
</span>
<span v-else>
Here you can find projects that use Minekube libraries on GitHub!
<br />
To add your own project, simply import any <code class="font-bold mx-1">go.minekube.com</code> library in your go.mod file.
</span>
</p>

<!-- Toggle Button for Search Mode -->
<div class="mb-4">
<label class="font-semibold mr-2">Search Mode:</label>
<button
@click="toggleSearchMode"
:class="{'bg-vp-c-brand-3 text-white': searchMode === 'extensions', 'bg-vp-c-border text-vp-c-text-1': searchMode === 'go-modules'}"
class="rounded-lg px-4 py-2 mr-2 focus:outline-none"
>
{{ searchMode === 'extensions' ? 'Extensions' : 'Minekube Libraries' }}
</button>
</div>

<!-- Search Input -->
<input
v-model="searchText"
class="rounded-lg px-3 py-2 w-[calc(100%-2px)] translate-x-[1px] bg-vp-c-bg focus:ring-vp-c-brand-2 text-vp-c-text-2 transition-colors font-base ring-vp-c-border ring-1"
placeholder="Search..."
/>

<!-- Show message when cached data is being used -->
<div v-if="isCachedData && !loading" class="my-3 text-center text-yellow-600">
<strong>Warning:</strong> Showing locally cached results. To see updated results, please try again later.
</div>

<!-- Show loading indicator while data is being fetched -->
<div v-if="loading" class="my-3 text-center">Loading...</div>

<!-- Show error message -->
<div v-if="error && !isCachedData" class="my-3 text-center text-red-600">
Error reaching the API. To see updated results, please try again later.
</div>

<!-- Display Results -->
<ul
v-else-if="filteredExtensions.length > 0"
class="grid grid-cols-1 lg:grid-cols-2 gap-2"
>
<a
v-for="item in filteredExtensions"
:key="item.name"
:href="item.url"
class="p-4 group bg-vp-c-bg transition-all flex flex-col rounded-lg border border-vp-c-border hover:border-vp-c-brand-2 animate-in fade-in-40 relative"
>
<h2 class="font-bold">
{{ item.name }}
<span class="font-normal"> by </span>
<span>{{ item.owner }}</span>
</h2>
<p class="text-vp-c-text-2 mb-2">
{{ item.description }}
</p>
<p class="text-vp-c-text-3 mt-auto flex flex-row">
<span class="mr-auto">{{ item.stars }} stars</span>
<span
class="group-hover:text-vp-c-brand-2 transition-colors"
>View on GitHub</span
>
</p>
</a>
</ul>

<!-- No Results Message -->
<p v-else class="my-3">{{ noResultsMessage }}</p>
</div>
</div>
</template>

<script>
export default {
name: "ExtensionsList",
data() {
return {
extensions: [], // To store extensions data
goModules: [], // To store go-modules data
searchText: "",
loading: false,
searchMode: "extensions", // Default mode is 'extensions'
error: null, // To store error message
isCachedData: false, // Flag to indicate if we're showing cached data
};
},
created() {
this.fetchData(); // Fetch data for both categories on initial load
this.updateTitle(); // Set the initial title based on default searchMode
},
methods: {
toggleSearchMode() {
// Toggle between 'extensions' and 'go-modules'
this.searchMode = this.searchMode === "extensions" ? "go-modules" : "extensions";
this.updateTitle(); // Update the title when searchMode changes
},
async fetchData() {
const cacheKey = "extensionsAndGoModulesData";
this.loading = true;
this.error = null; // Reset error message before fetching data
try {
// Attempt to fetch data from the API
const [extensionsResponse, goModulesResponse] = await Promise.all([
fetch("/api/extensions"),
fetch("/api/go-modules")
]);
if (!extensionsResponse.ok || !goModulesResponse.ok) {
throw new Error("Error fetching data from API");
}
const extensionsData = await extensionsResponse.json();
const goModulesData = await goModulesResponse.json();
// Process and sort data
this.extensions = extensionsData
.map(item => ({ ...item, stars: Number(item.stars) }))
.sort((a, b) => b.stars - a.stars);
this.goModules = goModulesData
.map(item => ({ ...item, stars: Number(item.stars) }))
.sort((a, b) => b.stars - a.stars);
// Cache the data if API request is successful (only if window is available)
if (typeof window !== "undefined" && window.localStorage) {
const currentTime = new Date().getTime();
localStorage.setItem(cacheKey, JSON.stringify({
extensions: this.extensions,
goModules: this.goModules,
timestamp: currentTime,
}));
}
this.isCachedData = false; // No need to show cached data warning
} catch (error) {
console.error("Error fetching data:", error);
this.error = "Error reaching the API."; // Set error message
// Check if there is cached data (only if window is available)
if (typeof window !== "undefined" && window.localStorage) {
const cachedData = JSON.parse(localStorage.getItem(cacheKey));
if (cachedData) {
this.extensions = cachedData.extensions;
this.goModules = cachedData.goModules;
this.isCachedData = true; // Indicate cached data is being used
}
}
} finally {
this.loading = false;
}
},
updateTitle() {
// Dynamically set the tab title based on the current search mode
const title = this.searchMode === "extensions"
? "Extensions | Gate Proxy"
: "Minekube Libraries | Gate Proxy";
document.title = title;
},
},
computed: {
filteredExtensions() {
const data = this.searchMode === "extensions" ? this.extensions : this.goModules;
return data.filter((item) =>
item.name.toLowerCase().includes(this.searchText.toLowerCase())
);
},
noResultsMessage() {
// If no results are found, show a generic message
if (this.filteredExtensions.length === 0) {
const message = this.searchMode === "extensions"
? "No extensions found"
: "No projects found";
return `${message}. Make sure you're typing the name correctly, or try again later.`;
}
return "";
},
},
watch: {
// Watch for changes in searchMode and update the title accordingly
searchMode() {
this.updateTitle();
},
},
};
</script>
4 changes: 3 additions & 1 deletion .web/docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import VPBadge from 'vitepress/dist/client/theme-default/components/VPBadge.vue'
import './styles/vars.css'
import type {Theme} from 'vitepress'
import Layout from "./components/Layout.vue";
import Extensions from "./components/Extensions.vue"

export default {
extends: DefaultTheme,
Layout: Layout,
enhanceApp({app}) {
app.component('VPButton', VPButton)
app.component('VPBadge', VPBadge)
app.component('Extensions', Extensions)
}
} satisfies Theme
} satisfies Theme
5 changes: 5 additions & 0 deletions .web/docs/extensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: "Extensions" # This is technically overridden by the updateTitle function on line 145 in Extensions.vue
sidebar: false
layout: Extensions
---
25 changes: 15 additions & 10 deletions .web/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@ hero:
text: Quick Start
link: /guide/quick-start
- theme: alt
text: View on GitHub
link: https://github.com/minekube/gate
text: Lite mode
link: /guide/lite
- theme: alt
text: Extensions
link: /extensions
- theme: alt
text: Join Discord
text: Discord
link: https://minekube.com/discord
- theme: alt
text: Lite mode
link: /guide/lite
text: GitHub
link: https://github.com/minekube/gate

features:
- icon: 📦
Expand All @@ -32,8 +35,9 @@ features:
linkText: Install
- icon: 🚀
title: Developer Friendly
details: Gate is written in Go, a modern programming language that is easy to learn and
has a great ecosystem of tools and libraries.
details:
Gate is written in Go, a modern programming language that is easy to learn and
has a great ecosystem of tools and libraries.
link: /developers/
linkText: Developers & Starter Template
- icon: 🌐
Expand All @@ -43,8 +47,9 @@ features:
linkText: Enable Connect
- icon: ✨️
title: Multi-Version Support
details: Gate supports Minecraft server versions 1.8 to latest and is constantly updated to support
new versions.
details:
Gate supports Minecraft server versions 1.8 to latest and is constantly updated to support
new versions.
link: /guide/compatibility
linkText: Compatibility
- icon: 🪄
Expand All @@ -54,7 +59,7 @@ features:
linkText: Lite Mode
- icon: ⚡️
title: High Performance
details: Gate is designed to be fast and efficient. A proxy that can handle thousands of players with ease.
details: Gate is designed to be fast and efficient. A proxy that can handle thousands of players with ease.
link: /guide/why
linkText: Why Gate?
---
10 changes: 10 additions & 0 deletions .web/functions/_routes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"include": [
"/api/extensions",
"/api/go-modules"
],
"exclude": [
"/*"
]
}

61 changes: 61 additions & 0 deletions .web/functions/api/extensions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// functions/api/extensions.js

const CACHE_DURATION = 60 * 60; // Cache duration in seconds

export async function onRequest(context) {
const githubApiUrl = 'https://api.github.com/search/repositories?q=topic:gate-extension&sort=stars&order=desc';
const cacheKey = 'gate-extension-repositories';

// Access the KV namespace and GitHub token from context.env
const GITHUB_CACHE = context.env.GITHUB_CACHE;
const githubToken = context.env.GITHUB_TOKEN;

// Check for cached data
const cachedResponse = await GITHUB_CACHE.get(cacheKey);
if (cachedResponse) {
return new Response(cachedResponse, {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}

try {
// Fetch data from GitHub API
const response = await fetch(githubApiUrl, {
headers: {
'Accept': 'application/vnd.github.v3+json',
'Authorization': `token ${githubToken}`,
'User-Agent': 'CloudflarePagesGateExtension/1.0 (+https://developers.cloudflare.com/pages)',
},
});

if (!response.ok) {
return new Response('Error fetching data from GitHub API', { status: 500 });
}

const data = await response.json();
const libraries = data.items.map((item) => ({
name: item.name,
owner: item.owner.login,
description: item.description,
stars: item.stargazers_count,
url: item.html_url,
}));

// Cache the response
await GITHUB_CACHE.put(cacheKey, JSON.stringify(libraries), { expirationTtl: CACHE_DURATION });

return new Response(JSON.stringify(libraries), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
} catch (error) {
return new Response(`Error fetching data: ${error}`, { status: 500 });
}
}
Loading

0 comments on commit 81a1528

Please sign in to comment.