mirror of
https://github.com/LukeHagar/OpenAPI.gg.git
synced 2025-12-06 04:20:29 +00:00
feature/add route parameters (#18)
* add package manager field to package json * Remove TODO * design rework and path basics * update packagemanager * remove package manager
This commit is contained in:
77
TODO.md
77
TODO.md
@@ -1,77 +0,0 @@
|
||||
# TODO
|
||||
|
||||
## Functionality
|
||||
|
||||
- [x] Add path renaming
|
||||
- [x] Add path sub-page for editing a specific route
|
||||
- [x] Check that a path variable can only be included once
|
||||
- [ ] Add schema editor component
|
||||
- [ ] Add route-parameters
|
||||
- [ ] path
|
||||
- [ ] query
|
||||
- [ ] headers
|
||||
- [ ] cookies
|
||||
- [ ] Add route operations
|
||||
- [ ] request template
|
||||
- [ ] response template
|
||||
- [ ] get
|
||||
- [ ] put
|
||||
- [ ] post
|
||||
- [ ] delete
|
||||
- [ ] options
|
||||
- [ ] head
|
||||
- [ ] patch
|
||||
- [ ] trace
|
||||
|
||||
---
|
||||
|
||||
- [ ] Add Component Builder
|
||||
- [ ] Strings
|
||||
- [ ] Numbers
|
||||
- [ ] Booleans
|
||||
- [ ] Arrays
|
||||
- [ ] Objects
|
||||
- [ ] OneOf
|
||||
- [ ] AllOf
|
||||
- [ ] AnyOf
|
||||
- [ ] Enums
|
||||
- [ ] null (really easy :))
|
||||
- [ ] Retrofit $ref components into the following:
|
||||
- [ ] schema
|
||||
- [ ] securitySchemas
|
||||
- [ ] pathItems
|
||||
- [ ] parameters
|
||||
- [ ] requestBodies
|
||||
- [ ] responses
|
||||
- [ ] headers
|
||||
- [ ] examples
|
||||
- [ ] callbacks
|
||||
- [ ] links
|
||||
|
||||
---
|
||||
|
||||
- [ ] Add Support for webhooks
|
||||
|
||||
---
|
||||
|
||||
- [ ] Add external documentation field
|
||||
|
||||
---
|
||||
|
||||
- [ ] Replace prompt, alert and confirms with skeleton-modals
|
||||
- [x] Create path prompt
|
||||
- [x] Edit path prompt
|
||||
- [x] Rename path prompt
|
||||
- [x] Delete path confirm
|
||||
- [ ] Add extensions
|
||||
|
||||
---
|
||||
|
||||
- [ ] Add integration test(s) to check functionality
|
||||
|
||||
## UI/UX
|
||||
|
||||
- [x] Lightswitch
|
||||
- [ ] Theme switcher
|
||||
- [ ] Breadcrumbs
|
||||
- [ ] Document import/export
|
||||
@@ -42,7 +42,11 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "1.6.5",
|
||||
"@sveltejs/enhanced-img": "^0.2.0",
|
||||
"openapi-types": "^12.1.3",
|
||||
"svelte-persisted-store": "^0.9.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">20.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
3790
pnpm-lock.yaml
generated
3790
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
<html lang="en" class="dark h-full">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover" data-theme="skeleton">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
<body data-sveltekit-preload-data="hover" class="h-full" data-theme="skeleton">
|
||||
%sveltekit.body%
|
||||
</body>
|
||||
</html>
|
||||
|
||||
36
src/lib/components/atoms/ExampleInput.svelte
Normal file
36
src/lib/components/atoms/ExampleInput.svelte
Normal file
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import type { OpenAPIV3 } from '$lib/openAPITypes';
|
||||
import { SlideToggle } from '@skeletonlabs/skeleton';
|
||||
|
||||
export let example: OpenAPIV3.ExampleObject;
|
||||
export let name: string;
|
||||
|
||||
let schema = false;
|
||||
</script>
|
||||
|
||||
<div class="ml-6 ring-1 p-4 rounded-container-token ring-surface-500">
|
||||
<SlideToggle name="schema" bind:checked={schema}>Schema</SlideToggle>
|
||||
<p class="-ml-2 font-bold">{name}</p>
|
||||
{#if schema}
|
||||
<select class="select" name="schemaSelect">
|
||||
<option value="schema">Schema not yet implemented</option>
|
||||
</select>
|
||||
{:else}
|
||||
<label>
|
||||
<p>Summary</p>
|
||||
<input type="text" class="input" name="summary" bind:value={example.summary} />
|
||||
</label>
|
||||
<label>
|
||||
<p>Description</p>
|
||||
<textarea class="textarea" name="description" bind:value={example.description} />
|
||||
</label>
|
||||
<label>
|
||||
<p>Value</p>
|
||||
<input type="text" class="input" name="value" bind:value={example.value} />
|
||||
</label>
|
||||
<label>
|
||||
<p>External Value</p>
|
||||
<input type="text" class="input" name="externalValue" bind:value={example.externalValue} />
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
115
src/lib/components/atoms/ParameterInput.svelte
Normal file
115
src/lib/components/atoms/ParameterInput.svelte
Normal file
@@ -0,0 +1,115 @@
|
||||
<script lang="ts">
|
||||
import type { OpenAPIV3 } from '$lib/openAPITypes';
|
||||
import { SlideToggle } from '@skeletonlabs/skeleton';
|
||||
import ExampleInput from '$lib/components/atoms/ExampleInput.svelte';
|
||||
|
||||
export let variableName: string;
|
||||
export let value: OpenAPIV3.ParameterObject;
|
||||
export let location: 'path' | 'query' | 'header' | 'cookie';
|
||||
|
||||
value.name = variableName;
|
||||
value.in = location;
|
||||
if (location === 'path') value.required = true;
|
||||
let multipleExamples = value.examples && Object.keys(value.examples).length > 1;
|
||||
|
||||
const addParameterExample = () => {
|
||||
const exampleName = prompt('Enter a name for the example');
|
||||
if (!exampleName) return;
|
||||
// @ts-expect-error - working we are setting a new key
|
||||
value.examples[exampleName] = {
|
||||
$ref: '',
|
||||
summary: '',
|
||||
description: '',
|
||||
value: '',
|
||||
externalValue: ''
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="card py-6 px-4 flex flex-col gap-4">
|
||||
<h4 class="h4">{{variableName}}</h4>
|
||||
|
||||
<span class="flex items-center gap-2">
|
||||
<p>Location:</p>
|
||||
<select
|
||||
name="location"
|
||||
class="select w-min"
|
||||
disabled={location === 'path'}
|
||||
bind:value={location}
|
||||
>
|
||||
<option value="path">Path</option>
|
||||
<option value="query">Query</option>
|
||||
<option value="header">Header</option>
|
||||
<option value="cookie">Cookie</option>
|
||||
</select>
|
||||
</span>
|
||||
|
||||
<label class="space-y-2">
|
||||
<p>Description</p>
|
||||
<textarea
|
||||
class="textarea"
|
||||
bind:value={value.description}
|
||||
placeholder="Description of the parameter. Supports markdown."
|
||||
/>
|
||||
</label>
|
||||
<SlideToggle name="required" disabled={location === 'path'} bind:checked={value.required}>
|
||||
required
|
||||
</SlideToggle>
|
||||
<SlideToggle name="deprecated" bind:checked={value.deprecated} disabled={location === 'path'}>
|
||||
deprecated
|
||||
</SlideToggle>
|
||||
<SlideToggle name="allowEmptyValue" bind:value={value.allowEmptyValue}>
|
||||
Allow Empty Value
|
||||
</SlideToggle>
|
||||
<label class="space-y-2">
|
||||
<p>Style</p>
|
||||
<select name="style" class="select" bind:value={value.style}>
|
||||
<option value={undefined} selected>none</option>
|
||||
<option value="matrix">Matrix</option>
|
||||
<option value="label">Label</option>
|
||||
<option value="form">Form</option>
|
||||
<option value="simple">Simple</option>
|
||||
<option value="spaceDelimited">Space Delimited</option>
|
||||
<option value="pipeDelimited">Pipe Delimited</option>
|
||||
<option value="deepObject">Deep Object</option>
|
||||
</select>
|
||||
</label>
|
||||
<SlideToggle name="explode" bind:checked={value.explode}>Explode</SlideToggle>
|
||||
<SlideToggle name="allowReserved" bind:checked={value.allowReserved}>Allow Reserved</SlideToggle>
|
||||
<label class="space-y-2">
|
||||
<p>Schema</p>
|
||||
<!-- Subject to change -->
|
||||
<select class="select" name="schema" bind:value={value.schema}>
|
||||
<option value="schema">Schema not yet implemented</option>
|
||||
</select>
|
||||
</label>
|
||||
<SlideToggle
|
||||
name="multiExample"
|
||||
bind:checked={multipleExamples}
|
||||
on:click={() => {
|
||||
if (!value.examples) value.examples = {};
|
||||
}}
|
||||
>
|
||||
Multiple Examples?
|
||||
</SlideToggle>
|
||||
{#if multipleExamples}
|
||||
<div class="space-y-2">
|
||||
<p>Examples</p>
|
||||
{#each Object.keys(value.examples) as example}
|
||||
<ExampleInput bind:example={value.examples[example]} name={example} />
|
||||
{/each}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm variant-filled-primary"
|
||||
on:click={addParameterExample}
|
||||
>
|
||||
Add Example
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<label class="space-y-2">
|
||||
<p>Example</p>
|
||||
<input type="text" class="input" name="example" placeholder="An example of the value." />
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -2,15 +2,15 @@
|
||||
import { InputChip } from '@skeletonlabs/skeleton';
|
||||
import Info from '../icons/Info.svelte';
|
||||
import type { OpenAPIV3_1 } from '$lib/openAPITypes';
|
||||
import { pathVariables } from '$lib';
|
||||
|
||||
export let id: number;
|
||||
export let server: OpenAPIV3_1.ServerObject;
|
||||
|
||||
const variableRegex = /\{([^}]+)\}/gm;
|
||||
const addVariables = () => {
|
||||
if (!server.url) return;
|
||||
|
||||
const matches = server.url.match(variableRegex);
|
||||
const matches = server.url.match(pathVariables);
|
||||
|
||||
if (!matches) {
|
||||
server.variables = undefined;
|
||||
@@ -73,9 +73,9 @@
|
||||
<div>
|
||||
<h4 class="h4">Variables</h4>
|
||||
<p class="text-sm">
|
||||
Define variables by adding them to the server URL using curly-braces like so: <code
|
||||
>{id}</code
|
||||
>.
|
||||
Define variables by adding them to the server URL using curly-braces like so: <code>
|
||||
{id}
|
||||
</code>.
|
||||
</p>
|
||||
</div>
|
||||
{#if server.variables}
|
||||
|
||||
@@ -1,59 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { addPath } from '$lib';
|
||||
import { openApiStore, pathRegex, sortPathsAlphabetically } from '$lib';
|
||||
import { pathTemplate } from '$lib/pathTemplate';
|
||||
import { openApiStore, sortPathsAlphabetically } from '$lib';
|
||||
import type { OpenAPIV3 } from '$lib/openAPITypes';
|
||||
import PathListItem from '../atoms/PathListItem.svelte';
|
||||
import PathButtons from '../atoms/PathButtons.svelte';
|
||||
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||
|
||||
let paths: OpenAPIV3.PathsObject = {};
|
||||
// @ts-expect-error - working with a potentially empty object
|
||||
openApiStore.subscribe((store) => (paths = store.paths));
|
||||
|
||||
const modalStore = getModalStore();
|
||||
|
||||
// match path with parameters
|
||||
|
||||
// add path
|
||||
// const addPath = () => {
|
||||
// // prompt user to enter path
|
||||
// const path = prompt(
|
||||
// 'Enter path. Wrap path parameters in curly braces. Example: /users/{userId}'
|
||||
// );
|
||||
// if (!path) return;
|
||||
// // check if path is valid
|
||||
// if (!pathRegex.test(path)) {
|
||||
// alert('Invalid path');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // check if path already exists
|
||||
// // @ts-expect-error - we are working with an initially empty object
|
||||
// if ($openApiStore.paths[path]) {
|
||||
// alert('Path already exists');
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // create a temporary object to store paths
|
||||
// // add path to paths object
|
||||
// $openApiStore.paths = {
|
||||
// ...$openApiStore.paths,
|
||||
// [path]: pathTemplate
|
||||
// };
|
||||
|
||||
// // sort paths alphabetically
|
||||
// sortPathsAlphabetically();
|
||||
// };
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="container mx-auto border-token rounded-container-token bg-surface-backdrop-token px-6 py-4 min-h-20 space-y-3"
|
||||
class="container mx-auto border-token rounded-container-token bg-surface-backdrop-token px-6 py-4 space-y-4"
|
||||
>
|
||||
{#if Object.keys($openApiStore.paths).length > 0}
|
||||
<PathButtons />
|
||||
<hr />
|
||||
{#each Object.keys($openApiStore.paths) as pathName, index}
|
||||
<PathListItem {pathName} id={index} />
|
||||
{/each}
|
||||
|
||||
<PathButtons />
|
||||
{:else}
|
||||
<PathButtons justify="justify-center" sort={false} />
|
||||
{/if}
|
||||
{#each Object.keys(paths) as pathName, index}
|
||||
<PathListItem {pathName} id={index} />
|
||||
{/each}
|
||||
<span class="w-full flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="btn variant-filled-primary"
|
||||
on:click={() => {
|
||||
addPath(modalStore);
|
||||
}}
|
||||
>
|
||||
Add Path
|
||||
</button>
|
||||
</span>
|
||||
<span class="w-full flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm variant-filled-secondary"
|
||||
on:click={sortPathsAlphabetically}
|
||||
>
|
||||
Sort paths
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -39,5 +39,16 @@ export const openApiStore = persisted<OpenAPIV3_1.Document>(`${localStoragePrefi
|
||||
}
|
||||
});
|
||||
|
||||
export enum HttpMethods {
|
||||
GET = 'get',
|
||||
PUT = 'put',
|
||||
POST = 'post',
|
||||
DELETE = 'delete',
|
||||
OPTIONS = 'options',
|
||||
HEAD = 'head',
|
||||
PATCH = 'patch',
|
||||
TRACE = 'trace'
|
||||
}
|
||||
|
||||
// export path handling functions
|
||||
export * from './pathHandling';
|
||||
|
||||
@@ -2,6 +2,9 @@ import { get } from 'svelte/store';
|
||||
import type { ModalSettings, ModalStore } from '@skeletonlabs/skeleton';
|
||||
import { pathTemplate } from './pathTemplate';
|
||||
import { openApiStore } from '$lib';
|
||||
import type { OpenAPIV3 } from './openAPITypes';
|
||||
|
||||
export const pathVariables = /\{([^}]+)\}/gm;
|
||||
|
||||
export const pathRegex = /\/([/]*[{]?[a-zA-Z]+[}]?)*/gm;
|
||||
|
||||
@@ -39,8 +42,6 @@ export const addPath = (modalStore: ModalStore, startingPoint: string = '/') =>
|
||||
const store = get(openApiStore);
|
||||
if (!store.paths) store.paths = {};
|
||||
store.paths[userPath] = pathTemplate;
|
||||
// add path to store
|
||||
openApiStore.set(store);
|
||||
|
||||
// sort paths alphabetically
|
||||
sortPathsAlphabetically();
|
||||
@@ -136,11 +137,12 @@ export const isValidPath = (path: string) => {
|
||||
}
|
||||
|
||||
// check if path is a valid path
|
||||
const pathWithoutVariables = path.replace('{', '').replace('}', '');
|
||||
const pathWithoutVariables = path.replaceAll('{', '').replaceAll('}', '');
|
||||
console.log(pathWithoutVariables);
|
||||
const pathRegex = /(\/[a-zA-Z-]+)+|\//gm;
|
||||
const pathParts = pathWithoutVariables.match(pathRegex);
|
||||
// the fallback is to return false if the pathParts array is null
|
||||
return pathParts?.length === 1 ? true : false;
|
||||
return pathParts?.length === 1;
|
||||
};
|
||||
|
||||
/// sorts the paths in the OpenAPI document alphabetically
|
||||
@@ -161,3 +163,20 @@ export const sortPathsAlphabetically = () => {
|
||||
return data;
|
||||
});
|
||||
};
|
||||
|
||||
export const getPathVariables = (path: string) => {
|
||||
const variables = path.match(pathVariables);
|
||||
if (!variables) return [];
|
||||
|
||||
return variables.map((variable) => variable.replace('{', '').replace('}', ''));
|
||||
};
|
||||
|
||||
export const sortPathParameters = (parameters: OpenAPIV3.ParameterObject[]) => {
|
||||
const tempParameters = parameters;
|
||||
tempParameters.sort((a, b) => {
|
||||
if (a.in < b.in) return -1;
|
||||
if (a.in > b.in) return 1;
|
||||
return 0;
|
||||
});
|
||||
return tempParameters;
|
||||
};
|
||||
|
||||
@@ -2,10 +2,10 @@ import type { OpenAPIV3_1 } from './openAPITypes';
|
||||
|
||||
export const pathTemplate: OpenAPIV3_1.PathItemObject = {
|
||||
$ref: undefined,
|
||||
summary: undefined,
|
||||
description: undefined,
|
||||
servers: undefined,
|
||||
parameters: undefined,
|
||||
summary: "",
|
||||
description: "",
|
||||
servers: [],
|
||||
parameters: [],
|
||||
get: undefined,
|
||||
put: undefined,
|
||||
post: undefined,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import FancyAppRail from './FancyAppRail.svelte';
|
||||
|
||||
import '../app.postcss';
|
||||
import { AppBar } from '@skeletonlabs/skeleton';
|
||||
import { AppBar, AppShell } from '@skeletonlabs/skeleton';
|
||||
|
||||
// Floating UI for Popups
|
||||
import { computePosition, autoUpdate, flip, shift, offset, arrow } from '@floating-ui/dom';
|
||||
@@ -15,72 +17,9 @@
|
||||
|
||||
<Modal />
|
||||
|
||||
<div class="w-full h-full">
|
||||
<AppBar padding="p-2">
|
||||
<svelte:fragment slot="lead">
|
||||
<h1>
|
||||
OpenAPI generator
|
||||
<code class="text-xs ml-2 variant-filled-success p-1 px-2 rounded-container-token">
|
||||
3.1.0
|
||||
</code>
|
||||
</h1>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="trail">
|
||||
<LightSwitch />
|
||||
<a
|
||||
href="https://www.speakeasyapi.dev/openapi"
|
||||
target="_blank"
|
||||
class="btn variant-filled-surface btn-sm hover:variant-filled-primary"
|
||||
>
|
||||
Schema Reference
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
class="btn variant-ringed-error btn-sm hover:variant-filled-error"
|
||||
on:click={() => {
|
||||
if (confirm('Are you sure you want to reset ALL current inputs?')) {
|
||||
// remove `openApi` from localStorage
|
||||
localStorage.removeItem(`${localStoragePrefix}openApi`);
|
||||
window.location.reload();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</svelte:fragment>
|
||||
</AppBar>
|
||||
<div class="flex flex-row w-full">
|
||||
<nav class="list-nav">
|
||||
<!-- (optionally you can provide a label here) -->
|
||||
<ul>
|
||||
<li class="">
|
||||
<a href="/info">
|
||||
<!-- <span class="badge bg-primary-500">(icon)</span> -->
|
||||
<span class="flex-auto">Info</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<a href="/servers">
|
||||
<!-- <span class="badge bg-primary-500">(icon)</span> -->
|
||||
<span class="flex-auto">Servers</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<a href="/authentication">
|
||||
<!-- <span class="badge bg-primary-500">(icon)</span> -->
|
||||
<span class="flex-auto">Security</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<a href="/paths">
|
||||
<!-- <span class="badge bg-primary-500">(icon)</span> -->
|
||||
<span class="flex-auto">Paths</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="mx-8 my-4 grow">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AppShell slotPageContent="px-6 py-4">
|
||||
<svelte:fragment slot="sidebarLeft">
|
||||
<FancyAppRail />
|
||||
</svelte:fragment>
|
||||
<slot />
|
||||
</AppShell>
|
||||
|
||||
@@ -1,42 +1,26 @@
|
||||
<script lang="ts">
|
||||
import Servers from '$lib/components/sections/Servers.svelte';
|
||||
import Info from '$lib/components/sections/Info.svelte';
|
||||
import { TabGroup, Tab } from '@skeletonlabs/skeleton';
|
||||
import { persisted } from 'svelte-persisted-store';
|
||||
import Authentication from '$lib/components/sections/Authentication.svelte';
|
||||
import Paths from '$lib/components/sections/Paths.svelte';
|
||||
|
||||
// let tabSet: number = 0;
|
||||
const tabSet = persisted<number>('tabSet', 0);
|
||||
</script>
|
||||
|
||||
<TabGroup justify="justify-center">
|
||||
<Tab bind:group={$tabSet} name="info" value={0}>
|
||||
<h4 class="h4">Info</h4>
|
||||
</Tab>
|
||||
<Tab bind:group={$tabSet} name="authentication" value={1}>
|
||||
<h4 class="h4">Authentication Schemas</h4>
|
||||
</Tab>
|
||||
<Tab bind:group={$tabSet} name="servers" value={2}>
|
||||
<h4 class="h4">Servers</h4>
|
||||
</Tab>
|
||||
<Tab bind:group={$tabSet} name="paths" value={3}>
|
||||
<h4 class="h4">Paths</h4>
|
||||
</Tab>
|
||||
<Tab bind:group={$tabSet} name="components" value={4}>
|
||||
<h4 class="h4">Components</h4>
|
||||
</Tab>
|
||||
<svelte:fragment slot="panel">
|
||||
{#if $tabSet === 0}
|
||||
<Info />
|
||||
{:else if $tabSet === 1}
|
||||
<Authentication />
|
||||
{:else if $tabSet === 2}
|
||||
<Servers />
|
||||
{:else if $tabSet === 3}
|
||||
<Paths />
|
||||
{:else if $tabSet === 4}
|
||||
<p>Components</p>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</TabGroup>
|
||||
<div class="w-full h-full flex flex-col items-center justify-center">
|
||||
<h1 class="h1">
|
||||
<span
|
||||
class="bg-gradient-to-br from-blue-500 to-cyan-300 bg-clip-text text-transparent box-decoration-clone"
|
||||
>
|
||||
Design.
|
||||
</span>
|
||||
</h1>
|
||||
<h1 class="h1">
|
||||
<span
|
||||
class="bg-gradient-to-br from-red-500 to-yellow-500 bg-clip-text text-transparent box-decoration-clone"
|
||||
>
|
||||
Build.
|
||||
</span>
|
||||
</h1>
|
||||
<h1 class="h1">
|
||||
<span
|
||||
class="bg-gradient-to-br from-pink-500 to-violet-500 bg-clip-text text-transparent box-decoration-clone"
|
||||
>
|
||||
Deploy.
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
144
src/routes/FancyAppRail.svelte
Normal file
144
src/routes/FancyAppRail.svelte
Normal file
@@ -0,0 +1,144 @@
|
||||
<script lang="ts">
|
||||
import { AppRail, AppRailAnchor, AppRailTile, LightSwitch } from '@skeletonlabs/skeleton';
|
||||
import { page } from '$app/stores';
|
||||
import { localStoragePrefix } from '$lib';
|
||||
import { goto } from '$app/navigation';
|
||||
</script>
|
||||
|
||||
<AppRail width="w-28" aspectRatio="aspect-[3/2]" background="variant-ghost-surface" border="ring-0">
|
||||
<svelte:fragment slot="lead">
|
||||
<div class="my-4">
|
||||
<AppRailAnchor href="/">
|
||||
<p class="font-bold mb-1">OpenAPI</p>
|
||||
<p class=" mb-2">Generator</p>
|
||||
<code class="text-xs ml-2 variant-filled-success p-1 px-2 rounded-container-token">
|
||||
3.1.0
|
||||
</code>
|
||||
</AppRailAnchor>
|
||||
</div>
|
||||
<hr />
|
||||
</svelte:fragment>
|
||||
<AppRailAnchor href="/info" selected={$page.url.pathname === '/info'}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6 mx-auto"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
|
||||
/>
|
||||
</svg>
|
||||
Info
|
||||
</AppRailAnchor>
|
||||
<AppRailAnchor href="/servers" selected={$page.url.pathname === '/servers'}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6 mx-auto"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.7L5.737 5.1a3.375 3.375 0 0 1 2.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 0 1 .9 2.7m0 0a3 3 0 0 1-3 3m0 3h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Zm-3 6h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Z"
|
||||
/>
|
||||
</svg>
|
||||
Servers
|
||||
</AppRailAnchor>
|
||||
<AppRailAnchor href="/authentication" selected={$page.url.pathname === '/authentication'}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6 mx-auto"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z"
|
||||
/>
|
||||
</svg>
|
||||
Authentication
|
||||
</AppRailAnchor>
|
||||
<AppRailAnchor href="/paths" selected={$page.url.pathname === '/paths'}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6 mx-auto"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
|
||||
/>
|
||||
</svg>
|
||||
Paths
|
||||
</AppRailAnchor>
|
||||
<svelte:fragment slot="trail">
|
||||
<button
|
||||
type="button"
|
||||
class="btn text-sm rounded-none text-wrap variant-soft-primary flex flex-col justify-center items-center h-20 w-full"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m.75 12 3 3m0 0 3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
|
||||
/>
|
||||
</svg>
|
||||
Download JSON
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn text-sm rounded-none text-wrap variant-ringed-error hover:variant-soft-error flex justify-center items-center h-16 w-full"
|
||||
on:click={() => {
|
||||
if (confirm('Are you sure you want to reset ALL current inputs?')) {
|
||||
// remove `openApi` from localStorage
|
||||
localStorage.removeItem(`${localStoragePrefix}openApi`);
|
||||
window.location.pathname = '/';
|
||||
}
|
||||
}}
|
||||
>
|
||||
Clear all inputs
|
||||
</button>
|
||||
<div class="flex justify-center items-center h-10 w-full my-4">
|
||||
<LightSwitch />
|
||||
</div>
|
||||
<AppRailAnchor href="https://www.speakeasyapi.dev/openapi" target="_blank">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6 mx-auto"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"
|
||||
/>
|
||||
</svg>
|
||||
API Reference
|
||||
</AppRailAnchor>
|
||||
</svelte:fragment>
|
||||
</AppRail>
|
||||
@@ -1 +1,17 @@
|
||||
Route does not exist.
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full flex justify-center items-center">
|
||||
<div class="card max-w-lg">
|
||||
<header class="card-header">
|
||||
<enhanced:img src="./error.jpg" alt="stormy, grayscale seaside" />
|
||||
</header>
|
||||
<section class="p-4">
|
||||
<h3 class="h3">An unfortunate error occured.</h3>
|
||||
</section>
|
||||
<footer class="card-footer">
|
||||
{$page.status} - {$page.error?.message}
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,107 +1,160 @@
|
||||
<script lang="ts">
|
||||
import ServerInput from '$lib/components/atoms/ServerInput.svelte';
|
||||
import { openApiStore } from '$lib';
|
||||
import { SlideToggle } from '@skeletonlabs/skeleton';
|
||||
import { pathTemplate } from '$lib/pathTemplate';
|
||||
import { HttpMethods, openApiStore } from '$lib';
|
||||
import ParameterInput from '$lib/components/atoms/ParameterInput.svelte';
|
||||
import { getPathVariables } from '$lib/pathHandling';
|
||||
import type { OpenAPIV3 } from '$lib/openAPITypes';
|
||||
import type { PageData } from './$types';
|
||||
import { Accordion, AccordionItem } from '@skeletonlabs/skeleton';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
// create local path object to work with
|
||||
const localPathObject = (() => {
|
||||
// @ts-expect-error - damn those stolen types for their inaccuracy
|
||||
return $openApiStore.paths[data.pathName] ?? pathTemplate;
|
||||
})();
|
||||
|
||||
let customServersEnabled: boolean = false;
|
||||
|
||||
if (localPathObject.servers?.length > 0) {
|
||||
customServersEnabled = true;
|
||||
}
|
||||
|
||||
const addCustomServer = () => {
|
||||
if (!Array.isArray(localPathObject.servers)) {
|
||||
localPathObject.servers = [];
|
||||
}
|
||||
localPathObject.servers.push({ url: '', description: '' });
|
||||
// yay svelte reactivity
|
||||
localPathObject.servers = localPathObject.servers;
|
||||
let newParam: 'query' | 'header' | 'cookie' = 'query';
|
||||
let tempPath: OpenAPIV3.PathItemObject = {
|
||||
parameters: []
|
||||
};
|
||||
openApiStore.subscribe((store) => {
|
||||
if (!data.pathName) return;
|
||||
if (store.paths == undefined) tempPath = {};
|
||||
if (!store.paths!.hasOwnProperty(data.pathName)) tempPath = {};
|
||||
// @ts-expect-error - working with a known not empty object
|
||||
tempPath = store.paths[data.pathName] ?? {};
|
||||
|
||||
const removeCustomServer = (index: number) => {
|
||||
if (!Array.isArray(localPathObject.servers)) {
|
||||
return;
|
||||
}
|
||||
localPathObject.servers.splice(index, 1);
|
||||
// yay svelte reactivity
|
||||
localPathObject.servers = localPathObject.servers;
|
||||
};
|
||||
if (!tempPath.hasOwnProperty('parameters')) tempPath.parameters = [];
|
||||
});
|
||||
|
||||
const pathVariables = getPathVariables(data.pathName ?? '');
|
||||
|
||||
pathVariables.forEach((variable) => {
|
||||
// push path variables to the parameters array
|
||||
// @ts-expect-error - working with a array thats loosely defined
|
||||
tempPath.parameters.push({
|
||||
name: variable,
|
||||
in: 'path',
|
||||
required: true
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="border-token rounded-container-token bg-surface-backdrop-token p-4 space-y-4">
|
||||
<h1>{data.pathName}</h1>
|
||||
<label class="space-y-1">
|
||||
<h5 class="h5">Summary</h5>
|
||||
<p class="text-sm">A short summary of what the path item represents.</p>
|
||||
<input type="text" class="input" placeholder="Summary" bind:value={localPathObject.summary} />
|
||||
</label>
|
||||
<label class="space-y-1">
|
||||
<h5 class="h5">Description</h5>
|
||||
<p class="text-sm">A description of the path item. Supports Markdown.</p>
|
||||
<textarea class="textarea" placeholder="Description" bind:value={localPathObject.description} />
|
||||
</label>
|
||||
<div>
|
||||
<span class="flex flex-col gap-4">
|
||||
<h5 class="h5">Servers</h5>
|
||||
<SlideToggle name="slide" size="sm" bind:checked={customServersEnabled} />
|
||||
{#if customServersEnabled && Array.isArray(localPathObject.servers)}
|
||||
<ul class="list space-y-6">
|
||||
{#each localPathObject.servers as server, index}
|
||||
<li class="!block">
|
||||
<span class="flex w-full justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm variant-ringed-error hover:variant-filled-error"
|
||||
on:click={() => removeCustomServer(index)}
|
||||
>
|
||||
Remove Server
|
||||
</button>
|
||||
</span>
|
||||
<ServerInput id={1} bind:server />
|
||||
</li>
|
||||
{#if index < localPathObject.servers.length - 1}
|
||||
<hr />
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if customServersEnabled}
|
||||
<span class="flex w-full justify-center">
|
||||
<button type="button" class="btn variant-filled-primary" on:click={addCustomServer}>
|
||||
Add Server
|
||||
<div
|
||||
class="border-token border-surface-500 space-y-4 px-6 py-4 rounded-container-token variant-glass-surface"
|
||||
>
|
||||
<h3 class="h3">
|
||||
{data.pathName}
|
||||
</h3>
|
||||
<hr />
|
||||
|
||||
<Accordion>
|
||||
<AccordionItem>
|
||||
<svelte:fragment slot="summary">
|
||||
<h4 class="h4">General</h4>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<label class="space-y-2">
|
||||
<p>Summary</p>
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
bind:value={tempPath.summary}
|
||||
placeholder="Summary of the path"
|
||||
/>
|
||||
</label>
|
||||
<label class="space-y-2">
|
||||
<p>Description</p>
|
||||
<textarea
|
||||
class="textarea"
|
||||
bind:value={tempPath.description}
|
||||
placeholder="Description of the path. Supports markdown."
|
||||
/>
|
||||
</label>
|
||||
</svelte:fragment>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<svelte:fragment slot="summary">
|
||||
<h4 class="h4">Custom Servers</h4>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<p>Here you can add custom servers for this specific call.</p>
|
||||
{#each tempPath.servers as server, index}
|
||||
<label class="space-y-2">
|
||||
<p>Server {index + 1}</p>
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
bind:value={server.url}
|
||||
placeholder="URL of the server"
|
||||
/>
|
||||
</label>
|
||||
{/each}
|
||||
<button type="button" class="btn variant-filled-primary"> Add Server </button>
|
||||
</svelte:fragment>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<svelte:fragment slot="summary">
|
||||
<h4 class="h4">Parameters</h4>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
{#each tempPath.parameters as param}
|
||||
<ParameterInput variableName={param.name} bind:value={param} location="path" />
|
||||
{/each}
|
||||
|
||||
<span class="flex items-center gap-2">
|
||||
<select name="newParameter" bind:value={newParam} class="select w-min">
|
||||
<option value="query">Query</option>
|
||||
<option value="header">Header</option>
|
||||
<option value="cookie">Cookie</option>
|
||||
</select>
|
||||
<button type="button" class="btn variant-filled-primary">
|
||||
Add {newParam} Parameter
|
||||
</button>
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="h5">Parameters</h5>
|
||||
<div class="ml-2">
|
||||
<div>
|
||||
<h6 class="h6">Path Parameters</h6>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="h6">Query Parameters</h6>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="h6">Header Parameters</h6>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="h6">Cookie Parameters</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="h5">Operations</h5>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<svelte:fragment slot="summary">
|
||||
<h4 class="h4">Operations</h4>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<p>
|
||||
Here you can add operations for this path. Select only the operations you want to support
|
||||
</p>
|
||||
|
||||
<div class="flex gap-4">
|
||||
{#each Object.values(HttpMethods) as method}
|
||||
<label class="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
on:input={(event) => {
|
||||
if (event.target.checked) {
|
||||
tempPath[method] = {
|
||||
tags: [],
|
||||
summary: '',
|
||||
description: '',
|
||||
externalDocs: {
|
||||
description: '',
|
||||
url: ''
|
||||
},
|
||||
operationId: '',
|
||||
parameters: [],
|
||||
requestBody: {
|
||||
content: {},
|
||||
description: '',
|
||||
required: false
|
||||
},
|
||||
responses: {},
|
||||
callbacks: {},
|
||||
deprecated: false,
|
||||
security: [],
|
||||
servers: []
|
||||
};
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{method.toUpperCase()}
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
|
||||
@@ -1,27 +1,30 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageLoad } from './$types';
|
||||
import { openApiStore } from '$lib';
|
||||
import { get } from 'svelte/store';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { OpenAPIV3_1 } from '$lib/openAPITypes';
|
||||
|
||||
export const load = (async (event) => {
|
||||
const index = Number(event.params.index);
|
||||
if (isNaN(index)) {
|
||||
console.error('Index is not a number', index);
|
||||
error(404, 'Not found');
|
||||
}
|
||||
const paths = get(openApiStore).paths;
|
||||
if (!paths) {
|
||||
console.error('No paths found in OpenAPI document');
|
||||
error(404, 'Not found');
|
||||
}
|
||||
// check if path param is an integer
|
||||
if (!/^\d+$/.test(event.params.index)) error(404, 'Invalid path index');
|
||||
const index = parseInt(event.params.index);
|
||||
let apiObject: OpenAPIV3_1.Document;
|
||||
openApiStore.subscribe((value) => (apiObject = value));
|
||||
let pathName: string | undefined;
|
||||
// @ts-expect-error - svelte stores populate the value
|
||||
if (apiObject && !apiObject.paths) error(404, 'No paths found in the OpenAPI document');
|
||||
|
||||
const pathObjectName = Object.keys(paths)[index];
|
||||
|
||||
if (!pathObjectName) {
|
||||
console.error('No path found in OpenAPI document');
|
||||
error(404, 'Not found');
|
||||
}
|
||||
const path = (() => {
|
||||
// @ts-expect-error - svelte stores populate the value
|
||||
const paths = Object.keys(apiObject.paths) || [];
|
||||
if (paths.length === 0) return;
|
||||
if (paths.length <= index) return;
|
||||
pathName = paths[index];
|
||||
// @ts-expect-error - svelte stores populate the value
|
||||
if (!apiObject || !apiObject.paths) return;
|
||||
return apiObject.paths[paths[index]];
|
||||
})();
|
||||
return {
|
||||
pathName: pathObjectName
|
||||
path,
|
||||
pathName
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
|
||||
BIN
src/routes/paths/[index]/error.jpg
Normal file
BIN
src/routes/paths/[index]/error.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
@@ -1,7 +1,8 @@
|
||||
import { purgeCss } from 'vite-plugin-tailwind-purgecss';
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import { enhancedImages } from '@sveltejs/enhanced-img';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit(), purgeCss()]
|
||||
plugins: [enhancedImages(), sveltekit(), purgeCss()]
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user