Saving progress on paths and methods

This commit is contained in:
Luke Hagar
2024-07-04 21:48:25 +00:00
parent e23eae5f80
commit 7e5aa901b1
20 changed files with 432 additions and 342 deletions

View File

@@ -10,7 +10,7 @@
'This operation clears all the current values, unsaved data will be lost, are you sure?' 'This operation clears all the current values, unsaved data will be lost, are you sure?'
) )
) { ) {
loadSpec(structuredClone(newSpec)); loadSpec(newSpec());
} }
}} }}
> >

View File

@@ -11,7 +11,7 @@
on:click={async () => { on:click={async () => {
if (confirm(`Are you sure you want to delete all saved specs?`)) { if (confirm(`Are you sure you want to delete all saved specs?`)) {
db.apiSpecs.clear(); db.apiSpecs.clear();
loadSpec(structuredClone(newSpec)); loadSpec(newSpec());
} }
}} }}
> >

View File

@@ -17,7 +17,7 @@
if (specs.length > 0){ if (specs.length > 0){
loadSpec(specs[0]); loadSpec(specs[0]);
} else { } else {
loadSpec(structuredClone(newSpec)); loadSpec(newSpec());
} }
} }
}} }}

View File

@@ -3,6 +3,9 @@
import filenamify from 'filenamify'; import filenamify from 'filenamify';
import { stringify } from 'yaml'; 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'; $: fileName = filenamify($selectedSpec.spec?.info?.title) || 'openapi';
const saveYAML = () => { const saveYAML = () => {
@@ -36,12 +39,10 @@
}; };
</script> </script>
<div class="grid grid-cols-1 text-xs gap-2">
<span class="text-center font-bold text-sm">Download</span>
<button <button
type="button" type="button"
on:click={saveYAML} 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." aria-label="Download a YAML representation of the OpenAPI document."
> >
YAML YAML
@@ -63,7 +64,7 @@
<button <button
type="button" type="button"
on:click={saveJSON} 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." aria-label="Download a JSON representation of the OpenAPI document."
> >
JSON JSON
@@ -82,4 +83,3 @@
/> />
</svg> </svg>
</button> </button>
</div>

View File

@@ -3,6 +3,7 @@
import type { CssClasses } from '@skeletonlabs/skeleton'; import type { CssClasses } from '@skeletonlabs/skeleton';
export let width: CssClasses = "w-full" export let width: CssClasses = "w-full"
export let padding: CssClasses = "px-3 py-1"
async function onSave(e: Event): Promise<void> { async function onSave(e: Event): Promise<void> {
console.log('Save button clicked'); console.log('Save button clicked');
@@ -12,4 +13,4 @@
</script> </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>

View File

@@ -1,36 +1,75 @@
<script lang="ts"> <script lang="ts">
import { selectedSpec } from '$lib/db'; import { selectedSpec } from '$lib/db';
import spdxLicenseList from 'spdx-license-list'; 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']; 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> </script>
<div class="border-token rounded-container-token space-y-1 p-4"> <div class="border-token grow rounded-container-token bg-surface-backdrop-token space-y-1 p-4">
<div class="flex flex-row justify-between"> <div class="flex flex-row flex-wrap justify-between">
<h4 class="h4">License</h4> <h4 class="h4">License</h4>
{#if $selectedSpec.spec.info.license} {#if $selectedSpec.spec.info.license}
<label class="text-sm space-x-2"> <label class="text-sm ">
<span>Pick a license</span> <span>Pick a license</span>
<select <select
class="select w-56 text-sm" class="select md:w-56 text-sm"
bind:value={$selectedSpec.spec.info.license.identifier} bind:value={selectedLicense}
on:change={() => { on:change={() => {
// @ts-expect-error - This is literally inside a null check $selectedSpec.spec.info.license = {
$selectedSpec.spec.info.license.url = null name: selectedLicense.name,
// @ts-expect-error - This is only running on identifier change url: selectedLicense.url
$selectedSpec.spec.info.license.name = };
// @ts-expect-error - This is only running on identifier change
spdxLicenseList[$selectedSpec.spec.info.license.identifier].name;
}} }}
> >
<optgroup label="Popular Licenses"> <optgroup label="Popular Licenses">
{#each Object.keys(spdxLicenseList).filter( (entry) => popularLicenses.includes(entry) ) as license} {#each modifiedLicenseList.filter( (entry) => popularLicenses.includes(entry.identifier) ) as license}
<option value={license}>{spdxLicenseList[license].name}</option> <option value={license}>{license.name}</option>
{/each} {/each}
</optgroup> </optgroup>
<optgroup label="All Licenses"> <optgroup label="All Licenses">
{#each Object.keys(spdxLicenseList).sort() as license} {#each modifiedLicenseList
<option value={license}>{spdxLicenseList[license].name}</option> .filter((entry) => !popularLicenses.includes(entry.identifier))
.sort() as license}
<option value={license}>{license.name}</option>
{/each} {/each}
</optgroup> </optgroup>
</select> </select>
@@ -75,9 +114,8 @@
class="btn variant-filled-primary" class="btn variant-filled-primary"
on:click={() => { on:click={() => {
$selectedSpec.spec.info.license = { $selectedSpec.spec.info.license = {
name: '', name: spdxLicenseList['MIT'].name,
identifier: '', url: spdxLicenseList['MIT'].url
url: ''
}; };
}} }}
> >

View File

@@ -1,9 +1,5 @@
<script lang="ts"> <script lang="ts">
import { import { addPath, deletePath, renamePath } from '$lib';
addPath,
deletePath,
renamePath
} from '$lib';
import { getModalStore } from '@skeletonlabs/skeleton'; import { getModalStore } from '@skeletonlabs/skeleton';
const modalStore = getModalStore(); const modalStore = getModalStore();
@@ -13,13 +9,14 @@
export let id: number; export let id: number;
</script> </script>
<div class="flex justify-between"> <div class="flex flex-wrap justify-between gap-1">
<h3 class="">{pathName}</h3> <h3 class="truncate font-mono"><span class="md:hidden">Path: </span>{pathName}</h3>
<div class="flex gap-2"> <div class="flex md:gap-2 flex-wrap gap-1">
<a href="/paths/{id}" class="btn btn-sm variant-filled-primary">Edit</a> <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 <button
type="button" type="button"
class="btn btn-sm variant-filled-warning" class="btn btn-sm variant-filled-warning grow"
on:click={() => { on:click={() => {
renamePath(modalStore, pathName); renamePath(modalStore, pathName);
}} }}
@@ -28,16 +25,17 @@
</button> </button>
<button <button
type="button" type="button"
class="btn btn-sm variant-filled-secondary" class="btn btn-sm variant-filled-secondary grow"
on:click={() => { on:click={() => {
addPath(modalStore, pathName); addPath(modalStore, pathName);
}} }}
> >
Add Sub-Route Add Sub-Route
</button> </button>
</div>
<button <button
type="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={() => { on:click={() => {
deletePath(modalStore, pathName); deletePath(modalStore, pathName);
}} }}

View File

@@ -68,15 +68,15 @@
<hr /> <hr />
{/each} {/each}
<span class="flex justify-center items-center gap-2 max-w-sm mx-auto"> <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"> <select name="security-schema" bind:value={selectedSchema} class="input w-fit text-sm grow">
<option value="basicAuth" selected>Basic Auth</option> <option value="basicAuth" selected>Basic Auth</option>
<option value="bearerAuth">Bearer Auth</option> <option value="bearerAuth">Bearer Auth</option>
<option value="ApiKeyAuth">API Key Auth</option> <option value="ApiKeyAuth">API Key Auth</option>
<option value="openId">OpenID</option> <option value="openId">OpenID</option>
<option value="oAuthSample">OAuth2</option> <option value="oAuthSample">OAuth2</option>
</select> </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 Add Security Schema
</button> </button>
</span> </span>

View File

@@ -48,7 +48,10 @@
/> />
</label> </label>
</div> </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> <h4 class="h4">Contact Information</h4>
{#if $selectedSpec.spec.info.contact} {#if $selectedSpec.spec.info.contact}
<label class="space-y-1"> <label class="space-y-1">
@@ -99,4 +102,5 @@
</div> </div>
<LicenseAtom /> <LicenseAtom />
</div>
</form> </form>

View File

@@ -18,12 +18,12 @@
<div class="w-full flex flex-row justify-center"> <div class="w-full flex flex-row justify-center">
<div <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"> <span class="flex justify-center">
<button <button
type="button" type="button"
class="btn gap-2 variant-filled-primary" class="btn btn-sm grow gap-2 variant-filled-primary"
on:click={() => { on:click={() => {
addPath(modalStore); addPath(modalStore);
}} }}
@@ -48,7 +48,7 @@
<span class="flex justify-center"> <span class="flex justify-center">
<button <button
type="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} on:click={sortPathsAlphabetically}
> >
<svg <svg
@@ -71,7 +71,7 @@
</div> </div>
</div> </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} {#if Object.keys(paths).length === 0}
<p class="text-center">Wow such empty.</p> <p class="text-center">Wow such empty.</p>

View File

@@ -41,23 +41,26 @@ export const blankSpec: OpenAPIV3_1.Document = {
} }
}; };
export const newSpec: APISpec = { export const newSpec: () => APISpec = () => structuredClone({
name: 'OpenAPI', name: 'OpenAPI',
spec: blankSpec spec: blankSpec
} as const })
export const selectedSpecId: Writable<string | undefined> = persisted("selectedSpecId",undefined) 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) => { selectedSpec.subscribe((spec) => {
if(!spec){ if(!spec){
spec = structuredClone(newSpec) spec = newSpec()
} }
if(spec.id){ if(spec.id){
selectedSpecId.set(spec.id) selectedSpecId.set(spec.id)
} }
}) })
export const specLoaded: Writable<boolean> = writable(false);
export const pageLoaded: Writable<boolean> = writable(false);
export interface APISpec { export interface APISpec {
id?: string; id?: string;
name: string; name: string;

View File

@@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import FancyAppRail from './FancyAppRail.svelte'; import FancyAppRail from './FancyAppRail.svelte';
import { AppShell, type ModalComponent } from '@skeletonlabs/skeleton'; import { AppBar, AppShell, type ModalComponent } from '@skeletonlabs/skeleton';
import '../app.postcss'; import '../app.postcss';
// Floating UI for Popups // Floating UI for Popups
import { arrow, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom'; import { arrow, autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom';
@@ -9,19 +9,89 @@
// Modal // Modal
import { initializeStores, Modal } from '@skeletonlabs/skeleton'; import { initializeStores, Modal } from '@skeletonlabs/skeleton';
import UploadModal from '$lib/components/FileManagement/UploadModal.svelte'; 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(); initializeStores();
const components: Record<string, ModalComponent> = { const components: Record<string, ModalComponent> = {
// Set a unique modal ID, then pass the component reference // 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> </script>
<Modal {components} /> <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"> <svelte:fragment slot="sidebarLeft">
<FancyAppRail /> <FancyAppRail />
</svelte:fragment> </svelte:fragment>

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { pathCount, operationCount } from '$lib'; import { operationCount, pathCount } from '$lib';
import CreateNewButton from '$lib/components/FileManagement/CreateNewButton.svelte'; import CreateNewButton from '$lib/components/FileManagement/CreateNewButton.svelte';
import DeleteAllButton from '$lib/components/FileManagement/DeleteAllButton.svelte'; import DeleteAllButton from '$lib/components/FileManagement/DeleteAllButton.svelte';
import DeleteButton from '$lib/components/FileManagement/DeleteButton.svelte'; import DeleteButton from '$lib/components/FileManagement/DeleteButton.svelte';
@@ -7,30 +7,11 @@
import SaveButton from '$lib/components/FileManagement/SaveButton.svelte'; import SaveButton from '$lib/components/FileManagement/SaveButton.svelte';
import SaveNewButton from '$lib/components/FileManagement/SaveNewButton.svelte'; import SaveNewButton from '$lib/components/FileManagement/SaveNewButton.svelte';
import UploadButton from '$lib/components/FileManagement/UploadButton.svelte'; import UploadButton from '$lib/components/FileManagement/UploadButton.svelte';
import { db, loadSpec, newSpec, selectedSpec, selectedSpecId } from '$lib/db'; import { db, pageLoaded, selectedSpec, specLoaded } from '$lib/db';
import { liveQuery } from 'dexie';
import { ProgressRadial } from '@skeletonlabs/skeleton'; import { ProgressRadial } from '@skeletonlabs/skeleton';
import { onMount } from 'svelte'; import { liveQuery } from 'dexie';
let apiSpecs = liveQuery(() => db.apiSpecs.toArray()); const 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;
}
});
$: stats = [ $: stats = [
{ {
@@ -42,15 +23,6 @@
value: operationCount($selectedSpec.spec) 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> </script>
<div class="grid place-content-center h-full gap-2 px-1"> <div class="grid place-content-center h-full gap-2 px-1">

View File

@@ -5,7 +5,7 @@
</script> </script>
<AppRail <AppRail
width="w-28" width="md:w-28 w-20"
aspectRatio="aspect-[20/14]" aspectRatio="aspect-[20/14]"
background="variant-ghost-surface" background="variant-ghost-surface"
border="ring-0" border="ring-0"
@@ -13,15 +13,21 @@
<svelte:fragment slot="lead"> <svelte:fragment slot="lead">
<div> <div>
<AppRailAnchor href="/"> <AppRailAnchor href="/">
<div class="flex flex-col gap-2 py-4"> <svg
<p class="font-bold">OpenAPI</p> xmlns="http://www.w3.org/2000/svg"
<p>Generator</p> fill="none"
<code viewBox="0 0 24 24"
class="mx-auto w-min text-xs variant-filled-success p-1 px-2 rounded-container-token" stroke-width="1.5"
stroke="currentColor"
class="size-6 mx-auto"
> >
3.1.0 <path
</code> stroke-linecap="round"
</div> 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> </AppRailAnchor>
</div> </div>
<hr /> <hr />
@@ -129,11 +135,7 @@
Components Components
</AppRailAnchor> </AppRailAnchor>
<svelte:fragment slot="trail"> <svelte:fragment slot="trail">
<div class="p-2">
<DownloadButtons />
</div>
<div class="flex justify-center my-4"> <div class="flex justify-center my-4">
<LightSwitch /> <LightSwitch />
</div> </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" 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> </svg>
API Reference <span class=" whitespace-break-spaces px-1">API Reference</span>
</AppRailAnchor> </AppRailAnchor>
</svelte:fragment> </svelte:fragment>
</AppRail> </AppRail>

View 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>

View 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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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