Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce asset registration system #9

Merged
merged 1 commit into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 5 additions & 17 deletions cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { render } from '@react-email/components';
import { Command } from 'commander';
import fs from 'fs';
import { resolveAll } from '../utils/watcloud-uri';
import { waitForAssets } from '../utils/watcloud-uri';

const program = new Command();

Expand All @@ -19,14 +19,8 @@ program
.option('-p, --pretty', 'Pretty print the output')
.option('-t, --text', 'Generate plain text output')
.action(async (template_name, options) => {
const mod = require(`../emails/${template_name}`);
const template = mod.default;

const images = mod.images;
if (images) {
// Preload all images
await resolveAll(Object.values(images));
}
const template = require(`../emails/${template_name}`).default;
await waitForAssets();

let data = {};
if (options.data) {
Expand All @@ -51,14 +45,8 @@ program
.option('-d, --data <data>', 'Data to pass to the template. Should be a JSON array of objects')
.option('-o, --output <output>', 'Output file. A JSON array of HTML and text emails will be written to this file')
.action(async (template_name, options) => {
const mod = require(`../emails/${template_name}`);
const template = mod.default;

const images = mod.images;
if (images) {
// Preload all images
await resolveAll(Object.values(images));
}
const template = require(`../emails/${template_name}`).default;
await waitForAssets();

let data = [];
if (options.data) {
Expand Down
33 changes: 33 additions & 0 deletions emails/_common/watcloud-email.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
Body,
Container,
Head,
Html,
Img,
Preview
} from "@react-email/components";
import { getAsset, registerAsset, WATcloudURI } from "../../utils/watcloud-uri";

registerAsset('watcloud-logo', new WATcloudURI("watcloud://v1/sha256:393767e36d5387815c15d11c506c3c820de5db41723ffc062751673621dedb15?name=1024x512%20black%401x.png"))

// Wrapper for WATcloud-themed emails
export function WATcloudEmail({
previewText,
children,
}: {
previewText: string,
children: React.ReactNode
}) {
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Body style={{ backgroundColor: "#ffffff", margin: "auto", fontFamily: "sans-serif" }}>
<Container style={{ border: "1px solid #eaeaea", borderRadius: "5px", margin: "40px auto", padding: "20px", maxWidth: "600px" }}>
<Img src={getAsset('watcloud-logo').resolveFromCache()} alt="WATcloud Logo" style={{ display: "block", margin: "0 auto" }} height="100" />
{children}
</Container>
</Body>
</Html>
)
}
77 changes: 33 additions & 44 deletions emails/onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ import {
} from "@react-email/components";
import dedent from "dedent-js";
import { z } from "zod";
import { WATcloudURI } from "../utils/watcloud-uri";

export const images = {
'watcloud-logo': new WATcloudURI("watcloud://v1/sha256:393767e36d5387815c15d11c506c3c820de5db41723ffc062751673621dedb15?name=1024x512%20black%401x.png")
}
import { WATcloudEmail } from "./_common/watcloud-email";

const WATcloudOnboardingEmailProps = z.object({
name: z.string(),
Expand All @@ -37,51 +33,44 @@ export const WATcloudOnboardingEmail = (props: WATcloudOnboardingEmailProps) =>
)

return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Body style={{ backgroundColor: "#ffffff", margin: "auto", fontFamily: "sans-serif" }}>
<Container style={{ border: "1px solid #eaeaea", borderRadius: "5px", margin: "40px auto", padding: "20px", maxWidth: "600px" }}>
<Img src={images['watcloud-logo'].resolveFromCache()} alt="WATcloud Logo" style={{ display: "block", margin: "0 auto" }} height="100" />
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px" }}>
Hi {name},
</Text>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px", marginTop: "10px" }}>
Welcome to WATcloud, WATonomous's compute cluster and infrastructure. We are excited to have you on board!
</Text>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px", marginTop: "10px" }}>
This email confirms that your access to WATcloud has been successfully updated.
</Text>
<Section style={{ marginTop: "20px", marginBottom: "20px" }}>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px" }}>
Here's a list of services that you have access to:
</Text>
{accessInstructions}
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px", marginTop: "10px" }}>
Access instructions for each service can be found in the <Link href="https://cloud.watonomous.ca/docs/services" style={{ color: "#1e90ff", textDecoration: "none" }}>Services</Link> documentation.
</Text>
</Section>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px", marginTop: "10px" }}>
If you have any questions, please reach out to your <Link href="https://cloud.watonomous.ca/docs/services#watcloud-contact" style={{ color: "#1e90ff", textDecoration: "none" }}>WATcloud contact</Link> or the WATcloud team at <Link href={`mailto:[email protected]`} style={{ color: "#1e90ff", textDecoration: "none" }}>[email protected]</Link>.
</Text>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px", marginTop: "20px" }}>
Vroom vroom,
</Text>
<pre style={{ fontFamily: "Courier New, monospace" }}>
{dedent(String.raw`
<WATcloudEmail previewText={previewText}>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px" }}>
Hi {name},
</Text>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px", marginTop: "10px" }}>
Welcome to WATcloud, WATonomous's compute cluster and infrastructure. We are excited to have you on board!
</Text>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px", marginTop: "10px" }}>
This email confirms that your access to WATcloud has been successfully updated.
</Text>
<Section style={{ marginTop: "20px", marginBottom: "20px" }}>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px" }}>
Here's a list of services that you have access to:
</Text>
{accessInstructions}
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px", marginTop: "10px" }}>
Access instructions for each service can be found in the <Link href="https://cloud.watonomous.ca/docs/services" style={{ color: "#1e90ff", textDecoration: "none" }}>Services</Link> documentation.
</Text>
</Section>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px", marginTop: "10px" }}>
If you have any questions, please reach out to your <Link href="https://cloud.watonomous.ca/docs/services#watcloud-contact" style={{ color: "#1e90ff", textDecoration: "none" }}>WATcloud contact</Link> or the WATcloud team at <Link href={`mailto:[email protected]`} style={{ color: "#1e90ff", textDecoration: "none" }}>[email protected]</Link>.
</Text>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px", marginTop: "20px" }}>
Vroom vroom,
</Text>
<pre style={{ fontFamily: "Courier New, monospace" }}>
{dedent(String.raw`
_∩_
__|_|_
/|__|__\____
| |
${"`"}.(o)-----(o).'
`)}
</pre>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px", marginTop: "10px" }}>
WATcloud Onboarding Bot 🤖
</Text>
</Container>
</Body>
</Html>
</pre>
<Text style={{ color: "#000", fontSize: "14px", lineHeight: "24px", marginTop: "10px" }}>
WATcloud Onboarding Bot 🤖
</Text>
</WATcloudEmail>
);
};

Expand Down
38 changes: 37 additions & 1 deletion utils/watcloud-uri.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,45 @@ export class WATcloudURI extends URL {

return ret
}

equals(other: WATcloudURI) {
return this.toString() === other.toString();
}
}

// Resolve all assets. This can also be used to prewarm the cache.
export function resolveAll(assets: WATcloudURI[]) {
return Promise.all(assets.map((asset) => asset.resolveToURL()));
}
}

// MARK: Asset registration
// This section contains code for the asset registration system.
// This system allows for assets to be resolved synchronously.
// Usage:
// 1. Register assets using `registerAsset(name, uri)`.
// 2. Wait for all assets to resolve using `await waitForAssets()`.
// 3. Get an asset using `getAsset(name)`.

const assets = new Map<string, WATcloudURI>();
const assetResolverPromises = new Map<string, Promise<string>>();

// Register an asset with a name and a URI. This will also start resolving the asset.
// To wait for all assets to resolve, `await waitForAssets()` after all assets have been registered.
export function registerAsset(name: string, uri: WATcloudURI) {
if (assets.has(name) && !assets.get(name)!.equals(uri)) {
throw new Error(`Asset ${name} is already registered with a different URI (${assets.get(name)!.toString()}). Cannot add asset with URI ${uri.toString()}.`);
}
assets.set(name, uri);
assetResolverPromises.set(name, uri.resolveToURL());
}

export function getAsset(name: string) {
if (!assets.has(name)) {
throw new Error(`Asset ${name} is not registered.`);
}
return assets.get(name)!;
}

export function waitForAssets() {
return Promise.all(assetResolverPromises.values());
}