UI v1
This commit is contained in:
parent
85afa783e5
commit
1b54a08dc1
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": ["next", "next/core-web-vitals"]
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
|
@ -0,0 +1,20 @@
|
|||

|
||||
|
||||

|
||||
|
||||
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
|
|
@ -0,0 +1,41 @@
|
|||
import { alpha, Button } from "@mui/material";
|
||||
import { useRouter } from "next/router";
|
||||
import React from "react";
|
||||
|
||||
const LanguageLink = (props) => {
|
||||
const { link } = props;
|
||||
const router = useRouter();
|
||||
|
||||
const handleClick = () => {
|
||||
router.push(`/editor/${link.id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleClick}
|
||||
disableElevation
|
||||
sx={{
|
||||
fontSize: 12,
|
||||
minHeight: 70,
|
||||
minWidth: "calc(calc(100vw - 120px) / 6)",
|
||||
color: (theme) => theme.palette.text.primary,
|
||||
background: (theme) => theme.palette.common.white,
|
||||
border: `1px solid rgb(226,232,240)`,
|
||||
":hover": {
|
||||
background: (theme) => theme.palette.common.white,
|
||||
borderColor: (theme) => theme.palette.primary.main,
|
||||
color: (theme) => theme.palette.primary.main,
|
||||
boxShadow: (theme) =>
|
||||
`0 4px 4px ${alpha(theme.palette.primary.main, 0.25)}`,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{link.name}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageLink;
|
|
@ -0,0 +1,47 @@
|
|||
import Box from "@mui/material/Box";
|
||||
import React, { useEffect } from "react";
|
||||
import { Terminal } from "xterm";
|
||||
import { FitAddon } from "xterm-addon-fit";
|
||||
import "xterm/css/xterm.css";
|
||||
import { EventEmitter } from "../utils/EventEmitter";
|
||||
|
||||
function RijuTerminal() {
|
||||
useEffect(() => {
|
||||
const term = new Terminal({
|
||||
fontFamily: "Fira Code",
|
||||
theme: {
|
||||
background: "#292D3E",
|
||||
},
|
||||
});
|
||||
term.open(document.getElementById("riju-term"));
|
||||
term.write("Connecting to server...");
|
||||
const fitAddon = new FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
|
||||
window.addEventListener("resize", () => fitAddon.fit());
|
||||
EventEmitter.subscribe("terminal", (payload) => {
|
||||
if (!payload) return;
|
||||
const { type, data } = payload;
|
||||
switch (type) {
|
||||
case "terminalClear":
|
||||
term.reset();
|
||||
break;
|
||||
case "terminalOutput":
|
||||
term.write(data);
|
||||
break;
|
||||
default:
|
||||
term.write(data);
|
||||
break;
|
||||
}
|
||||
});
|
||||
term.onData((data) => {
|
||||
EventEmitter.dispatch("send", { event: "terminalInput", input: data });
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box id="riju-term" sx={{ height: `calc(100% - 8px)`, mt: 1, ml: 2 }} />
|
||||
);
|
||||
}
|
||||
|
||||
export default RijuTerminal;
|
|
@ -0,0 +1,48 @@
|
|||
import { alpha, InputBase, styled } from "@mui/material";
|
||||
|
||||
export const Search = styled("div")(({ theme }) => ({
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.15),
|
||||
"&:hover": {
|
||||
backgroundColor: alpha(theme.palette.common.white, 0.25),
|
||||
},
|
||||
width: "100%",
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
width: "auto",
|
||||
},
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
// boxShadow: theme.shadows[3],
|
||||
minHeight: 70,
|
||||
border: `1px solid`,
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: `0 2px 5px ${alpha(theme.palette.primary.main, 0.5)}`,
|
||||
}));
|
||||
|
||||
export const SearchIconWrapper = styled("div")(() => ({
|
||||
position: "absolute",
|
||||
pointerEvents: "none",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginLeft: 16,
|
||||
}));
|
||||
|
||||
export const StyledInputBase = styled(InputBase)(({ theme }) => ({
|
||||
color: "inherit",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
"& .MuiInputBase-input": {
|
||||
fontSize: 30,
|
||||
// minHeight: 70,
|
||||
// fontWeight: "bolder",
|
||||
// vertical padding + font size from searchIcon
|
||||
paddingLeft: 32 + 24,
|
||||
transition: theme.transitions.create("width"),
|
||||
width: "100%",
|
||||
[theme.breakpoints.up("md")]: {
|
||||
// width: "20ch",
|
||||
},
|
||||
},
|
||||
flexGrow: 1,
|
||||
}));
|
|
@ -0,0 +1,5 @@
|
|||
import createCache from "@emotion/cache";
|
||||
|
||||
export default function createEmotionCache() {
|
||||
return createCache({ key: "css" });
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import "setimmediate";
|
||||
import Head from "next/head";
|
||||
import Router from "next/router";
|
||||
import NProgress from "nprogress";
|
||||
import "../../node_modules/nprogress/nprogress.css";
|
||||
import React from "react";
|
||||
import "../styles/globals.css";
|
||||
import { theme } from "../theme";
|
||||
|
||||
Router.events.on("routeChangeStart", () => {
|
||||
console.log("routeChangeStart");
|
||||
NProgress.start();
|
||||
});
|
||||
Router.events.on("routeChangeComplete", () => {
|
||||
console.log("routeChangeComplete");
|
||||
NProgress.done();
|
||||
});
|
||||
Router.events.on("routeChangeError", () => {
|
||||
console.log("routeChangeError");
|
||||
NProgress.done();
|
||||
});
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
React.useEffect(() => {
|
||||
// Remove the server-side injected CSS.
|
||||
const jssStyles = document.querySelector("#jss-server-side");
|
||||
if (jssStyles) {
|
||||
jssStyles.parentElement.removeChild(jssStyles);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Head>
|
||||
<title>Riju</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="minimum-scale=1, initial-scale=1, width=device-width"
|
||||
/>
|
||||
</Head>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Component {...pageProps} />
|
||||
</ThemeProvider>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyApp;
|
|
@ -0,0 +1,83 @@
|
|||
import * as React from "react";
|
||||
import Document, { Html, Head, Main, NextScript } from "next/document";
|
||||
import createEmotionServer from "@emotion/server/create-instance";
|
||||
import { theme } from "../theme";
|
||||
import createEmotionCache from "../createEmotionCache";
|
||||
|
||||
export default class MyDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
{/* PWA primary color */}
|
||||
<meta name="theme-color" content={theme.palette.primary.main} />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// `getInitialProps` belongs to `_document` (instead of `_app`),
|
||||
// it's compatible with static-site generation (SSG).
|
||||
MyDocument.getInitialProps = async (ctx) => {
|
||||
// Resolution order
|
||||
//
|
||||
// On the server:
|
||||
// 1. app.getInitialProps
|
||||
// 2. page.getInitialProps
|
||||
// 3. document.getInitialProps
|
||||
// 4. app.render
|
||||
// 5. page.render
|
||||
// 6. document.render
|
||||
//
|
||||
// On the server with error:
|
||||
// 1. document.getInitialProps
|
||||
// 2. app.render
|
||||
// 3. page.render
|
||||
// 4. document.render
|
||||
//
|
||||
// On the client
|
||||
// 1. app.getInitialProps
|
||||
// 2. page.getInitialProps
|
||||
// 3. app.render
|
||||
// 4. page.render
|
||||
|
||||
const originalRenderPage = ctx.renderPage;
|
||||
|
||||
// You can consider sharing the same emotion cache between all the SSR requests to speed up performance.
|
||||
// However, be aware that it can have global side effects.
|
||||
const cache = createEmotionCache();
|
||||
const { extractCriticalToChunks } = createEmotionServer(cache);
|
||||
|
||||
ctx.renderPage = () =>
|
||||
originalRenderPage({
|
||||
// eslint-disable-next-line react/display-name
|
||||
enhanceApp: (App) => (props) => <App emotionCache={cache} {...props} />,
|
||||
});
|
||||
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
// This is important. It prevents emotion to render invalid HTML.
|
||||
// See https://github.com/mui-org/material-ui/issues/26561#issuecomment-855286153
|
||||
const emotionStyles = extractCriticalToChunks(initialProps.html);
|
||||
const emotionStyleTags = emotionStyles.styles.map((style) => (
|
||||
<style
|
||||
data-emotion={`${style.key} ${style.ids.join(" ")}`}
|
||||
key={style.key}
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: style.css }}
|
||||
/>
|
||||
));
|
||||
|
||||
return {
|
||||
...initialProps,
|
||||
// Styles fragment is rendered after the app and page rendering finish.
|
||||
styles: [
|
||||
...React.Children.toArray(initialProps.styles),
|
||||
...emotionStyleTags,
|
||||
],
|
||||
};
|
||||
};
|
|
@ -1,84 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" style="overflow: hidden">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title><%= config.name %> - Riju</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.9.3/css/bulma.min.css"
|
||||
integrity="sha512-IgmDkwzs96t4SrChW29No3NXBIBv8baW490zk5aXvhCD8vuZM3yUSkbyTBcXohkySecyzIrUwiF/qV0cuPcL3Q=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"
|
||||
integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<link rel="stylesheet" href="/css/app.css" />
|
||||
<script>
|
||||
window.rijuConfig = <%- JSON.stringify(config) %>;
|
||||
</script>
|
||||
<script src="/js/app.js" defer></script>
|
||||
<% if (fathomSiteId) { %>
|
||||
<script src="https://cdn.usefathom.com/script.js" data-site="<%= fathomSiteId %>" defer></script>
|
||||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<div class="columns" style="height: 100vh; margin: 0">
|
||||
<div class="column" style="padding: 0">
|
||||
<div id="header" style="border-bottom-style: solid; border-bottom-width: 1px; border-bottom-color: lightgray">
|
||||
<a href="/" class="button is-small is-info">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-home"></i>
|
||||
</span>
|
||||
</a>
|
||||
<span style="display: inline-block; vertical-align: middle; margin-left: 6px; padding-top: 2px">
|
||||
<b>Riju :: <%= config.name %></b>
|
||||
</span>
|
||||
<span style="display: inline-block; vertical-align: middle; margin-left: 6px; padding-top: 2px">
|
||||
<i id="connectionStatus" style="color: lightgray"></i>
|
||||
</span>
|
||||
<span style="display: inline-block; vertical-align: middle; height: 100%"></span>
|
||||
<button id="runButton" type="button" class="button is-small is-success is-pulled-right">
|
||||
<span>Run</span>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-play"></i>
|
||||
</span>
|
||||
</button>
|
||||
<button id="formatButton" type="button" class="button is-small is-info is-pulled-right is-hidden">
|
||||
<span>Prettify</span>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-code"></i>
|
||||
</span>
|
||||
</button>
|
||||
<button id="lspButton" type="button" class="button is-small is-warning is-light is-pulled-right is-hidden">
|
||||
<span>Autocomplete <span id="lspButtonState">OFF</span></span>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-bolt"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div style="height: 100%">
|
||||
<div id="editor" style="height: 100%; margin: 12px; margin-left: 0"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column" id="terminal" style="background: black; padding: 0">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal" id="modal">
|
||||
<div class="modal-background will-close-modal"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title" id="modal-title"></p>
|
||||
<button class="delete will-close-modal" aria-label="close"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<pre id="modal-data"></pre>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,495 @@
|
|||
import MonacoEditor, { useMonaco } from "@monaco-editor/react";
|
||||
import { Circle, Code as Format, Home, PlayArrow } from "@mui/icons-material";
|
||||
import { LoadingButton } from "@mui/lab";
|
||||
import { Box, Button, Divider, Stack, Typography } from "@mui/material";
|
||||
import ansi from "ansicolor";
|
||||
import dynamic from "next/dynamic";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { createMessageConnection } from "vscode-jsonrpc";
|
||||
import langs from "../../static/langs.json";
|
||||
import { EventEmitter } from "../../utils/EventEmitter";
|
||||
ansi.rgb = {
|
||||
green: "#00FD61",
|
||||
};
|
||||
const RijuTerminal = dynamic(() => import("../../components/RijuTerminal"), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const DEBUG = true;
|
||||
let clientDisposable = null;
|
||||
let servicesDisposable = null;
|
||||
const serviceLogBuffers = {};
|
||||
|
||||
const CodeRunner = (props) => {
|
||||
const router = useRouter();
|
||||
const { langConfig } = props;
|
||||
const editorRef = useRef(null);
|
||||
const [config, setConfig] = useState(langConfig);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [isRunning, setRunning] = useState(false);
|
||||
const [isFormatting, setFormatting] = useState(false);
|
||||
const [isLspStarted, setLspStarted] = useState(false);
|
||||
const [isLspRequested, setIsLspRequested] = useState(false);
|
||||
const monaco = useMonaco();
|
||||
|
||||
function sendToTerminal(type, data) {
|
||||
EventEmitter.dispatch("terminal", { type, data });
|
||||
}
|
||||
|
||||
function connect() {
|
||||
const socket = new WebSocket(
|
||||
// (document.location.protocol === "http:" ? "ws://" : "wss://") +
|
||||
"wss://" +
|
||||
"riju.codes" +
|
||||
`/api/v1/ws?lang=${encodeURIComponent(config.id)}`
|
||||
);
|
||||
socket.addEventListener("open", () => {
|
||||
console.log("Successfully connected to server playground");
|
||||
});
|
||||
EventEmitter.subscribe("send", (payload) => {
|
||||
if (DEBUG) {
|
||||
console.log("SEND:", payload);
|
||||
}
|
||||
if (socket) {
|
||||
socket.send(JSON.stringify(payload));
|
||||
}
|
||||
});
|
||||
socket.addEventListener("message", async (event) => {
|
||||
let message;
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
} catch (err) {
|
||||
console.error("Malformed message from server:", event.data);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
DEBUG &&
|
||||
message &&
|
||||
message.event !== "lspOutput" &&
|
||||
message.event !== "serviceLog"
|
||||
) {
|
||||
console.log("RECEIVE:", message);
|
||||
}
|
||||
if (message && message.event && message.event !== "error") {
|
||||
// retryDelayMs = initialRetryDelayMs;
|
||||
}
|
||||
switch (message && message.event) {
|
||||
case "terminalClear":
|
||||
// term.reset();
|
||||
sendToTerminal("terminalClear");
|
||||
return;
|
||||
case "terminalOutput":
|
||||
if (typeof message.output !== "string") {
|
||||
console.error("Unexpected message from server:", message);
|
||||
return;
|
||||
}
|
||||
sendToTerminal("terminalOutput", ansi.white(message.output));
|
||||
setRunning(false);
|
||||
return;
|
||||
case "formattedCode":
|
||||
if (
|
||||
typeof message.code !== "string" ||
|
||||
typeof message.originalCode !== "string"
|
||||
) {
|
||||
console.error("Unexpected message from server:", message);
|
||||
return;
|
||||
}
|
||||
if (editorRef.current?.getValue() === message.originalCode) {
|
||||
editorRef.current?.setValue(message.code);
|
||||
}
|
||||
setFormatting(false);
|
||||
return;
|
||||
case "lspStopped":
|
||||
setIsLspRequested(false);
|
||||
setLspStarted(false);
|
||||
if (clientDisposable) {
|
||||
clientDisposable.dispose();
|
||||
clientDisposable = null;
|
||||
}
|
||||
if (servicesDisposable) {
|
||||
servicesDisposable.dispose();
|
||||
servicesDisposable = null;
|
||||
}
|
||||
break;
|
||||
case "lspStarted":
|
||||
setLspStarted(true);
|
||||
setIsLspRequested(false);
|
||||
if (typeof message.root !== "string") {
|
||||
console.error("Unexpected message from server:", message);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Started", message.root, config.main);
|
||||
// EventEmitter.dispatch("lspStarted", message);
|
||||
const {
|
||||
createConnection,
|
||||
MonacoLanguageClient,
|
||||
MonacoServices,
|
||||
Services,
|
||||
} = await import("monaco-languageclient");
|
||||
const services = MonacoServices.create(editorRef.current, {
|
||||
rootUri: `file://${message.root}`,
|
||||
});
|
||||
servicesDisposable = Services.install(services);
|
||||
const newURI = `file://${message.root}/${config.main}`;
|
||||
const oldModel = editorRef.current.getModel();
|
||||
console.log("Check 4", oldModel.uri, newURI);
|
||||
if (oldModel.uri.toString() !== newURI) {
|
||||
// This code is likely to be buggy as it will probably
|
||||
// never run and has thus never been tested.
|
||||
editorRef.current.setModel(
|
||||
monaco.editor.createModel(
|
||||
oldModel.getValue(),
|
||||
undefined,
|
||||
monaco.Uri.parse(newURI)
|
||||
)
|
||||
);
|
||||
oldModel.dispose();
|
||||
}
|
||||
|
||||
const RijuMessageReader = (
|
||||
await import("../../services/RijuMessageReader")
|
||||
).default;
|
||||
const RijuMessageWriter = (
|
||||
await import("../../services/RijuMessageWriter")
|
||||
).default;
|
||||
|
||||
const connection = createMessageConnection(
|
||||
new RijuMessageReader(socket),
|
||||
new RijuMessageWriter(socket, config)
|
||||
);
|
||||
const client = new MonacoLanguageClient({
|
||||
name: "Riju",
|
||||
clientOptions: {
|
||||
documentSelector: [{ pattern: "**" }],
|
||||
middleware: {
|
||||
workspace: {
|
||||
configuration: (params, token, configuration) => {
|
||||
return Array(configuration(params, token).length).fill(
|
||||
config.lsp.config !== undefined ? config.lsp.config : {}
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
initializationOptions: config.lsp.init || {},
|
||||
},
|
||||
connectionProvider: {
|
||||
get: (errorHandler, closeHandler) =>
|
||||
Promise.resolve(
|
||||
createConnection(connection, errorHandler, closeHandler)
|
||||
),
|
||||
},
|
||||
});
|
||||
clientDisposable = client.start();
|
||||
return;
|
||||
case "lspOutput":
|
||||
// Should be handled by RijuMessageReader
|
||||
return;
|
||||
case "serviceLog":
|
||||
if (
|
||||
typeof message.service !== "string" ||
|
||||
typeof message.output !== "string"
|
||||
) {
|
||||
console.error("Unexpected message from server:", message);
|
||||
return;
|
||||
}
|
||||
let buffer = serviceLogBuffers[message.service] || "";
|
||||
let lines = serviceLogLines[message.service] || [];
|
||||
buffer += message.output;
|
||||
while (buffer.includes("\n")) {
|
||||
const idx = buffer.indexOf("\n");
|
||||
const line = buffer.slice(0, idx);
|
||||
buffer = buffer.slice(idx + 1);
|
||||
lines.push(line);
|
||||
if (DEBUG) {
|
||||
console.log(`${message.service.toUpperCase()} || ${line}`);
|
||||
}
|
||||
}
|
||||
serviceLogBuffers[message.service] = buffer;
|
||||
serviceLogLines[message.service] = lines;
|
||||
return;
|
||||
case "serviceFailed":
|
||||
if (
|
||||
typeof message.service !== "string" ||
|
||||
typeof message.error !== "string"
|
||||
) {
|
||||
console.error("Unexpected message from server:", message);
|
||||
return;
|
||||
}
|
||||
switch (message.service) {
|
||||
case "formatter":
|
||||
setFormatting(false);
|
||||
// showError({
|
||||
// message: "Could not prettify code!",
|
||||
// data: serviceLogLines["formatter"].join("\n"),
|
||||
// });
|
||||
break;
|
||||
case "lsp":
|
||||
setLspStarted(false);
|
||||
setIsLspRequested(false);
|
||||
// lspButton.classList.add("is-light");
|
||||
// lspButtonState.innerText = "CRASHED";
|
||||
break;
|
||||
case "terminal":
|
||||
sendToTerminal(
|
||||
"terminalOutput",
|
||||
ansi.red(`\r\n[${message.error}]`)
|
||||
);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
console.error("Unexpected message from server:", message);
|
||||
}
|
||||
});
|
||||
socket.addEventListener("close", (event) => {
|
||||
if (event.wasClean) {
|
||||
console.log("Connection closed cleanly");
|
||||
} else {
|
||||
console.error("Connection died");
|
||||
}
|
||||
if (clientDisposable) {
|
||||
clientDisposable.dispose();
|
||||
clientDisposable = null;
|
||||
}
|
||||
if (servicesDisposable) {
|
||||
servicesDisposable.dispose();
|
||||
servicesDisposable = null;
|
||||
}
|
||||
setRunning(false);
|
||||
setLspStarted(false);
|
||||
setIsLspRequested(false);
|
||||
});
|
||||
|
||||
return () => socket && socket.close();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!config || !mounted) return;
|
||||
const socket = connect();
|
||||
return () => socket && socket.close();
|
||||
}, [config, mounted]);
|
||||
|
||||
function showValue() {
|
||||
setRunning(true);
|
||||
EventEmitter.dispatch("send", {
|
||||
event: "runCode",
|
||||
code: editorRef.current.getValue(),
|
||||
});
|
||||
}
|
||||
|
||||
function sendFormat() {
|
||||
setFormatting(true);
|
||||
serviceLogBuffers["formatter"] = "";
|
||||
serviceLogLines["formatter"] = [];
|
||||
EventEmitter.dispatch("send", {
|
||||
event: "formatCode",
|
||||
code: editorRef.current.getValue(),
|
||||
});
|
||||
}
|
||||
|
||||
function editorDidMount(editor, monaco) {
|
||||
editorRef.current = editor;
|
||||
|
||||
editor.addAction({
|
||||
id: "runCode",
|
||||
label: "Run",
|
||||
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],
|
||||
contextMenuGroupId: "2_execution",
|
||||
run: () => {
|
||||
showValue();
|
||||
},
|
||||
});
|
||||
// editor.getModel().onDidChangeContent(() => recordActivity());
|
||||
// window.addEventListener("resize", () => editor.layout());
|
||||
editor.getModel().setValue(config.template + "\n");
|
||||
monaco.editor.setModelLanguage(
|
||||
editor.getModel(),
|
||||
config.monacoLang || "plaintext"
|
||||
);
|
||||
setMounted(true);
|
||||
}
|
||||
|
||||
const handleLspClick = () => {
|
||||
setIsLspRequested(true);
|
||||
if (isLspStarted) {
|
||||
EventEmitter.dispatch("send", {
|
||||
event: "lspStop",
|
||||
});
|
||||
} else {
|
||||
EventEmitter.dispatch("send", {
|
||||
event: "lspStart",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Riju</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Riju - fast playground for any language"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="minimum-scale=1, initial-scale=1, width=device-width"
|
||||
/>
|
||||
</Head>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
bgcolor: "#fff",
|
||||
}}
|
||||
component="main"
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
boxShadow: `0 2px 4px rgb(0 0 0 / 2%)`,
|
||||
width: "60%",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ flexGrow: 1, display: "flex", alignItems: "center" }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ borderRadius: 0, minWidth: 0 }}
|
||||
disableElevation
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
router.push("/");
|
||||
}}
|
||||
>
|
||||
<Home fontSize={"small"} />
|
||||
</Button>
|
||||
<Typography sx={{ fontSize: 14, px: 2, fontWeight: 600 }}>
|
||||
{config.name}
|
||||
</Typography>
|
||||
</Box>
|
||||
<LoadingButton
|
||||
onClick={handleLspClick}
|
||||
size="small"
|
||||
variant="text"
|
||||
loading={isLspRequested}
|
||||
sx={{
|
||||
borderRadius: 0,
|
||||
visibility: config.lsp ? "visible" : "hidden",
|
||||
mr: 1,
|
||||
color: (t) =>
|
||||
isLspStarted ? t.palette.success.main : t.palette.text.disabled,
|
||||
}}
|
||||
disableElevation
|
||||
endIcon={
|
||||
<Circle
|
||||
fontSize="small"
|
||||
sx={{
|
||||
color: "inherit",
|
||||
fontSize: "0.6em !important",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Typography sx={{ fontSize: 12 }}>Autocomplete</Typography>
|
||||
</LoadingButton>
|
||||
<Button
|
||||
onClick={sendFormat}
|
||||
disabled={isFormatting || isRunning}
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="contained"
|
||||
sx={{
|
||||
borderRadius: 0,
|
||||
visibility: config.format ? "visible" : "hidden",
|
||||
}}
|
||||
disableElevation
|
||||
>
|
||||
<Stack direction="row" gap={1} alignItems="center">
|
||||
<Typography sx={{ fontSize: 12 }}>Prettify</Typography>
|
||||
<Format fontSize="small" />
|
||||
</Stack>
|
||||
</Button>
|
||||
<Divider orientation="vertical" />
|
||||
<Button
|
||||
onClick={showValue}
|
||||
disabled={isRunning || isRunning}
|
||||
size="small"
|
||||
color="success"
|
||||
variant="contained"
|
||||
sx={{ borderRadius: 0 }}
|
||||
disableElevation
|
||||
>
|
||||
<Stack direction="row" gap={1} alignItems="center">
|
||||
<Typography sx={{ fontSize: 12, color: "#fff" }}>Run</Typography>
|
||||
<PlayArrow fontSize="small" htmlColor="#fff" />
|
||||
</Stack>
|
||||
</Button>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
flexGrow: 1,
|
||||
alignItems: "stretch",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ backgroundColor: "white", width: "60%" }}>
|
||||
<Box
|
||||
component={MonacoEditor}
|
||||
wrapperClassName={"rijuEditor"}
|
||||
height="90vh"
|
||||
defaultLanguage="javascript"
|
||||
defaultValue="// some comment"
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
scrollbar: { verticalScrollbarSize: 0 },
|
||||
fontLigatures: true,
|
||||
fontFamily: "Fira Code",
|
||||
}}
|
||||
onMount={editorDidMount}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
backgroundColor: "#292D3E",
|
||||
width: "40%",
|
||||
}}
|
||||
>
|
||||
<RijuTerminal />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CodeRunner.getInitialProps = async (ctx) => {
|
||||
const { req, query } = ctx;
|
||||
console.log("Query", query);
|
||||
let config = langs.javascript;
|
||||
if (query.lang) config = langs[query.lang];
|
||||
return {
|
||||
langConfig: config, // will be passed to the page component as props
|
||||
};
|
||||
};
|
||||
|
||||
// export async function getServerSideProps(ctx) {
|
||||
// // TODO: Fetch language details using api route
|
||||
// const { req, query } = ctx;
|
||||
// let lsp = langs.javascript;
|
||||
// if (query.lang) lsp = langs[query.lang];
|
||||
// return {
|
||||
// props: { lsp }, // will be passed to the page component as props
|
||||
// };
|
||||
// }
|
||||
|
||||
export default CodeRunner;
|
|
@ -1,37 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Riju</title>
|
||||
<link rel="stylesheet" href="/css/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Riju: <i>fast</i> online playground for every programming language</h1>
|
||||
<% if (Object.keys(langs).length > 0) { %>
|
||||
<i>Pick your favorite language to get started:</i>
|
||||
<div class="grid">
|
||||
<% for (const [id, {name}] of Object.entries(langs).sort(
|
||||
([id1, {name: name1}], [id2, {name: name2}]) => name1.toLowerCase().localeCompare(name2.toLowerCase()))) { %>
|
||||
<a href=<%= "/" + encodeURIComponent(id) %> class="language">
|
||||
<div class="language">
|
||||
<%= name %>
|
||||
</div>
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
<p>
|
||||
<i>
|
||||
Created by
|
||||
<a href="https://github.com/raxod502">Radon Rosborough</a>.
|
||||
Check out the project
|
||||
<a href="https://github.com/raxod502/riju">on GitHub</a>.
|
||||
</i>
|
||||
</p>
|
||||
<% } else { %>
|
||||
<i>Riju is loading language configuration...</i>
|
||||
<% } %>
|
||||
<% if (fathomSiteId) { %>
|
||||
<script src="https://cdn.usefathom.com/script.js" data-site="<%= fathomSiteId %>" defer></script>
|
||||
<% } %>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,123 @@
|
|||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import { Autocomplete, Box, Typography } from "@mui/material";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useRef, useState } from "react";
|
||||
import LanguageLink from "../components/LanguageLink";
|
||||
import { Search, SearchIconWrapper, StyledInputBase } from "../components/UI";
|
||||
import langs from "../static/data.json";
|
||||
|
||||
export default function Home() {
|
||||
const [selected, setSelected] = useState(null);
|
||||
const router = useRouter();
|
||||
const search = useRef();
|
||||
|
||||
const moveToEditor = (link) => {
|
||||
router.push(`/editor/${link.id}`);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (search.current) {
|
||||
search.current.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Riju</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Riju - fast playground for any language"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="minimum-scale=1, initial-scale=1, width=device-width"
|
||||
/>
|
||||
</Head>
|
||||
<Box component="main" sx={{ m: 3 }}>
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h2"
|
||||
component="h2"
|
||||
sx={{ fontWeight: "bolder", color: "#000000" }}
|
||||
>
|
||||
Riju
|
||||
</Typography>
|
||||
<Typography variant="subtitle2" sx={{ color: "rgb(113,128,150)" }}>
|
||||
fast online playground for every programming language
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
width: "70%",
|
||||
m: 3,
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
zIndex: (theme) => theme.zIndex.appBar + 1,
|
||||
borderRadius: (theme) => theme.shape.borderRadius,
|
||||
backgroundColor: (theme) =>
|
||||
theme.palette.type === "dark"
|
||||
? theme.palette.common.black
|
||||
: theme.palette.common.white,
|
||||
}}
|
||||
id="search"
|
||||
>
|
||||
<Search>
|
||||
<SearchIconWrapper>
|
||||
<SearchIcon fontSize="large" color="action" />
|
||||
</SearchIconWrapper>
|
||||
<Autocomplete
|
||||
sx={{ width: "100%" }}
|
||||
options={langs}
|
||||
getOptionLabel={(option) => option.name}
|
||||
value={selected}
|
||||
onChange={(event, newValue) => {
|
||||
if (!newValue) return;
|
||||
setSelected(newValue);
|
||||
moveToEditor(newValue);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<StyledInputBase
|
||||
inputRef={(e) => {
|
||||
params.InputProps.ref(e);
|
||||
search.current = e;
|
||||
return e;
|
||||
}}
|
||||
placeholder="Search…"
|
||||
inputProps={{
|
||||
...params.inputProps,
|
||||
"aria-label": "search",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Search>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
mt: 1,
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: 1,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{langs.map((link, i) => (
|
||||
<LanguageLink key={i} link={link} />
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -0,0 +1,4 @@
|
|||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,49 @@
|
|||
import { AbstractMessageReader } from "vscode-jsonrpc/lib/messageReader";
|
||||
|
||||
const DEBUG = window.location.hash === "#debug";
|
||||
|
||||
class RijuMessageReader extends AbstractMessageReader {
|
||||
constructor(socket) {
|
||||
super();
|
||||
this.state = "initial";
|
||||
this.callback = null;
|
||||
this.messageQueue = [];
|
||||
this.socket = socket;
|
||||
this.socket.addEventListener("message", (event) => {
|
||||
this.readMessage(event.data);
|
||||
});
|
||||
}
|
||||
|
||||
listen(callback) {
|
||||
if (this.state === "initial") {
|
||||
this.state = "listening";
|
||||
this.callback = callback;
|
||||
while (this.messageQueue.length > 0) {
|
||||
this.readMessage(this.messageQueue.pop());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readMessage(rawMessage) {
|
||||
if (this.state === "initial") {
|
||||
this.messageQueue.splice(0, 0, rawMessage);
|
||||
} else if (this.state === "listening") {
|
||||
let message;
|
||||
try {
|
||||
message = JSON.parse(rawMessage);
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
switch (message && message.event) {
|
||||
case "lspOutput":
|
||||
if (DEBUG) {
|
||||
console.log("RECEIVE LSP:", message.output);
|
||||
}
|
||||
this.callback(message.output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default RijuMessageReader;
|
|
@ -0,0 +1,42 @@
|
|||
import { AbstractMessageWriter } from "vscode-jsonrpc/lib/messageWriter";
|
||||
|
||||
const DEBUG = window.location.hash === "#debug";
|
||||
|
||||
class RijuMessageWriter extends AbstractMessageWriter {
|
||||
constructor(socket, config) {
|
||||
super();
|
||||
this.socket = socket;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
write(msg) {
|
||||
switch (msg.method) {
|
||||
case "initialize":
|
||||
msg.params.processId = null;
|
||||
if (this.config.lsp.disableDynamicRegistration) {
|
||||
this.disableDynamicRegistration(msg);
|
||||
}
|
||||
break;
|
||||
case "textDocument/didOpen":
|
||||
if (this.config.lsp.lang) {
|
||||
msg.params.textDocument.languageId = this.config.lsp.lang;
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
console.log("SEND LSP:", msg);
|
||||
}
|
||||
this.socket.send(JSON.stringify({ event: "lspInput", input: msg }));
|
||||
}
|
||||
|
||||
disableDynamicRegistration(msg) {
|
||||
if (!msg || typeof msg !== "object") return;
|
||||
for (const [key, val] of Object.entries(msg)) {
|
||||
if (key === "dynamicRegistration" && val === true) {
|
||||
msg.dynamicRegistration = false;
|
||||
}
|
||||
this.disableDynamicRegistration(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default RijuMessageWriter;
|
|
@ -1,441 +0,0 @@
|
|||
import * as monaco from "monaco-editor";
|
||||
import {
|
||||
createConnection,
|
||||
MonacoLanguageClient,
|
||||
MonacoServices,
|
||||
Services,
|
||||
} from "monaco-languageclient";
|
||||
import { createMessageConnection } from "vscode-jsonrpc";
|
||||
import { AbstractMessageReader } from "vscode-jsonrpc/lib/messageReader.js";
|
||||
import { AbstractMessageWriter } from "vscode-jsonrpc/lib/messageWriter.js";
|
||||
import { Terminal } from "xterm";
|
||||
import { FitAddon } from "xterm-addon-fit";
|
||||
|
||||
import "xterm/css/xterm.css";
|
||||
|
||||
const DEBUG = window.location.hash === "#debug";
|
||||
const config = window.rijuConfig;
|
||||
|
||||
const formatButton = document.getElementById("formatButton");
|
||||
const lspButton = document.getElementById("lspButton");
|
||||
const lspButtonState = document.getElementById("lspButtonState");
|
||||
const connectionStatus = document.getElementById("connectionStatus");
|
||||
|
||||
function closeModal() {
|
||||
document.querySelector("html").classList.remove("is-clipped");
|
||||
document.getElementById("modal").classList.remove("is-active");
|
||||
}
|
||||
|
||||
function showError({ message, data }) {
|
||||
document.getElementById("modal-title").innerText = message;
|
||||
document.getElementById("modal-data").innerText =
|
||||
data || "(no output on stderr)";
|
||||
document.getElementById("modal").classList.add("is-active");
|
||||
document.querySelector("html").classList.add("is-clipped");
|
||||
}
|
||||
|
||||
class RijuMessageReader extends AbstractMessageReader {
|
||||
constructor(socket) {
|
||||
super();
|
||||
this.state = "initial";
|
||||
this.callback = null;
|
||||
this.messageQueue = [];
|
||||
this.socket = socket;
|
||||
this.socket.addEventListener("message", (event) => {
|
||||
this.readMessage(event.data);
|
||||
});
|
||||
}
|
||||
|
||||
listen(callback) {
|
||||
if (this.state === "initial") {
|
||||
this.state = "listening";
|
||||
this.callback = callback;
|
||||
while (this.messageQueue.length > 0) {
|
||||
this.readMessage(this.messageQueue.pop());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readMessage(rawMessage) {
|
||||
if (this.state === "initial") {
|
||||
this.messageQueue.splice(0, 0, rawMessage);
|
||||
} else if (this.state === "listening") {
|
||||
let message;
|
||||
try {
|
||||
message = JSON.parse(rawMessage);
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
switch (message && message.event) {
|
||||
case "lspOutput":
|
||||
if (DEBUG) {
|
||||
console.log("RECEIVE LSP:", message.output);
|
||||
}
|
||||
this.callback(message.output);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RijuMessageWriter extends AbstractMessageWriter {
|
||||
constructor(socket) {
|
||||
super();
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
write(msg) {
|
||||
switch (msg.method) {
|
||||
case "initialize":
|
||||
msg.params.processId = null;
|
||||
if (config.lsp.disableDynamicRegistration) {
|
||||
this.disableDynamicRegistration(msg);
|
||||
}
|
||||
break;
|
||||
case "textDocument/didOpen":
|
||||
if (config.lsp.lang) {
|
||||
msg.params.textDocument.languageId = config.lsp.lang;
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
console.log("SEND LSP:", msg);
|
||||
}
|
||||
this.socket.send(JSON.stringify({ event: "lspInput", input: msg }));
|
||||
}
|
||||
|
||||
disableDynamicRegistration(msg) {
|
||||
if (!msg || typeof msg !== "object") return;
|
||||
for (const [key, val] of Object.entries(msg)) {
|
||||
if (key === "dynamicRegistration" && val === true)
|
||||
msg.dynamicRegistration = false;
|
||||
this.disableDynamicRegistration(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let serviceLogBuffers = {};
|
||||
let serviceLogLines = {};
|
||||
|
||||
let lastActivityTimestamp = new Date();
|
||||
let idleDueToInactivity = false;
|
||||
|
||||
function recordActivity() {
|
||||
lastActivityTimestamp = new Date();
|
||||
if (idleDueToInactivity) {
|
||||
scheduleConnect();
|
||||
}
|
||||
}
|
||||
|
||||
const term = new Terminal();
|
||||
const fitAddon = new FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
term.open(document.getElementById("terminal"));
|
||||
|
||||
fitAddon.fit();
|
||||
window.addEventListener("resize", () => fitAddon.fit());
|
||||
|
||||
await new Promise((resolve) =>
|
||||
term.write("Connecting to server...", resolve)
|
||||
);
|
||||
|
||||
const initialRetryDelayMs = 200;
|
||||
let retryDelayMs = initialRetryDelayMs;
|
||||
|
||||
function sendMessage(message) {
|
||||
if (DEBUG) {
|
||||
console.log("SEND:", message);
|
||||
}
|
||||
if (socket) {
|
||||
socket.send(JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
function tryConnect() {
|
||||
serviceLogBuffers = {};
|
||||
serviceLogLines = {};
|
||||
let clientDisposable = null;
|
||||
let servicesDisposable = null;
|
||||
connectionStatus.innerText = "connecting...";
|
||||
console.log("Connecting to server...");
|
||||
socket = new WebSocket(
|
||||
(document.location.protocol === "http:" ? "ws://" : "wss://") +
|
||||
document.location.host +
|
||||
`/api/v1/ws?lang=${encodeURIComponent(config.id)}`
|
||||
);
|
||||
socket.addEventListener("open", () => {
|
||||
connectionStatus.innerText = "connected";
|
||||
console.log("Successfully connected to server");
|
||||
});
|
||||
socket.addEventListener("message", (event) => {
|
||||
let message;
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
} catch (err) {
|
||||
console.error("Malformed message from server:", event.data);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
DEBUG &&
|
||||
message &&
|
||||
message.event !== "lspOutput" &&
|
||||
message.event !== "serviceLog"
|
||||
) {
|
||||
console.log("RECEIVE:", message);
|
||||
}
|
||||
if (message && message.event && message.event !== "error") {
|
||||
retryDelayMs = initialRetryDelayMs;
|
||||
}
|
||||
switch (message && message.event) {
|
||||
case "terminalClear":
|
||||
term.reset();
|
||||
return;
|
||||
case "terminalOutput":
|
||||
if (typeof message.output !== "string") {
|
||||
console.error("Unexpected message from server:", message);
|
||||
return;
|
||||
}
|
||||
term.write(message.output);
|
||||
return;
|
||||
case "formattedCode":
|
||||
formatButton.disabled = false;
|
||||
formatButton.classList.remove("is-loading");
|
||||
if (
|
||||
typeof message.code !== "string" ||
|
||||
typeof message.originalCode !== "string"
|
||||
) {
|
||||
console.error("Unexpected message from server:", message);
|
||||
return;
|
||||
}
|
||||
if (editor.getValue() === message.originalCode) {
|
||||
editor.setValue(message.code);
|
||||
}
|
||||
return;
|
||||
case "lspStopped":
|
||||
lspButton.disabled = false;
|
||||
lspButton.classList.remove("is-loading");
|
||||
lspButton.classList.add("is-light");
|
||||
lspButtonState.innerText = "OFF";
|
||||
if (clientDisposable) {
|
||||
clientDisposable.dispose();
|
||||
clientDisposable = null;
|
||||
}
|
||||
if (servicesDisposable) {
|
||||
servicesDisposable.dispose();
|
||||
servicesDisposable = null;
|
||||
}
|
||||
break;
|
||||
case "lspStarted":
|
||||
lspButton.disabled = false;
|
||||
lspButton.classList.remove("is-loading");
|
||||
lspButton.classList.remove("is-light");
|
||||
lspButtonState.innerText = "ON";
|
||||
if (typeof message.root !== "string") {
|
||||
console.error("Unexpected message from server:", message);
|
||||
return;
|
||||
}
|
||||
const services = MonacoServices.create(editor, {
|
||||
rootUri: `file://${message.root}`,
|
||||
});
|
||||
servicesDisposable = Services.install(services);
|
||||
const newURI = `file://${message.root}/${config.main}`;
|
||||
const oldModel = editor.getModel();
|
||||
if (oldModel.uri.toString() !== newURI) {
|
||||
// This code is likely to be buggy as it will probably
|
||||
// never run and has thus never been tested.
|
||||
editor.setModel(
|
||||
monaco.editor.createModel(
|
||||
oldModel.getValue(),
|
||||
undefined,
|
||||
monaco.Uri.parse(newURI)
|
||||
)
|
||||
);
|
||||
oldModel.dispose();
|
||||
}
|
||||
const connection = createMessageConnection(
|
||||
new RijuMessageReader(socket),
|
||||
new RijuMessageWriter(socket)
|
||||
);
|
||||
const client = new MonacoLanguageClient({
|
||||
name: "Riju",
|
||||
clientOptions: {
|
||||
documentSelector: [{ pattern: "**" }],
|
||||
middleware: {
|
||||
workspace: {
|
||||
configuration: (params, token, configuration) => {
|
||||
return Array(configuration(params, token).length).fill(
|
||||
config.lsp.config !== undefined ? config.lsp.config : {}
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
initializationOptions: config.lsp.init || {},
|
||||
},
|
||||
connectionProvider: {
|
||||
get: (errorHandler, closeHandler) =>
|
||||
Promise.resolve(
|
||||
createConnection(connection, errorHandler, closeHandler)
|
||||
),
|
||||
},
|
||||
});
|
||||
clientDisposable = client.start();
|
||||
return;
|
||||
case "lspOutput":
|
||||
// Should be handled by RijuMessageReader
|
||||
return;
|
||||
case "serviceLog":
|
||||
if (
|
||||
typeof message.service !== "string" ||
|
||||
typeof message.output !== "string"
|
||||
) {
|
||||
console.error("Unexpected message from server:", message);
|
||||
return;
|
||||
}
|
||||
let buffer = serviceLogBuffers[message.service] || "";
|
||||
let lines = serviceLogLines[message.service] || [];
|
||||
buffer += message.output;
|
||||
while (buffer.includes("\n")) {
|
||||
const idx = buffer.indexOf("\n");
|
||||
const line = buffer.slice(0, idx);
|
||||
buffer = buffer.slice(idx + 1);
|
||||
lines.push(line);
|
||||
if (DEBUG) {
|
||||
console.log(`${message.service.toUpperCase()} || ${line}`);
|
||||
}
|
||||
}
|
||||
serviceLogBuffers[message.service] = buffer;
|
||||
serviceLogLines[message.service] = lines;
|
||||
return;
|
||||
case "serviceFailed":
|
||||
if (
|
||||
typeof message.service !== "string" ||
|
||||
typeof message.error !== "string"
|
||||
) {
|
||||
console.error("Unexpected message from server:", message);
|
||||
return;
|
||||
}
|
||||
switch (message.service) {
|
||||
case "formatter":
|
||||
formatButton.disabled = false;
|
||||
formatButton.classList.remove("is-loading");
|
||||
showError({
|
||||
message: "Could not prettify code!",
|
||||
data: serviceLogLines["formatter"].join("\n"),
|
||||
});
|
||||
break;
|
||||
case "lsp":
|
||||
lspButton.disabled = false;
|
||||
lspButton.classList.remove("is-loading");
|
||||
lspButton.classList.add("is-light");
|
||||
lspButtonState.innerText = "CRASHED";
|
||||
break;
|
||||
case "terminal":
|
||||
term.write(`\r\n[${message.error}]`);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
console.error("Unexpected message from server:", message);
|
||||
return;
|
||||
}
|
||||
});
|
||||
socket.addEventListener("close", (event) => {
|
||||
if (event.wasClean) {
|
||||
console.log("Connection closed cleanly");
|
||||
} else {
|
||||
console.error("Connection died");
|
||||
}
|
||||
if (clientDisposable) {
|
||||
clientDisposable.dispose();
|
||||
clientDisposable = null;
|
||||
}
|
||||
if (servicesDisposable) {
|
||||
servicesDisposable.dispose();
|
||||
servicesDisposable = null;
|
||||
}
|
||||
if (lspButtonState.innerText === "ON") {
|
||||
lspButton.disabled = false;
|
||||
lspButton.classList.remove("is-loading");
|
||||
lspButton.classList.add("is-light");
|
||||
lspButtonState.innerText = "DISCONNECTED";
|
||||
}
|
||||
scheduleConnect();
|
||||
});
|
||||
}
|
||||
|
||||
function scheduleConnect() {
|
||||
idleDueToInactivity = new Date() - lastActivityTimestamp > 10 * 60 * 1000;
|
||||
if (idleDueToInactivity) {
|
||||
connectionStatus.innerText = "idle";
|
||||
return;
|
||||
}
|
||||
const delay = retryDelayMs * Math.random();
|
||||
console.log(`Trying to reconnect in ${Math.floor(delay)}ms`);
|
||||
setTimeout(tryConnect, delay);
|
||||
retryDelayMs *= 2;
|
||||
}
|
||||
|
||||
let socket = null;
|
||||
tryConnect();
|
||||
|
||||
term.onData((data) => {
|
||||
sendMessage({ event: "terminalInput", input: data });
|
||||
recordActivity();
|
||||
});
|
||||
|
||||
const editor = monaco.editor.create(document.getElementById("editor"), {
|
||||
minimap: { enabled: false },
|
||||
scrollbar: { verticalScrollbarSize: 0 },
|
||||
});
|
||||
editor.addAction({
|
||||
id: "runCode",
|
||||
label: "Run",
|
||||
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter],
|
||||
contextMenuGroupId: "2_execution",
|
||||
run: () => {
|
||||
sendMessage({ event: "runCode", code: editor.getValue() });
|
||||
},
|
||||
});
|
||||
editor.getModel().onDidChangeContent(() => recordActivity());
|
||||
window.addEventListener("resize", () => editor.layout());
|
||||
editor.getModel().setValue(config.template + "\n");
|
||||
monaco.editor.setModelLanguage(
|
||||
editor.getModel(),
|
||||
config.monacoLang || "plaintext"
|
||||
);
|
||||
|
||||
document.getElementById("runButton").addEventListener("click", () => {
|
||||
sendMessage({ event: "runCode", code: editor.getValue() });
|
||||
});
|
||||
if (config.format) {
|
||||
formatButton.classList.remove("is-hidden");
|
||||
formatButton.addEventListener("click", () => {
|
||||
formatButton.classList.add("is-loading");
|
||||
formatButton.disabled = true;
|
||||
serviceLogBuffers["formatter"] = "";
|
||||
serviceLogLines["formatter"] = [];
|
||||
sendMessage({ event: "formatCode", code: editor.getValue() });
|
||||
});
|
||||
}
|
||||
if (config.lsp) {
|
||||
lspButton.classList.remove("is-hidden");
|
||||
lspButton.addEventListener("click", () => {
|
||||
lspButton.classList.add("is-loading");
|
||||
lspButton.disabled = true;
|
||||
lspButton.classList.remove("is-light");
|
||||
if (lspButtonState.innerText === "ON") {
|
||||
sendMessage({ event: "lspStop" });
|
||||
} else {
|
||||
serviceLogBuffers["lsp"] = "";
|
||||
serviceLogLines["lsp"] = [];
|
||||
sendMessage({ event: "lspStart" });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (const elt of document.querySelectorAll(".will-close-modal")) {
|
||||
elt.addEventListener("click", closeModal);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
|
@ -0,0 +1,226 @@
|
|||
[
|
||||
{ "name": "><>", "id": "fishlang" },
|
||||
{ "name": "A+", "id": "aplus" },
|
||||
{ "name": "ABC", "id": "abc" },
|
||||
{ "name": "Ada", "id": "ada" },
|
||||
{ "name": "Afnix", "id": "afnix" },
|
||||
{ "name": "ALGOL 68", "id": "algol" },
|
||||
{ "name": "Ante", "id": "ante" },
|
||||
{ "name": "Ante (Cards)", "id": "antecards" },
|
||||
{ "name": "APL", "id": "apl" },
|
||||
{ "name": "ARM", "id": "arm" },
|
||||
{ "name": "AsciiDoc", "id": "asciidoc" },
|
||||
{ "name": "AspectC++", "id": "aspectcpp" },
|
||||
{ "name": "AspectJ", "id": "aspectj" },
|
||||
{ "name": "Asymptote", "id": "asymptote" },
|
||||
{ "name": "ATS", "id": "ats" },
|
||||
{ "name": "Awk", "id": "awk" },
|
||||
{ "name": "Bash", "id": "bash" },
|
||||
{ "name": "BASIC", "id": "basic" },
|
||||
{ "name": "Battlestar", "id": "battlestar" },
|
||||
{ "name": "bc", "id": "bc" },
|
||||
{ "name": "Beanshell", "id": "beanshell" },
|
||||
{ "name": "Beatnik", "id": "beatnik" },
|
||||
{ "name": "Befunge", "id": "befunge" },
|
||||
{ "name": "Binary Lambda Calculus", "id": "blc" },
|
||||
{ "name": "Boo", "id": "boo" },
|
||||
{ "name": "Brainf***", "id": "brainf" },
|
||||
{ "name": "Bython", "id": "bython" },
|
||||
{ "name": "C", "id": "c" },
|
||||
{ "name": "C#", "id": "csharp" },
|
||||
{ "name": "C++", "id": "cpp" },
|
||||
{ "name": "Carp", "id": "carp" },
|
||||
{ "name": "Cat", "id": "cat" },
|
||||
{ "name": "Ceylon", "id": "ceylon" },
|
||||
{ "name": "Chef", "id": "chef" },
|
||||
{ "name": "CIL", "id": "cil" },
|
||||
{ "name": "Clean", "id": "clean" },
|
||||
{ "name": "Clojure", "id": "clojure" },
|
||||
{ "name": "ClojureScript", "id": "clojurescript" },
|
||||
{ "name": "CMake", "id": "cmake" },
|
||||
{ "name": "Cmd", "id": "cmd" },
|
||||
{ "name": "COBOL", "id": "cobol" },
|
||||
{ "name": "Coconut", "id": "coconut" },
|
||||
{ "name": "CoffeeScript", "id": "coffeescript" },
|
||||
{ "name": "Common Lisp", "id": "commonlisp" },
|
||||
{ "name": "Confluence", "id": "confluence" },
|
||||
{ "name": "Crystal", "id": "crystal" },
|
||||
{ "name": "Curry", "id": "curry" },
|
||||
{ "name": "D", "id": "d" },
|
||||
{ "name": "Dafny", "id": "dafny" },
|
||||
{ "name": "Dart", "id": "dart" },
|
||||
{ "name": "dc", "id": "dc" },
|
||||
{ "name": "Dhall", "id": "dhall" },
|
||||
{ "name": "Dogescript", "id": "dogescript" },
|
||||
{ "name": "DokuWiki", "id": "dokuwiki" },
|
||||
{ "name": "Dylan", "id": "dylan" },
|
||||
{ "name": "eC", "id": "ec" },
|
||||
{ "name": "Elixir", "id": "elixir" },
|
||||
{ "name": "Elm", "id": "elm" },
|
||||
{ "name": "Elvish", "id": "elvish" },
|
||||
{ "name": "Emacs Lisp", "id": "emacslisp" },
|
||||
{ "name": "Emojicode", "id": "emojicode" },
|
||||
{ "name": "Entropy", "id": "entropy" },
|
||||
{ "name": "Erlang", "id": "erlang" },
|
||||
{ "name": "Euphoria", "id": "euphoria" },
|
||||
{ "name": "F#", "id": "fsharp" },
|
||||
{ "name": "Factor", "id": "factor" },
|
||||
{ "name": "FALSE", "id": "false" },
|
||||
{ "name": "Fish", "id": "fish" },
|
||||
{ "name": "Flex", "id": "flex" },
|
||||
{ "name": "Forth", "id": "forth" },
|
||||
{ "name": "FORTRAN", "id": "fortran" },
|
||||
{ "name": "Gambas", "id": "gambas" },
|
||||
{ "name": "GAP", "id": "gap" },
|
||||
{ "name": "GDB", "id": "gdb" },
|
||||
{ "name": "GEL", "id": "gel" },
|
||||
{ "name": "Gnuplot", "id": "gnuplot" },
|
||||
{ "name": "Go", "id": "go" },
|
||||
{ "name": "GolfScript", "id": "golfscript" },
|
||||
{ "name": "Grass", "id": "grass" },
|
||||
{ "name": "Groovy", "id": "groovy" },
|
||||
{ "name": "Hack", "id": "hack" },
|
||||
{ "name": "Haskell", "id": "haskell" },
|
||||
{ "name": "Haxe", "id": "haxe" },
|
||||
{ "name": "HCL", "id": "hcl" },
|
||||
{ "name": "Hexagony", "id": "hexagony" },
|
||||
{ "name": "HMMM", "id": "hmmm" },
|
||||
{ "name": "Hy", "id": "hy" },
|
||||
{ "name": "Icon", "id": "icon" },
|
||||
{ "name": "Idris", "id": "idris" },
|
||||
{ "name": "Ink", "id": "ink" },
|
||||
{ "name": "INTERCAL", "id": "intercal" },
|
||||
{ "name": "Io", "id": "io" },
|
||||
{ "name": "Ioke", "id": "ioke" },
|
||||
{ "name": "J", "id": "j" },
|
||||
{ "name": "Jasmin", "id": "jasmin" },
|
||||
{ "name": "Java", "id": "java" },
|
||||
{ "name": "JavaScript", "id": "javascript" },
|
||||
{ "name": "jq", "id": "jq" },
|
||||
{ "name": "JSF***", "id": "jsf" },
|
||||
{ "name": "Julia", "id": "julia" },
|
||||
{ "name": "Kalyn", "id": "kalyn" },
|
||||
{ "name": "Kitten", "id": "kitten" },
|
||||
{ "name": "Kotlin", "id": "kotlin" },
|
||||
{ "name": "Ksh", "id": "ksh" },
|
||||
{ "name": "Lazy K", "id": "lazyk" },
|
||||
{ "name": "Less", "id": "less" },
|
||||
{ "name": "Limbo", "id": "limbo" },
|
||||
{ "name": "Lisaac", "id": "lisaac" },
|
||||
{ "name": "LiveScript", "id": "livescript" },
|
||||
{ "name": "LLVM", "id": "llvm" },
|
||||
{ "name": "LOLCODE", "id": "lolcode" },
|
||||
{ "name": "Lua", "id": "lua" },
|
||||
{ "name": "m4", "id": "m4" },
|
||||
{ "name": "Make", "id": "make" },
|
||||
{ "name": "Malbolge", "id": "malbolge" },
|
||||
{ "name": "MariaDB", "id": "mariadb" },
|
||||
{ "name": "Markdown", "id": "markdown" },
|
||||
{ "name": "MediaWiki", "id": "mediawiki" },
|
||||
{ "name": "MiniZinc", "id": "minizinc" },
|
||||
{ "name": "MIPS", "id": "mips" },
|
||||
{ "name": "Miranda", "id": "miranda" },
|
||||
{ "name": "MongoDB", "id": "mongodb" },
|
||||
{ "name": "MUMPS", "id": "mumps" },
|
||||
{ "name": "MySQL", "id": "mysql" },
|
||||
{ "name": "Neko", "id": "neko" },
|
||||
{ "name": "Nelua", "id": "nelua" },
|
||||
{ "name": "Nickle", "id": "nickle" },
|
||||
{ "name": "Nim", "id": "nim" },
|
||||
{ "name": "Oberon", "id": "oberon" },
|
||||
{ "name": "Objective-C", "id": "objectivec" },
|
||||
{ "name": "Objective-C++", "id": "objectivecpp" },
|
||||
{ "name": "OCaml", "id": "ocaml" },
|
||||
{ "name": "Octave", "id": "octave" },
|
||||
{ "name": "Odin", "id": "odin" },
|
||||
{ "name": "Omgrofl", "id": "omgrofl" },
|
||||
{ "name": "Ook", "id": "ook" },
|
||||
{ "name": "OpenSCAD", "id": "openscad" },
|
||||
{ "name": "Org", "id": "org" },
|
||||
{ "name": "Oz", "id": "oz" },
|
||||
{ "name": "PARI/GP", "id": "parigp" },
|
||||
{ "name": "Parser3", "id": "parser3" },
|
||||
{ "name": "Pascal", "id": "pascal" },
|
||||
{ "name": "PAWN", "id": "pawn" },
|
||||
{ "name": "Perl", "id": "perl" },
|
||||
{ "name": "PHP", "id": "php" },
|
||||
{ "name": "Pikachu", "id": "pikachu" },
|
||||
{ "name": "Pike", "id": "pike" },
|
||||
{ "name": "PostgreSQL", "id": "postgresql" },
|
||||
{ "name": "PostScript", "id": "postscript" },
|
||||
{ "name": "PowerShell", "id": "powershell" },
|
||||
{ "name": "Prolog", "id": "prolog" },
|
||||
{ "name": "PROMELA", "id": "promela" },
|
||||
{ "name": "PSeInt", "id": "pseint" },
|
||||
{ "name": "Pug", "id": "pug" },
|
||||
{ "name": "PureScript", "id": "purescript" },
|
||||
{ "name": "Python", "id": "python" },
|
||||
{ "name": "Q#", "id": "qsharp" },
|
||||
{ "name": "R", "id": "r" },
|
||||
{ "name": "Racket", "id": "racket" },
|
||||
{ "name": "Ratfor", "id": "ratfor" },
|
||||
{ "name": "rc", "id": "rc" },
|
||||
{ "name": "ReasonML", "id": "reasonml" },
|
||||
{ "name": "REBOL", "id": "rebol" },
|
||||
{ "name": "Red", "id": "red" },
|
||||
{ "name": "Redis", "id": "redis" },
|
||||
{ "name": "reStructuredText", "id": "restructuredtext" },
|
||||
{ "name": "REXX", "id": "rexx" },
|
||||
{ "name": "RISC-V", "id": "riscv" },
|
||||
{ "name": "roff", "id": "roff" },
|
||||
{ "name": "Ruby", "id": "ruby" },
|
||||
{ "name": "Rust", "id": "rust" },
|
||||
{ "name": "S-Lang", "id": "slang" },
|
||||
{ "name": "SageMath", "id": "sagemath" },
|
||||
{ "name": "Sass", "id": "sass" },
|
||||
{ "name": "Scala", "id": "scala" },
|
||||
{ "name": "Scheme", "id": "scheme" },
|
||||
{ "name": "Scilab", "id": "scilab" },
|
||||
{ "name": "SCSS", "id": "scss" },
|
||||
{ "name": "Sed", "id": "sed" },
|
||||
{ "name": "SETL", "id": "setl" },
|
||||
{ "name": "Sh", "id": "sh" },
|
||||
{ "name": "Shakespeare", "id": "shakespeare" },
|
||||
{ "name": "Slick", "id": "slick" },
|
||||
{ "name": "Smalltalk", "id": "smalltalk" },
|
||||
{ "name": "SNOBOL", "id": "snobol" },
|
||||
{ "name": "SQLite", "id": "sqlite" },
|
||||
{ "name": "Squirrel", "id": "squirrel" },
|
||||
{ "name": "Standard ML", "id": "standardml" },
|
||||
{ "name": "Subleq", "id": "subleq" },
|
||||
{ "name": "Swift", "id": "swift" },
|
||||
{ "name": "Tabloid", "id": "tabloid" },
|
||||
{ "name": "Tcl", "id": "tcl" },
|
||||
{ "name": "Tcsh", "id": "tcsh" },
|
||||
{ "name": "TECO", "id": "teco" },
|
||||
{ "name": "TeX", "id": "tex" },
|
||||
{ "name": "Textile", "id": "textile" },
|
||||
{ "name": "Thue", "id": "thue" },
|
||||
{ "name": "Tiki Wiki", "id": "tikiwiki" },
|
||||
{ "name": "TOML", "id": "toml" },
|
||||
{ "name": "TWiki", "id": "twiki" },
|
||||
{ "name": "TypeScript", "id": "typescript" },
|
||||
{ "name": "Unison", "id": "unison" },
|
||||
{ "name": "Unlambda", "id": "unlambda" },
|
||||
{ "name": "Vala", "id": "vala" },
|
||||
{ "name": "Velato", "id": "velato" },
|
||||
{ "name": "Verilog", "id": "verilog" },
|
||||
{ "name": "Vimscript", "id": "vimscript" },
|
||||
{ "name": "Vimwiki", "id": "vimwiki" },
|
||||
{ "name": "Visual Basic", "id": "visualbasic" },
|
||||
{ "name": "Whitespace", "id": "whitespace" },
|
||||
{ "name": "Wolfram Language", "id": "wolframlanguage" },
|
||||
{ "name": "x86", "id": "x86" },
|
||||
{ "name": "XSLT", "id": "xslt" },
|
||||
{ "name": "YAML", "id": "yaml" },
|
||||
{ "name": "YoptaScript", "id": "yoptascript" },
|
||||
{ "name": "Yorick", "id": "yorick" },
|
||||
{ "name": "Zig", "id": "zig" },
|
||||
{ "name": "Zoem", "id": "zoem" },
|
||||
{ "name": "Zot", "id": "zot" },
|
||||
{ "name": "Zsh", "id": "zsh" },
|
||||
{ "name": "Рапира", "id": "rapira" },
|
||||
{ "name": "قلب", "id": "qalb" },
|
||||
{ "name": "எழில்", "id": "ezhil" },
|
||||
{ "name": "아희", "id": "aheui" }
|
||||
]
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,121 @@
|
|||
.container {
|
||||
min-height: 100vh;
|
||||
padding: 0 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 5rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
border-top: 1px solid #eaeaea;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.title a {
|
||||
color: #0070f3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.title a:hover,
|
||||
.title a:focus,
|
||||
.title a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.title,
|
||||
.description {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
line-height: 1.5;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.code {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 800px;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 1rem;
|
||||
padding: 1.5rem;
|
||||
text-align: left;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 10px;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:focus,
|
||||
.card:active {
|
||||
color: #0070f3;
|
||||
border-color: #0070f3;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 1em;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
.xterm {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#header .button {
|
||||
border-radius: 0;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
background-image: radial-gradient(#ddd 1px, transparent 0),
|
||||
radial-gradient(#ddd 1px, transparent 0);
|
||||
background-position: 0 0, 25px 25px;
|
||||
background-size: 50px 50px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.rijuEdutor {
|
||||
overflow: hidden;
|
||||
height: calc(100% - 8px);
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
div.language {
|
||||
width: 140px;
|
||||
height: 60px;
|
||||
border: solid;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
a.language {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { createTheme } from "@mui/material/styles";
|
||||
|
||||
export const theme = createTheme({
|
||||
palette: {
|
||||
mode: "light",
|
||||
primary: {
|
||||
main: "#4d4dff",
|
||||
},
|
||||
background: {
|
||||
default: "white",
|
||||
},
|
||||
success: {
|
||||
main: "#48c78e",
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
fontFamily: "Fira Code",
|
||||
},
|
||||
components: {
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
textTransform: "none",
|
||||
},
|
||||
disableElevation: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
export const EventEmitter = {
|
||||
events: {},
|
||||
dispatch: function (event, data) {
|
||||
if (!this.events[event]) return;
|
||||
this.events[event].forEach((callback) => callback(data));
|
||||
},
|
||||
subscribe: function (event, callback) {
|
||||
if (!this.events[event]) this.events[event] = [];
|
||||
this.events[event].push(callback);
|
||||
},
|
||||
isSubscribed: function (event) {
|
||||
if (!Array.isArray(this.events[event])) return false;
|
||||
else {
|
||||
if (this.events[event].length == 0) return false;
|
||||
else return true;
|
||||
}
|
||||
},
|
||||
};
|
Loading…
Reference in New Issue