Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
876 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
<script setup> | ||
import Knex from 'knex'; | ||
import * as sqlFormatter from 'sql-formatter'; | ||
import Split from 'split.js'; | ||
import { computed, onMounted, ref, shallowRef, watch } from 'vue'; | ||
import { useBreakpoints } from './breakpoint'; | ||
import { useDialect } from './dialect'; | ||
import { useDarkMode } from './dark-mode'; | ||
const formatter = { | ||
cockroachdb: 'postgresql', | ||
mssql: 'tsql', | ||
mysql: 'mysql', | ||
mysql2: 'mysql', | ||
oracledb: 'plsql', | ||
pg: 'postgresql', | ||
pgnative: 'postgresql', | ||
redshift: 'redshift', | ||
sqlite3: 'sqlite', | ||
}; | ||
const { dialect } = useDialect(); | ||
const { isDark } = useDarkMode(); | ||
const { type } = useBreakpoints(); | ||
const isVertical = computed(() => ['xs', 'sm'].includes(type.value)); | ||
const split = shallowRef(); | ||
const sql = ref(''); | ||
const code = ref(''); | ||
onMounted(() => { | ||
split.value = Split(['#editor', '#output'], { | ||
direction: isVertical.value ? 'vertical' : 'horizontal', | ||
sizes: [50, 50], | ||
}); | ||
}); | ||
watch(isVertical, () => { | ||
split.value?.destroy(false, false); | ||
split.value = Split(['#editor', '#output'], { | ||
direction: isVertical.value ? 'vertical' : 'horizontal', | ||
sizes: [50, 50], | ||
}); | ||
}); | ||
const editorOptions = { | ||
scrollBeyondLastLine: false, | ||
wordWrap: 'on', | ||
}; | ||
const outputEditorOptions = { | ||
readOnly: true, | ||
scrollBeyondLastLine: false, | ||
wordWrap: 'on', | ||
}; | ||
function handleMountEditor() { | ||
code.value = (() => { | ||
const hash = location.hash.slice(1); | ||
const code = '// Knex code\nknex("table").select()\n'; | ||
try { | ||
return hash ? decodeURIComponent(atob(hash)) || code : code; | ||
} catch { | ||
return code; | ||
} | ||
})(); | ||
fetch('/playground-assets/types/index.d.ts') | ||
.then((res) => res.text()) | ||
.then((typeRes) => { | ||
const mappedTypes = typeRes | ||
.replace(/^import .*$/gmu, '') | ||
.replace(/[^ ]export /gu, ' ') | ||
.replace( | ||
'declare function knex<TRecord extends {} = any, TResult = unknown[]>(\n config: Knex.Config | string\n): Knex<TRecord, TResult>;', | ||
'declare const knex: Knex.Client & (<TRecord extends {} = any, TResult = unknown[]>(config: Knex.Config | string) => Knex<TRecord, TResult>);' | ||
); | ||
// @ts-ignore | ||
monaco.languages.typescript.typescriptDefaults.addExtraLib( | ||
mappedTypes, | ||
'knex.d.ts' | ||
); | ||
}) | ||
.catch(() => { | ||
window.location.reload(); | ||
}); | ||
} | ||
watch([code, dialect], () => { | ||
let output = '--- generated SQL code\n'; | ||
try { | ||
const knex = Knex({ client: dialect.value }); | ||
output += eval(code.value).toQuery(); | ||
try { | ||
output = sqlFormatter.format(output, { | ||
language: formatter[dialect.value], | ||
}); | ||
} catch (e) { | ||
output += `\n--- sqlFormatter failed to run: ${e?.toString() ?? e}\n`; | ||
} | ||
} catch (err) { | ||
output = `--- ${err?.toString() ?? err}\n`; | ||
} | ||
sql.value = `${output}\n`; | ||
window.history.replaceState( | ||
null, | ||
'', | ||
`#${btoa(encodeURIComponent(code.value))}` | ||
); | ||
}); | ||
</script> | ||
<template> | ||
<div | ||
class="playground split" | ||
:class="{ 'split-vertical': isVertical, 'split-horizontal': !isVertical }" | ||
> | ||
<vue-monaco-editor | ||
id="editor" | ||
language="typescript" | ||
:theme="isDark ? 'vs-dark' : 'vs-light'" | ||
v-model:value="code" | ||
:options="editorOptions" | ||
:saveViewState="false" | ||
@mount="handleMountEditor" | ||
/> | ||
<vue-monaco-editor | ||
id="output" | ||
language="sql" | ||
:theme="isDark ? 'vs-dark' : 'vs-light'" | ||
v-model:value="sql" | ||
:options="outputEditorOptions" | ||
:saveViewState="false" | ||
/> | ||
</div> | ||
</template> | ||
<style scoped> | ||
.playground { | ||
width: 100vw; | ||
height: 100vh; | ||
padding-top: var(--header-height); | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { computed, onMounted, onUnmounted, ref } from 'vue'; | ||
|
||
/** | ||
* @ref https://stackoverflow.com/a/63944126 | ||
*/ | ||
export function useBreakpoints() { | ||
let windowWidth = ref(window.innerWidth); | ||
|
||
const onWidthChange = () => (windowWidth.value = window.innerWidth); | ||
onMounted(() => window.addEventListener('resize', onWidthChange)); | ||
onUnmounted(() => window.removeEventListener('resize', onWidthChange)); | ||
|
||
/** | ||
* @ref https://getbootstrap.com/docs/5.3/layout/breakpoints/#available-breakpoints | ||
*/ | ||
const type = computed(() => { | ||
if (windowWidth.value < 576) return 'xs'; | ||
if (windowWidth.value >= 576 && windowWidth.value < 768) return 'sm'; | ||
if (windowWidth.value >= 768 && windowWidth.value < 992) return 'md'; | ||
if (windowWidth.value >= 992 && windowWidth.value < 1200) return 'lg'; | ||
if (windowWidth.value >= 1200 && windowWidth.value < 1400) return 'xl'; | ||
else return 'xxl'; // Fires when windowWidth.value >= 1400 | ||
}); | ||
|
||
const width = computed(() => windowWidth.value); | ||
|
||
return { width, type }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { ref, computed, inject } from 'vue'; | ||
|
||
export function createDarkMode(app) { | ||
const prefersDark = ref(false); | ||
const setting = ref('auto'); | ||
|
||
const isDark = computed( | ||
() => | ||
setting.value === 'dark' || | ||
(prefersDark.value && setting.value !== 'light') | ||
); | ||
const toggleDark = () => { | ||
setting.value = setting.value === 'dark' ? 'light' : 'dark'; | ||
localStorage.setItem('color-scheme', setting.value); | ||
}; | ||
|
||
prefersDark.value = | ||
window.matchMedia && | ||
window.matchMedia('(prefers-color-scheme: dark)').matches; | ||
setting.value = localStorage.getItem('color-scheme') || 'auto'; | ||
|
||
app.provide('is-dark', { isDark, toggleDark }); | ||
} | ||
|
||
export function useDarkMode() { | ||
return inject('is-dark'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.