Skip to content

Commit

Permalink
Fix Remote Source Code Loading Bug (#23)
Browse files Browse the repository at this point in the history
* add remote source example

* fix: init model when files loaded
  • Loading branch information
sleslein authored Feb 26, 2024
1 parent dde4f48 commit 066f356
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 1 deletion.
9 changes: 9 additions & 0 deletions docs/lib/remote-source-examples/remote-src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from "react";
export default function Index() {
return (
<>
<h1>Hello World!</h1>
<p>This source code was loaded from the server!</p>
</>
);
}
81 changes: 81 additions & 0 deletions docs/lib/remote-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Note: this file will be used in getStaticProps and must use CJS
const fsp = require("fs/promises");
const path = require("path");
const fs = require("fs");

const entryFilePattern = /^index\..*\.[tj]sx?/;
const demosBaseDir = path.resolve(
process.cwd(),
"./lib/remote-source-examples"
);
// const demosBaseDir = path.resolve(process.cwd(), './pages/demos');

/**
*
* @param {string} pathOrDir
* @returns {Promise<string[]>}
*/
async function getRemoteEntries(pathOrDir) {
// check if dir is a directory
const stat = await fsp.stat(pathOrDir);
if (!stat.isDirectory()) {
if (entryFilePattern.test(path.basename(pathOrDir))) {
return [pathOrDir];
}
return [];
}
// build directory structure recursively
const paths = await fsp.readdir(pathOrDir);
const results = await Promise.all(
paths.map(async (p) => {
const fullPath = path.join(pathOrDir, p);
return getRemoteEntries(fullPath);
})
);
return results.flat();
}

async function getRemoteSourceExamples() {
const paths = await getRemoteEntries(demosBaseDir);
return paths.map((p) => {
const entry = path.relative(demosBaseDir, p);
return entry.substring(0, entry.lastIndexOf("/"));
});
}

/**
*
* @param {string} entryDir
* @returns {Promise<string[]>}
*/
async function getRemoteSourceFiles(entryDir) {
const filenames = await fsp.readdir(path.join(demosBaseDir, entryDir));

filenames.sort((a, b) => {
// makes sure entry is at the top
return entryFilePattern.test(a) ? -1 : 1;
});

const inputFiles = await Promise.all(
filenames.map(async (filename) => {
const content = await fsp.readFile(
path.join(demosBaseDir, entryDir, filename),
"utf8"
);
// Strip off page extension
const newFilename = filename.replace(".page", "");
return {
code: content,
filename: newFilename,
};
})
);

return inputFiles;
}

module.exports = {
getDemos: getRemoteSourceExamples,
getDemoFiles: getRemoteSourceFiles,
getDemoEntries: getRemoteEntries,
};
18 changes: 18 additions & 0 deletions docs/pages/api/remote-files.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { getDemoFiles } from "../../lib/remote-source";
import { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { src } = req.query;
if (Array.isArray(src) || !src) {
return res.status(400).json({
error: "src must be a string",
});
}
res.json({
files: await getDemoFiles(src),
});
}
83 changes: 83 additions & 0 deletions docs/pages/remote-source.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React, { useState } from "react";
import { useInitMonaco } from "../components/use-init-monaco";
import { InputFile } from "code-kitchen/src/types";
import { Playground } from "code-kitchen";
import dependencies from "../components/dependencies";

export default function Page() {
const [isOpen, setIsOpen] = useState(false);
const buttonText = isOpen ? "Close Playground" : "Open Playground";
return (
<>
<button
onClick={() => setIsOpen(!isOpen)}
style={{
backgroundColor: "lightgray",
border: "solid 1px darkgrey",
borderRadius: "4px",
padding: "4px 8px",
margin: "8px 16px",
}}
>
{buttonText}
</button>
{isOpen ? <RemoteSourcePlayground src="remote-src" /> : null}
</>
);
}

function useRemoteSources(src?: string) {
const [files, setFiles] = React.useState<InputFile[] | null>(null);
React.useEffect(() => {
if (src) {
let cancelled = false;
fetch(`/api/remote-files?src=${encodeURIComponent(src)}`).then(
async (res) => {
if (cancelled) {
return;
}
const { files } = await res.json();
setFiles(files);
}
);
return () => {
cancelled = true;
};
}
}, [src]);
return files;
}

const customRequire = (key: string) => {
const res = (dependencies as any)[key];

if (res) {
return res;
}

throw new Error("DEP: " + key + " not found");
};

function RemoteSourcePlayground({ src }: { src: string }) {
useInitMonaco();
const remoteSources = useRemoteSources(src);
const [initialFiles, setInitialFiles] = React.useState<InputFile[]>([]);
const latestInitialFiles = React.useRef<InputFile[]>([]);

React.useEffect(() => {
const newInitialFiles = remoteSources ?? [];
setInitialFiles(newInitialFiles);
latestInitialFiles.current = newInitialFiles;
}, [remoteSources]);

return (
<Playground
id={"code-kitchen-playground"}
allowDisconnect
name="Playground"
className="h-screen"
require={customRequire}
initialFiles={initialFiles}
/>
);
}
5 changes: 4 additions & 1 deletion packages/code-kitchen/src/files-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ function useModels(id: string, files: InputFile[]) {
}
const getFileUri = (filename: string) =>
monaco.Uri.file(join(id, filename));
if (!modelsRef.current) {
if (
!modelsRef.current ||
(modelsRef.current.length === 0 && files.length > 0)
) {
const newModels = files.map((f) => {
return monaco.editor.createModel(
f.code,
Expand Down

0 comments on commit 066f356

Please sign in to comment.