feat: port bulk editor textareas to codemirror

This commit is contained in:
liyasthomas
2021-09-08 21:52:26 +05:30
parent e1a25fa894
commit b15fd6c75a
4 changed files with 303 additions and 404 deletions

View File

@@ -36,7 +36,8 @@
} }
input::placeholder, input::placeholder,
textarea::placeholder { textarea::placeholder,
.CodeMirror-empty {
@apply text-secondaryDark; @apply text-secondaryDark;
@apply opacity-25; @apply opacity-25;
} }

View File

@@ -42,9 +42,7 @@
/> />
<ButtonSecondary <ButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="`${$t( :title="$t('action.prettify')"
'action.prettify'
)} <kbd>${getSpecialKey()}</kbd><kbd>P</kbd>`"
:svg="prettifyQueryIcon" :svg="prettifyQueryIcon"
@click.native="prettifyQuery" @click.native="prettifyQuery"
/> />
@@ -174,25 +172,7 @@
</div> </div>
</div> </div>
<div v-if="bulkMode" class="flex"> <div v-if="bulkMode" class="flex">
<textarea-autosize <div ref="bulkEditor" class="w-full block"></div>
v-model="bulkHeaders"
v-focus
name="bulk-parameters"
class="
bg-transparent
border-b border-dividerLight
flex
font-mono
flex-1
py-2
px-4
whitespace-pre
resize-y
overflow-auto
"
rows="10"
:placeholder="$t('state.bulk_mode_placeholder')"
/>
</div> </div>
<div v-else> <div v-else>
<div <div
@@ -229,7 +209,9 @@
/> />
<input <input
class="bg-transparent flex flex-1 py-2 px-4" class="bg-transparent flex flex-1 py-2 px-4"
:placeholder="$t('count.value', { count: index + 1 })" :placeholder="
$t('count.value', { count: index + 1 }).toString()
"
:name="`value ${index}`" :name="`value ${index}`"
:value="header.value" :value="header.value"
autofocus autofocus
@@ -311,17 +293,9 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { import { onMounted, ref, useContext, watch } from "@nuxtjs/composition-api"
defineComponent,
onMounted,
PropType,
ref,
useContext,
watch,
} from "@nuxtjs/composition-api"
import clone from "lodash/clone" import clone from "lodash/clone"
import { getPlatformSpecialKey } from "~/helpers/platformutils"
import { copyToClipboard } from "~/helpers/utils/clipboard" import { copyToClipboard } from "~/helpers/utils/clipboard"
import { import {
useNuxt, useNuxt,
@@ -348,208 +322,180 @@ import { makeGQLHistoryEntry, addGraphqlHistoryEntry } from "~/newstore/history"
import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics" import { logHoppRequestRunToAnalytics } from "~/helpers/fb/analytics"
import { getCurrentStrategyID } from "~/helpers/network" import { getCurrentStrategyID } from "~/helpers/network"
import { makeGQLRequest } from "~/helpers/types/HoppGQLRequest" import { makeGQLRequest } from "~/helpers/types/HoppGQLRequest"
import { useCodemirror } from "~/helpers/editor/codemirror"
export default defineComponent({ const props = defineProps<{
props: { conn: GQLConnection
conn: { }>()
type: Object as PropType<GQLConnection>,
required: true,
},
},
setup(props) {
const {
$toast,
app: { i18n },
} = useContext()
const t = i18n.t.bind(i18n)
const nuxt = useNuxt()
const bulkMode = ref(false) const {
const bulkHeaders = ref("") $toast,
app: { i18n },
} = useContext()
const t = i18n.t.bind(i18n)
const nuxt = useNuxt()
watch(bulkHeaders, () => { const bulkMode = ref(false)
try { const bulkHeaders = ref("")
const transformation = bulkHeaders.value.split("\n").map((item) => ({
key: item.substring(0, item.indexOf(":")).trim().replace(/^\/\//, ""), watch(bulkHeaders, () => {
value: item.substring(item.indexOf(":") + 1).trim(), try {
active: !item.trim().startsWith("//"), const transformation = bulkHeaders.value.split("\n").map((item) => ({
})) key: item.substring(0, item.indexOf(":")).trim().replace(/^\/\//, ""),
setGQLHeaders(transformation) value: item.substring(item.indexOf(":") + 1).trim(),
} catch (e) { active: !item.trim().startsWith("//"),
$toast.error(t("error.something_went_wrong").toString(), { }))
icon: "error_outline", setGQLHeaders(transformation)
}) } catch (e) {
console.error(e) $toast.error(t("error.something_went_wrong").toString(), {
} icon: "error_outline",
}) })
console.error(e)
}
})
const url = useReadonlyStream(gqlURL$, "") const url = useReadonlyStream(gqlURL$, "")
const gqlQueryString = useStream(gqlQuery$, "", setGQLQuery) const gqlQueryString = useStream(gqlQuery$, "", setGQLQuery)
const variableString = useStream(gqlVariables$, "", setGQLVariables) const variableString = useStream(gqlVariables$, "", setGQLVariables)
const headers = useStream(gqlHeaders$, [], setGQLHeaders) const headers = useStream(gqlHeaders$, [], setGQLHeaders)
const queryEditor = ref<any | null>(null) const bulkEditor = ref<any | null>(null)
const copyQueryIcon = ref("copy") useCodemirror(bulkEditor, bulkHeaders, {
const prettifyQueryIcon = ref("align-left") extendedEditorConfig: {
const copyVariablesIcon = ref("copy") mode: "text/x-yaml",
placeholder: t("state.bulk_mode_placeholder").toString(),
},
linter: null,
completer: null,
})
const showSaveRequestModal = ref(false) const queryEditor = ref<any | null>(null)
const schema = useReadonlyStream(props.conn.schemaString$, "") const copyQueryIcon = ref("copy")
const prettifyQueryIcon = ref("align-left")
const copyVariablesIcon = ref("copy")
watch( const showSaveRequestModal = ref(false)
headers,
() => { watch(
if ( headers,
(headers.value[headers.value.length - 1]?.key !== "" || () => {
headers.value[headers.value.length - 1]?.value !== "") && if (
headers.value.length (headers.value[headers.value.length - 1]?.key !== "" ||
) headers.value[headers.value.length - 1]?.value !== "") &&
addRequestHeader() headers.value.length
}, )
{ deep: true } addRequestHeader()
},
{ deep: true }
)
onMounted(() => {
if (!headers.value?.length) {
addRequestHeader()
}
})
const copyQuery = () => {
copyToClipboard(gqlQueryString.value)
copyQueryIcon.value = "check"
setTimeout(() => (copyQueryIcon.value = "copy"), 1000)
}
const response = useStream(gqlResponse$, "", setGQLResponse)
const runQuery = async () => {
const startTime = Date.now()
nuxt.value.$loading.start()
response.value = t("state.loading").toString()
try {
const runURL = clone(url.value)
const runHeaders = clone(headers.value)
const runQuery = clone(gqlQueryString.value)
const runVariables = clone(variableString.value)
const responseText = await props.conn.runQuery(
runURL,
runHeaders,
runQuery,
runVariables
)
const duration = Date.now() - startTime
nuxt.value.$loading.finish()
response.value = JSON.stringify(JSON.parse(responseText), null, 2)
addGraphqlHistoryEntry(
makeGQLHistoryEntry({
request: makeGQLRequest({
name: "",
url: runURL,
query: runQuery,
headers: runHeaders,
variables: runVariables,
}),
response: response.value,
star: false,
})
) )
onMounted(() => { $toast.success(t("state.finished_in", { duration }).toString(), {
if (!headers.value?.length) { icon: "done",
addRequestHeader()
}
}) })
} catch (e: any) {
response.value = `${e}. ${t("error.check_console_details")}`
nuxt.value.$loading.finish()
const copyQuery = () => { $toast.error(`${e} ${t("error.f12_details").toString()}`, {
copyToClipboard(gqlQueryString.value) icon: "error_outline",
copyQueryIcon.value = "check" })
setTimeout(() => (copyQueryIcon.value = "copy"), 1000) console.error(e)
} }
const response = useStream(gqlResponse$, "", setGQLResponse) logHoppRequestRunToAnalytics({
platform: "graphql-query",
strategy: getCurrentStrategyID(),
})
}
const runQuery = async () => { const hideRequestModal = () => {
const startTime = Date.now() showSaveRequestModal.value = false
}
nuxt.value.$loading.start() const prettifyQuery = () => {
response.value = t("state.loading").toString() queryEditor.value.prettifyQuery()
prettifyQueryIcon.value = "check"
setTimeout(() => (prettifyQueryIcon.value = "align-left"), 1000)
}
try { const saveRequest = () => {
const runURL = clone(url.value) showSaveRequestModal.value = true
const runHeaders = clone(headers.value) }
const runQuery = clone(gqlQueryString.value)
const runVariables = clone(variableString.value)
const responseText = await props.conn.runQuery( // Why ?
runURL, const updateQuery = (updatedQuery: string) => {
runHeaders, gqlQueryString.value = updatedQuery
runQuery, }
runVariables
)
const duration = Date.now() - startTime
nuxt.value.$loading.finish() const copyVariables = () => {
copyToClipboard(variableString.value)
copyVariablesIcon.value = "check"
setTimeout(() => (copyVariablesIcon.value = "copy"), 1000)
}
response.value = JSON.stringify(JSON.parse(responseText), null, 2) const addRequestHeader = () => {
addGQLHeader({
key: "",
value: "",
active: true,
})
}
addGraphqlHistoryEntry( const removeRequestHeader = (index: number) => {
makeGQLHistoryEntry({ removeGQLHeader(index)
request: makeGQLRequest({ }
name: "",
url: runURL,
query: runQuery,
headers: runHeaders,
variables: runVariables,
}),
response: response.value,
star: false,
})
)
$toast.success(t("state.finished_in", { duration }).toString(), {
icon: "done",
})
} catch (e: any) {
response.value = `${e}. ${t("error.check_console_details")}`
nuxt.value.$loading.finish()
$toast.error(`${e} ${t("error.f12_details").toString()}`, {
icon: "error_outline",
})
console.error(e)
}
logHoppRequestRunToAnalytics({
platform: "graphql-query",
strategy: getCurrentStrategyID(),
})
}
const hideRequestModal = () => {
showSaveRequestModal.value = false
}
const prettifyQuery = () => {
queryEditor.value.prettifyQuery()
prettifyQueryIcon.value = "check"
setTimeout(() => (prettifyQueryIcon.value = "align-left"), 1000)
}
const saveRequest = () => {
showSaveRequestModal.value = true
}
// Why ?
const updateQuery = (updatedQuery: string) => {
gqlQueryString.value = updatedQuery
}
const copyVariables = () => {
copyToClipboard(variableString.value)
copyVariablesIcon.value = "check"
setTimeout(() => (copyVariablesIcon.value = "copy"), 1000)
}
const addRequestHeader = () => {
addGQLHeader({
key: "",
value: "",
active: true,
})
}
const removeRequestHeader = (index: number) => {
removeGQLHeader(index)
}
return {
gqlQueryString,
variableString,
headers,
copyQueryIcon,
prettifyQueryIcon,
copyVariablesIcon,
queryEditor,
showSaveRequestModal,
hideRequestModal,
schema,
copyQuery,
runQuery,
prettifyQuery,
saveRequest,
updateQuery,
copyVariables,
addRequestHeader,
removeRequestHeader,
getSpecialKey: getPlatformSpecialKey,
commonHeaders,
updateGQLHeader,
bulkMode,
bulkHeaders,
}
},
})
</script> </script>

View File

@@ -48,25 +48,7 @@
</div> </div>
</div> </div>
<div v-if="bulkMode" class="flex"> <div v-if="bulkMode" class="flex">
<textarea-autosize <div ref="bulkEditor" class="w-full block"></div>
v-model="bulkHeaders"
v-focus
name="bulk-headers"
class="
bg-transparent
border-b border-dividerLight
flex
font-mono
flex-1
py-2
px-4
whitespace-pre
resize-y
overflow-auto
"
rows="10"
:placeholder="$t('state.bulk_mode_placeholder')"
/>
</div> </div>
<div v-else> <div v-else>
<div <div
@@ -193,96 +175,86 @@
</AppSection> </AppSection>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { ref, useContext, watch } from "@nuxtjs/composition-api"
import { useCodemirror } from "~/helpers/editor/codemirror"
import { import {
defineComponent,
ref,
useContext,
watch,
} from "@nuxtjs/composition-api"
import {
restHeaders$,
addRESTHeader, addRESTHeader,
updateRESTHeader,
deleteRESTHeader,
deleteAllRESTHeaders, deleteAllRESTHeaders,
deleteRESTHeader,
restHeaders$,
setRESTHeaders, setRESTHeaders,
updateRESTHeader,
} from "~/newstore/RESTSession" } from "~/newstore/RESTSession"
import { commonHeaders } from "~/helpers/headers" import { commonHeaders } from "~/helpers/headers"
import { useSetting } from "~/newstore/settings" import { useSetting } from "~/newstore/settings"
import { useReadonlyStream } from "~/helpers/utils/composables" import { useReadonlyStream } from "~/helpers/utils/composables"
import { HoppRESTHeader } from "~/helpers/types/HoppRESTRequest" import { HoppRESTHeader } from "~/helpers/types/HoppRESTRequest"
export default defineComponent({ const {
setup() { $toast,
const { app: { i18n },
$toast, } = useContext()
app: { i18n }, const t = i18n.t.bind(i18n)
} = useContext()
const t = i18n.t.bind(i18n)
const bulkMode = ref(false) const bulkMode = ref(false)
const bulkHeaders = ref("") const bulkHeaders = ref("")
const bulkEditor = ref<any | null>(null)
watch(bulkHeaders, () => { useCodemirror(bulkEditor, bulkHeaders, {
try { extendedEditorConfig: {
const transformation = bulkHeaders.value.split("\n").map((item) => ({ mode: "text/x-yaml",
key: item.substring(0, item.indexOf(":")).trim().replace(/^\/\//, ""), placeholder: t("state.bulk_mode_placeholder").toString(),
value: item.substring(item.indexOf(":") + 1).trim(),
active: !item.trim().startsWith("//"),
}))
setRESTHeaders(transformation)
} catch (e) {
$toast.error(t("error.something_went_wrong").toString(), {
icon: "error_outline",
})
console.error(e)
}
})
return {
headers$: useReadonlyStream(restHeaders$, []),
EXPERIMENTAL_URL_BAR_ENABLED: useSetting("EXPERIMENTAL_URL_BAR_ENABLED"),
bulkMode,
bulkHeaders,
}
},
data() {
return {
commonHeaders,
}
},
watch: {
headers$: {
handler(newValue) {
if (
(newValue[newValue.length - 1]?.key !== "" ||
newValue[newValue.length - 1]?.value !== "") &&
newValue.length
)
this.addHeader()
},
deep: true,
},
},
// mounted() {
// if (!this.headers$?.length) {
// this.addHeader()
// }
// },
methods: {
addHeader() {
addRESTHeader({ key: "", value: "", active: true })
},
updateHeader(index: number, item: HoppRESTHeader) {
updateRESTHeader(index, item)
},
deleteHeader(index: number) {
deleteRESTHeader(index)
},
clearContent() {
deleteAllRESTHeaders()
},
}, },
linter: null,
completer: null,
}) })
watch(bulkHeaders, () => {
try {
const transformation = bulkHeaders.value.split("\n").map((item) => ({
key: item.substring(0, item.indexOf(":")).trim().replace(/^\/\//, ""),
value: item.substring(item.indexOf(":") + 1).trim(),
active: !item.trim().startsWith("//"),
}))
setRESTHeaders(transformation)
} catch (e) {
$toast.error(t("error.something_went_wrong").toString(), {
icon: "error_outline",
})
console.error(e)
}
})
const headers$ = useReadonlyStream(restHeaders$, [])
watch(
headers$,
(newValue) => {
if (
(newValue[newValue.length - 1]?.key !== "" ||
newValue[newValue.length - 1]?.value !== "") &&
newValue.length
)
addHeader()
},
{ deep: true }
)
const addHeader = () => {
addRESTHeader({ key: "", value: "", active: true })
}
const updateHeader = (index: number, item: HoppRESTHeader) => {
updateRESTHeader(index, item)
}
const deleteHeader = (index: number) => {
deleteRESTHeader(index)
}
const clearContent = () => {
deleteAllRESTHeaders()
}
const EXPERIMENTAL_URL_BAR_ENABLED = useSetting("EXPERIMENTAL_URL_BAR_ENABLED")
</script> </script>

View File

@@ -48,25 +48,7 @@
</div> </div>
</div> </div>
<div v-if="bulkMode" class="flex"> <div v-if="bulkMode" class="flex">
<textarea-autosize <div ref="bulkEditor" class="w-full block"></div>
v-model="bulkParams"
v-focus
name="bulk-parameters"
class="
bg-transparent
border-b border-dividerLight
flex
font-mono font-medium
flex-1
py-2
px-4
whitespace-pre
resize-y
overflow-auto
"
rows="10"
:placeholder="$t('state.bulk_mode_placeholder')"
/>
</div> </div>
<div v-else> <div v-else>
<div <div
@@ -96,7 +78,7 @@
<input <input
v-else v-else
class="bg-transparent flex flex-1 py-2 px-4" class="bg-transparent flex flex-1 py-2 px-4"
:placeholder="$t('count.parameter', { count: index + 1 })" :placeholder="$t('count.parameter', { count: index + 1 }).toString()"
:name="'param' + index" :name="'param' + index"
:value="param.key" :value="param.key"
autofocus autofocus
@@ -130,7 +112,7 @@
<input <input
v-else v-else
class="bg-transparent flex flex-1 py-2 px-4" class="bg-transparent flex flex-1 py-2 px-4"
:placeholder="$t('count.value', { count: index + 1 })" :placeholder="$t('count.value', { count: index + 1 }).toString()"
:name="'value' + index" :name="'value' + index"
:value="param.value" :value="param.value"
@change=" @change="
@@ -202,13 +184,9 @@
</AppSection> </AppSection>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { import { ref, useContext, watch } from "@nuxtjs/composition-api"
defineComponent, import { useCodemirror } from "~/helpers/editor/codemirror"
ref,
useContext,
watch,
} from "@nuxtjs/composition-api"
import { HoppRESTParam } from "~/helpers/types/HoppRESTRequest" import { HoppRESTParam } from "~/helpers/types/HoppRESTRequest"
import { useReadonlyStream } from "~/helpers/utils/composables" import { useReadonlyStream } from "~/helpers/utils/composables"
import { import {
@@ -220,72 +198,74 @@ import {
setRESTParams, setRESTParams,
} from "~/newstore/RESTSession" } from "~/newstore/RESTSession"
import { useSetting } from "~/newstore/settings" import { useSetting } from "~/newstore/settings"
import "codemirror/mode/yaml/yaml"
export default defineComponent({ const {
setup() { $toast,
const { app: { i18n },
$toast, } = useContext()
app: { i18n }, const t = i18n.t.bind(i18n)
} = useContext()
const t = i18n.t.bind(i18n)
const bulkMode = ref(false) const bulkMode = ref(false)
const bulkParams = ref("") const bulkParams = ref("")
watch(bulkParams, () => { watch(bulkParams, () => {
try { try {
const transformation = bulkParams.value.split("\n").map((item) => ({ const transformation = bulkParams.value.split("\n").map((item) => ({
key: item.substring(0, item.indexOf(":")).trim().replace(/^\/\//, ""), key: item.substring(0, item.indexOf(":")).trim().replace(/^\/\//, ""),
value: item.substring(item.indexOf(":") + 1).trim(), value: item.substring(item.indexOf(":") + 1).trim(),
active: !item.trim().startsWith("//"), active: !item.trim().startsWith("//"),
})) }))
setRESTParams(transformation) setRESTParams(transformation)
} catch (e) { } catch (e) {
$toast.error(t("error.something_went_wrong").toString(), { $toast.error(t("error.something_went_wrong").toString(), {
icon: "error_outline", icon: "error_outline",
})
console.error(e)
}
}) })
console.error(e)
return { }
params$: useReadonlyStream(restParams$, []),
EXPERIMENTAL_URL_BAR_ENABLED: useSetting("EXPERIMENTAL_URL_BAR_ENABLED"),
bulkMode,
bulkParams,
}
},
watch: {
params$: {
handler(newValue) {
if (
(newValue[newValue.length - 1]?.key !== "" ||
newValue[newValue.length - 1]?.value !== "") &&
newValue.length
)
this.addParam()
},
deep: true,
},
},
// mounted() {
// if (!this.params$?.length) {
// this.addParam()
// }
// },
methods: {
addParam() {
addRESTParam({ key: "", value: "", active: true })
},
updateParam(index: number, item: HoppRESTParam) {
updateRESTParam(index, item)
},
deleteParam(index: number) {
deleteRESTParam(index)
},
clearContent() {
deleteAllRESTParams()
},
},
}) })
const bulkEditor = ref<any | null>(null)
useCodemirror(bulkEditor, bulkParams, {
extendedEditorConfig: {
mode: "text/x-yaml",
placeholder: t("state.bulk_mode_placeholder").toString(),
},
linter: null,
completer: null,
})
const params$ = useReadonlyStream(restParams$, [])
watch(
params$,
(newValue) => {
if (
(newValue[newValue.length - 1]?.key !== "" ||
newValue[newValue.length - 1]?.value !== "") &&
newValue.length
)
addParam()
},
{ deep: true }
)
const addParam = () => {
addRESTParam({ key: "", value: "", active: true })
}
const updateParam = (index: number, item: HoppRESTParam) => {
updateRESTParam(index, item)
}
const deleteParam = (index: number) => {
deleteRESTParam(index)
}
const clearContent = () => {
deleteAllRESTParams()
}
const EXPERIMENTAL_URL_BAR_ENABLED = useSetting("EXPERIMENTAL_URL_BAR_ENABLED")
</script> </script>