Skip to content

Commit

Permalink
feat: add contract file sharing with Base64-encoded URL
Browse files Browse the repository at this point in the history
  • Loading branch information
rahulyadav-57 committed Sep 24, 2024
1 parent 144af9d commit 7bc4aaa
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 56 deletions.
16 changes: 8 additions & 8 deletions src/components/project/MigrateToUnifiedFS/MigrateToUnifiedFS.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ const MigrateToUnifiedFS: FC<Props> = ({ hasDescription = false }) => {
const project = projects[i];
const isLastProject = i === projects.length - 1;

await createProject(
project.projectDetails.name as string,
project.projectDetails.language as ContractLanguage,
'import',
null,
project.files as Tree[],
isLastProject,
);
await createProject({
name: project.projectDetails.name as string,
language: project.projectDetails.language as ContractLanguage,
template: 'import',
file: null,
defaultFiles: project.files as Tree[],
autoActivate: isLastProject,
});

migratedProjects.push(project.projectDetails.name);
}
Expand Down
66 changes: 58 additions & 8 deletions src/components/project/NewProject/NewProject.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Tooltip } from '@/components/ui';
import AppIcon, { AppIconType } from '@/components/ui/icon';
import { useLogActivity } from '@/hooks/logActivity.hooks';
import { useProject } from '@/hooks/projectV2.hooks';
import {
ContractLanguage,
Expand All @@ -8,6 +9,7 @@ import {
} from '@/interfaces/workspace.interface';
import { Analytics } from '@/utility/analytics';
import EventEmitter from '@/utility/eventEmitter';
import { decodeBase64 } from '@/utility/utils';
import { Button, Form, Input, Modal, Radio, Upload, message } from 'antd';
import { useForm } from 'antd/lib/form/Form';
import type { RcFile } from 'antd/lib/upload';
Expand Down Expand Up @@ -43,9 +45,20 @@ const NewProject: FC<Props> = ({
const [isActive, setIsActive] = useState(active);
const { createProject } = useProject();
const [isLoading, setIsLoading] = useState(false);
const { createLog } = useLogActivity();

const router = useRouter();
const { importURL, name: projectName, lang: importLanguage } = router.query;
const {
importURL,
name: projectName,
lang: importLanguage,
code: codeToImport,
} = router.query as {
importURL?: string;
name?: string;
lang?: ContractLanguage;
code?: string;
};

const [form] = useForm();

Expand Down Expand Up @@ -83,13 +96,13 @@ const NewProject: FC<Props> = ({
// files = await downloadRepo(githubUrl as string);
}

await createProject(
projectName,
await createProject({
name: projectName,
language,
values.template ?? 'import',
values.file?.file ?? null,
files,
);
template: values.template ?? 'import',
file: values.file?.file ?? null,
defaultFiles: files,
});

form.resetFields();
closeModal();
Expand All @@ -115,7 +128,44 @@ const NewProject: FC<Props> = ({
}
};

const importFromCode = async (code: string) => {
try {
const fileName = `main.${importLanguage}`;
if (!importLanguage || !['tact', 'func'].includes(importLanguage)) {
createLog(`Invalid language: ${importLanguage}`, 'error');
return;
}
await createProject({
name: 'temp',
language: importLanguage,
template: 'import',
file: null,
defaultFiles: [
{
id: '',
parent: null,
path: fileName,
type: 'file' as const,
name: fileName,
content: decodeBase64(code),
},
],
isTemporary: true,
});
} catch (error) {
if (error instanceof Error) {
createLog(error.message, 'error');
return;
}
}
};

useEffect(() => {
if (codeToImport) {
importFromCode(codeToImport as string);
return;
}

if (!importURL || !active) {
return;
}
Expand All @@ -132,7 +182,7 @@ const NewProject: FC<Props> = ({
delete finalQueryParam.name;
router.replace({ query: finalQueryParam }).catch(() => {});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [importURL, projectName, form]);
}, [importURL, projectName, form, codeToImport]);

const closeModal = () => {
setIsActive(false);
Expand Down
6 changes: 5 additions & 1 deletion src/components/ui/icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
AiOutlineProject,
AiOutlineReload,
} from 'react-icons/ai';
import { BsShare } from 'react-icons/bs';

import { BsFillPlayFill } from 'react-icons/bs';
import { FaRegClone } from 'react-icons/fa';
import { FiEdit2, FiEye } from 'react-icons/fi';
Expand Down Expand Up @@ -64,7 +66,8 @@ export type AppIconType =
| 'Clear'
| 'Download'
| 'Import'
| 'Reload';
| 'Reload'
| 'Share';

export interface AppIconInterface {
name: AppIconType;
Expand Down Expand Up @@ -103,6 +106,7 @@ const Components = {
Download: AiOutlineDownload,
Import,
Reload: AiOutlineReload,
Share: BsShare,
};

const AppIcon: FC<AppIconInterface> = ({ name, className = '' }) => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/workspace/BuildProject/BuildProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ const BuildProject: FC<Props> = ({ projectId, contract, updateContract }) => {

Analytics.track('Deploy project', {
platform: 'IDE',
type: 'TON-func',
type: `TON-${activeProject?.language}`,
environment: environment.toLowerCase(),
});
createLog(
Expand Down
20 changes: 19 additions & 1 deletion src/components/workspace/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AppIcon from '@/components/ui/icon';
import { useFileTab } from '@/hooks';
import { useProject } from '@/hooks/projectV2.hooks';
import fileSystem from '@/lib/fs';
import EventEmitter from '@/utility/eventEmitter';
import { fileTypeFromFileName } from '@/utility/utils';
import { FC, useEffect } from 'react';
Expand All @@ -9,7 +10,7 @@ import s from './Tabs.module.scss';
const Tabs: FC = () => {
const { fileTab, open, close, syncTabSettings, updateFileDirty } =
useFileTab();
const { activeProject } = useProject();
const { activeProject, setActiveProject } = useProject();

const closeTab = (e: React.MouseEvent, filePath: string) => {
e.preventDefault();
Expand All @@ -25,6 +26,23 @@ const Tabs: FC = () => {
syncTabSettings();
}, [activeProject]);

useEffect(() => {
(async () => {
// If the active project is a temp project, the file tab is active and file does not exist
if (activeProject?.path?.includes('temp')) {
setActiveProject('non-existing-dir');
try {
if (!fileTab.active) {
return;
}
await fileSystem.exists(fileTab.active);
} catch (error) {
syncTabSettings({ items: [], active: null });
}
}
})();
}, []);

useEffect(() => {
EventEmitter.on('FILE_SAVED', onFileSave);
return () => {
Expand Down
7 changes: 7 additions & 0 deletions src/components/workspace/tree/FileTree/ItemActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface Props {
onNewFile?: () => void;
onNewDirectory?: () => void;
onDelete?: () => void;
onShare?: () => void;
}

const ItemAction: FC<Props> = ({
Expand All @@ -23,6 +24,7 @@ const ItemAction: FC<Props> = ({
onNewFile,
onNewDirectory,
onDelete,
onShare,
}) => {
const rootClassName = cn(s.actionRoot, className, 'actions');
const handleOnClick = (
Expand Down Expand Up @@ -51,6 +53,11 @@ const ItemAction: FC<Props> = ({
label: 'New Folder',
action: onNewDirectory,
},
{
title: 'Share',
label: 'Share',
action: onShare,
},
{
title: 'Close',
label: 'Delete',
Expand Down
42 changes: 39 additions & 3 deletions src/components/workspace/tree/FileTree/TreeNode.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useFileTab } from '@/hooks';
import { useFile, useFileTab } from '@/hooks';
import { useLogActivity } from '@/hooks/logActivity.hooks';
import { useProject } from '@/hooks/projectV2.hooks';
import { Project, Tree } from '@/interfaces/workspace.interface';
import { fileTypeFromFileName } from '@/utility/utils';
import { encodeBase64, fileTypeFromFileName } from '@/utility/utils';
import { NodeModel } from '@minoru/react-dnd-treeview';
import { message } from 'antd';
import cn from 'clsx';
import { FC, useState } from 'react';
import s from './FileTree.module.scss';
Expand Down Expand Up @@ -32,6 +33,7 @@ const TreeNode: FC<Props> = ({ node, depth, isOpen, onToggle }) => {
const { deleteProjectFile, renameProjectFile, newFileFolder } = useProject();
const { open: openTab } = useFileTab();
const { createLog } = useLogActivity();
const { getFile } = useFile();

const disallowedFile = [
'message.cell.ts',
Expand Down Expand Up @@ -100,7 +102,12 @@ const TreeNode: FC<Props> = ({ node, depth, isOpen, onToggle }) => {
if (node.droppable) {
return ['Edit', 'NewFile', 'NewFolder', 'Close'];
}
return ['Edit', 'Close'];
const options = ['Edit', 'Close'];
const allowedLanguages = ['tact', 'func'];
if (allowedLanguages.includes(fileTypeFromFileName(node.text))) {
options.push('Share');
}
return options;
};

const deleteItemFromNode = async () => {
Expand All @@ -113,6 +120,32 @@ const TreeNode: FC<Props> = ({ node, depth, isOpen, onToggle }) => {
await deleteProjectFile(nodePath);
};

const onShare = async () => {
try {
const fileContent =
((await getFile(node.data?.path as string)) as string) || '';
const maxAllowedCharacters = 32779; // Maximum allowed characters in a Chrome. Firefox has more limit but we are using less for compatibility
if (!fileContent) {
message.error('File is empty');
return;
}
if (fileContent && fileContent.length > maxAllowedCharacters) {
message.error(
`File is too large to share. Maximum allowed characters is ${maxAllowedCharacters}`,
);
return;
}
const language = fileTypeFromFileName(node.text);
const shareableLink = `${window.location.origin}/?code=${encodeBase64(fileContent)}&lang=${language}`;

navigator.clipboard.writeText(shareableLink);

message.success("File's shareable link copied to clipboard");
} catch (error) {
message.error((error as Error).message);
}
};

const isAllowed = () => {
const isEditingItem = document.body.classList.contains(
'editing-file-folder',
Expand Down Expand Up @@ -168,6 +201,9 @@ const TreeNode: FC<Props> = ({ node, depth, isOpen, onToggle }) => {
onDelete={() => {
deleteItemFromNode().catch(() => {});
}}
onShare={() => {
onShare();
}}
/>
</div>
)}
Expand Down
1 change: 1 addition & 0 deletions src/enum/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export enum FileExtensionToFileType {
jsx = FileType.JavaScriptReact,
rs = FileType.Rust,
fc = FileType.FC,
func = FileType.FC,
tact = FileType.TACT,
json = FileType.JSON,
}
Expand Down
Loading

0 comments on commit 7bc4aaa

Please sign in to comment.