Skip to content

Commit

Permalink
feat: add share functionality to website
Browse files Browse the repository at this point in the history
  • Loading branch information
Shurtu-gal committed Sep 16, 2024
1 parent 1b02833 commit 156c5e9
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 15 deletions.
45 changes: 45 additions & 0 deletions apps/studio-next/src/components/Editor/EditorDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ImportBase64Modal,
GeneratorModal,
ConvertModal,
ImportUUIDModal,
} from '../Modals';
import { Dropdown } from '../common';

Expand All @@ -34,6 +35,17 @@ export const EditorDropdown: React.FunctionComponent<EditorDropdownProps> = () =
</button>
);

const importShareIdButton = (
<button
type="button"
className="px-4 py-1 w-full text-left text-sm rounded-md focus:outline-none transition ease-in-out duration-150"
title="Import from UUID"
onClick={() => show(ImportUUIDModal)}
>
Import from UUID
</button>
);

const importFileButton = (
<label
className="block px-4 py-1 w-full text-left text-sm rounded-md focus:outline-none transition ease-in-out duration-150 cursor-pointer"
Expand Down Expand Up @@ -208,6 +220,31 @@ export const EditorDropdown: React.FunctionComponent<EditorDropdownProps> = () =
</button>
);

const shareButtonBase64 = (
<button
type="button"
className="px-4 py-1 w-full text-left text-sm rounded-md focus:outline-none transition ease-in-out duration-150 disabled:cursor-not-allowed"
title='Share as Base64'
onClick={() => {
toast.promise(
(async function () {
const base64 = await editorSvc.exportAsBase64();
const url = `${window.location.origin}/?base64=${encodeURIComponent(
base64
)}`;
await navigator.clipboard.writeText(url);
}()),
{
loading: 'Copying URL to clipboard...',
success: 'URL copied to clipboard!',
error: 'Failed to copy URL to clipboard.',
}
);
}}>
Share as Base64
</button>
);

return (
<Dropdown
opener={<FaEllipsisH />}
Expand All @@ -224,12 +261,20 @@ export const EditorDropdown: React.FunctionComponent<EditorDropdownProps> = () =
<li className="hover:bg-gray-900">
{importBase64Button}
</li>
<li className="hover:bg-gray-900">
{importShareIdButton}
</li>
</div>
<div className="border-b border-gray-700">
<li className="hover:bg-gray-900">
{generateButton}
</li>
</div>
<div className="border-b border-gray-700">
<li className="hover:bg-gray-900">
{shareButtonBase64}
</li>
</div>
<div className="border-b border-gray-700">
<li className="hover:bg-gray-900">
{saveFileButton}
Expand Down
7 changes: 2 additions & 5 deletions apps/studio-next/src/components/Editor/ShareButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,13 @@ export const ShareButton: React.FunctionComponent<ShareButtonProps> = () => {
const handleShare = () => {
toast.promise(
(async function () {
const base64 = await editorSvc.exportAsBase64();
const url = `${window.location.origin}/?base64=${encodeURIComponent(
base64
)}`;
const url = await editorSvc.exportAsURL();
await navigator.clipboard.writeText(url);
}()),
{
loading: 'Copying URL to clipboard...',
success: 'URL copied to clipboard!',
error: 'Failed to copy URL to clipboard.',
error: 'Failed to share document.',
}
);
};
Expand Down
57 changes: 57 additions & 0 deletions apps/studio-next/src/components/Modals/ImportUUIDModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useState } from 'react';
import toast from 'react-hot-toast';
import { create } from '@ebay/nice-modal-react';

import { ConfirmModal } from './index';

import { useServices } from '../../services';

export const ImportUUIDModal = create(() => {
const [shareID, setShareID] = useState('');
const { editorSvc } = useServices();

const onSubmit = () => {
toast.promise(editorSvc.importFromShareID(shareID), {
loading: 'Importing...',
success: (
<div>
<span className="block text-bold">
Document succesfully imported!
</span>
</div>
),
error: (
<div>
<span className="block text-bold text-red-400">
Failed to import document.
</span>
</div>
),
});
};

return (
<ConfirmModal
title="Import AsyncAPI document from Shared UUID"
confirmText="Import"
confirmDisabled={!shareID}
onSubmit={onSubmit}
>
<div className="flex content-center justify-center">
<label
htmlFor="url"
className="flex justify-right items-center content-center text-sm font-medium text-gray-700 hidden"
>
Shared UUID
</label>
<input
type="url"
name="url"
placeholder="Paste UUID here"
className="shadow-sm focus:ring-pink-500 focus:border-pink-500 block w-full sm:text-sm border-gray-300 rounded-md py-2 px-3 text-gray-700 border-pink-300 border-2"
onChange={e => setShareID(e.target.value)}
/>
</div>
</ConfirmModal>
);
});
1 change: 1 addition & 0 deletions apps/studio-next/src/components/Modals/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './ConvertModal';
export * from './ConvertToLatestModal';
export * from './ImportBase64Modal';
export * from './ImportURLModal';
export * from './ImportUUIDModal';
export * from './NewFileModal';
export * from './RedirectedModal';
export * from './ConfirmNewFileModal';
22 changes: 13 additions & 9 deletions apps/studio-next/src/services/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ export class ApplicationService extends AbstractService {
// subscribe to state to hide preloader
this.hidePreloader();

const { readOnly, url, base64 } =
const { readOnly, url, base64, share } =
this.svcs.navigationSvc.getUrlParameters();
// readOnly state should be only set to true when someone pass also url or base64 parameter
const isStrictReadonly = Boolean(readOnly && (url || base64));
// readOnly state should be only set to true when someone pass also url or base64 or share parameter
const isStrictReadonly = Boolean(readOnly && (url || base64 || share ));

let error: any;
try {
await this.fetchResource(url, base64);
await this.fetchResource(url, base64, share);
} catch (err) {
error = err;
console.error(err);
Expand All @@ -37,18 +37,18 @@ export class ApplicationService extends AbstractService {
}

public async afterAppInit() {
const { readOnly, url, base64, redirectedFrom } =
const { readOnly, url, base64, share, redirectedFrom } =
this.svcs.navigationSvc.getUrlParameters();
const isStrictReadonly = Boolean(readOnly && (url || base64));
const isStrictReadonly = Boolean(readOnly && (url || base64 || share));

// show RedirectedModal modal if the redirectedFrom is set (only when readOnly state is set to false)
if (!isStrictReadonly && redirectedFrom) {
show(RedirectedModal);
}
}

private async fetchResource(url: string | null, base64: string | null) {
if (!url && !base64) {
private async fetchResource(url: string | null, base64: string | null, share: string | null) {
if (!url && !base64 && !share) {
return;
}

Expand All @@ -58,6 +58,10 @@ export class ApplicationService extends AbstractService {
content = await fetch(url).then((res) => res.text());
} else if (base64) {
content = this.svcs.formatSvc.decodeBase64(base64);
} else if (share) {
const response = await fetch(`/share/${share}`);
const data = await response.json();
content = data.content;
}

const language = this.svcs.formatSvc.retrieveLangauge(content);
Expand All @@ -66,7 +70,7 @@ export class ApplicationService extends AbstractService {
content,
language,
source,
from: url ? 'url' : 'base64',
from: url ? 'url' : base64 ? 'base64' : 'storage',
});
await this.svcs.parserSvc.parse('asyncapi', content, { source });
}
Expand Down
39 changes: 39 additions & 0 deletions apps/studio-next/src/services/editor.service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,45 @@ export class EditorService extends AbstractService {
}
}

async importFromShareID(shareID: string) {
try {
const response = await fetch(`/share/${shareID}`);
if (!response.ok) {
throw new Error('Failed to fetch shared document');
}

const data = await response.json();
this.updateState({
content: data.content,
updateModel: true,
file: {
from: 'share',
source: undefined,
},
});
} catch (err) {
console.error(err);
throw err;
}
}

async exportAsURL() {
try {
const file = filesState.getState().files['asyncapi'];
const shareID = await fetch('/share', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ content: file.content }),
}).then(res => res.text());
return `${window.location.origin}/?share=${shareID}`;
} catch (err) {
console.error(err);
throw err;
}
}

async exportAsBase64() {
try {
const file = filesState.getState().files['asyncapi'];
Expand Down
1 change: 1 addition & 0 deletions apps/studio-next/src/services/navigation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class NavigationService extends AbstractService {
return {
url: urlParams.get('url') || urlParams.get('load'),
base64: urlParams.get('base64'),
share: urlParams.get('share'),
readOnly: urlParams.get('readOnly') === 'true' || urlParams.get('readOnly') === '',
liveServer: urlParams.get('liveServer'),
redirectedFrom: urlParams.get('redirectedFrom'),
Expand Down
2 changes: 1 addition & 1 deletion apps/studio-next/src/state/files.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export type File = {
uri: string;
name: string;
content: string;
from: 'storage' | 'url' | 'base64';
from: 'storage' | 'url' | 'base64' | 'share';
source?: string;
language: 'json' | 'yaml';
modified: boolean;
Expand Down

0 comments on commit 156c5e9

Please sign in to comment.