diff --git a/components.d.ts b/components.d.ts
index f2c3146f..e42a8dda 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -11,6 +11,7 @@ declare module '@vue/runtime-core' {
export interface GlobalComponents {
'404.page': typeof import('./src/pages/404.page.vue')['default']
About: typeof import('./src/pages/About.vue')['default']
+ ApiTester: typeof import('./src/tools/api-tester/api-tester.vue')['default']
App: typeof import('./src/App.vue')['default']
AsciiTextDrawer: typeof import('./src/tools/ascii-text-drawer/ascii-text-drawer.vue')['default']
'Base.layout': typeof import('./src/layouts/base.layout.vue')['default']
@@ -127,10 +128,12 @@ declare module '@vue/runtime-core' {
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
+ NCheckbox: typeof import('naive-ui')['NCheckbox']
NCode: typeof import('naive-ui')['NCode']
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDivider: typeof import('naive-ui')['NDivider']
+ NDynamicInput: typeof import('naive-ui')['NDynamicInput']
NEllipsis: typeof import('naive-ui')['NEllipsis']
NFormItem: typeof import('naive-ui')['NFormItem']
NGi: typeof import('naive-ui')['NGi']
@@ -138,6 +141,7 @@ declare module '@vue/runtime-core' {
NH1: typeof import('naive-ui')['NH1']
NH3: typeof import('naive-ui')['NH3']
NIcon: typeof import('naive-ui')['NIcon']
+ NInput: typeof import('naive-ui')['NInput']
NInputNumber: typeof import('naive-ui')['NInputNumber']
NLabel: typeof import('naive-ui')['NLabel']
NLayout: typeof import('naive-ui')['NLayout']
diff --git a/src/components/TextareaCopyable.vue b/src/components/TextareaCopyable.vue
index 8b0aae61..1bf3b5ff 100644
--- a/src/components/TextareaCopyable.vue
+++ b/src/components/TextareaCopyable.vue
@@ -16,6 +16,7 @@ const props = withDefaults(
language?: string
copyPlacement?: 'top-right' | 'bottom-right' | 'outside' | 'none'
copyMessage?: string
+ wordWrap?: boolean
}>(),
{
followHeightOf: null,
@@ -47,7 +48,7 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage.
:style="height ? `min-height: ${height - 40 /* card padding */ + 10 /* negative margin compensation */}px` : ''"
>
-
+
diff --git a/src/composable/queryParams.ts b/src/composable/queryParams.ts
index 9699abbc..7cc8cc0d 100644
--- a/src/composable/queryParams.ts
+++ b/src/composable/queryParams.ts
@@ -1,7 +1,8 @@
import { useRouteQuery } from '@vueuse/router';
import { computed } from 'vue';
+import { useStorage } from '@vueuse/core';
-export { useQueryParam };
+export { useQueryParam, useQueryParamOrStorage };
const transformers = {
number: {
@@ -16,6 +17,12 @@ const transformers = {
fromQuery: (value: string) => value.toLowerCase() === 'true',
toQuery: (value: boolean) => (value ? 'true' : 'false'),
},
+ object: {
+ fromQuery: (value: string) => {
+ return JSON.parse(value);
+ },
+ toQuery: (value: object) => JSON.stringify(value),
+ },
};
function useQueryParam
({ name, defaultValue }: { name: string; defaultValue: T }) {
@@ -33,3 +40,27 @@ function useQueryParam({ name, defaultValue }: { name: string; defaultValue:
},
});
}
+
+function useQueryParamOrStorage({ name, storageName, defaultValue }: { name: string; storageName: string; defaultValue: T }) {
+ const type = typeof defaultValue;
+ const transformer = transformers[type as keyof typeof transformers] ?? transformers.string;
+
+ const storageRef = useStorage(storageName, defaultValue);
+ const proxyDefaultValue = transformer.toQuery(defaultValue as never);
+ const proxy = useRouteQuery(name, proxyDefaultValue);
+
+ const r = ref(defaultValue);
+
+ watch(r,
+ (value) => {
+ proxy.value = transformer.toQuery(value as never);
+ storageRef.value = value as never;
+ },
+ { deep: true });
+
+ r.value = (proxy.value && proxy.value !== proxyDefaultValue
+ ? transformer.fromQuery(proxy.value) as unknown as T
+ : storageRef.value as T) as never;
+
+ return r;
+}
diff --git a/src/tools/api-tester/api-tester.vue b/src/tools/api-tester/api-tester.vue
new file mode 100644
index 00000000..f96fee11
--- /dev/null
+++ b/src/tools/api-tester/api-tester.vue
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Add a new HTTP Header
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Add a new Query Parameter
+
+
+
+
+
+
+
+
+
+
+
+
+ No CORS
+
+
+
+
+ Call API
+
+
+
+
+
+ Status code = {{ apiCallResult.code }}
+
+
+
+
+
+
+
diff --git a/src/tools/api-tester/index.ts b/src/tools/api-tester/index.ts
new file mode 100644
index 00000000..5ea35536
--- /dev/null
+++ b/src/tools/api-tester/index.ts
@@ -0,0 +1,12 @@
+import { World } from '@vicons/tabler';
+import { defineTool } from '../tool';
+
+export const tool = defineTool({
+ name: 'API Tester',
+ path: '/api-tester',
+ description: 'HTTP API Tester',
+ keywords: ['api', 'http', 'call', 'tester'],
+ component: () => import('./api-tester.vue'),
+ icon: World,
+ createdAt: new Date('2024-05-11'),
+});
diff --git a/src/tools/index.ts b/src/tools/index.ts
index aa861c93..f8e5c40c 100644
--- a/src/tools/index.ts
+++ b/src/tools/index.ts
@@ -6,6 +6,7 @@ import { tool as asciiTextDrawer } from './ascii-text-drawer';
import { tool as textToUnicode } from './text-to-unicode';
import { tool as safelinkDecoder } from './safelink-decoder';
+import { tool as apiTester } from './api-tester';
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
import { tool as numeronymGenerator } from './numeronym-generator';
import { tool as macAddressGenerator } from './mac-address-generator';
@@ -128,6 +129,7 @@ export const toolsByCategory: ToolCategory[] = [
httpStatusCodes,
jsonDiff,
safelinkDecoder,
+ apiTester,
],
},
{