fix(playground): add dark theme, align ui more akin to upstream playground

This commit is contained in:
Amaan Qureshi 2024-12-25 18:51:18 -05:00
parent 5bd0d11982
commit b70843a033
3 changed files with 317 additions and 75 deletions

View file

@ -12,51 +12,67 @@
<body>
<div id="playground-container" style="visibility: hidden;">
<header>
<div class=header-item>
<bold>THE_LANGUAGE_NAME</bold>
<div class="header-item">
<span class="language-name">Language: THE_LANGUAGE_NAME</span>
</div>
<div class=header-item>
<div class="header-item">
<input id="logging-checkbox" type="checkbox">
<label for="logging-checkbox">log</label>
<input id="logging-checkbox" type="checkbox"></input>
</div>
<div class=header-item>
<div class="header-item">
<input id="anonymous-nodes-checkbox" type="checkbox">
<label for="anonymous-nodes-checkbox">show anonymous nodes</label>
<input id="anonymous-nodes-checkbox" type="checkbox"></input>
</div>
<div class=header-item>
<div class="header-item">
<input id="query-checkbox" type="checkbox">
<label for="query-checkbox">query</label>
<input id="query-checkbox" type="checkbox"></input>
</div>
<div class=header-item>
<div class="header-item">
<label for="update-time">parse time: </label>
<span id="update-time"></span>
</div>
<div class=header-item>
<div class="header-item">
<a href="https://tree-sitter.github.io/tree-sitter/7-playground.html#about">(?)</a>
</div>
<select id="language-select" style="display: none;">
<option value="parser">Parser</option>
</select>
<div class="header-item">
<button id="theme-toggle" class="theme-toggle" aria-label="Toggle theme">
<svg class="sun-icon" viewBox="0 0 24 24" width="16" height="16">
<path fill="currentColor"
d="M12 17.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zm0 1.5a7 7 0 1 1 0-14 7 7 0 0 1 0 14zm0-16a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V4a1 1 0 0 1 1-1zm0 15a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0v-2a1 1 0 0 1 1-1zm9-9a1 1 0 0 1-1 1h-2a1 1 0 1 1 0-2h2a1 1 0 0 1 1 1zM4 12a1 1 0 0 1-1 1H1a1 1 0 1 1 0-2h2a1 1 0 0 1 1 1z" />
</svg>
<svg class="moon-icon" viewBox="0 0 24 24" width="16" height="16">
<path fill="currentColor"
d="M12.1 22c-5.5 0-10-4.5-10-10s4.5-10 10-10c.2 0 .3 0 .5.1-1.3 1.4-2 3.2-2 5.2 0 4.1 3.4 7.5 7.5 7.5 2 0 3.8-.7 5.2-2 .1.2.1.3.1.5 0 5.4-4.5 9.7-10 9.7z" />
</svg>
</button>
</div>
</header>
<main>
<div id="input-pane">
<div class="panel-header">Code</div>
<div id="code-container">
<textarea id="code-input"></textarea>
</div>
<div id="query-container" style="visibility: hidden; position: absolute;">
<div class="panel-header">Query</div>
<textarea id="query-input"></textarea>
</div>
</div>
<div id="output-container-scroll">
<div class="panel-header">Tree</div>
<pre id="output-container" class="highlight"></pre>
</div>
</main>
@ -73,115 +89,318 @@
<script src=playground.js></script>
<script>
setTimeout(() => {
window.initializePlayground()
window.initializePlayground({local: true});
}, 1)
</script>
<style>
/* Base Variables */
:root {
--light-bg: #f9f9f9;
--light-border: #e0e0e0;
--light-text: #333;
--light-hover-border: #c1c1c1;
--light-scrollbar-track: #f1f1f1;
--light-scrollbar-thumb: #c1c1c1;
--light-scrollbar-thumb-hover: #a8a8a8;
--dark-bg: #1d1f21;
--dark-border: #2d2d2d;
--dark-text: #c5c8c6;
--dark-panel-bg: #252526;
--dark-code-bg: #1e1e1e;
--dark-scrollbar-track: #25282c;
--dark-scrollbar-thumb: #4a4d51;
--dark-scrollbar-thumb-hover: #5a5d61;
--primary-color: #0550ae;
--primary-color-alpha: rgba(5, 80, 174, 0.1);
--primary-color-alpha-dark: rgba(121, 192, 255, 0.1);
--selection-color: rgba(39, 95, 255, 0.3);
}
/* Theme Colors */
[data-theme="dark"] {
--bg-color: var(--dark-bg);
--border-color: var(--dark-border);
--text-color: var(--dark-text);
--panel-bg: var(--dark-panel-bg);
--code-bg: var(--dark-code-bg);
}
[data-theme="light"] {
--bg-color: var(--light-bg);
--border-color: var(--light-border);
--text-color: var(--light-text);
--panel-bg: white;
--code-bg: white;
}
/* Base Styles */
body {
margin: 0;
padding: 0;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
}
/* Layout */
#playground-container {
width: 100%;
height: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background-color: var(--bg-color);
}
header {
box-sizing: border-box;
padding: 16px 24px;
border-bottom: 1px solid var(--border-color);
display: flex;
padding: 20px;
height: 60px;
border-bottom: 1px solid #aaa;
align-items: center;
gap: 20px;
background-color: var(--panel-bg);
font-size: 14px;
}
.header-item {
display: flex;
align-items: center;
gap: 8px;
}
.language-name {
font-weight: 600;
}
main {
flex: 1;
position: relative;
display: flex;
overflow: hidden;
}
#input-pane {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 50%;
width: 50%;
display: flex;
flex-direction: column;
border-right: 1px solid var(--border-color);
background-color: var(--panel-bg);
overflow: hidden;
}
#code-container {
flex: 1;
min-height: 0;
position: relative;
border-bottom: 1px solid var(--border-color);
display: flex;
flex-direction: column;
}
#code-container,
#query-container {
#query-container:not([style*="visibility: hidden"]) {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
#query-container .panel-header {
flex: 0 0 auto;
}
#query-container .CodeMirror {
flex: 1;
position: relative;
overflow: hidden;
border-right: 1px solid #aaa;
border-bottom: 1px solid #aaa;
min-height: 0;
}
#output-container-scroll {
position: absolute;
top: 0;
left: 50%;
bottom: 0;
right: 0;
}
.header-item {
margin-right: 30px;
}
#playground-container .CodeMirror {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 100%;
}
#output-container-scroll {
flex: 1;
padding: 0;
width: 50%;
overflow: auto;
background-color: var(--panel-bg);
padding: 0;
display: flex;
flex-direction: column;
}
#output-container {
padding: 0 10px;
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
line-height: 1.5;
margin: 0;
padding: 16px;
}
#logging-checkbox {
vertical-align: middle;
.panel-header {
padding: 8px 16px;
font-weight: 600;
font-size: 14px;
border-bottom: 1px solid var(--border-color);
background-color: var(--panel-bg);
}
.CodeMirror div.CodeMirror-cursor {
border-left: 3px solid red;
}
a {
text-decoration: none;
color: #040404;
padding: 2px;
}
a:hover {
text-decoration: underline;
}
a.highlighted {
background-color: #d9d9d9;
color: red;
border-radius: 3px;
text-decoration: underline;
.CodeMirror {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100%;
font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
font-size: 14px;
line-height: 1.6;
background-color: var(--code-bg) !important;
color: var(--text-color) !important;
}
.query-error {
text-decoration: underline red dashed;
-webkit-text-decoration: underline red dashed;
}
/* Scrollbars */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
border-radius: 4px;
background: var(--light-scrollbar-track);
}
::-webkit-scrollbar-thumb {
border-radius: 4px;
background: var(--light-scrollbar-thumb);
}
::-webkit-scrollbar-thumb:hover {
background: var(--light-scrollbar-thumb-hover);
}
[data-theme="dark"] {
::-webkit-scrollbar-track {
background: var(--dark-scrollbar-track) !important;
}
::-webkit-scrollbar-thumb {
background: var(--dark-scrollbar-thumb) !important;
}
::-webkit-scrollbar-thumb:hover {
background: var(--dark-scrollbar-thumb-hover) !important;
}
}
/* Theme Toggle */
.theme-toggle {
background: none;
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 6px;
cursor: pointer;
color: var(--text-color);
}
.theme-toggle:hover {
background-color: var(--primary-color-alpha);
}
[data-theme="light"] .moon-icon,
[data-theme="dark"] .sun-icon {
display: none;
}
/* Form Elements */
input[type="checkbox"] {
margin-right: 6px;
vertical-align: middle;
}
label {
font-size: 14px;
margin-right: 16px;
cursor: pointer;
}
#output-container a {
cursor: pointer;
text-decoration: none;
color: #040404;
padding: 2px;
}
#output-container a:hover {
text-decoration: underline;
}
#output-container a.node-link.named {
color: #0550ae;
}
#output-container a.node-link.anonymous {
color: #116329;
}
#output-container a.node-link.anonymous:before {
content: '"';
}
#output-container a.node-link.anonymous:after {
content: '"';
}
#output-container a.node-link.error {
color: #cf222e;
}
#output-container a.highlighted {
background-color: #d9d9d9;
color: red;
border-radius: 3px;
text-decoration: underline;
}
/* Dark Theme Node Colors */
[data-theme="dark"] {
& #output-container a {
color: #d4d4d4;
}
& #output-container a.node-link.named {
color: #79c0ff;
}
& #output-container a.node-link.anonymous {
color: #7ee787;
}
& #output-container a.node-link.error {
color: #ff7b72;
}
& #output-container a.highlighted {
background-color: #373b41;
color: red;
}
& .CodeMirror {
background-color: var(--dark-code-bg) !important;
color: var(--dark-text) !important;
}
& .CodeMirror-gutters {
background-color: var(--dark-panel-bg) !important;
border-color: var(--dark-border) !important;
}
& .CodeMirror-cursor {
border-color: var(--dark-text) !important;
}
& .CodeMirror-selected {
background-color: rgba(255, 255, 255, 0.1) !important;
}
}
</style>
</body>

View file

@ -101,6 +101,6 @@ you must use at least one capture, like <code>(node_name) @capture-name</code></
<script src="https://cdnjs.cloudflare.com/ajax/libs/clusterize.js/0.19.0/clusterize.min.js"></script>
<script>
setTimeout(() => {
window.initializePlayground()
window.initializePlayground({local: false})
}, 1)
</script>

View file

@ -1,3 +1,23 @@
function initializeLocalTheme() {
const themeToggle = document.getElementById('theme-toggle');
if (!themeToggle) return;
// Load saved theme or use system preference
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const initialTheme = savedTheme || (prefersDark ? 'dark' : 'light');
// Set initial theme
document.documentElement.setAttribute('data-theme', initialTheme);
themeToggle.addEventListener('click', () => {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
});
}
function initializeCustomSelect() {
const button = document.getElementById('language-button');
const select = document.getElementById('language-select');
@ -31,7 +51,11 @@ function initializeCustomSelect() {
});
}
window.initializePlayground = async function initializePlayground() {
window.initializePlayground = async function initializePlayground(opts) {
const { local } = opts;
if (local) {
initializeLocalTheme();
}
initializeCustomSelect();
let tree;
@ -214,7 +238,6 @@ window.initializePlayground = async function initializePlayground() {
}
let displayName;
let displayClass = 'plain';
if (cursor.nodeIsMissing) {
const nodeTypeText = cursor.nodeIsNamed ? cursor.nodeType : `"${cursor.nodeType}"`;
displayName = `MISSING ${nodeTypeText}`;