Skip to content

Commit

Permalink
Initial batch of sample projects and related website updates (#937)
Browse files Browse the repository at this point in the history
  • Loading branch information
GarboMuffin committed Aug 24, 2023
1 parent 9e2a930 commit a774eac
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 21 deletions.
122 changes: 109 additions & 13 deletions development/builder.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const fs = require("fs");
const AdmZip = require("adm-zip");
const pathUtil = require("path");
const compatibilityAliases = require("./compatibility-aliases");
const parseMetadata = require("./parse-extension-metadata");
Expand Down Expand Up @@ -118,7 +119,7 @@ class ExtensionFile extends BuildFile {
}

class HomepageFile extends BuildFile {
constructor(extensionFiles, extensionImages, mode) {
constructor(extensionFiles, extensionImages, docs, samples, mode) {
super(pathUtil.join(__dirname, "homepage-template.ejs"));

/** @type {Record<string, ExtensionFile>} */
Expand All @@ -127,6 +128,12 @@ class HomepageFile extends BuildFile {
/** @type {Record<string, string>} */
this.extensionImages = extensionImages;

/** @type {Record<string, DocsFile>} */
this.docs = docs;

/** @type {SampleFile[]} */
this.samples = samples;

/** @type {Mode} */
this.mode = mode;

Expand All @@ -144,12 +151,25 @@ class HomepageFile extends BuildFile {
return `${this.host}${extensionSlug}.js`;
}

getDocumentationURL(extensionSlug) {
return `${this.host}${extensionSlug}`;
}

getRunExtensionURL(extensionSlug) {
return `https://turbowarp.org/editor?extension=${this.getFullExtensionURL(
extensionSlug
)}`;
}

/**
* @param {SampleFile} sampleFile
* @returns {string}
*/
getRunSampleURL(sampleFile) {
const path = encodeURIComponent(`samples/${sampleFile.getSlug()}`);
return `https://turbowarp.org/editor?project_url=${this.host}${path}`;
}

read() {
const renderTemplate = require("./render-template");

Expand All @@ -158,10 +178,28 @@ class HomepageFile extends BuildFile {
.slice(0, 5)
.map((i) => i[0]);

/** @type {Map<string, SampleFile[]>} */
const samplesBySlug = new Map();
for (const sample of this.samples) {
for (const url of sample.getExtensionURLs()) {
const slug = new URL(url).pathname.substring(1).replace(".js", "");

if (samplesBySlug.has(slug)) {
samplesBySlug.get(slug).push(sample);
} else {
samplesBySlug.set(slug, [sample]);
}
}
}

const extensionMetadata = Object.fromEntries(
featuredExtensionSlugs.map((id) => [
id,
this.extensionFiles[id].getMetadata(),
featuredExtensionSlugs.map((slug) => [
slug,
{
...this.extensionFiles[slug].getMetadata(),
hasDocumentation: !!this.docs[slug],
samples: samplesBySlug.get(slug) || [],
},
])
);

Expand All @@ -172,6 +210,8 @@ class HomepageFile extends BuildFile {
extensionMetadata,
getFullExtensionURL: this.getFullExtensionURL.bind(this),
getRunExtensionURL: this.getRunExtensionURL.bind(this),
getDocumentationURL: this.getDocumentationURL.bind(this),
getRunSampleURL: this.getRunSampleURL.bind(this),
});
}
}
Expand Down Expand Up @@ -252,6 +292,11 @@ class SVGFile extends ImageFile {
}
}

const IMAGE_FORMATS = new Map();
IMAGE_FORMATS.set(".png", ImageFile);
IMAGE_FORMATS.set(".jpg", ImageFile);
IMAGE_FORMATS.set(".svg", SVGFile);

class SitemapFile extends BuildFile {
constructor(build) {
super(null);
Expand Down Expand Up @@ -284,11 +329,6 @@ class SitemapFile extends BuildFile {
}
}

const IMAGE_FORMATS = new Map();
IMAGE_FORMATS.set(".png", ImageFile);
IMAGE_FORMATS.set(".jpg", ImageFile);
IMAGE_FORMATS.set(".svg", SVGFile);

class DocsFile extends BuildFile {
constructor(absolutePath, extensionSlug) {
super(absolutePath);
Expand All @@ -306,6 +346,44 @@ class DocsFile extends BuildFile {
}
}

class SampleFile extends BuildFile {
getSlug() {
return pathUtil.basename(this.sourcePath);
}

getTitle() {
return this.getSlug().replace(".sb3", "");
}

/** @returns {string[]} list of full URLs */
getExtensionURLs() {
const zip = new AdmZip(this.sourcePath);
const entry = zip.getEntry("project.json");
if (!entry) {
throw new Error("package.json missing");
}
const data = JSON.parse(entry.getData().toString("utf-8"));
return data.extensionURLs ? Object.values(data.extensionURLs) : [];
}

validate() {
const urls = this.getExtensionURLs();

if (urls.length === 0) {
throw new Error("Has no extensions");
}

for (const url of urls) {
if (
!url.startsWith("https://extensions.turbowarp.org/") ||
!url.endsWith(".js")
) {
throw new Error(`Invalid extension URL for sample: ${url}`);
}
}
}
}

class Build {
constructor() {
this.files = {};
Expand Down Expand Up @@ -361,6 +439,7 @@ class Builder {
this.websiteRoot = pathUtil.join(__dirname, "../website");
this.imagesRoot = pathUtil.join(__dirname, "../images");
this.docsRoot = pathUtil.join(__dirname, "../docs");
this.samplesRoot = pathUtil.join(__dirname, "../samples");
}

build() {
Expand Down Expand Up @@ -405,17 +484,31 @@ class Builder {
build.files[`/${filename}`] = new BuildFile(absolutePath);
}

/** @type {Record<string, DocsFile>} */
const docs = {};
for (const [filename, absolutePath] of recursiveReadDirectory(
this.docsRoot
)) {
if (!filename.endsWith(".md")) {
continue;
}
const extensionSlug = filename.split(".")[0];
build.files[`/${extensionSlug}.html`] = new DocsFile(
absolutePath,
extensionSlug
);
const file = new DocsFile(absolutePath, extensionSlug);
docs[extensionSlug] = file;
build.files[`/${extensionSlug}.html`] = file;
}

/** @type {SampleFile[]} */
const samples = [];
for (const [filename, absolutePath] of recursiveReadDirectory(
this.samplesRoot
)) {
if (!filename.endsWith(".sb3")) {
continue;
}
const file = new SampleFile(absolutePath);
build.files[`/samples/${filename}`] = file;
samples.push(file);
}

const scratchblocksPath = pathUtil.join(
Expand All @@ -429,6 +522,8 @@ class Builder {
build.files["/index.html"] = new HomepageFile(
extensionFiles,
extensionImages,
docs,
samples,
this.mode
);
build.files["/sitemap.xml"] = new SitemapFile(build);
Expand Down Expand Up @@ -472,6 +567,7 @@ class Builder {
`${this.imagesRoot}/**/*`,
`${this.websiteRoot}/**/*`,
`${this.docsRoot}/**/*`,
`${this.samplesRoot}/**/*`,
],
{
ignoreInitial: true,
Expand Down
79 changes: 72 additions & 7 deletions development/homepage-template.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
}
.extension {
position: relative;
border: 2px solid #ccc;
border-radius: 8px;
margin: 4px;
Expand All @@ -121,6 +122,9 @@
.extension h3 {
font-size: 1.5em;
}
.extension > :last-child {
margin-bottom: 0;
}
.extension-banner {
position: relative;
margin: -8px -8px 0 -8px;
Expand All @@ -137,25 +141,26 @@
top: 0;
left: 0;
display: flex;
flex-wrap: wrap;
width: 100%;
height: 100%;
align-items: center;
align-content: center;
justify-content: center;
gap: 0.5rem;
opacity: 0;
transition: opacity .15s;
background: rgba(0, 0, 0, 0.5);
pointer-events: none;
}
.extension:hover .extension-buttons, .extension:focus-within .extension-buttons {
opacity: 1;
}
.extension:hover .extension-buttons {
backdrop-filter: blur(0.6px);
}
.extension-buttons > * {
padding: 8px;
padding: 0.5rem;
background-color: #4c97ff;
border-radius: 8px;
border-radius: 0.5rem;
border: none;
font: inherit;
cursor: pointer;
Expand All @@ -175,17 +180,52 @@
.extension-buttons *:disabled {
opacity: 0.5;
}
.extension-buttons .copy {
margin: 0 8px 0 0;
}
.extension-buttons .open {
background-color: #ff4c4c;
color: white;
}
.extension-buttons .docs {
background-color: #FFAB19;
color: white;
}
.extension-buttons .sample {
background-color: #40BF4A;
color: white;
}
.extension-buttons :disabled {
opacity: 0.5;
}
.sample-list {
display: none;
position: absolute;
left: 0.5rem;
right: 0.5rem;
width: calc(100% - 1rem);
margin-top: -1.5rem;
padding: 0.5rem;
box-sizing: border-box;
background-color: white;
border-radius: 0.5rem;
box-shadow: 0px 0px 8px 1px rgba(0, 0, 0, .3);
border: 1px solid rgba(0, 0, 0, 0.15);
flex-direction: column;
gap: 0.5rem;
}
.sample-list h3 {
font-size: 1rem;
margin: 0;
}
.extension:hover[data-samples-open="true"] .sample-list {
display: flex;
}
@media (prefers-color-scheme: dark) {
.sample-list {
border: 1px solid rgba(255, 255, 255, 0.15);
background-color: #333;
}
}
footer {
opacity: 0.8;
width: 100%;
Expand Down Expand Up @@ -230,6 +270,7 @@
</div>

<div class="infobox">
<div class="infobox-title">Some extensions may not work in TurboWarp Desktop.</div>
For compatibility, security, and offline support, each <a href="https://desktop.turbowarp.org/">TurboWarp Desktop</a> update contains an offline copy of these extensions from its release date, so some extensions may be outdated or missing.
Use the latest update for best results.
</div>
Expand Down Expand Up @@ -273,6 +314,11 @@
e.target.focus();
}
}
if (e.target.className.includes('sample-list-button')) {
var extension = e.target.closest('.extension');
extension.dataset.samplesOpen = extension.dataset.samplesOpen !== 'true';
}
});
</script>

Expand Down Expand Up @@ -306,8 +352,27 @@
<div class="extension-buttons">
<button class="copy" data-copy="<%= getFullExtensionURL(extensionSlug) %>">Copy URL</button>
<a class="open" href="<%= getRunExtensionURL(extensionSlug) %>">Open Extension</a>
<% if (metadata.hasDocumentation) { %>
<a class="docs" href="<%= getDocumentationURL(extensionSlug) %>">Documentation</a>
<% } %>
<% if (metadata.samples.length === 1) { %>
<a class="sample" href="<%= getRunSampleURL(metadata.samples[0]) %>">Sample Project</a>
<% } else if (metadata.samples.length > 1) { %>
<button class="sample sample-list-button">Sample Projects</button>
<% } %>
</div>
</div>
<% if (metadata.samples.length > 1) { %>
<div class="sample-list">
<h3><%= metadata.name %> Sample Projects:</h3>
<% for (const sample of metadata.samples) { %>
<a class="sample-list-item" href="<%= getRunSampleURL(sample)%>"><%= sample.getTitle() %></a>
<% } %>
</div>
<% } %>
<h2><%= metadata.name %></h2>
<p>
Expand Down
2 changes: 1 addition & 1 deletion development/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ app.get("/*", (req, res, next) => {
return;
}

const fileInBuild = mostRecentBuild.getFile(req.path);
const fileInBuild = mostRecentBuild.getFile(decodeURIComponent(req.path));
if (!fileInBuild) {
return next();
}
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a774eac

Please sign in to comment.