New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Knex Playground #6059
base: master
Are you sure you want to change the base?
Knex Playground #6059
Changes from all commits
2a23b3f
3fd9e68
14825c0
6dd49e0
4d6efe1
683916e
ff56012
b55e579
74f282b
8cb0b24
98f760e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why eval? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We receive the code as string, and we need to call the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Too risky, lets try find another way There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You might wanna look how https://github.com/wirekang/kysely-playground does it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are using iframe which is better approach |
||
|
||
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> |
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 }; | ||
} |
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'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it should probably support multi instances, but you should probably attempt to measure page performance, because it instantiates two monaco-editors for each instance