Skip to content
Open
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
2 changes: 2 additions & 0 deletions client/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ interface NodeModule {
accept(path?: string, callback?: () => void): void;
};
}

declare module 'loop-protect';
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import {
} from '../../../server/utils/fileUtils';
import { getAllScriptOffsets } from '../../utils/consoleUtils';
import { registerFrame } from '../../utils/dispatcher';
import { createBlobUrl } from './filesReducer';
import { createBlobUrl, File } from './filesReducer';
import resolvePathsForElementsWithAttribute from '../../../server/utils/resolveUtils';

let objectUrls = {};
let objectPaths = {};
let objectUrls: Record<string, string> = {};
let objectPaths: Record<string, string> = {};

const Frame = styled.iframe`
const Frame = styled.iframe<{ fullView?: boolean }>`
min-height: 100%;
min-width: 100%;
position: absolute;
Expand All @@ -34,29 +34,32 @@ const Frame = styled.iframe`
`}
`;

function resolveCSSLinksInString(content, files) {
function resolveCSSLinksInString(content: string, files: File[]): string {
let newContent = content;
let cssFileStrings = content.match(STRING_REGEX);
cssFileStrings = cssFileStrings || [];
cssFileStrings.forEach((cssFileString) => {
if (cssFileString.match(MEDIA_FILE_QUOTED_REGEX)) {
const filePath = cssFileString.substr(1, cssFileString.length - 2);
const quoteCharacter = cssFileString.substr(0, 1);
const resolvedFile = resolvePathToFile(filePath, files);
if (resolvedFile) {
if (resolvedFile.url) {
newContent = newContent.replace(
cssFileString,
quoteCharacter + resolvedFile.url + quoteCharacter
);
const cssFileStrings = content.match(STRING_REGEX);
if (cssFileStrings) {
cssFileStrings.forEach((cssFileString) => {
if (cssFileString.match(MEDIA_FILE_QUOTED_REGEX)) {
const filePath = cssFileString.substr(1, cssFileString.length - 2);
const quoteCharacter = cssFileString.substr(0, 1);
const resolvedFile = resolvePathToFile(filePath, files) as
| File
| undefined;
if (resolvedFile && typeof resolvedFile === 'object') {
if (resolvedFile.url) {
newContent = newContent.replace(
cssFileString,
quoteCharacter + resolvedFile.url + quoteCharacter
);
}
}
}
}
});
});
}
return newContent;
}

function jsPreprocess(jsText) {
function jsPreprocess(jsText: string): string {
let newContent = jsText;
// check the code for js errors before sending it to strip comments
// or loops.
Expand All @@ -72,53 +75,59 @@ function jsPreprocess(jsText) {
return newContent;
}

function resolveJSLinksInString(content, files) {
function resolveJSLinksInString(content: string, files: File[]): string {
let newContent = content;
let jsFileStrings = content.match(STRING_REGEX);
jsFileStrings = jsFileStrings || [];
jsFileStrings.forEach((jsFileString) => {
if (jsFileString.match(MEDIA_FILE_QUOTED_REGEX)) {
const filePath = jsFileString.substr(1, jsFileString.length - 2);
const quoteCharacter = jsFileString.substr(0, 1);
const resolvedFile = resolvePathToFile(filePath, files);
const jsFileStrings = content.match(STRING_REGEX);
if (jsFileStrings) {
jsFileStrings.forEach((jsFileString) => {
if (jsFileString.match(MEDIA_FILE_QUOTED_REGEX)) {
const filePath = jsFileString.substr(1, jsFileString.length - 2);
const quoteCharacter = jsFileString.substr(0, 1);
const resolvedFile = resolvePathToFile(filePath, files) as
| File
| undefined;

if (resolvedFile) {
if (resolvedFile.url) {
newContent = newContent.replace(
jsFileString,
quoteCharacter + resolvedFile.url + quoteCharacter
);
} else if (resolvedFile.name.match(PLAINTEXT_FILE_REGEX)) {
newContent = newContent.replace(
jsFileString,
quoteCharacter + resolvedFile.blobUrl + quoteCharacter
);
if (resolvedFile && typeof resolvedFile === 'object') {
if (resolvedFile.url) {
newContent = newContent.replace(
jsFileString,
quoteCharacter + resolvedFile.url + quoteCharacter
);
} else if (resolvedFile.name.match(PLAINTEXT_FILE_REGEX)) {
newContent = newContent.replace(
jsFileString,
quoteCharacter + resolvedFile.blobUrl + quoteCharacter
);
}
}
}
}
});
});
}

return jsPreprocess(newContent);
}

function resolveScripts(sketchDoc, files) {
function resolveScripts(sketchDoc: Document, files: File[]): void {
const scriptsInHTML = sketchDoc.getElementsByTagName('script');
const scriptsInHTMLArray = Array.prototype.slice.call(scriptsInHTML);
scriptsInHTMLArray.forEach((script) => {
if (
script.getAttribute('src') &&
script.getAttribute('src').match(NOT_EXTERNAL_LINK_REGEX) !== null
) {
const resolvedFile = resolvePathToFile(script.getAttribute('src'), files);
if (resolvedFile) {
const resolvedFile = resolvePathToFile(
script.getAttribute('src'),
files
) as File | undefined;
if (resolvedFile && typeof resolvedFile === 'object') {
if (resolvedFile.url) {
script.setAttribute('src', resolvedFile.url);
} else {
// in the future, when using y.js, could remake the blob for only the file(s)
// that changed
const blobUrl = createBlobUrl(resolvedFile);
script.setAttribute('src', blobUrl);
const blobPath = blobUrl.split('/').pop();
const blobPath = blobUrl.split('/').pop() || '';
// objectUrls[blobUrl] = `${resolvedFile.filePath}${
// resolvedFile.filePath.length > 0 ? '/' : ''
// }${resolvedFile.name}`;
Expand All @@ -141,7 +150,7 @@ function resolveScripts(sketchDoc, files) {
});
}

function resolveStyles(sketchDoc, files) {
function resolveStyles(sketchDoc: Document, files: File[]): void {
const inlineCSSInHTML = sketchDoc.getElementsByTagName('style');
const inlineCSSInHTMLArray = Array.prototype.slice.call(inlineCSSInHTML);
inlineCSSInHTMLArray.forEach((style) => {
Expand All @@ -155,8 +164,11 @@ function resolveStyles(sketchDoc, files) {
css.getAttribute('href') &&
css.getAttribute('href').match(NOT_EXTERNAL_LINK_REGEX) !== null
) {
const resolvedFile = resolvePathToFile(css.getAttribute('href'), files);
if (resolvedFile) {
const resolvedFile = resolvePathToFile(
css.getAttribute('href'),
files
) as File | undefined;
if (resolvedFile && typeof resolvedFile === 'object') {
if (resolvedFile.url) {
css.href = resolvedFile.url; // eslint-disable-line
} else {
Expand All @@ -170,8 +182,8 @@ function resolveStyles(sketchDoc, files) {
});
}

function resolveJSAndCSSLinks(files) {
const newFiles = [];
function resolveJSAndCSSLinks(files: File[]): File[] {
const newFiles: File[] = [];
files.forEach((file) => {
const newFile = { ...file };
if (file.name.match(/.*\.js$/i)) {
Expand All @@ -184,15 +196,25 @@ function resolveJSAndCSSLinks(files) {
return newFiles;
}

function addLoopProtect(sketchDoc) {
function addLoopProtect(sketchDoc: Document): void {
const scriptsInHTML = sketchDoc.getElementsByTagName('script');
const scriptsInHTMLArray = Array.prototype.slice.call(scriptsInHTML);
scriptsInHTMLArray.forEach((script) => {
script.innerHTML = jsPreprocess(script.innerHTML); // eslint-disable-line
});
}

function injectLocalFiles(files, htmlFile, options) {
interface InjectOptions {
basePath: string;
gridOutput: boolean;
textOutput: boolean;
}

function injectLocalFiles(
files: File[],
htmlFile: File,
options: InjectOptions
): string {
const { basePath, gridOutput, textOutput } = options;
let scriptOffs = [];
objectUrls = {};
Expand Down Expand Up @@ -253,53 +275,83 @@ p5.prototype.registerMethod('afterSetup', p5.prototype.ensureAccessibleCanvas);`
return `<!DOCTYPE HTML>\n${sketchDoc.documentElement.outerHTML}`;
}

function getHtmlFile(files) {
return files.filter((file) => file.name.match(/.*\.html$/i))[0];
function getHtmlFile(files: File[]): File {
return files.filter((file: File) => file.name.match(/.*\.html$/i))[0];
}

interface EmbedFrameProps {
files: File[];
isPlaying: boolean;
basePath: string;
gridOutput: boolean;
textOutput: boolean;
}

function EmbedFrame({ files, isPlaying, basePath, gridOutput, textOutput }) {
const iframe = useRef();
function EmbedFrame({
files,
isPlaying,
basePath,
gridOutput,
textOutput
}: EmbedFrameProps) {
const iframe = useRef<HTMLIFrameElement>(null);
const htmlFile = useMemo(() => getHtmlFile(files), [files]);
const srcRef = useRef();
const srcRef = useRef<string | undefined>();

useEffect(() => {
if (!iframe.current?.contentWindow) {
return;
}

const unsubscribe = registerFrame(
iframe.current.contentWindow,
window.origin
);
// eslint-disable-next-line consistent-return
return () => {
unsubscribe();
};
});

function renderSketch() {
const doc = iframe.current;
if (isPlaying) {
if (isPlaying && doc) {
const htmlDoc = injectLocalFiles(files, htmlFile, {
basePath,
gridOutput,
textOutput
});
const generatedHtmlFile = {
const generatedHtmlFile: File = {
id: '',
name: 'index.html',
content: htmlDoc
content: htmlDoc,
children: []
};
const htmlUrl = createBlobUrl(generatedHtmlFile);
const toRevoke = srcRef.current;
srcRef.current = htmlUrl;
// BRO FOR SOME REASON YOU HAVE TO DO THIS TO GET IT TO WORK ON SAFARI
setTimeout(() => {
doc.src = htmlUrl;
if (doc) {
doc.src = htmlUrl;
}
if (toRevoke) {
blobUtil.revokeObjectURL(toRevoke);
}
}, 0);
} else {
} else if (doc) {
doc.src = '';
}
}

useEffect(renderSketch, [files, isPlaying]);
useEffect(renderSketch, [
files,
isPlaying,
htmlFile,
basePath,
gridOutput,
textOutput
]);
return (
<Frame
aria-label="Sketch Preview"
Expand All @@ -315,7 +367,9 @@ EmbedFrame.propTypes = {
PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
content: PropTypes.string.isRequired
content: PropTypes.string.isRequired,
blobUrl: PropTypes.string, // Optional as per the File interface
children: PropTypes.arrayOf(PropTypes.string).isRequired // Matches the File interface
})
).isRequired,
isPlaying: PropTypes.bool.isRequired,
Expand Down
Loading