Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ func (a *App) GetScanRoot() (string, error) {
return a.cfg.GetScanRoot(), nil
}

func (a *App) GetRecentScanRoots() ([]string, error) {
return a.cfg.GetRecentScanRoots(), nil
}

func (a *App) GetResultFilePath() (string, error) {
return a.cfg.GetResultFilePath(), nil
}
Expand Down
132 changes: 132 additions & 0 deletions frontend/src/components/WelcomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (C) 2018-2025 SCANOSS.COM
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

import { FolderOpen, Search } from 'lucide-react';
import { useState } from 'react';

import { withErrorHandling } from '@/lib/errors';
import useConfigStore from '@/stores/useConfigStore';

import { SelectDirectory } from '../../wailsjs/go/main/App';
import { entities } from '../../wailsjs/go/models';
import { EventsOn } from '../../wailsjs/runtime/runtime';
import ScanDialog from './ScanDialog';
import { Button } from './ui/button';
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
import { toast } from './ui/use-toast';

export default function WelcomeScreen() {
const setScanRoot = useConfigStore((state) => state.setScanRoot);
const recentScanRoots = useConfigStore((state) => state.recentScanRoots);
const [scanModal, setScanModal] = useState(false);

const handleSelectScanRoot = withErrorHandling({
asyncFn: async () => {
const selectedDir = await SelectDirectory();
if (selectedDir) {
await setScanRoot(selectedDir);
}
},
onError: () => {
toast({
variant: 'destructive',
title: 'Error',
description: 'An error occurred while selecting the scan root. Please try again.',
});
},
});

const handleSelectRecentFolder = withErrorHandling({
asyncFn: async (path: string) => {
await setScanRoot(path);
},
onError: () => {
toast({
variant: 'destructive',
title: 'Error',
description: 'An error occurred while selecting the recent folder. Please try again.',
});
},
});

const handleCloseScanModal = () => setScanModal(false);
const handleShowScanModal = () => setScanModal(true);

EventsOn(entities.Action.ScanWithOptions, () => {
handleShowScanModal();
});

return (
<div className="flex h-screen w-screen flex-col items-center justify-center space-y-8 p-8">
<div className="space-y-2 text-center">
<h1 className="text-3xl font-semibold tracking-tight">SCANOSS Code Compare</h1>
<p className="text-lg text-muted-foreground">Select a folder to start</p>
</div>

<div className="grid w-full max-w-4xl gap-6">
<Card>
<CardHeader>
<CardTitle className="text-lg font-medium">Start</CardTitle>
</CardHeader>
<CardContent className="grid gap-4">
<Button variant="ghost" className="w-full justify-start gap-2 px-2" onClick={handleSelectScanRoot}>
<FolderOpen className="h-5 w-5" />
Open Folder...
</Button>
<Button variant="ghost" className="w-full justify-start gap-2 px-2" onClick={handleShowScanModal}>
<Search className="h-5 w-5" />
Run a new scan...
</Button>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle className="text-lg font-medium">Recent</CardTitle>
</CardHeader>
<CardContent>
{recentScanRoots.length > 0 ? (
<div className="grid gap-2">
{recentScanRoots.map((path) => (
<Button
key={path}
variant="ghost"
className="w-full justify-start gap-2 px-2 text-sm"
onClick={() => handleSelectRecentFolder(path)}
>
<FolderOpen className="h-4 w-4" />
{path}
</Button>
))}
</div>
) : (
<p className="text-sm text-muted-foreground">No recent folders</p>
)}
</CardContent>
</Card>
</div>

<ScanDialog open={scanModal} onOpenChange={handleCloseScanModal} />
</div>
);
}
76 changes: 76 additions & 0 deletions frontend/src/components/ui/card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as React from "react"

import { cn } from "@/lib/utils"

const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"

const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"

const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"

const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"

const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"

const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"

export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
3 changes: 2 additions & 1 deletion frontend/src/hooks/useResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export const useResults = () => {
};

return {
...queryResult,
data: queryResult.data,
isLoading: queryResult.isLoading,
reset,
};
};
24 changes: 14 additions & 10 deletions frontend/src/routes/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,22 @@
* SOFTWARE.
*/

import { useEffect, useState } from 'react';
import { useState } from 'react';
import { Outlet } from 'react-router-dom';

import KeyboardShortcutsDialog from '@/components/KeyboardShortcutsDialog';
import ScanDialog from '@/components/ScanDialog';
import Sidebar from '@/components/Sidebar';
import StatusBar from '@/components/StatusBar';
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable';
import WelcomeScreen from '@/components/WelcomeScreen';
import useConfigStore from '@/stores/useConfigStore';

import { entities } from '../../wailsjs/go/models';
import { EventsOn } from '../../wailsjs/runtime/runtime';

export default function Root() {
const scanRoot = useConfigStore((state) => state.scanRoot);
const [showKeyboardShortcuts, setShowKeyboardShortcuts] = useState(false);
const [scanModal, setScanModal] = useState(false);
const handleCloseScanModal = () => {
Expand All @@ -44,15 +47,16 @@ export default function Root() {
setScanModal(true);
};

useEffect(() => {
// Register event listeners
EventsOn(entities.Action.ShowKeyboardShortcutsModal, () => {
setShowKeyboardShortcuts(true);
});
EventsOn(entities.Action.ScanWithOptions, () => {
handleShowScanModal();
});
}, []);
EventsOn(entities.Action.ShowKeyboardShortcutsModal, () => {
setShowKeyboardShortcuts(true);
});
EventsOn(entities.Action.ScanWithOptions, () => {
handleShowScanModal();
});

if (scanRoot === '/') {
return <WelcomeScreen />;
}

return (
<div className="flex h-screen w-full flex-col bg-background backdrop-blur-lg">
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/stores/useConfigStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

import {
GetRecentScanRoots,
GetResultFilePath,
GetScanRoot,
GetScanSettingsFilePath,
Expand All @@ -37,6 +38,7 @@ interface ResultsState {
scanRoot: string;
resultsFile: string;
settingsFile: string;
recentScanRoots: string[];
}

interface ResultsActions {
Expand All @@ -54,6 +56,7 @@ export default create<ConfigStore>()(
scanRoot: '',
resultsFile: '',
settingsFile: '',
recentScanRoots: [],

setScanRoot: async (scanRoot: string) => {
await SetScanRoot(scanRoot);
Expand All @@ -72,7 +75,8 @@ export default create<ConfigStore>()(
const scanRoot = await GetScanRoot();
const resultsFile = await GetResultFilePath();
const settingsFile = await GetScanSettingsFilePath();
set({ scanRoot, resultsFile, settingsFile });
const recentScanRoots = await GetRecentScanRoots();
set({ scanRoot, resultsFile, settingsFile, recentScanRoots });
},
}))
);
9 changes: 9 additions & 0 deletions frontend/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,12 @@ body {
width: 3px !important;
margin-left: 5px;
}

@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}
2 changes: 2 additions & 0 deletions frontend/wailsjs/go/main/App.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {service} from '../models';

export function BeforeClose(arg1:context.Context):Promise<boolean>;

export function GetRecentScanRoots():Promise<Array<string>>;

export function GetResultFilePath():Promise<string>;

export function GetScanRoot():Promise<string>;
Expand Down
4 changes: 4 additions & 0 deletions frontend/wailsjs/go/main/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export function BeforeClose(arg1) {
return window['go']['main']['App']['BeforeClose'](arg1);
}

export function GetRecentScanRoots() {
return window['go']['main']['App']['GetRecentScanRoots']();
}

export function GetResultFilePath() {
return window['go']['main']['App']['GetResultFilePath']();
}
Expand Down
Loading
Loading