Skip to content

Commit

Permalink
Merge pull request alefmanvladimir#23 from d0rich/beautify-frontend-1
Browse files Browse the repository at this point in the history
Beautify frontend
  • Loading branch information
d0rich authored Oct 30, 2023
2 parents 46bb419 + 0e412a8 commit d23e123
Show file tree
Hide file tree
Showing 22 changed files with 531 additions and 236 deletions.
2 changes: 1 addition & 1 deletion ton-drive-frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function App() {
<StrictMode>
<StyledApp>
<AppContainer>
<TonConnectButton/>
<TonConnectButton className="my-2" />
<FilesListPage />
</AppContainer>
</StyledApp>
Expand Down
74 changes: 42 additions & 32 deletions ton-drive-frontend/src/entities/file/ui/FilesListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,50 @@
import type {TonStorageFile} from "../model/TonStorageFile"
import FileSize from "./FileSize"
import FileDate from "./FileDate"
import FileTypeIcon from "./FileTypeIcon"
import type { TonStorageFile } from "../model/TonStorageFile";
import FileSize from "./FileSize";
import FileDate from "./FileDate";
import FileTypeIcon from "./FileTypeIcon";
import ContractLink from "../../../shared/ui/ContractLink";

export interface FilesListItemProps {
file: TonStorageFile
className?: string
actions?: JSX.Element
file: TonStorageFile;
className?: string;
actions?: JSX.Element;
}

export default function FilesListItem({file, className, actions}: FilesListItemProps) {
return (
<li className={`card card-compact flex-row items-center gap-2 bg-base-300 p-3 ${className ?? ''}`}>
export default function FilesListItem({ file, className, actions }: FilesListItemProps) {
return (
<li className={`card card-compact bg-base-300 p-3 ${className ?? ""}`}>
<div className="font-bold text-md">
{file.name}
<div className="badge badge-neutral ml-1">{file.extension}</div>
</div>
<div className="flex flex-col sm:flex-row gap-2 ">
<div className="flex flex-col sm:flex-row sm:items-center gap-2 text-ellipsis overflow-hidden">
<div className="flex items-center gap-2 sm:w-1/4">
<div>
<FileTypeIcon extension={file.extension}/>
<FileTypeIcon extension={file.extension} />
</div>
<div className="text-ellipsis overflow-hidden">
<div className="card-title">
{file.name}
<div className="badge badge-neutral">{file.extension}</div>
</div>
<div className="text-accent-content text-sm"><FileSize size={file.size}/> | <FileDate date={file.date}/>
</div>
<span className="text-accent-content text-sm">{file.bagId}</span>
<div>
<span className="text-accent-content text-sm">
Storage Contract: {
file.storageContractInfo.address ? file.storageContractInfo.address?.toString() : <span className={'text-error-content text-sm'}>Not ready</span>
}
</span>
</div>
<div className="sm:flex sm:flex-col">
<div className="text-accent-content text-xs">
<FileSize size={file.size} /> | <FileDate date={file.date} />
</div>
</div>
{/* Actions */}
<div className="ml-auto">
{actions}
</div>
</li>
)
</div>
<table className="table table-xs">
<tbody>
<tr>
<th>Bag ID</th>
<td>{file.bagId}</td>
</tr>
<tr>
<th>Contract</th>
<td>{file.storageContractInfo.address ? <ContractLink address={file.storageContractInfo.address?.toString()} /> : <span className={"text-error-content text-sm"}>Not ready</span>}</td>
</tr>
</tbody>
</table>
</div>
{/* Actions */}
<div className="ml-auto">{actions}</div>
</div>
</li>
);
}
38 changes: 38 additions & 0 deletions ton-drive-frontend/src/features/drive/hook/useCollectionInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {CollectionInfo} from "../../../services/FilesService";
import {useEffect, useState} from "react";
import {useMyCollection} from "./useMyCollection";
import { useRealtimeRemoteState } from "../../../shared/hooks/useRealtimeRemoteState";

export function useCollectionInfo(): CollectionInfo | null {
const [info, setInfo] = useState<CollectionInfo | null>(null)
const myCollection = useMyCollection()

useEffect(() => {
if (myCollection == null) {
return;
}
myCollection
.info()
.then(setInfo)
}, [myCollection])

return info
}

export function useRealtimeCollectionInfo(): CollectionInfo | null {
const myCollection = useMyCollection()
return useRealtimeRemoteState({
fetchFn: async () => {
if (myCollection == null) {
return null;
}
return myCollection.info()
},
isEqualFn: (oldData, newData) => {
const isAddressEqual = oldData?.address.toString() === newData?.address.toString()
const isBalanceEqual = oldData?.balance === newData?.balance
return isAddressEqual && isBalanceEqual
},
deps: [myCollection]
})
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {useTonAddress} from "@tonconnect/ui-react";
import {useTonClient} from "../../shared/hooks/useTonClient";
import {useTonClient} from "../../../shared/hooks/useTonClient";
import {useEffect, useState} from "react";
import tonDrive, {UserCollection} from "../../services/FilesService";
import {useTonConnect} from "../../shared/hooks/useTonConnect";
import tonDrive, {UserCollection} from "../../../services/FilesService";
import {useTonConnect} from "../../../shared/hooks/useTonConnect";
import {Address} from "ton-core";

export function useMyCollection() {
Expand All @@ -15,6 +15,7 @@ export function useMyCollection() {
const client = tonClient.client

if (wallet == null || wallet == '' || !client) {
setCollection(null);
return;
}
const userAddr = Address.parse(wallet)
Expand Down
16 changes: 16 additions & 0 deletions ton-drive-frontend/src/features/drive/ui/RequireUserDrive.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useRealtimeCollectionInfo } from "../hook/useCollectionInfo";

export interface RequireUserDriveProps {
children?: JSX.Element | JSX.Element[];
fallback?: JSX.Element;
}

export default function RequireUserDrive({ children, fallback = <></> }: RequireUserDriveProps) {
const userDrive = useRealtimeCollectionInfo();

const showChildren = userDrive != null && userDrive.balance > 0;

return (<>
{showChildren ? children : fallback}
</>)
}
53 changes: 53 additions & 0 deletions ton-drive-frontend/src/features/file/api/uploadFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Sender, TonClient } from "ton";
import {Address, Cell} from "ton-core";
import tonDrive, {storageProviderAddress} from "../../../services/FilesService";

export interface FileUploadOptions {
file: File;
tonClient: {
client: TonClient | undefined;
};
sender: Sender;
wallet: string;
}

export async function uploadFile({ file, tonClient, sender, wallet }: FileUploadOptions) {
const host = 'https://api.bigfiles.cloud'

const formData = new FormData()
formData.append('file', file)

if (!tonClient || !sender || !wallet) {
console.log("Wallet not connected")
return;
}
const tonDriveService = tonDrive(tonClient.client!!, sender)
const response = await fetch(`${host}/upload`, {
method: 'POST',
body: formData
})

const {bagId} = await response.json() as {bagId: string}
const contractParams = new URLSearchParams()
contractParams.append('bagId', bagId)
contractParams.append('providerAddress', storageProviderAddress.toRawString())
// TODO: use POST method body instead of query params
const contractResponse = await fetch(new URL('/contracts?' + contractParams.toString(), host).toString(), {
method: 'POST'
})
const contractFile = await contractResponse.arrayBuffer()

const base64 = btoa(new Uint8Array(contractFile)
.reduce((data, byte) => data + String.fromCharCode(byte), ''))

const msgBody = Cell.fromBase64(base64)
const slice = msgBody.beginParse()
const opQueryId = slice.loadUint(32 + 64)
const torrentHash = slice.loadRef().hash().toString('hex')
console.log(`HEX: ${torrentHash}`, BigInt(`0x${torrentHash}`))
const userCollection = tonDriveService.userCollection(Address.parse(wallet))
return userCollection.createContract(msgBody, {
name: file.name,
size: file.size
})
}
85 changes: 85 additions & 0 deletions ton-drive-frontend/src/features/file/hooks/useUserFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {useEffect, useState} from "react";
import type {TonStorageFile} from "../../../entities/file/model/TonStorageFile";
import {useMyCollection} from "../../drive/hook/useMyCollection";
import { useRealtimeRemoteState } from "../../../shared/hooks/useRealtimeRemoteState";
import { FileInfo } from "../../../services/FilesService";

export function useUserFiles(): TonStorageFile[] {
const [files, setFiles] = useState<TonStorageFile[]>([]);
const myCollection = useMyCollection()

useEffect(() => {
if (myCollection == null) {
return
}
myCollection.fileList()
.then(mapFiles)
.then(setFiles)
}, [myCollection])


return files;
}

export function useRealtimeUserFiles() {
const myCollection = useMyCollection()

return useRealtimeRemoteState({
fetchFn: async () => {
if (myCollection == null) {
return [];
}
const files = await myCollection.fileList()
return mapFiles(files)
},
isEqualFn: (oldFiles, newFiles) => {
if (oldFiles === null) {
return newFiles !== null;
}
if (oldFiles.length !== newFiles.length) {
return false;
}
for (let i = 0; i < oldFiles.length; i++) {
const oldFile = oldFiles[i];
const newFile = newFiles[i];
if (oldFile.bagId !== newFile.bagId) {
return false;
}
if (oldFile.storageContractInfo.address?.toRawString() !== newFile.storageContractInfo.address?.toRawString()) {
return false;
}
}
return true;
},
deps: [myCollection]
}) || [];
}

function mapFiles(files: FileInfo[]): TonStorageFile[] {
return files.map(file => {
const [name, extension] = splitFileName(file.name)
return {
bagId: `${BigInt(file.bagId).toString(16)}`,
name: name,
extension: extension,
//TODO: replace with a proper code
size: parseInt(`${file.fileSize}`),
//TODO: rework once corresponding field is handled by the contract
date: new Date('2021-08-03'),
storageContractInfo: {
address: file.storageContractAddress
}
}
})
}

function splitFileName(fileName: string): [string, string] {
const lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex === -1) {
return [fileName, ''];
}
return [
fileName.substring(0, lastDotIndex),
fileName.substring(lastDotIndex + 1)
];
}
24 changes: 18 additions & 6 deletions ton-drive-frontend/src/features/file/ui/FilesListWithActions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import FilesList, {type FilesListProps} from "../../../entities/file/ui/FilesList";
import {useMyCollection} from "../../../widgets/hooks/useMyCollection";
import {useMyCollection} from "../../drive/hook/useMyCollection";
import {TonStorageFile} from "../../../entities/file/model/TonStorageFile";

export interface FilesListWithActionsProps {
Expand All @@ -9,17 +9,24 @@ export interface FilesListWithActionsProps {

const createActions = (close: (hexBagId: string) => Promise<void>) => (file: TonStorageFile) => {
return (
<>
<button className={"btn btn-outline btn-sm btn-primary"} onClick={() => close(file.bagId)}>Close</button>
<button className="btn btn-outline btn-accent">
<div className="join sm:join-vertical">
<button className="join-item btn btn-outline btn-sm btn-accent" onClick={() => close(file.bagId)}>
{/* trash icon */}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
</button>
<a
download href={createDownloadLink(file)} target="_blank"
className="join-item btn btn-outline btn-sm btn-accent">
{/* Download icon */}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5}
stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round"
d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"/>
</svg>
</button>
</>
</a>
</div>
)
}

Expand All @@ -35,3 +42,8 @@ export default function FilesListWithActions(
</>
)
}

function createDownloadLink(file: TonStorageFile) {
// TODO: replace link with personal
return `https://storage.ton.run/gateway/${file.bagId}/${file.name}.${file.extension}`
}
2 changes: 1 addition & 1 deletion ton-drive-frontend/src/features/file/ui/FilesSearchBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function FilesSearchBox({ files, onSearch, className = '' }: File
value={searchQuery} onChange={(event) => setSearchQuery(event.target.value)} />
<div className="indicator">
{ isNew ? <span className="indicator-item badge badge-accent">new</span> : null }
<button className="btn join-item" type="submit">
<button className="btn join-item btn-outline" type="submit">
{/* magnifying-glass */}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-6 h-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default function FilesSortControls({ files, onSort, className = '' }: Fil
{
buttons.map((buttonModel) => (
<button key={buttonModel.sortAttribute}
className={`btn btn-sm sm:btn-md join-item ${sortAttribute === buttonModel.sortAttribute ? 'btn-active btn-accent' : ''}`}
className={`btn btn-sm btn-outline sm:btn-md join-item ${sortAttribute === buttonModel.sortAttribute ? 'btn-active btn-accent' : ''}`}
onClick={() => onButtonClick(buttonModel.sortAttribute)}>
<SortOrderIcon asc={getAsc(buttonModel.sortAttribute)} /> {buttonModel.text}
</button>
Expand Down
16 changes: 9 additions & 7 deletions ton-drive-frontend/src/pages/FilesListPage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import FilesNavigation from "../widgets/FilesNavigation"
import FileUpload from "../widgets/FileUpload"
import DriveInfo from "../widgets/DriveInfo";
import FilesNavigationCard from "../widgets/FilesNavigationCard"
import FileUploadCard from "../widgets/FileUploadCard"
import DriveInfoCard from "../widgets/DriveInfoCard";
import RequireUserDrive from "../features/drive/ui/RequireUserDrive";

export default function FilesListPage() {
return (
<>
<h1 className="font-bold text-3xl">My Files</h1>
<DriveInfo className="my-2" />
<FileUpload/>
<FilesNavigation/>
<DriveInfoCard className="my-2" />
<RequireUserDrive>
<FileUploadCard className="my-2" />
<FilesNavigationCard className="my-2"/>
</RequireUserDrive>
</>
)
}
Loading

0 comments on commit d23e123

Please sign in to comment.