mirror of
https://github.com/LukeHagar/OpenAPI.gg.git
synced 2025-12-06 12:37:48 +00:00
Saving progress on paths and methods
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
'This operation clears all the current values, unsaved data will be lost, are you sure?'
|
||||
)
|
||||
) {
|
||||
loadSpec(structuredClone(newSpec));
|
||||
loadSpec(newSpec());
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
on:click={async () => {
|
||||
if (confirm(`Are you sure you want to delete all saved specs?`)) {
|
||||
db.apiSpecs.clear();
|
||||
loadSpec(structuredClone(newSpec));
|
||||
loadSpec(newSpec());
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
if (specs.length > 0){
|
||||
loadSpec(specs[0]);
|
||||
} else {
|
||||
loadSpec(structuredClone(newSpec));
|
||||
loadSpec(newSpec());
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
import filenamify from 'filenamify';
|
||||
import { stringify } from 'yaml';
|
||||
|
||||
export let width: CssClasses = "w-full"
|
||||
export let padding: CssClasses = "px-2 py-0.5"
|
||||
|
||||
$: fileName = filenamify($selectedSpec.spec?.info?.title) || 'openapi';
|
||||
|
||||
const saveYAML = () => {
|
||||
@@ -36,12 +39,10 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1 text-xs gap-2">
|
||||
<span class="text-center font-bold text-sm">Download</span>
|
||||
<button
|
||||
type="button"
|
||||
on:click={saveYAML}
|
||||
class="btn btn-sm rounded-lg w-full variant-ghost-tertiary gap-1"
|
||||
class="btn btn-sm {width} {padding} variant-ghost-tertiary gap-1"
|
||||
aria-label="Download a YAML representation of the OpenAPI document."
|
||||
>
|
||||
YAML
|
||||
@@ -63,7 +64,7 @@
|
||||
<button
|
||||
type="button"
|
||||
on:click={saveJSON}
|
||||
class="btn btn-sm rounded-lg w-full variant-ghost-tertiary gap-1"
|
||||
class="btn btn-sm {width} {padding} variant-ghost-tertiary gap-1"
|
||||
aria-label="Download a JSON representation of the OpenAPI document."
|
||||
>
|
||||
JSON
|
||||
@@ -82,4 +83,3 @@
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { CssClasses } from '@skeletonlabs/skeleton';
|
||||
|
||||
export let width: CssClasses = "w-full"
|
||||
export let padding: CssClasses = "px-3 py-1"
|
||||
|
||||
async function onSave(e: Event): Promise<void> {
|
||||
console.log('Save button clicked');
|
||||
@@ -12,4 +13,4 @@
|
||||
|
||||
</script>
|
||||
|
||||
<button class="btn variant-ghost-success {width}" on:click={onSave}> Save </button>
|
||||
<button class="btn variant-ghost-success {width} {padding}" on:click={onSave}> Save </button>
|
||||
|
||||
@@ -1,36 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { selectedSpec } from '$lib/db';
|
||||
import spdxLicenseList from 'spdx-license-list';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const modifiedLicenseList = Object.entries(spdxLicenseList).map((entry) => {
|
||||
return {
|
||||
identifier: entry[0],
|
||||
name: entry[1].name,
|
||||
url: entry[1].url
|
||||
};
|
||||
});
|
||||
|
||||
const popularLicenses = ['MIT', 'Apache-2.0', 'GPL-3.0', 'Unlicense'];
|
||||
|
||||
let selectedLicense: {
|
||||
name: string;
|
||||
url: string;
|
||||
identifier: string;
|
||||
};
|
||||
|
||||
selectedSpec.subscribe((spec) => {
|
||||
if (!spec.spec.info.license) return;
|
||||
|
||||
let licenseCandidate;
|
||||
|
||||
if (spec.spec.info.license.identifier) {
|
||||
licenseCandidate = modifiedLicenseList.find(
|
||||
(entry) => entry.identifier === spec.spec.info.license!.identifier
|
||||
);
|
||||
} else if (spec.spec.info.license.url) {
|
||||
licenseCandidate = modifiedLicenseList.find(
|
||||
(entry) => entry.url === spec.spec.info.license!.url
|
||||
);
|
||||
} else if (spec.spec.info.license.name) {
|
||||
licenseCandidate = modifiedLicenseList.find(
|
||||
(entry) => entry.name === spec.spec.info.license!.name
|
||||
);
|
||||
}
|
||||
|
||||
if (licenseCandidate) {
|
||||
selectedLicense = licenseCandidate;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="border-token rounded-container-token space-y-1 p-4">
|
||||
<div class="flex flex-row justify-between">
|
||||
<div class="border-token grow rounded-container-token bg-surface-backdrop-token space-y-1 p-4">
|
||||
<div class="flex flex-row flex-wrap justify-between">
|
||||
<h4 class="h4">License</h4>
|
||||
{#if $selectedSpec.spec.info.license}
|
||||
<label class="text-sm space-x-2">
|
||||
<label class="text-sm ">
|
||||
<span>Pick a license</span>
|
||||
<select
|
||||
class="select w-56 text-sm"
|
||||
bind:value={$selectedSpec.spec.info.license.identifier}
|
||||
class="select md:w-56 text-sm"
|
||||
bind:value={selectedLicense}
|
||||
on:change={() => {
|
||||
// @ts-expect-error - This is literally inside a null check
|
||||
$selectedSpec.spec.info.license.url = null
|
||||
// @ts-expect-error - This is only running on identifier change
|
||||
$selectedSpec.spec.info.license.name =
|
||||
// @ts-expect-error - This is only running on identifier change
|
||||
spdxLicenseList[$selectedSpec.spec.info.license.identifier].name;
|
||||
$selectedSpec.spec.info.license = {
|
||||
name: selectedLicense.name,
|
||||
url: selectedLicense.url
|
||||
};
|
||||
}}
|
||||
>
|
||||
<optgroup label="Popular Licenses">
|
||||
{#each Object.keys(spdxLicenseList).filter( (entry) => popularLicenses.includes(entry) ) as license}
|
||||
<option value={license}>{spdxLicenseList[license].name}</option>
|
||||
{#each modifiedLicenseList.filter( (entry) => popularLicenses.includes(entry.identifier) ) as license}
|
||||
<option value={license}>{license.name}</option>
|
||||
{/each}
|
||||
</optgroup>
|
||||
<optgroup label="All Licenses">
|
||||
{#each Object.keys(spdxLicenseList).sort() as license}
|
||||
<option value={license}>{spdxLicenseList[license].name}</option>
|
||||
{#each modifiedLicenseList
|
||||
.filter((entry) => !popularLicenses.includes(entry.identifier))
|
||||
.sort() as license}
|
||||
<option value={license}>{license.name}</option>
|
||||
{/each}
|
||||
</optgroup>
|
||||
</select>
|
||||
@@ -75,9 +114,8 @@
|
||||
class="btn variant-filled-primary"
|
||||
on:click={() => {
|
||||
$selectedSpec.spec.info.license = {
|
||||
name: '',
|
||||
identifier: '',
|
||||
url: ''
|
||||
name: spdxLicenseList['MIT'].name,
|
||||
url: spdxLicenseList['MIT'].url
|
||||
};
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
addPath,
|
||||
deletePath,
|
||||
renamePath
|
||||
} from '$lib';
|
||||
import { addPath, deletePath, renamePath } from '$lib';
|
||||
import { getModalStore } from '@skeletonlabs/skeleton';
|
||||
const modalStore = getModalStore();
|
||||
|
||||
@@ -13,13 +9,14 @@
|
||||
export let id: number;
|
||||
</script>
|
||||
|
||||
<div class="flex justify-between">
|
||||
<h3 class="">{pathName}</h3>
|
||||
<div class="flex gap-2">
|
||||
<a href="/paths/{id}" class="btn btn-sm variant-filled-primary">Edit</a>
|
||||
<div class="flex flex-wrap justify-between gap-1">
|
||||
<h3 class="truncate font-mono"><span class="md:hidden">Path: </span>{pathName}</h3>
|
||||
<div class="flex md:gap-2 flex-wrap gap-1">
|
||||
<a href="/paths/{pathName}" class="btn btn-sm variant-filled-primary grow md:w-fit w-full">Edit</a>
|
||||
<div class="grow flex flex-row gap-1">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm variant-filled-warning"
|
||||
class="btn btn-sm variant-filled-warning grow"
|
||||
on:click={() => {
|
||||
renamePath(modalStore, pathName);
|
||||
}}
|
||||
@@ -28,16 +25,17 @@
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm variant-filled-secondary"
|
||||
class="btn btn-sm variant-filled-secondary grow"
|
||||
on:click={() => {
|
||||
addPath(modalStore, pathName);
|
||||
}}
|
||||
>
|
||||
Add Sub-Route
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm variant-ringed-error hover:variant-filled-error"
|
||||
class="btn btn-sm variant-ringed-error hover:variant-filled-error grow"
|
||||
on:click={() => {
|
||||
deletePath(modalStore, pathName);
|
||||
}}
|
||||
|
||||
@@ -68,15 +68,15 @@
|
||||
<hr />
|
||||
{/each}
|
||||
|
||||
<span class="flex justify-center items-center gap-2 max-w-sm mx-auto">
|
||||
<select name="security-schema" bind:value={selectedSchema} class="input w-fit text-sm">
|
||||
<span class="flex flex-wrap justify-center items-center gap-2 max-w-sm mx-auto">
|
||||
<select name="security-schema" bind:value={selectedSchema} class="input w-fit text-sm grow">
|
||||
<option value="basicAuth" selected>Basic Auth</option>
|
||||
<option value="bearerAuth">Bearer Auth</option>
|
||||
<option value="ApiKeyAuth">API Key Auth</option>
|
||||
<option value="openId">OpenID</option>
|
||||
<option value="oAuthSample">OAuth2</option>
|
||||
</select>
|
||||
<button type="button" class="btn text-sm variant-filled-primary" on:click={addSecuritySchema}>
|
||||
<button type="button" class="btn text-sm variant-filled-primary grow" on:click={addSecuritySchema}>
|
||||
Add Security Schema
|
||||
</button>
|
||||
</span>
|
||||
|
||||
@@ -48,7 +48,10 @@
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="border-token rounded-container-token bg-surface-backdrop-token space-y-1 p-4">
|
||||
<div class="flex flex-row flex-wrap w-full gap-2">
|
||||
|
||||
|
||||
<div class="border-token grow rounded-container-token bg-surface-backdrop-token space-y-1 p-4">
|
||||
<h4 class="h4">Contact Information</h4>
|
||||
{#if $selectedSpec.spec.info.contact}
|
||||
<label class="space-y-1">
|
||||
@@ -99,4 +102,5 @@
|
||||
</div>
|
||||
|
||||
<LicenseAtom />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
|
||||
<div class="w-full flex flex-row justify-center">
|
||||
<div
|
||||
class="fixed bottom-10 px-5 w-full max-w-xs shadow-xl flex flex-row gap-2 justify-between bg-surface-100-800-token rounded-full p-4"
|
||||
class="fixed bottom-1 md:bottom-5 md:w-full md:max-w-[270px] w-fit shadow-xl flex flex-row gap-2 justify-between bg-surface-100-800-token rounded-full md:p-3 p-1"
|
||||
>
|
||||
<span class="flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="btn gap-2 variant-filled-primary"
|
||||
class="btn btn-sm grow gap-2 variant-filled-primary"
|
||||
on:click={() => {
|
||||
addPath(modalStore);
|
||||
}}
|
||||
@@ -48,7 +48,7 @@
|
||||
<span class="flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm gap-2 variant-filled-secondary"
|
||||
class="btn btn-sm grow gap-2 variant-filled-secondary"
|
||||
on:click={sortPathsAlphabetically}
|
||||
>
|
||||
<svg
|
||||
@@ -71,7 +71,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx-auto border-token rounded-container-token bg-surface-backdrop-token px-6 py-4 space-y-4"
|
||||
class="mx-auto border-token rounded-container-token bg-surface-backdrop-token md:px-6 px-2 md:py-4 py-2 space-y-4 mb-10"
|
||||
>
|
||||
{#if Object.keys(paths).length === 0}
|
||||
<p class="text-center">Wow such empty.</p>
|
||||
|
||||
@@ -41,23 +41,26 @@ export const blankSpec: OpenAPIV3_1.Document = {
|
||||
}
|
||||
};
|
||||
|
||||
export const newSpec: APISpec = {
|
||||
export const newSpec: () => APISpec = () => structuredClone({
|
||||
name: 'OpenAPI',
|
||||
spec: blankSpec
|
||||
} as const
|
||||
})
|
||||
|
||||
export const selectedSpecId: Writable<string | undefined> = persisted("selectedSpecId",undefined)
|
||||
export const selectedSpec: Writable<APISpec> = writable(newSpec)
|
||||
export const selectedSpec: Writable<APISpec> = writable(newSpec())
|
||||
|
||||
selectedSpec.subscribe((spec) => {
|
||||
if(!spec){
|
||||
spec = structuredClone(newSpec)
|
||||
spec = newSpec()
|
||||
}
|
||||
if(spec.id){
|
||||
selectedSpecId.set(spec.id)
|
||||
}
|
||||
})
|
||||
|
||||
export const specLoaded: Writable<boolean> = writable(false);
|
||||
export const pageLoaded: Writable<boolean> = writable(false);
|
||||
|
||||
export interface APISpec {
|
||||
id?: string;
|
||||
name: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import FancyAppRail from './FancyAppRail.svelte';
|
||||
|
||||
import { AppShell, type ModalComponent } from '@skeletonlabs/skeleton';
|
||||
import { AppBar, AppShell, type ModalComponent } from '@skeletonlabs/skeleton';
|
||||
import '../app.postcss';
|
||||
// Floating UI for Popups
|
||||
import { arrow, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';
|
||||
@@ -9,19 +9,89 @@
|
||||
// Modal
|
||||
import { initializeStores, Modal } from '@skeletonlabs/skeleton';
|
||||
import UploadModal from '$lib/components/FileManagement/UploadModal.svelte';
|
||||
import {
|
||||
db,
|
||||
loadSpec,
|
||||
pageLoaded,
|
||||
saveSpec,
|
||||
selectedSpec,
|
||||
selectedSpecId,
|
||||
specLoaded
|
||||
} from '$lib/db';
|
||||
import { pathCount } from '$lib';
|
||||
import { onMount } from 'svelte';
|
||||
import { liveQuery } from 'dexie';
|
||||
import SaveButton from '$lib/components/FileManagement/SaveButton.svelte';
|
||||
import DownloadButtons from '$lib/components/FileManagement/DownloadButtons.svelte';
|
||||
initializeStores();
|
||||
|
||||
const components: Record<string, ModalComponent> = {
|
||||
// Set a unique modal ID, then pass the component reference
|
||||
uploadModal: { ref: UploadModal },
|
||||
uploadModal: { ref: UploadModal }
|
||||
// ...
|
||||
};
|
||||
|
||||
const apiSpecs = liveQuery(() => db.apiSpecs.toArray());
|
||||
|
||||
apiSpecs.subscribe((specs) => {
|
||||
if ($specLoaded) return;
|
||||
|
||||
if ($selectedSpecId !== $selectedSpec.id) {
|
||||
const found = specs.find((spec) => spec.id === $selectedSpecId);
|
||||
|
||||
if (found) {
|
||||
loadSpec(found);
|
||||
specLoaded.set(true);
|
||||
}
|
||||
} else if (specs.length > 0) {
|
||||
loadSpec(specs[0]);
|
||||
specLoaded.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
console.log('onMount', $selectedSpecId, $selectedSpec);
|
||||
if ($selectedSpec) {
|
||||
pageLoaded.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
// $: console.log(
|
||||
// 'newSpec',
|
||||
// $selectedSpec,
|
||||
// $selectedSpecId,
|
||||
// $apiSpecs,
|
||||
// $apiSpecs?.length,
|
||||
// specLoaded
|
||||
// );
|
||||
|
||||
//window keydown event listener for ctrl+s for saving
|
||||
const saveListener = (e: KeyboardEvent) => {
|
||||
if (e.ctrlKey && e.key === 's') {
|
||||
e.preventDefault();
|
||||
console.log('ctrl+s');
|
||||
saveSpec($selectedSpec);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<Modal {components} />
|
||||
|
||||
<AppShell slotPageContent="px-6 py-4">
|
||||
<svelte:window on:keydown={saveListener} />
|
||||
|
||||
<AppShell slotPageContent="md:p-4 p-2">
|
||||
<svelte:fragment slot="header">
|
||||
<AppBar padding="px-2 py-1 ">
|
||||
<svelte:fragment slot="lead"> OpenAPI.gg</svelte:fragment>
|
||||
<svelte:fragment slot="trail">
|
||||
<div class="p-1 flex flex-row md:gap-3 gap-1">
|
||||
<DownloadButtons />
|
||||
<SaveButton />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<!-- <svelte:fragment slot="headline">(headline)</svelte:fragment> -->
|
||||
</AppBar>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="sidebarLeft">
|
||||
<FancyAppRail />
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { pathCount, operationCount } from '$lib';
|
||||
import { operationCount, pathCount } from '$lib';
|
||||
import CreateNewButton from '$lib/components/FileManagement/CreateNewButton.svelte';
|
||||
import DeleteAllButton from '$lib/components/FileManagement/DeleteAllButton.svelte';
|
||||
import DeleteButton from '$lib/components/FileManagement/DeleteButton.svelte';
|
||||
@@ -7,30 +7,11 @@
|
||||
import SaveButton from '$lib/components/FileManagement/SaveButton.svelte';
|
||||
import SaveNewButton from '$lib/components/FileManagement/SaveNewButton.svelte';
|
||||
import UploadButton from '$lib/components/FileManagement/UploadButton.svelte';
|
||||
import { db, loadSpec, newSpec, selectedSpec, selectedSpecId } from '$lib/db';
|
||||
import { liveQuery } from 'dexie';
|
||||
import { db, pageLoaded, selectedSpec, specLoaded } from '$lib/db';
|
||||
import { ProgressRadial } from '@skeletonlabs/skeleton';
|
||||
import { onMount } from 'svelte';
|
||||
import { liveQuery } from 'dexie';
|
||||
|
||||
let apiSpecs = liveQuery(() => db.apiSpecs.toArray());
|
||||
let specLoaded = false;
|
||||
let pageLoaded = false;
|
||||
|
||||
apiSpecs.subscribe((specs) => {
|
||||
if (specLoaded) return;
|
||||
|
||||
if ($selectedSpecId !== $selectedSpec.id) {
|
||||
const found = specs.find((spec) => spec.id === $selectedSpecId);
|
||||
|
||||
if (found) {
|
||||
loadSpec(found);
|
||||
specLoaded = true;
|
||||
}
|
||||
} else if (specs.length > 0) {
|
||||
loadSpec(specs[0]);
|
||||
specLoaded = true;
|
||||
}
|
||||
});
|
||||
const apiSpecs = liveQuery(() => db.apiSpecs.toArray());
|
||||
|
||||
$: stats = [
|
||||
{
|
||||
@@ -42,15 +23,6 @@
|
||||
value: operationCount($selectedSpec.spec)
|
||||
}
|
||||
];
|
||||
|
||||
onMount(() => {
|
||||
console.log('onMount', $selectedSpecId, $selectedSpec);
|
||||
if ($selectedSpec) {
|
||||
pageLoaded = true;
|
||||
}
|
||||
});
|
||||
|
||||
$: console.log('newSpec', newSpec, $selectedSpec, $selectedSpecId, $apiSpecs, $apiSpecs?.length, specLoaded);
|
||||
</script>
|
||||
|
||||
<div class="grid place-content-center h-full gap-2 px-1">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</script>
|
||||
|
||||
<AppRail
|
||||
width="w-28"
|
||||
width="md:w-28 w-20"
|
||||
aspectRatio="aspect-[20/14]"
|
||||
background="variant-ghost-surface"
|
||||
border="ring-0"
|
||||
@@ -13,15 +13,21 @@
|
||||
<svelte:fragment slot="lead">
|
||||
<div>
|
||||
<AppRailAnchor href="/">
|
||||
<div class="flex flex-col gap-2 py-4">
|
||||
<p class="font-bold">OpenAPI</p>
|
||||
<p>Generator</p>
|
||||
<code
|
||||
class="mx-auto w-min text-xs variant-filled-success p-1 px-2 rounded-container-token"
|
||||
<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"
|
||||
>
|
||||
3.1.0
|
||||
</code>
|
||||
</div>
|
||||
<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>
|
||||
File
|
||||
</AppRailAnchor>
|
||||
</div>
|
||||
<hr />
|
||||
@@ -129,11 +135,7 @@
|
||||
Components
|
||||
</AppRailAnchor>
|
||||
|
||||
|
||||
<svelte:fragment slot="trail">
|
||||
<div class="p-2">
|
||||
<DownloadButtons />
|
||||
</div>
|
||||
<div class="flex justify-center my-4">
|
||||
<LightSwitch />
|
||||
</div>
|
||||
@@ -152,7 +154,7 @@
|
||||
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
|
||||
<span class=" whitespace-break-spaces px-1">API Reference</span>
|
||||
</AppRailAnchor>
|
||||
</svelte:fragment>
|
||||
</AppRail>
|
||||
|
||||
206
src/routes/paths/[...pathName]/+page.svelte
Normal file
206
src/routes/paths/[...pathName]/+page.svelte
Normal file
@@ -0,0 +1,206 @@
|
||||
<script lang="ts">
|
||||
import { HttpMethods } from '$lib';
|
||||
import ParameterInput from '$lib/components/atoms/ParameterInput.svelte';
|
||||
import { getPathVariables } from '$lib/pathHandling';
|
||||
import type { OpenAPIV3_1 } from '$lib/openAPITypes';
|
||||
import { Accordion, AccordionItem, filter } from '@skeletonlabs/skeleton';
|
||||
import { selectedSpec } from '$lib/db';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
$: console.log($page.params.pathName);
|
||||
|
||||
const filterParams = (
|
||||
param: OpenAPIV3_1.ParameterObject | OpenAPIV3_1.ReferenceObject
|
||||
): param is OpenAPIV3_1.ParameterObject => {
|
||||
return !('$ref' in param);
|
||||
};
|
||||
|
||||
let newParam: 'query' | 'header' | 'cookie' = 'query';
|
||||
let tempPath: OpenAPIV3_1.PathItemObject = {
|
||||
parameters: []
|
||||
};
|
||||
|
||||
const pathVariables = getPathVariables($page.params.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
|
||||
});
|
||||
});
|
||||
|
||||
const newMethod = () =>
|
||||
structuredClone({
|
||||
tags: [],
|
||||
summary: '',
|
||||
description: '',
|
||||
externalDocs: {
|
||||
description: '',
|
||||
url: ''
|
||||
},
|
||||
operationId: '',
|
||||
parameters: [],
|
||||
requestBody: {
|
||||
content: {},
|
||||
description: '',
|
||||
required: false
|
||||
},
|
||||
responses: {},
|
||||
callbacks: {},
|
||||
deprecated: false,
|
||||
security: [],
|
||||
servers: []
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="border-token border-surface-500 space-y-4 px-6 py-4 rounded-container-token variant-glass-surface"
|
||||
>
|
||||
<h3 class="h3 font-mono">
|
||||
Path: {$page.params.pathName}
|
||||
</h3>
|
||||
<hr />
|
||||
|
||||
{#if $selectedSpec.spec.paths != undefined && $page.params.pathName != undefined && $selectedSpec.spec.paths[$page.params.pathName] != undefined}
|
||||
<Accordion>
|
||||
<AccordionItem open>
|
||||
<svelte:fragment slot="summary">
|
||||
<h4 class="h4">General</h4>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
<label class="space-y-2">
|
||||
<p>Summary</p>
|
||||
{#if $selectedSpec.spec.paths[$page.params.pathName]?.summary != undefined}
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
bind:value={$selectedSpec.spec.paths[$page.params.pathName].summary}
|
||||
placeholder="Summary of the path"
|
||||
/>
|
||||
{:else}
|
||||
<button
|
||||
class="btn variant-ghost-tertiary"
|
||||
on:click={() => {
|
||||
// @ts-expect-error - already inside a nullcheck
|
||||
$selectedSpec.spec.paths[$page.params.pathName].summary = '';
|
||||
}}
|
||||
>
|
||||
Add Summary
|
||||
</button>
|
||||
{/if}
|
||||
</label>
|
||||
<label class="space-y-2">
|
||||
<p>Description</p>
|
||||
{#if $selectedSpec.spec.paths[$page.params.pathName]?.description != undefined}
|
||||
<textarea
|
||||
class="textarea"
|
||||
bind:value={$selectedSpec.spec.paths[$page.params.pathName].description}
|
||||
placeholder="Description of the path. Supports markdown."
|
||||
/>
|
||||
{:else}
|
||||
<button
|
||||
class="btn variant-ghost-tertiary"
|
||||
on:click={() => {
|
||||
// @ts-expect-error - already inside a nullcheck
|
||||
$selectedSpec.spec.paths[$page.params.pathName].description = '';
|
||||
}}
|
||||
>
|
||||
Add Description
|
||||
</button>
|
||||
{/if}
|
||||
</label>
|
||||
</svelte:fragment>
|
||||
</AccordionItem>
|
||||
<AccordionItem open>
|
||||
<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>
|
||||
{#if tempPath.servers && tempPath.servers.length > 0}
|
||||
{#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}
|
||||
{/if}
|
||||
|
||||
<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">
|
||||
{#if tempPath.parameters}
|
||||
{#each tempPath.parameters.filter(filterParams) as param}
|
||||
<ParameterInput variableName={param.name} bind:value={param} location="path" />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<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>
|
||||
</svelte:fragment>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<h4 class="h4">Operations</h4>
|
||||
|
||||
<p>Here you can add operations for this path. Select only the operations you want to support</p>
|
||||
|
||||
{#if $selectedSpec.spec.paths[$page.params.pathName]}
|
||||
<div class="flex flex-col gap-2">
|
||||
{#each Object.values(HttpMethods) as method}
|
||||
<div class="flex flex-row gap-2 justify-between max-w-xs">
|
||||
<label class="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
checked={$selectedSpec.spec.paths[$page.params.pathName]?.hasOwnProperty(method)}
|
||||
on:change={(event) => {
|
||||
// @ts-expect-error - this is a valid access
|
||||
switch (event.target.checked) {
|
||||
case true:
|
||||
// @ts-expect-error - already inside a nullcheck
|
||||
$selectedSpec.spec.paths[$page.params.pathName][method] = newMethod();
|
||||
break;
|
||||
|
||||
case false:
|
||||
if (confirm('Are you sure you want to remove this operation?')) {
|
||||
// @ts-expect-error - already inside a nullcheck
|
||||
$selectedSpec.spec.paths[$page.params.pathName][method] = undefined;
|
||||
} else {
|
||||
event.target.checked = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{method.toUpperCase()}
|
||||
</label>
|
||||
<a href={`/paths/${$page.params.pathName}/z/${method}`} class="btn btn-sm variant-ghost-success">
|
||||
Edit
|
||||
</a>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
14
src/routes/paths/[...pathName]/z/[method]/+page.svelte
Normal file
14
src/routes/paths/[...pathName]/z/[method]/+page.svelte
Normal file
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
|
||||
$: console.log($page.params.method);
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="border-token border-surface-500 space-y-4 px-6 py-4 rounded-container-token variant-glass-surface"
|
||||
>
|
||||
<h3 class="h3 font-mono">
|
||||
Path: {$page.params.pathName}#{$page.params.method}
|
||||
</h3>
|
||||
<hr />
|
||||
</div>
|
||||
@@ -1,17 +0,0 @@
|
||||
<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,171 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { HttpMethods } from '$lib';
|
||||
import ParameterInput from '$lib/components/atoms/ParameterInput.svelte';
|
||||
import { getPathVariables } from '$lib/pathHandling';
|
||||
import type { OpenAPIV3_1 } from '$lib/openAPITypes';
|
||||
import type { PageData } from './$types';
|
||||
import { Accordion, AccordionItem, filter } from '@skeletonlabs/skeleton';
|
||||
import { selectedSpec } from '$lib/db';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
const filterParams = (param: OpenAPIV3_1.ParameterObject | OpenAPIV3_1.ReferenceObject): param is OpenAPIV3_1.ParameterObject => {
|
||||
return !("$ref" in param);
|
||||
};
|
||||
|
||||
let newParam: 'query' | 'header' | 'cookie' = 'query';
|
||||
let tempPath: OpenAPIV3_1.PathItemObject = {
|
||||
parameters: []
|
||||
};
|
||||
selectedSpec.subscribe((store) => {
|
||||
if (!data.pathName) return;
|
||||
if (store.spec.paths == undefined) tempPath = {};
|
||||
if (!store.spec.paths!.hasOwnProperty(data.pathName)) tempPath = {};
|
||||
// @ts-expect-error - working with a known not empty object
|
||||
tempPath = store.paths[data.pathName] ?? {};
|
||||
|
||||
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 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>
|
||||
{#if tempPath.servers && tempPath.servers.length > 0}
|
||||
{#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}
|
||||
{/if}
|
||||
|
||||
<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">
|
||||
{#if tempPath.parameters}
|
||||
{#each tempPath.parameters.filter(filterParams) as param}
|
||||
<ParameterInput variableName={param.name} bind:value={param} location="path" />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<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>
|
||||
</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) => {
|
||||
//@ts-expect-error - working with a known object
|
||||
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,30 +0,0 @@
|
||||
import type { PageLoad } from './$types';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { OpenAPIV3_1 } from '$lib/openAPITypes';
|
||||
import { selectedSpec } from '$lib/db';
|
||||
|
||||
export const load = (async (event) => {
|
||||
// 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;
|
||||
selectedSpec.subscribe((value) => (apiObject = value.spec));
|
||||
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 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 {
|
||||
path,
|
||||
pathName
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB |
Reference in New Issue
Block a user