Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,873 changes: 2,340 additions & 1,533 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
"@ton-community/contract-verifier-sdk": "1.4.0",
"@ton-community/func-js": "^0.3.0",
"@tonconnect/ui-react": "^1.0.0-beta.6",
"bigint-buffer": "^1.1.5",
"buffer": "^6.0.3",
"file-saver": "^2.0.5",
"func-js-bin-0.2.0": "npm:@ton-community/func-js-bin@^0.2.0",
Expand Down Expand Up @@ -59,9 +58,12 @@
"@types/node": "^18.11.4",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^2.1.0",
"@vitejs/plugin-react": "^4.6.0",
"husky": "^8.0.0",
"typescript": "^4.6.4",
"vite": "^3.1.0"
"vite": "^6.3.5"
},
"overrides": {
"axios": "^1.10.0"
}
}
23 changes: 12 additions & 11 deletions src/components/FileTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ function Cells({
isDragging: boolean;
isHover: boolean;
}) {
const fileName = file.fileObj.name;
const fileId = file.fileId;
const { attributes, listeners } = useSortable({
id: fileName,
id: fileId,
});
const theme = useTheme();
const headerSpacings = useMediaQuery(theme.breakpoints.down("lg"));
Expand Down Expand Up @@ -74,16 +74,17 @@ function Cells({
disabled={canPublish}
value={file.folder}
onBlur={(e) => {
setDirectory(fileName, trimDirectory(e.target.value));
setDirectory(fileId, trimDirectory(e.target.value));
}}
onChange={(e) => {
setDirectory(fileName, e.target.value);
setDirectory(fileId, e.target.value);
}}></DirectoryBox>
</BorderLessCell>
<BorderLessCell sx={{ paddingLeft: headerSpacings ? 2 : 0 }}>
<CenteringBox
sx={{ flexDirection: "column", justifyContent: "center", alignItems: "flex-start" }}>
<Typography sx={{ fontSize: 14 }}>{file.fileObj.name}</Typography>
<Typography sx={{ fontSize: 11, color: "#9E9E9E" }}>{file.relativePath}</Typography>
<Typography sx={{ fontSize: 12, color: "#C1C1C1" }}>{file.fileObj.size} bytes</Typography>
</CenteringBox>
</BorderLessCell>
Expand All @@ -92,7 +93,7 @@ function Cells({
disabled={canPublish}
checked={file.includeInCommand}
onChange={(e) => {
setInclueInCommand(fileName, e.target.checked);
setInclueInCommand(fileId, e.target.checked);
}}
/>
</BorderLessCell>
Expand All @@ -105,7 +106,7 @@ function Cells({
marginRight: 1,
}}
onClick={() => {
removeFile(fileName);
removeFile(fileId);
}}>
<img src={deleteIcon} alt="Delete icon" width={18} height={18} />
</IconButton>
Expand All @@ -115,14 +116,14 @@ function Cells({
}

function SortableRow({ file, pos }: { file: FileToUpload; pos: number }) {
const fileName = file.fileObj.name;
const fileId = file.fileId;
const { hoverRef, isHover } = useHover();
const { data } = useSubmitSources();

const canPublish = !!data?.result?.msgCell;

const { setNodeRef, transform, transition, isDragging } = useSortable({
id: fileName,
id: fileId,
});

const style = {
Expand All @@ -147,7 +148,7 @@ function SortableRow({ file, pos }: { file: FileToUpload; pos: number }) {
background: "#FAFAFA",
},
}}
key={fileName}
key={fileId}
ref={(r) => {
setNodeRef(r);
hoverRef.current = r;
Expand Down Expand Up @@ -244,10 +245,10 @@ export function FileTable() {
<TableBody>
<SortableContext
disabled={canPublish}
items={files.map((file) => file.fileObj.name)}
items={files.map((file) => file.fileId)}
strategy={verticalListSortingStrategy}>
{files.map((file, i) => {
return <SortableRow file={file} pos={i + 1} key={file.fileObj.name} />;
return <SortableRow file={file} pos={i + 1} key={file.fileId} />;
})}
</SortableContext>
</TableBody>
Expand Down
54 changes: 43 additions & 11 deletions src/components/FileUploaderArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,31 @@ export function FileUploaderArea() {
</IconBox>
<TitleText>Add sources</TitleText>
</CenteringBox>
{hasFiles() && step !== STEPS.PUBLISH && (
<div {...getRootProps()}>
{step !== STEPS.PUBLISH && (
<CenteringBox sx={{ gap: 1 }}>
<AppButton
fontSize={12}
fontWeight={700}
hoverBackground="#e3e3e3"
background="#F8F8F8"
height={44}
width={159}>
width={159}
onClick={() => document.getElementById("fileUpload")?.click()}>
<img src={upload} alt="Sources icon" width={19} height={19} />
Upload source
Upload directory
</AppButton>
</div>
<AppButton
fontSize={12}
fontWeight={700}
hoverBackground="#e3e3e3"
background="#F8F8F8"
height={44}
width={149}
onClick={() => document.getElementById("individualFileUpload")?.click()}>
<img src={upload} alt="Sources icon" width={19} height={19} />
Upload files
</AppButton>
</CenteringBox>
)}
</CenteringBox>
</TitleBox>
Expand All @@ -88,14 +100,19 @@ export function FileUploaderArea() {
}}>
{!hasFiles() && (
<FilesDropzone {...getRootProps()}>
Drop sources ({acceptedFileExtensions.map((ext) => `.${ext}`).join(", ")}) here
<input {...getInputProps()} />
Drop directories or files ({acceptedFileExtensions.map((ext) => `.${ext}`).join(", ")}
) here
</FilesDropzone>
)}
</Box>

<input
{...getInputProps()}
// onChange={onUploadFiles}
onChange={(e) => {
if (e.target.files) {
addFiles(Array.from(e.target.files));
}
}}
onClick={(e) => {
// @ts-ignore
e.target.value = "";
Expand All @@ -104,9 +121,24 @@ export function FileUploaderArea() {
id="fileUpload"
type="file"
multiple
accept={acceptedFileExtensions.join(",")}
// ref={inputRef}
// @ts-ignore
{...({ webkitdirectory: "" } as any)}
accept={acceptedFileExtensions.map((ext) => `.${ext}`).join(",")}
/>
<input
onChange={(e) => {
if (e.target.files) {
addFiles(Array.from(e.target.files));
}
}}
onClick={(e) => {
// @ts-ignore
e.target.value = "";
}}
style={{ display: "none" }}
id="individualFileUpload"
type="file"
multiple
accept={acceptedFileExtensions.map((ext) => `.${ext}`).join(",")}
/>
</Box>
</>
Expand Down
2 changes: 1 addition & 1 deletion src/components/admin/VerifierRegistry.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import InfoPiece from "../InfoPiece";
import { useLoadVerifierRegistryInfo } from "../../lib/useLoadVerifierRegistryInfo";
import { Dictionary, beginCell, toNano, DictionaryValue, Slice, Address } from "ton";
import { toBigIntBE } from "bigint-buffer";
import { toBigIntBE } from "../../lib/utils/bigint-utils";
import { useMemo } from "react";
import { Stack, Grid, CircularProgress, Alert } from "@mui/material";
import Button from "../Button";
Expand Down
74 changes: 57 additions & 17 deletions src/lib/useFileStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,32 @@ import { AnalyticsAction, sendAnalyticsEvent } from "./googleAnalytics";
export let acceptedFileExtensions = ["fc", "func", "pkg", "tolk"];
if (import.meta.env.VITE_ALLOW_FIFT) acceptedFileExtensions.push("fift");

// Utility function to sort files by directory depth (deeper directories first)
const sortFilesByDepth = (files: FileToUpload[]): FileToUpload[] => {
return files.sort((a, b) => {
// Calculate directory depth by counting path separators
const depthA = (a.relativePath.match(/\//g) || []).length;
const depthB = (b.relativePath.match(/\//g) || []).length;

// Primary sort: deeper directories first (higher depth first)
if (depthA !== depthB) {
return depthB - depthA;
}

// Secondary sort: alphabetical by relative path for same depth
return a.relativePath.localeCompare(b.relativePath);
});
};

export type FileToUpload = {
fileObj: File;
fileId: string; // unique identifier: folder/filename or just filename
includeInCommand: boolean;
hasIncludeDirectives: boolean;
isEntrypoint: boolean;
isStdlib: boolean;
folder: string;
relativePath: string; // full relative path including filename
};

type State = {
Expand All @@ -25,9 +44,9 @@ type DerivedState = {

type Actions = {
addFiles: (files: File[]) => void;
setInclueInCommand: (name: string, include: boolean) => void;
setDirectory: (name: string, folder: string) => void;
removeFile: (name: string) => void;
setInclueInCommand: (fileId: string, include: boolean) => void;
setDirectory: (fileId: string, folder: string) => void;
removeFile: (fileId: string) => void;
reorderFiles: (fileBeingReplaced: string, fileToReplaceWith: string) => void;
reset: () => void;
};
Expand All @@ -45,12 +64,19 @@ export const useFileStore = create(
const modifiedFiles = await Promise.all(
files.map(async (f) => {
const content = await f.text();
// @ts-ignore
const folders = f.path?.split("/").filter((f) => f) ?? [];

// Get relative path from webkitRelativePath (directory upload) or path property (drag-and-drop)
const relativePath = (f as any).webkitRelativePath || (f as any).path || f.name;
const pathParts = relativePath.split("/").filter((part: string) => part);
const folder = pathParts.length > 1 ? pathParts.slice(0, -1).join("/") : "";
const fileId = folder ? `${folder}/${f.name}` : f.name;

return {
fileObj: f,
fileId,
relativePath,
includeInCommand: true,
folder: folders.slice(0, folders.length - 1).join("/"),
folder,
hasIncludeDirectives: content.includes("#include"),
isEntrypoint:
/\(\)\s*(recv_internal|recv_external|main)\s*\(/.test(content) ||
Expand All @@ -64,37 +90,51 @@ export const useFileStore = create(
const filesToAdd = modifiedFiles.filter(
(f) =>
f.fileObj.name.match(new RegExp(`.*\.(${acceptedFileExtensions.join("|")})$`)) &&
!state.files.find((existingF) => existingF.fileObj.name === f.fileObj.name),
!state.files.find((existingF) => existingF.fileId === f.fileId),
);

if (filesToAdd) {
sendAnalyticsEvent(AnalyticsAction.ADD_FILE);
state.files.push(...filesToAdd);
// Auto-sort files by directory depth after adding new files
state.files = sortFilesByDepth(state.files);
}
});
},
setInclueInCommand: (name: string, include: boolean) => {
setInclueInCommand: (fileId: string, include: boolean) => {
set((state) => {
state.files.find((f) => f.fileObj.name === name)!.includeInCommand = include;
const file = state.files.find((f) => f.fileId === fileId);
if (file) {
file.includeInCommand = include;
}
});
},
setDirectory: (name: string, folder: string) => {
setDirectory: (fileId: string, folder: string) => {
set((state) => {
state.files.find((f) => f.fileObj.name === name)!.folder = folder;
const file = state.files.find((f) => f.fileId === fileId);
if (file) {
file.folder = folder;
// Update fileId and relativePath when directory changes
const newFileId = folder ? `${folder}/${file.fileObj.name}` : file.fileObj.name;
file.fileId = newFileId;
file.relativePath = newFileId;
}
});
},
removeFile: (name: string) => {
removeFile: (fileId: string) => {
set((state) => {
state.files = state.files.filter((f) => f.fileObj.name !== name);
state.files = state.files.filter((f) => f.fileId !== fileId);
});
},
reorderFiles: (fileBeingReplaced: string, fileToReplaceWith: string) => {
set((state) => {
const files = state.files;
const oldIndex = files.findIndex((f) => f.fileObj.name === fileBeingReplaced);
const newIndex = files.findIndex((f) => f.fileObj.name === fileToReplaceWith);
const [removed] = files.splice(oldIndex, 1);
files.splice(newIndex, 0, removed);
const oldIndex = files.findIndex((f) => f.fileId === fileBeingReplaced);
const newIndex = files.findIndex((f) => f.fileId === fileToReplaceWith);
if (oldIndex !== -1 && newIndex !== -1) {
const [removed] = files.splice(oldIndex, 1);
files.splice(newIndex, 0, removed);
}
});
},
reset: () => {
Expand Down
Loading