Skip to content

Commit

Permalink
Backend implementation & more icons
Browse files Browse the repository at this point in the history
  • Loading branch information
Mersho committed Sep 24, 2024
1 parent 19ab832 commit d013597
Show file tree
Hide file tree
Showing 29 changed files with 591 additions and 76 deletions.
66 changes: 66 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Build and Release

on:
push:
branches:
- main
tags:
- 'v*.*.*'
pull_request:

permissions:
contents: write

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
goos: [linux, darwin, windows]
goarch: [amd64, arm64]
include:
- goos: windows
extension: .exe
- goos: linux
extension: ''
- goos: darwin
extension: ''

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install dependencies
run: |
cd FileBox
npm install
- name: Build React app
run: |
cd FileBox
npm run build
- name: Build Go binary
run: |
mkdir -p bin
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -o bin/FileBox-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.extension }} ./main.go
- name: Upload Release Asset
if: startsWith(github.ref, 'refs/tags/')
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: bin/FileBox-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.extension }}
asset_name: FileBox-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.extension }}
tag: ${{ github.ref }}
overwrite: true
25 changes: 25 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work
go.work.sum

# env file
.env
30 changes: 15 additions & 15 deletions FileBox/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

177 changes: 152 additions & 25 deletions FileBox/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
import { Checkbox } from "@headlessui/react"
import { Button, Checkbox } from "@headlessui/react"
import moment from "moment"
import { useState, useEffect } from "react"
import { Icon } from "./components/ui/icon"
import { bytesToSize } from "./lib/utils"
import { bytesToSize, getParentDirectory, joinPaths } from "./lib/utils"
import { IconName } from "./assets/icons"

interface FileInfo {
type: "file" | "directory"
name: string
modifiedDate: string
size?: number
}

interface DirectoryResponse {
currentPath: string
files: FileInfo[]
}

interface ErrorResponse {
error: string
}

interface ItemProps {
name: string
onCheckChange: (checked: boolean) => void
modifiedDate: Date
icon: React.ReactNode
size?: number
onDoubleClick?: () => void
}

function Item({ name, onCheckChange, modifiedDate, icon, size }: ItemProps) {
function Item({ name, onCheckChange, modifiedDate, icon, size, onDoubleClick }: ItemProps) {
const [isChecked, setIsChecked] = useState(false)

function handleChange(checked?: boolean) {
Expand All @@ -33,15 +51,19 @@ function Item({ name, onCheckChange, modifiedDate, icon, size }: ItemProps) {
<path d="M3 8L6 11L11 3.5" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" />
</svg>
</Checkbox>
<button className="absolute top-2 right-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<Button className="absolute top-2 right-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<Icon name="threeDots" className="w-5 h-5" />
</button>
</Button>
<div
className="flex flex-col items-center text-center"
className="flex flex-col items-center text-center group relative"
onClick={() => handleChange()}
onDoubleClick={onDoubleClick}
>
{icon}
<span className="mt-2 text-sm lg:text-base dark:text-white">{name}</span>
<span className="w-full truncate mt-2 text-sm lg:text-base dark:text-white">{name}</span>
<span className="absolute left-1/2 transform -translate-x-1/2 -top-8 bg-gray-800 text-white text-xs rounded py-1 px-2 opacity-0 group-hover:opacity-100 transition-opacity delay-1000 duration-300 whitespace-nowrap z-10 overflow-hidden w-0 group-hover:w-auto">
{name}
</span>
<span className="mt-1 text-xs text-gray-500 dark:text-gray-400">{moment(modifiedDate).fromNow()} {size && `- ${bytesToSize(size)}`}</span>
</div>
</div>
Expand All @@ -53,12 +75,29 @@ const FolderItem = (props: Omit<ItemProps, 'icon'>) => (
)

const FileItem = (props: Omit<ItemProps, 'icon'>) => (
<Item {...props} icon={<Icon name="pdf" className="w-12 h-12 lg:w-20 lg:h-20" />} />
<Item
{...props}
icon={
<Icon
name={
props.name.split('.').length > 1
? (props.name.split('.').pop()?.toLowerCase() || 'unknown') as IconName
: 'unknown'
}
className="w-12 h-12 lg:w-20 lg:h-20"
/>
}
/>
)

function App() {
const [selectedCount, setSelectedCount] = useState(0)
const [isDarkMode, setIsDarkMode] = useState(false)
const [currentPath, setCurrentPath] = useState("")
const [newPath, setNewPath] = useState("")
const [ws, setWs] = useState<WebSocket | null>(null)
const [fileInfos, setFileInfos] = useState<FileInfo[]>([])
const [error, setError] = useState<string | null>(null)

useEffect(() => {
if (isDarkMode) {
Expand All @@ -68,6 +107,48 @@ function App() {
}
}, [isDarkMode])

useEffect(() => {
const ws = new WebSocket(`ws://${window.location.hostname}:3000/ws`)
setWs(ws)

ws.onopen = () => {
console.log('Connected to WebSocket')
}

ws.onmessage = (event) => {
const data = JSON.parse(event.data)
if ('error' in data) {
setError((data as ErrorResponse).error)
setFileInfos([])
} else {
const response = data as DirectoryResponse
setCurrentPath(response.currentPath)
setNewPath("")
setFileInfos(response.files)
setError(null)
}
}

ws.onerror = (error) => {
console.error('WebSocket error:', error)
setError('Failed to connect to the server')
}

ws.onclose = () => {
console.log('WebSocket connection closed')
}

return () => {
ws.close()
}
}, [])

useEffect(() => {
if (ws && ws.readyState === WebSocket.OPEN && newPath !== "") {
ws.send(newPath)
}
}, [newPath, ws])

function handleCheckChange(checked: boolean) {
setSelectedCount(prevCount => checked ? prevCount + 1 : prevCount - 1)
}
Expand All @@ -76,31 +157,77 @@ function App() {
setIsDarkMode(!isDarkMode)
}

function handleDoubleClick(fileInfo: FileInfo) {
if (fileInfo.type === 'directory') {
setNewPath(joinPaths(currentPath, fileInfo.name));
setSelectedCount(0);
}
}

function handleBackButton() {
setNewPath(getParentDirectory(currentPath));
setSelectedCount(0);
}

return (
<div className="min-h-screen bg-white dark:bg-gray-900 transition-colors duration-300">
<div className="container mx-auto p-8">
<button
<Button
onClick={toggleDarkMode}
className="fixed top-4 right-4 p-2 rounded-full bg-gray-800 dark:bg-gray-700 text-gray-800 dark:text-white transition-colors duration-300"
>
{isDarkMode ? '🌞' : '🌙'}
</button>
<div className="flex items-center mb-6">
<h1 className="text-3xl font-bold dark:text-white">My Drive</h1>
{selectedCount > 0 && (
<span className="ml-4 px-2 py-1 bg-blue-500 text-white text-sm rounded-full">
{selectedCount} selected
</span>
)}
</div>
<div className="flex flex-wrap gap-6 lg:gap-8">
<FolderItem name="Video" onCheckChange={handleCheckChange} modifiedDate={new Date((new Date()).getTime() - 10 * 60 * 1000)} />
<FolderItem name="Photo" onCheckChange={handleCheckChange} modifiedDate={new Date((new Date()).getTime() - 30 * 60 * 1000)} />
<FolderItem name="Documents" onCheckChange={handleCheckChange} modifiedDate={new Date()} />
<FolderItem name="Music" onCheckChange={handleCheckChange} modifiedDate={new Date((new Date()).getTime() - 20 * 60 * 1000)} />
<FolderItem name="Others" onCheckChange={handleCheckChange} modifiedDate={new Date((new Date()).getTime() - 40 * 60 * 1000)} />
<FileItem name="Report.pdf" onCheckChange={handleCheckChange} modifiedDate={new Date((new Date()).getTime() - 5 * 60 * 1000)} size={1000000} />
</Button>
<div className="mb-4">
<div className="flex items-center">
<Button
className="rounded data-[hover]:bg-sky-500 data-[active]:bg-sky-700 mr-2 text-white"
onClick={() => handleBackButton()}>
<Icon name="back" className="w-6 h-6 sm:w-7 sm:h-7" />
<defs>
<style>{`.cls-1{fill:none;stroke:${isDarkMode ? '#ffffff' : '#000000'};stroke-linecap:round;stroke-linejoin:round;stroke-width:20px;}`}</style>
</defs>
</Button>
<h1 className="text-2xl sm:text-3xl font-bold dark:text-white">My Drive</h1>
</div>
<div className="flex flex-col items-start w-full mt-2">
{currentPath && (
<span className="px-3 py-1 bg-gray-700 text-white text-xs sm:text-sm rounded-full">
{currentPath}
</span>
)}
{selectedCount > 0 && (
<span className="px-3 py-1 mt-1 bg-blue-500 text-white text-xs sm:text-sm rounded-full">
{selectedCount} selected
</span>
)}
</div>
</div>
{error ? (
<div className="text-red-500 dark:text-red-400 mb-4">{error}</div>
) : (
<div className="flex flex-wrap gap-4 lg:gap-6">
{fileInfos.map((fileInfo, index) => (
fileInfo.type === 'directory' ? (
<FolderItem
key={index}
name={fileInfo.name}
onCheckChange={handleCheckChange}
modifiedDate={new Date(fileInfo.modifiedDate)}
onDoubleClick={() => handleDoubleClick(fileInfo)}
/>
) : (
<FileItem
key={index}
name={fileInfo.name}
onCheckChange={handleCheckChange}
modifiedDate={new Date(fileInfo.modifiedDate)}
size={fileInfo.size}
/>
)
))}
</div>
)}
</div>
</div>
)
Expand Down
Loading

0 comments on commit d013597

Please sign in to comment.