commit 80536e27e069322f31ffde936dc567dcf8fd6743 Author: anthonyrawlins Date: Wed Aug 27 09:35:36 2025 +1000 Initial commit - RUSTLE Wails implementation - Added Go-based Wails application for RUSTLE UCXL browser - Implemented basic application structure and configuration - Added project documentation and setup instructions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude diff --git a/rustle/.gitignore b/rustle/.gitignore new file mode 100644 index 0000000..129d522 --- /dev/null +++ b/rustle/.gitignore @@ -0,0 +1,3 @@ +build/bin +node_modules +frontend/dist diff --git a/rustle/README.md b/rustle/README.md new file mode 100644 index 0000000..d2169cc --- /dev/null +++ b/rustle/README.md @@ -0,0 +1,19 @@ +# README + +## About + +This is the official Wails React-TS template. + +You can configure the project by editing `wails.json`. More information about the project settings can be found +here: https://wails.io/docs/reference/project-config + +## Live Development + +To run in live development mode, run `wails dev` in the project directory. This will run a Vite development +server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser +and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect +to this in your browser, and you can call your Go code from devtools. + +## Building + +To build a redistributable, production mode package, use `wails build`. diff --git a/rustle/app.go b/rustle/app.go new file mode 100644 index 0000000..ac7d408 --- /dev/null +++ b/rustle/app.go @@ -0,0 +1,438 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/wailsapp/wails/v2/pkg/runtime" +) + +// App struct +type App struct { + ctx context.Context +} + +// Envelope represents a UCXL content envelope +type Envelope struct { + UCXLURI string `json:"ucxl_uri"` + Content Content `json:"content"` + Metadata map[string]string `json:"metadata"` + Timestamp string `json:"timestamp"` + Version string `json:"version"` + ContentHash string `json:"content_hash"` +} + +// Content represents the actual content data +type Content struct { + Raw string `json:"raw"` + ContentType string `json:"content_type"` + Encoding string `json:"encoding"` +} + +// UCXL Address parsing types and functions +type UCXLAddress struct { + Agent string `json:"agent"` + Role string `json:"role"` + Project string `json:"project"` + Task string `json:"task"` + Temporal string `json:"temporal"` + Path string `json:"path"` + Raw string `json:"raw"` +} + +// Regular expression for UCXL address parsing +var ucxlAddressPattern = regexp.MustCompile(`^ucxl://([^:]+):([^@]+)@([^:]+):([^/]+)/([^/]+)/?(.*)$`) + +// ParseUCXL parses a UCXL address string +func ParseUCXL(address string) (*UCXLAddress, error) { + if address == "" { + return nil, fmt.Errorf("address cannot be empty") + } + + // Normalize the address + normalized := strings.TrimSpace(address) + if !strings.HasPrefix(strings.ToLower(normalized), "ucxl://") { + return nil, fmt.Errorf("address must start with 'ucxl://'") + } + + // Use regex for parsing + matches := ucxlAddressPattern.FindStringSubmatch(normalized) + if matches == nil || len(matches) != 7 { + return nil, fmt.Errorf("address format must be 'ucxl://agent:role@project:task/temporal_segment/path'") + } + + return &UCXLAddress{ + Agent: strings.ToLower(strings.TrimSpace(matches[1])), + Role: strings.ToLower(strings.TrimSpace(matches[2])), + Project: strings.ToLower(strings.TrimSpace(matches[3])), + Task: strings.ToLower(strings.TrimSpace(matches[4])), + Temporal: strings.TrimSpace(matches[5]), + Path: matches[6], // Path can be empty + Raw: address, + }, nil +} + +// String returns the canonical string representation +func (addr *UCXLAddress) String() string { + if addr.Path != "" { + return fmt.Sprintf("ucxl://%s:%s@%s:%s/%s/%s", addr.Agent, addr.Role, addr.Project, addr.Task, addr.Temporal, addr.Path) + } + return fmt.Sprintf("ucxl://%s:%s@%s:%s/%s", addr.Agent, addr.Role, addr.Project, addr.Task, addr.Temporal) +} + +// NewApp creates a new App application struct +func NewApp() *App { + return &App{} +} + +// startup is called when the app starts. The context is saved +// so we can call the runtime methods +func (a *App) startup(ctx context.Context) { + a.ctx = ctx + + // Log startup + runtime.LogInfo(ctx, "RUSTLE started with BZZZ integration (mock mode)") +} + +// GetUCXLContent retrieves content by UCXL address +func (a *App) GetUCXLContent(uri string) (map[string]interface{}, error) { + runtime.LogInfo(a.ctx, fmt.Sprintf("Fetching UCXL content: %s", uri)) + + // Parse the UCXL address + addr, err := ParseUCXL(uri) + if err != nil { + return nil, fmt.Errorf("invalid UCXL address: %w", err) + } + + // For now, return mock content based on the address + envelope, err := a.generateMockContent(addr) + if err != nil { + return nil, fmt.Errorf("failed to generate content: %w", err) + } + + // Convert to map for JSON serialization + result := map[string]interface{}{ + "ucxl_uri": envelope.UCXLURI, + "content": envelope.Content, + "metadata": envelope.Metadata, + "timestamp": envelope.Timestamp, + "version": envelope.Version, + "content_hash": envelope.ContentHash, + } + + return result, nil +} + +// PostUCXLContent stores content at UCXL address +func (a *App) PostUCXLContent(uri string, content string, contentType string, metadata map[string]string) error { + runtime.LogInfo(a.ctx, fmt.Sprintf("Storing UCXL content: %s", uri)) + + // Parse the UCXL address + addr, err := ParseUCXL(uri) + if err != nil { + return fmt.Errorf("invalid UCXL address: %w", err) + } + + // Create envelope + envelope := &Envelope{ + UCXLURI: addr.String(), + Content: Content{ + Raw: content, + ContentType: contentType, + Encoding: "utf-8", + }, + Metadata: metadata, + Timestamp: time.Now().Format(time.RFC3339), + Version: "1.0", + ContentHash: fmt.Sprintf("sha256:%x", []byte(content)), // Simple hash for demo + } + + // In a real implementation, this would store to the DHT + runtime.LogInfo(a.ctx, fmt.Sprintf("Mock stored content: %d bytes", len(content))) + + // For demo purposes, we'll just log the operation + envelopeJSON, _ := json.MarshalIndent(envelope, "", " ") + runtime.LogInfo(a.ctx, fmt.Sprintf("Stored envelope: %s", string(envelopeJSON))) + + return nil +} + +// SearchUCXLContent searches for content matching a pattern +func (a *App) SearchUCXLContent(query string, tags []string, limit int) ([]map[string]interface{}, error) { + runtime.LogInfo(a.ctx, fmt.Sprintf("Searching UCXL content: query=%s, tags=%v, limit=%d", query, tags, limit)) + + // For demo purposes, generate some mock search results + results := make([]map[string]interface{}, 0, limit) + + // Generate a few mock results based on the query + for i := 0; i < min(limit, 3); i++ { + mockURI := fmt.Sprintf("ucxl://search:result@%s:demo/*^/result_%d", strings.ToLower(query), i+1) + addr, err := ParseUCXL(mockURI) + if err != nil { + continue + } + + envelope, err := a.generateMockContent(addr) + if err != nil { + continue + } + + result := map[string]interface{}{ + "ucxl_uri": envelope.UCXLURI, + "content": envelope.Content, + "metadata": envelope.Metadata, + "timestamp": envelope.Timestamp, + "version": envelope.Version, + "content_hash": envelope.ContentHash, + } + + results = append(results, result) + } + + return results, nil +} + +// ValidateUCXLAddress validates a UCXL address format +func (a *App) ValidateUCXLAddress(uri string) map[string]interface{} { + result := map[string]interface{}{ + "valid": false, + "error": "", + "components": map[string]string{}, + } + + addr, err := ParseUCXL(uri) + if err != nil { + result["error"] = err.Error() + return result + } + + result["valid"] = true + result["components"] = map[string]string{ + "agent": addr.Agent, + "role": addr.Role, + "project": addr.Project, + "task": addr.Task, + "temporal": addr.Temporal, + "path": addr.Path, + } + + return result +} + +// GetBZZZStatus returns the status of BZZZ DHT connection +func (a *App) GetBZZZStatus() map[string]interface{} { + status := map[string]interface{}{ + "connected": false, + "mode": "mock", + "peers": 0, + "agent": "rustle", + "role": "browser", + "project": "ucxl-content-browser", + "capabilities": []string{"content-retrieval", "content-storage", "search", "address-validation"}, + } + + return status +} + +// generateMockContent creates mock content based on UCXL address components +func (a *App) generateMockContent(addr *UCXLAddress) (*Envelope, error) { + var content string + var contentType string + + // Generate different content types based on the path + switch { + case strings.HasSuffix(addr.Path, ".md"): + content = a.generateMarkdownContent(addr) + contentType = "text/markdown" + case strings.HasSuffix(addr.Path, ".json"): + content = a.generateJSONContent(addr) + contentType = "application/json" + case strings.HasSuffix(addr.Path, ".go") || strings.HasSuffix(addr.Path, ".rs") || strings.HasSuffix(addr.Path, ".js"): + content = a.generateCodeContent(addr) + contentType = "text/x-" + filepath.Ext(addr.Path)[1:] + default: + content = a.generateDefaultContent(addr) + contentType = "text/plain" + } + + return &Envelope{ + UCXLURI: addr.String(), + Content: Content{ + Raw: content, + ContentType: contentType, + Encoding: "utf-8", + }, + Metadata: map[string]string{ + "title": fmt.Sprintf("%s/%s Content", strings.Title(addr.Agent), strings.Title(addr.Role)), + "author": addr.Agent, + "tags": fmt.Sprintf("%s,%s,%s", addr.Project, addr.Task, addr.Role), + "source": "rustle-mock-generator", + }, + Timestamp: time.Now().Format(time.RFC3339), + Version: "1.0", + ContentHash: fmt.Sprintf("sha256:%x", []byte(content)), + }, nil +} + +func (a *App) generateMarkdownContent(addr *UCXLAddress) string { + return fmt.Sprintf(`# %s/%s - %s + +This is mock content generated for the UCXL address: %s + +## Project Context +- **Project**: %s +- **Task**: %s +- **Agent**: %s +- **Role**: %s + +## Content Details +- **Path**: %s +- **Temporal**: %s + +This content demonstrates the RUSTLE browser's ability to render Markdown content from UCXL addresses. + +### Features +- ✅ UCXL address parsing +- ✅ Content type detection +- ✅ Markdown rendering +- ✅ BZZZ DHT integration (mock mode) + +### Integration Notes +This content is being served through the Wails desktop application with Go backend and React frontend. +`, strings.Title(addr.Agent), strings.Title(addr.Role), strings.Title(addr.Task), + addr.String(), addr.Project, addr.Task, addr.Agent, addr.Role, addr.Path, addr.Temporal) +} + +func (a *App) generateJSONContent(addr *UCXLAddress) string { + data := map[string]interface{}{ + "ucxl_address": addr.String(), + "components": map[string]string{ + "agent": addr.Agent, + "role": addr.Role, + "project": addr.Project, + "task": addr.Task, + "path": addr.Path, + "temporal": addr.Temporal, + }, + "mock_data": map[string]interface{}{ + "generated_at": time.Now().Format(time.RFC3339), + "generator": "rustle-wails-backend", + "content_type": "application/json", + "capabilities": []string{ + "content-rendering", + "address-parsing", + "mock-generation", + }, + }, + "bzzz_integration": map[string]interface{}{ + "mode": "mock", + "agent": "rustle", + "role": "browser", + "project": "ucxl-content-browser", + }, + } + + jsonData, _ := json.MarshalIndent(data, "", " ") + return string(jsonData) +} + +func (a *App) generateCodeContent(addr *UCXLAddress) string { + ext := filepath.Ext(addr.Path) + switch ext { + case ".go": + return fmt.Sprintf(`package %s + +import ( + "context" + "fmt" + "log" +) + +// %sHandler handles %s operations +type %sHandler struct { + ctx context.Context +} + +// New%sHandler creates a new handler +func New%sHandler(ctx context.Context) *%sHandler { + return &%sHandler{ctx: ctx} +} + +// Process handles the %s task +func (h *%sHandler) Process() error { + log.Printf("Processing %s task for project %s") + return nil +} +`, addr.Task, strings.Title(addr.Role), addr.Task, strings.Title(addr.Role), + strings.Title(addr.Role), strings.Title(addr.Role), strings.Title(addr.Role), + strings.Title(addr.Role), addr.Task, strings.Title(addr.Role), addr.Task, addr.Project) + + case ".js": + return fmt.Sprintf(`// %s/%s - %s +// UCXL Address: %s + +class %sHandler { + constructor() { + this.project = '%s'; + this.task = '%s'; + this.agent = '%s'; + this.role = '%s'; + } + + async process() { + console.log('Processing task: ' + this.task + ' for project: ' + this.project); + // Mock implementation + return { + status: 'success', + ucxl_address: '%s', + timestamp: new Date().toISOString() + }; + } +} + +module.exports = %sHandler; +`, strings.Title(addr.Agent), strings.Title(addr.Role), strings.Title(addr.Task), addr.String(), + strings.Title(addr.Role), addr.Project, addr.Task, addr.Agent, addr.Role, addr.String(), strings.Title(addr.Role)) + + default: + return fmt.Sprintf("// Mock code content for %s\n// Generated from UCXL address: %s\n", addr.Path, addr.String()) + } +} + +func (a *App) generateDefaultContent(addr *UCXLAddress) string { + return fmt.Sprintf(`UCXL Content - %s + +Address: %s +Agent: %s +Role: %s +Project: %s +Task: %s +Path: %s +Temporal: %s + +This is mock content generated by the RUSTLE browser's BZZZ-integrated backend. +Generated at: %s + +Content demonstrates successful UCXL address parsing and mock content generation. +`, strings.Title(addr.Task), addr.String(), addr.Agent, addr.Role, + addr.Project, addr.Task, addr.Path, addr.Temporal, + time.Now().Format(time.RFC3339)) +} + +// Helper function for min +func min(a, b int) int { + if a < b { + return a + } + return b +} + +// Greet returns a greeting for the given name (legacy function) +func (a *App) Greet(name string) string { + return fmt.Sprintf("Hello %s, Welcome to RUSTLE - UCXL Content Browser!", name) +} \ No newline at end of file diff --git a/rustle/build/README.md b/rustle/build/README.md new file mode 100644 index 0000000..1ae2f67 --- /dev/null +++ b/rustle/build/README.md @@ -0,0 +1,35 @@ +# Build Directory + +The build directory is used to house all the build files and assets for your application. + +The structure is: + +* bin - Output directory +* darwin - macOS specific files +* windows - Windows specific files + +## Mac + +The `darwin` directory holds files specific to Mac builds. +These may be customised and used as part of the build. To return these files to the default state, simply delete them +and +build with `wails build`. + +The directory contains the following files: + +- `Info.plist` - the main plist file used for Mac builds. It is used when building using `wails build`. +- `Info.dev.plist` - same as the main plist file but used when building using `wails dev`. + +## Windows + +The `windows` directory contains the manifest and rc files used when building with `wails build`. +These may be customised for your application. To return these files to the default state, simply delete them and +build with `wails build`. + +- `icon.ico` - The icon used for the application. This is used when building using `wails build`. If you wish to + use a different icon, simply replace this file with your own. If it is missing, a new `icon.ico` file + will be created using the `appicon.png` file in the build directory. +- `installer/*` - The files used to create the Windows installer. These are used when building using `wails build`. +- `info.json` - Application details used for Windows builds. The data here will be used by the Windows installer, + as well as the application itself (right click the exe -> properties -> details) +- `wails.exe.manifest` - The main application manifest file. \ No newline at end of file diff --git a/rustle/build/appicon.png b/rustle/build/appicon.png new file mode 100644 index 0000000..63617fe Binary files /dev/null and b/rustle/build/appicon.png differ diff --git a/rustle/build/darwin/Info.dev.plist b/rustle/build/darwin/Info.dev.plist new file mode 100644 index 0000000..14121ef --- /dev/null +++ b/rustle/build/darwin/Info.dev.plist @@ -0,0 +1,68 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.Info.ProductName}} + CFBundleExecutable + {{.OutputFilename}} + CFBundleIdentifier + com.wails.{{.Name}} + CFBundleVersion + {{.Info.ProductVersion}} + CFBundleGetInfoString + {{.Info.Comments}} + CFBundleShortVersionString + {{.Info.ProductVersion}} + CFBundleIconFile + iconfile + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.Info.Copyright}} + {{if .Info.FileAssociations}} + CFBundleDocumentTypes + + {{range .Info.FileAssociations}} + + CFBundleTypeExtensions + + {{.Ext}} + + CFBundleTypeName + {{.Name}} + CFBundleTypeRole + {{.Role}} + CFBundleTypeIconFile + {{.IconName}} + + {{end}} + + {{end}} + {{if .Info.Protocols}} + CFBundleURLTypes + + {{range .Info.Protocols}} + + CFBundleURLName + com.wails.{{.Scheme}} + CFBundleURLSchemes + + {{.Scheme}} + + CFBundleTypeRole + {{.Role}} + + {{end}} + + {{end}} + NSAppTransportSecurity + + NSAllowsLocalNetworking + + + + diff --git a/rustle/build/darwin/Info.plist b/rustle/build/darwin/Info.plist new file mode 100644 index 0000000..d17a747 --- /dev/null +++ b/rustle/build/darwin/Info.plist @@ -0,0 +1,63 @@ + + + + CFBundlePackageType + APPL + CFBundleName + {{.Info.ProductName}} + CFBundleExecutable + {{.OutputFilename}} + CFBundleIdentifier + com.wails.{{.Name}} + CFBundleVersion + {{.Info.ProductVersion}} + CFBundleGetInfoString + {{.Info.Comments}} + CFBundleShortVersionString + {{.Info.ProductVersion}} + CFBundleIconFile + iconfile + LSMinimumSystemVersion + 10.13.0 + NSHighResolutionCapable + true + NSHumanReadableCopyright + {{.Info.Copyright}} + {{if .Info.FileAssociations}} + CFBundleDocumentTypes + + {{range .Info.FileAssociations}} + + CFBundleTypeExtensions + + {{.Ext}} + + CFBundleTypeName + {{.Name}} + CFBundleTypeRole + {{.Role}} + CFBundleTypeIconFile + {{.IconName}} + + {{end}} + + {{end}} + {{if .Info.Protocols}} + CFBundleURLTypes + + {{range .Info.Protocols}} + + CFBundleURLName + com.wails.{{.Scheme}} + CFBundleURLSchemes + + {{.Scheme}} + + CFBundleTypeRole + {{.Role}} + + {{end}} + + {{end}} + + diff --git a/rustle/build/windows/icon.ico b/rustle/build/windows/icon.ico new file mode 100644 index 0000000..f334798 Binary files /dev/null and b/rustle/build/windows/icon.ico differ diff --git a/rustle/build/windows/info.json b/rustle/build/windows/info.json new file mode 100644 index 0000000..9727946 --- /dev/null +++ b/rustle/build/windows/info.json @@ -0,0 +1,15 @@ +{ + "fixed": { + "file_version": "{{.Info.ProductVersion}}" + }, + "info": { + "0000": { + "ProductVersion": "{{.Info.ProductVersion}}", + "CompanyName": "{{.Info.CompanyName}}", + "FileDescription": "{{.Info.ProductName}}", + "LegalCopyright": "{{.Info.Copyright}}", + "ProductName": "{{.Info.ProductName}}", + "Comments": "{{.Info.Comments}}" + } + } +} \ No newline at end of file diff --git a/rustle/build/windows/installer/project.nsi b/rustle/build/windows/installer/project.nsi new file mode 100644 index 0000000..654ae2e --- /dev/null +++ b/rustle/build/windows/installer/project.nsi @@ -0,0 +1,114 @@ +Unicode true + +#### +## Please note: Template replacements don't work in this file. They are provided with default defines like +## mentioned underneath. +## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo. +## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually +## from outside of Wails for debugging and development of the installer. +## +## For development first make a wails nsis build to populate the "wails_tools.nsh": +## > wails build --target windows/amd64 --nsis +## Then you can call makensis on this file with specifying the path to your binary: +## For a AMD64 only installer: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app.exe +## For a ARM64 only installer: +## > makensis -DARG_WAILS_ARM64_BINARY=..\..\bin\app.exe +## For a installer with both architectures: +## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe +#### +## The following information is taken from the ProjectInfo file, but they can be overwritten here. +#### +## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}" +## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}" +## !define INFO_PRODUCTNAME "MyProduct" # Default "{{.Info.ProductName}}" +## !define INFO_PRODUCTVERSION "1.0.0" # Default "{{.Info.ProductVersion}}" +## !define INFO_COPYRIGHT "Copyright" # Default "{{.Info.Copyright}}" +### +## !define PRODUCT_EXECUTABLE "Application.exe" # Default "${INFO_PROJECTNAME}.exe" +## !define UNINST_KEY_NAME "UninstKeyInRegistry" # Default "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +#### +## !define REQUEST_EXECUTION_LEVEL "admin" # Default "admin" see also https://nsis.sourceforge.io/Docs/Chapter4.html +#### +## Include the wails tools +#### +!include "wails_tools.nsh" + +# The version information for this two must consist of 4 parts +VIProductVersion "${INFO_PRODUCTVERSION}.0" +VIFileVersion "${INFO_PRODUCTVERSION}.0" + +VIAddVersionKey "CompanyName" "${INFO_COMPANYNAME}" +VIAddVersionKey "FileDescription" "${INFO_PRODUCTNAME} Installer" +VIAddVersionKey "ProductVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "FileVersion" "${INFO_PRODUCTVERSION}" +VIAddVersionKey "LegalCopyright" "${INFO_COPYRIGHT}" +VIAddVersionKey "ProductName" "${INFO_PRODUCTNAME}" + +# Enable HiDPI support. https://nsis.sourceforge.io/Reference/ManifestDPIAware +ManifestDPIAware true + +!include "MUI.nsh" + +!define MUI_ICON "..\icon.ico" +!define MUI_UNICON "..\icon.ico" +# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314 +!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps +!define MUI_ABORTWARNING # This will warn the user if they exit from the installer. + +!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page. +# !insertmacro MUI_PAGE_LICENSE "resources\eula.txt" # Adds a EULA page to the installer +!insertmacro MUI_PAGE_DIRECTORY # In which folder install page. +!insertmacro MUI_PAGE_INSTFILES # Installing page. +!insertmacro MUI_PAGE_FINISH # Finished installation page. + +!insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page + +!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer + +## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1 +#!uninstfinalize 'signtool --file "%1"' +#!finalize 'signtool --file "%1"' + +Name "${INFO_PRODUCTNAME}" +OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file. +InstallDir "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}" # Default installing folder ($PROGRAMFILES is Program Files folder). +ShowInstDetails show # This will always show the installation details. + +Function .onInit + !insertmacro wails.checkArchitecture +FunctionEnd + +Section + !insertmacro wails.setShellContext + + !insertmacro wails.webview2runtime + + SetOutPath $INSTDIR + + !insertmacro wails.files + + CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" + + !insertmacro wails.associateFiles + !insertmacro wails.associateCustomProtocols + + !insertmacro wails.writeUninstaller +SectionEnd + +Section "uninstall" + !insertmacro wails.setShellContext + + RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath + + RMDir /r $INSTDIR + + Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" + Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" + + !insertmacro wails.unassociateFiles + !insertmacro wails.unassociateCustomProtocols + + !insertmacro wails.deleteUninstaller +SectionEnd diff --git a/rustle/build/windows/installer/wails_tools.nsh b/rustle/build/windows/installer/wails_tools.nsh new file mode 100644 index 0000000..2f6d321 --- /dev/null +++ b/rustle/build/windows/installer/wails_tools.nsh @@ -0,0 +1,249 @@ +# DO NOT EDIT - Generated automatically by `wails build` + +!include "x64.nsh" +!include "WinVer.nsh" +!include "FileFunc.nsh" + +!ifndef INFO_PROJECTNAME + !define INFO_PROJECTNAME "{{.Name}}" +!endif +!ifndef INFO_COMPANYNAME + !define INFO_COMPANYNAME "{{.Info.CompanyName}}" +!endif +!ifndef INFO_PRODUCTNAME + !define INFO_PRODUCTNAME "{{.Info.ProductName}}" +!endif +!ifndef INFO_PRODUCTVERSION + !define INFO_PRODUCTVERSION "{{.Info.ProductVersion}}" +!endif +!ifndef INFO_COPYRIGHT + !define INFO_COPYRIGHT "{{.Info.Copyright}}" +!endif +!ifndef PRODUCT_EXECUTABLE + !define PRODUCT_EXECUTABLE "${INFO_PROJECTNAME}.exe" +!endif +!ifndef UNINST_KEY_NAME + !define UNINST_KEY_NAME "${INFO_COMPANYNAME}${INFO_PRODUCTNAME}" +!endif +!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${UNINST_KEY_NAME}" + +!ifndef REQUEST_EXECUTION_LEVEL + !define REQUEST_EXECUTION_LEVEL "admin" +!endif + +RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" + +!ifdef ARG_WAILS_AMD64_BINARY + !define SUPPORTS_AMD64 +!endif + +!ifdef ARG_WAILS_ARM64_BINARY + !define SUPPORTS_ARM64 +!endif + +!ifdef SUPPORTS_AMD64 + !ifdef SUPPORTS_ARM64 + !define ARCH "amd64_arm64" + !else + !define ARCH "amd64" + !endif +!else + !ifdef SUPPORTS_ARM64 + !define ARCH "arm64" + !else + !error "Wails: Undefined ARCH, please provide at least one of ARG_WAILS_AMD64_BINARY or ARG_WAILS_ARM64_BINARY" + !endif +!endif + +!macro wails.checkArchitecture + !ifndef WAILS_WIN10_REQUIRED + !define WAILS_WIN10_REQUIRED "This product is only supported on Windows 10 (Server 2016) and later." + !endif + + !ifndef WAILS_ARCHITECTURE_NOT_SUPPORTED + !define WAILS_ARCHITECTURE_NOT_SUPPORTED "This product can't be installed on the current Windows architecture. Supports: ${ARCH}" + !endif + + ${If} ${AtLeastWin10} + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + Goto ok + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + Goto ok + ${EndIf} + !endif + + IfSilent silentArch notSilentArch + silentArch: + SetErrorLevel 65 + Abort + notSilentArch: + MessageBox MB_OK "${WAILS_ARCHITECTURE_NOT_SUPPORTED}" + Quit + ${else} + IfSilent silentWin notSilentWin + silentWin: + SetErrorLevel 64 + Abort + notSilentWin: + MessageBox MB_OK "${WAILS_WIN10_REQUIRED}" + Quit + ${EndIf} + + ok: +!macroend + +!macro wails.files + !ifdef SUPPORTS_AMD64 + ${if} ${IsNativeAMD64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_AMD64_BINARY}" + ${EndIf} + !endif + + !ifdef SUPPORTS_ARM64 + ${if} ${IsNativeARM64} + File "/oname=${PRODUCT_EXECUTABLE}" "${ARG_WAILS_ARM64_BINARY}" + ${EndIf} + !endif +!macroend + +!macro wails.writeUninstaller + WriteUninstaller "$INSTDIR\uninstall.exe" + + SetRegView 64 + WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayVersion" "${INFO_PRODUCTVERSION}" + WriteRegStr HKLM "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\${PRODUCT_EXECUTABLE}" + WriteRegStr HKLM "${UNINST_KEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\"" + WriteRegStr HKLM "${UNINST_KEY}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S" + + ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 + IntFmt $0 "0x%08X" $0 + WriteRegDWORD HKLM "${UNINST_KEY}" "EstimatedSize" "$0" +!macroend + +!macro wails.deleteUninstaller + Delete "$INSTDIR\uninstall.exe" + + SetRegView 64 + DeleteRegKey HKLM "${UNINST_KEY}" +!macroend + +!macro wails.setShellContext + ${If} ${REQUEST_EXECUTION_LEVEL} == "admin" + SetShellVarContext all + ${else} + SetShellVarContext current + ${EndIf} +!macroend + +# Install webview2 by launching the bootstrapper +# See https://docs.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#online-only-deployment +!macro wails.webview2runtime + !ifndef WAILS_INSTALL_WEBVIEW_DETAILPRINT + !define WAILS_INSTALL_WEBVIEW_DETAILPRINT "Installing: WebView2 Runtime" + !endif + + SetRegView 64 + # If the admin key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + + ${If} ${REQUEST_EXECUTION_LEVEL} == "user" + # If the installer is run in user level, check the user specific key exists and is not empty then webview2 is already installed + ReadRegStr $0 HKCU "Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv" + ${If} $0 != "" + Goto ok + ${EndIf} + ${EndIf} + + SetDetailsPrint both + DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}" + SetDetailsPrint listonly + + InitPluginsDir + CreateDirectory "$pluginsdir\webview2bootstrapper" + SetOutPath "$pluginsdir\webview2bootstrapper" + File "tmp\MicrosoftEdgeWebview2Setup.exe" + ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install' + + SetDetailsPrint both + ok: +!macroend + +# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b +!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0" + + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}" + + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open" + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}` + WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}` +!macroend + +!macro APP_UNASSOCIATE EXT FILECLASS + ; Backup the previously associated file class + ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup` + WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0" + + DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}` +!macroend + +!macro wails.associateFiles + ; Create file associations + {{range .Info.FileAssociations}} + !insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + + File "..\{{.IconName}}.ico" + {{end}} +!macroend + +!macro wails.unassociateFiles + ; Delete app associations + {{range .Info.FileAssociations}} + !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}" + + Delete "$INSTDIR\{{.IconName}}.ico" + {{end}} +!macroend + +!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" +!macroend + +!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" +!macroend + +!macro wails.associateCustomProtocols + ; Create custom protocols associations + {{range .Info.Protocols}} + !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + + {{end}} +!macroend + +!macro wails.unassociateCustomProtocols + ; Delete app custom protocol associations + {{range .Info.Protocols}} + !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}" + {{end}} +!macroend diff --git a/rustle/build/windows/wails.exe.manifest b/rustle/build/windows/wails.exe.manifest new file mode 100644 index 0000000..17e1a23 --- /dev/null +++ b/rustle/build/windows/wails.exe.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + true/pm + permonitorv2,permonitor + + + \ No newline at end of file diff --git a/rustle/frontend/index.html b/rustle/frontend/index.html new file mode 100644 index 0000000..5f71d08 --- /dev/null +++ b/rustle/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + rustle + + +
+ + + + diff --git a/rustle/frontend/package-lock.json b/rustle/frontend/package-lock.json new file mode 100644 index 0000000..3b12c51 --- /dev/null +++ b/rustle/frontend/package-lock.json @@ -0,0 +1,1419 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.17", + "@types/react-dom": "^18.0.6", + "@vitejs/plugin-react": "^2.0.1", + "typescript": "^4.6.4", + "vite": "^3.0.7" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz", + "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-2.2.0.tgz", + "integrity": "sha512-FFpefhvExd1toVRlokZgxgy2JtnBOdp4ZDsq7ldCWaqGSGn9UhWMAVm/1lxPL14JfNS5yGz+s9yFrQY6shoStA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.19.6", + "@babel/plugin-transform-react-jsx": "^7.19.0", + "@babel/plugin-transform-react-jsx-development": "^7.18.6", + "@babel/plugin-transform-react-jsx-self": "^7.18.6", + "@babel/plugin-transform-react-jsx-source": "^7.19.6", + "magic-string": "^0.26.7", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^3.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.2.tgz", + "integrity": "sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001733", + "electron-to-chromium": "^1.5.199", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001734", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001734.tgz", + "integrity": "sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.200", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.200.tgz", + "integrity": "sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.7.tgz", + "integrity": "sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rollup": { + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz", + "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.15.9", + "postcss": "^8.4.18", + "resolve": "^1.22.1", + "rollup": "^2.79.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/rustle/frontend/package.json b/rustle/frontend/package.json new file mode 100644 index 0000000..f0106ca --- /dev/null +++ b/rustle/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.17", + "@types/react-dom": "^18.0.6", + "@vitejs/plugin-react": "^2.0.1", + "typescript": "^4.6.4", + "vite": "^3.0.7" + } +} \ No newline at end of file diff --git a/rustle/frontend/package.json.md5 b/rustle/frontend/package.json.md5 new file mode 100755 index 0000000..1ab71cf --- /dev/null +++ b/rustle/frontend/package.json.md5 @@ -0,0 +1 @@ +f26173c7304a0bf8ea5c86eb567e7db2 \ No newline at end of file diff --git a/rustle/frontend/src/App.css b/rustle/frontend/src/App.css new file mode 100644 index 0000000..8b7cdf2 --- /dev/null +++ b/rustle/frontend/src/App.css @@ -0,0 +1,277 @@ +.container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +h1 { + color: #2c3e50; + text-align: center; + margin-bottom: 30px; +} + +/* Tab Styles */ +.tabs { + display: flex; + border-bottom: 2px solid #e1e8ed; + margin-bottom: 20px; +} + +.tab { + padding: 12px 24px; + background: none; + border: none; + cursor: pointer; + font-size: 14px; + color: #657786; + transition: all 0.2s ease; + border-bottom: 2px solid transparent; +} + +.tab:hover { + color: #1da1f2; + background-color: #f7f9fa; +} + +.tab-active { + padding: 12px 24px; + background: none; + border: none; + cursor: pointer; + font-size: 14px; + color: #1da1f2; + border-bottom: 2px solid #1da1f2; + font-weight: 600; +} + +/* Tab Content */ +.tab-content { + min-height: 400px; +} + +.section { + background: #ffffff; + border: 1px solid #e1e8ed; + border-radius: 8px; + padding: 20px; + margin-bottom: 20px; +} + +.section h3 { + margin-top: 0; + margin-bottom: 20px; + color: #14171a; +} + +/* Form Styles */ +.form-row { + display: flex; + gap: 12px; + margin-bottom: 16px; + align-items: flex-start; +} + +.form-row:last-child { + margin-bottom: 0; +} + +input[type="text"], .uri-input, .search-input { + flex: 1; + padding: 12px 16px; + border: 1px solid #ccd6dd; + border-radius: 6px; + font-size: 14px; + transition: border-color 0.2s ease; +} + +input[type="text"]:focus, .uri-input:focus, .search-input:focus { + outline: none; + border-color: #1da1f2; + box-shadow: 0 0 0 2px rgba(29, 161, 242, 0.1); +} + +.markdown-editor { + width: 100%; + padding: 12px 16px; + border: 1px solid #ccd6dd; + border-radius: 6px; + font-size: 14px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + resize: vertical; + transition: border-color 0.2s ease; +} + +.markdown-editor:focus { + outline: none; + border-color: #1da1f2; + box-shadow: 0 0 0 2px rgba(29, 161, 242, 0.1); +} + +button { + padding: 12px 24px; + background-color: #1da1f2; + color: white; + border: none; + border-radius: 6px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.2s ease; + white-space: nowrap; +} + +button:hover:not(:disabled) { + background-color: #1991da; +} + +button:disabled { + background-color: #aab8c2; + cursor: not-allowed; +} + +/* Response Section */ +.response-section { + margin-top: 30px; +} + +.response-section h3 { + margin-bottom: 10px; + color: #14171a; +} + +.response { + background-color: #f8f9fa; + border: 1px solid #e1e8ed; + border-radius: 6px; + padding: 16px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 12px; + line-height: 1.4; + max-height: 400px; + overflow-y: auto; + white-space: pre-wrap; + word-wrap: break-word; +} + +.response:empty { + color: #657786; + font-style: italic; +} + +.response:empty::before { + content: "Response will appear here..."; +} + +/* DHT and Network specific styles */ +.dht-status { + color: #657786; + font-size: 12px; + margin: 0; + font-style: italic; +} + +.network-info { + background: #f7f9fa; + border-radius: 6px; + padding: 16px; + margin-top: 16px; +} + +.network-info p { + margin: 0 0 12px 0; + color: #14171a; +} + +.network-info ul { + margin: 0; + padding-left: 20px; +} + +.network-info li { + margin: 6px 0; + color: #657786; + font-size: 14px; +} + +.network-info strong { + color: #14171a; +} + +hr { + border: none; + border-top: 1px solid #e1e8ed; + margin: 24px 0; +} + +/* Status indicators */ +.status-connected { + color: #17bf63; + font-weight: 600; +} + +.status-disconnected { + color: #e0245e; + font-weight: 600; +} + +.status-warning { + color: #ffad1f; + font-weight: 600; +} + +/* Demo Content Styles */ +.demo-content { + margin-top: 20px; + border: 1px solid #e1e8ed; + border-radius: 8px; + overflow: hidden; + background: white; +} + +.form-row label { + font-weight: 600; + color: #14171a; + min-width: 100px; + display: flex; + align-items: center; +} + +.form-row select { + padding: 8px 12px; + border: 1px solid #ccd6dd; + border-radius: 6px; + font-size: 14px; + background: white; + cursor: pointer; + transition: border-color 0.2s ease; +} + +.form-row select:focus { + outline: none; + border-color: #1da1f2; + box-shadow: 0 0 0 2px rgba(29, 161, 242, 0.1); +} + +/* Responsive Design */ +@media (max-width: 768px) { + .tabs { + flex-wrap: wrap; + } + + .form-row { + flex-direction: column; + align-items: stretch; + } + + .form-row input[type="text"], + .form-row .uri-input, + .form-row .search-input { + margin-bottom: 8px; + } + + .form-row label { + min-width: auto; + margin-bottom: 4px; + } +} \ No newline at end of file diff --git a/rustle/frontend/src/App.tsx b/rustle/frontend/src/App.tsx new file mode 100644 index 0000000..75267ad --- /dev/null +++ b/rustle/frontend/src/App.tsx @@ -0,0 +1,590 @@ +import { useState, useEffect } from 'react'; +import './App.css'; +import { ContentRenderer } from './components/ContentRenderer'; +import { createMockEnvelope, Envelope } from './types/Envelope'; +import * as WailsApp from './wails/go/main/App'; + +function App() { + const [activeTab, setActiveTab] = useState<'get' | 'post' | 'search' | 'renderer'>('renderer'); + const [uri, setUri] = useState('ucxl://rustle:browser@demo:showcase/*^/sample.md'); + const [response, setResponse] = useState(''); + const [loading, setLoading] = useState(false); + const [currentEnvelope, setCurrentEnvelope] = useState(null); + const [renderMode, setRenderMode] = useState<'full' | 'preview' | 'minimal'>('full'); + const [bzzzStatus, setBzzzStatus] = useState(null); + + // Form states + const [markdownContent, setMarkdownContent] = useState('# Example Document\\n\\nThis is example markdown content.'); + const [author, setAuthor] = useState('User'); + const [title, setTitle] = useState('Test Document'); + const [searchText, setSearchText] = useState(''); + const [searchTags, setSearchTags] = useState(''); + + // Load BZZZ status on mount + useEffect(() => { + const loadBZZZStatus = async () => { + try { + const status = await WailsApp.GetBZZZStatus(); + setBzzzStatus(status); + } catch (error) { + console.error('Failed to load BZZZ status:', error); + } + }; + + loadBZZZStatus(); + // Refresh status every 30 seconds + const interval = setInterval(loadBZZZStatus, 30000); + return () => clearInterval(interval); + }, []); + + // Function to fetch real content from backend + const fetchRealContent = async (uri: string): Promise => { + try { + const result = await WailsApp.GetUCXLContent(uri); + + return { + id: result.content_hash.substring(0, 8), // Use hash substring as ID + ucxl_uri: { toString: () => result.ucxl_uri }, + content: { + raw: result.content.raw, + content_type: result.content.content_type, + encoding: result.content.encoding + }, + metadata: { + author: result.metadata.author, + title: result.metadata.title, + tags: result.metadata.tags ? result.metadata.tags.split(',') : [], + source: result.metadata.source, + context_data: {} + }, + timestamp: result.timestamp, + version: result.version, + content_hash: result.content_hash + }; + } catch (error) { + console.error('Failed to fetch content:', error); + return null; + } + }; + + // Function to validate UCXL address + const validateAddress = async (uri: string) => { + try { + const result = await WailsApp.ValidateUCXLAddress(uri); + return result; + } catch (error) { + console.error('Failed to validate address:', error); + return { valid: false, error: 'Validation failed' }; + } + }; + + // Demo content creation functions + const createDemoMarkdownEnvelope = (): Envelope => { + return createMockEnvelope( + 'ucxl://claude:coder@chorus:content-demo/docs/markdown-sample.md', + `# RUSTLE - UCXL Content Browser + +This is a **demonstration** of the RUSTLE content rendering engine running on **Wails**. + +## Features + +- Native desktop application with Go backend +- React-based content rendering components +- Multi-format content support: + - Markdown with syntax highlighting + - Interactive JSON visualization + - Code rendering for multiple languages + - Image display with zoom functionality + - UCXL contextual metadata rendering + +### Code Example + +\`\`\`go +package main + +import ( + "context" + "fmt" + "log" + + "github.com/wailsapp/wails/v2/pkg/runtime" +) + +// App struct +type App struct { + ctx context.Context +} + +// NewApp creates a new App application struct +func NewApp() *App { + return &App{} +} + +// startup is called when the app starts +func (a *App) startup(ctx context.Context) { + a.ctx = ctx + runtime.LogInfo(ctx, "RUSTLE started successfully!") +} +\`\`\` + +### Integration with BZZZ + +RUSTLE integrates seamlessly with the BZZZ distributed system: + +1. **SLURP Integration** - Contextual intelligence processing +2. **COOEE Messaging** - Agent-to-agent communication +3. **DHT Storage** - Distributed content storage +4. **UCXL Addressing** - Universal contextual locators + +This native desktop application provides a much cleaner and more reliable experience than the previous Tauri implementation.`, + 'text/markdown', + { + title: 'RUSTLE Wails Demo', + author: 'BZZZ Team', + tags: ['demo', 'markdown', 'wails', 'native-app'], + source: 'rustle-wails-demo', + context_data: { + demo_type: 'markdown', + platform: 'wails', + features: ['native-desktop', 'go-backend', 'react-frontend'], + complexity: 'medium' + } + } + ); + }; + + const createDemoJSONEnvelope = (): Envelope => { + const jsonContent = { + application_info: { + name: "RUSTLE", + platform: "Wails v2", + backend: "Go", + frontend: "React + TypeScript" + }, + system_integration: { + bzzz_connected: true, + slurp_enabled: true, + cooee_messaging: true, + dht_available: true + }, + performance_metrics: { + startup_time_ms: 250, + memory_usage_mb: 45, + render_performance: "excellent", + native_integration: true + }, + content_capabilities: { + markdown_rendering: true, + code_highlighting: true, + json_visualization: true, + image_display: true, + context_metadata: true, + supported_formats: [ + "text/markdown", + "application/json", + "text/x-go", + "text/x-rust", + "application/javascript", + "text/x-python", + "image/png", + "image/jpeg" + ] + } + }; + + return createMockEnvelope( + 'ucxl://rustle:system@wails:info/app/capabilities.json', + JSON.stringify(jsonContent, null, 2), + 'application/json', + { + title: 'RUSTLE Application Info', + author: 'RUSTLE System', + tags: ['json', 'app-info', 'capabilities', 'wails'], + source: 'rustle-system-info', + context_data: { + demo_type: 'json', + data_structure: 'nested', + interactive: true, + platform: 'wails' + } + } + ); + }; + + const createDemoCodeEnvelope = (): Envelope => { + const goCode = `package main + +import ( + "context" + "embed" + "fmt" + + "github.com/wailsapp/wails/v2" + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" +) + +//go:embed all:frontend/dist +var assets embed.FS + +// App struct +type App struct { + ctx context.Context +} + +// NewApp creates a new App application struct +func NewApp() *App { + return &App{} +} + +// startup is called when the app starts +func (a *App) startup(ctx context.Context) { + a.ctx = ctx +} + +// GetUCXLContent retrieves content by UCXL address +func (a *App) GetUCXLContent(uri string) (map[string]interface{}, error) { + // Integration with BZZZ/SLURP system + content, err := bzzz.RetrieveContent(uri) + if err != nil { + return nil, fmt.Errorf("failed to retrieve content: %w", err) + } + + // Process through SLURP if needed + if contextRequired(uri) { + content, err = slurp.ProcessContext(content) + if err != nil { + return nil, fmt.Errorf("context processing failed: %w", err) + } + } + + return content, nil +} + +// PostUCXLContent stores content at UCXL address +func (a *App) PostUCXLContent(uri string, content string) error { + envelope := &bzzz.Envelope{ + UCXL: uri, + Content: content, + Timestamp: time.Now(), + } + + return bzzz.StoreContent(envelope) +} + +func main() { + // Create an instance of the app structure + app := NewApp() + + // Create application with options + err := wails.Run(&options.App{ + Title: "RUSTLE - UCXL Content Browser", + Width: 1200, + Height: 800, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + OnStartup: app.startup, + }) + + if err != nil { + println("Error:", err.Error()) + } +}`; + + return createMockEnvelope( + 'ucxl://rustle:wails@main:app/src/main.go', + goCode, + 'text/x-go', + { + title: 'RUSTLE Wails Main Application', + author: 'BZZZ Team', + tags: ['go', 'wails', 'main', 'application'], + source: 'rustle-main-go', + context_data: { + demo_type: 'code', + language: 'go', + framework: 'wails', + complexity: 'high' + } + } + ); + }; + + return ( +
+
+ 🚀 RUSTLE - UCXL Content Browser (Wails) +
+ + {/* Tab Navigation */} +
+ + +
+ + {/* Tab Content */} +
+ {activeTab === 'renderer' && ( +
+
+ + + + + + +
+ + {currentEnvelope && ( +
+ +
+ )} + + {!currentEnvelope && ( +
+ 👆 Click a demo button above to test the content rendering engine +
+ )} +
+ )} + + {activeTab === 'get' && ( +
+

Retrieve Content

+ + {/* BZZZ Status */} + {bzzzStatus && ( +
+ BZZZ Status: + + {bzzzStatus.connected ? 'Connected' : 'Mock Mode'} + + + Agent: {bzzzStatus.agent}/{bzzzStatus.role} | Project: {bzzzStatus.project} | Peers: {bzzzStatus.peers} + +
+ )} + +
+ setUri(e.target.value)} + placeholder="ucxl://agent:role@project:task/temporal/path" + style={{ + width: '70%', + padding: '8px 12px', + marginRight: '12px', + border: '1px solid #ced4da', + borderRadius: '4px' + }} + /> + + +
+
+ {response || 'Content will appear here...'} +
+ + {/* If we have current envelope, show it rendered */} + {currentEnvelope && ( +
+

Rendered Content:

+
+ +
+
+ )} +
+ )} +
+
+ ); +} + +export default App; \ No newline at end of file diff --git a/rustle/frontend/src/assets/fonts/OFL.txt b/rustle/frontend/src/assets/fonts/OFL.txt new file mode 100644 index 0000000..9cac04c --- /dev/null +++ b/rustle/frontend/src/assets/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com), + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/rustle/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/rustle/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 new file mode 100644 index 0000000..2f9cc59 Binary files /dev/null and b/rustle/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 differ diff --git a/rustle/frontend/src/assets/images/logo-universal.png b/rustle/frontend/src/assets/images/logo-universal.png new file mode 100644 index 0000000..99ac71f Binary files /dev/null and b/rustle/frontend/src/assets/images/logo-universal.png differ diff --git a/rustle/frontend/src/components/ContentRenderer.css b/rustle/frontend/src/components/ContentRenderer.css new file mode 100644 index 0000000..8bbd630 --- /dev/null +++ b/rustle/frontend/src/components/ContentRenderer.css @@ -0,0 +1,99 @@ +.content-renderer { + border: 1px solid #e0e0e0; + border-radius: 8px; + overflow: hidden; + background: white; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.content-renderer--minimal { + border: none; + box-shadow: none; + background: transparent; +} + +.content-renderer--preview { + max-height: 400px; + overflow: hidden; +} + +.content-body { + padding: 16px; +} + +.content-renderer--minimal .content-body { + padding: 8px; +} + +.content-unknown { + padding: 16px; + background: #f9f9f9; + border-left: 4px solid #ff9800; +} + +.content-unknown h4 { + margin: 0 0 12px 0; + color: #ff9800; + font-weight: 500; +} + +.content-raw { + background: #f5f5f5; + padding: 12px; + border-radius: 4px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 14px; + max-height: 300px; + overflow: auto; + white-space: pre-wrap; + word-break: break-word; +} + +.content-footer { + background: #f8f9fa; + border-top: 1px solid #e0e0e0; + padding: 8px 16px; +} + +.content-stats { + display: flex; + gap: 16px; + font-size: 12px; + color: #666; +} + +.content-stats span { + padding: 2px 6px; + background: #e9ecef; + border-radius: 3px; + font-family: monospace; +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .content-renderer { + background: #1e1e1e; + border-color: #333; + color: #e0e0e0; + } + + .content-unknown { + background: #2d2d2d; + color: #e0e0e0; + } + + .content-raw { + background: #2d2d2d; + color: #e0e0e0; + } + + .content-footer { + background: #2d2d2d; + border-color: #333; + } + + .content-stats span { + background: #333; + color: #ccc; + } +} \ No newline at end of file diff --git a/rustle/frontend/src/components/ContentRenderer.tsx b/rustle/frontend/src/components/ContentRenderer.tsx new file mode 100644 index 0000000..e867756 --- /dev/null +++ b/rustle/frontend/src/components/ContentRenderer.tsx @@ -0,0 +1,118 @@ +import React from 'react'; +import { Envelope } from '../types/Envelope'; +import { MarkdownRenderer } from './renderers/MarkdownRenderer'; +import { CodeRenderer } from './renderers/CodeRenderer'; +import { JSONRenderer } from './renderers/JSONRenderer'; +import { ImageRenderer } from './renderers/ImageRenderer'; +// import { ContextRenderer as UCXLContextRenderer } from './renderers/ContextRenderer'; +import { ErrorRenderer } from './renderers/ErrorRenderer'; +import { MetadataRenderer } from './renderers/MetadataRenderer'; +import './ContentRenderer.css'; + +interface ContentRendererProps { + envelope: Envelope; + showMetadata?: boolean; + renderMode?: 'full' | 'preview' | 'minimal'; +} + +export const ContentRenderer: React.FC = ({ + envelope, + showMetadata = true, + renderMode = 'full' +}) => { + const renderContent = () => { + const contentType = envelope.content.content_type; + const content = envelope.content.raw; + + try { + switch (contentType) { + // Markdown content + case 'text/markdown': + case 'text/x-markdown': + return ; + + // Code files + case 'text/plain': + case 'text/x-go': + case 'text/x-rust': + case 'text/x-python': + case 'text/x-javascript': + case 'text/x-typescript': + case 'application/javascript': + case 'application/typescript': + return ; + + // JSON and structured data + case 'application/json': + case 'text/json': + return ; + + // Image content + case 'image/png': + case 'image/jpeg': + case 'image/jpg': + case 'image/gif': + case 'image/webp': + case 'image/svg+xml': + return ; + + // UCXL contextual content + case 'application/ucxl-context': + case 'application/ucxl-metadata': + // return ; + return
Context rendering temporarily disabled
; + + // Decision records and reports + case 'application/ucxl-decision': + case 'application/ucxl-report': + return ; + + // Unknown or unsupported content types + default: + return ( +
+

Unknown Content Type: {contentType}

+
{content}
+
+ ); + } + } catch (error) { + return ; + } + }; + + return ( +
+ {showMetadata && ( + + )} + +
+ {renderContent()} +
+ + {renderMode === 'full' && ( +
+
+ Size: {envelope.content.raw.length} bytes + Type: {envelope.content.content_type} + Encoding: {envelope.content.encoding} + Hash: {envelope.content_hash.substring(0, 8)}... +
+
+ )} +
+ ); +}; + +export default ContentRenderer; \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/CodeRenderer.css b/rustle/frontend/src/components/renderers/CodeRenderer.css new file mode 100644 index 0000000..4f6bf48 --- /dev/null +++ b/rustle/frontend/src/components/renderers/CodeRenderer.css @@ -0,0 +1,262 @@ +.code-renderer { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; + overflow: hidden; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; +} + +.code-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background: #e9ecef; + border-bottom: 1px solid #dee2e6; +} + +.language-label { + display: flex; + align-items: center; + gap: 8px; +} + +.language-badge { + background: #007bff; + color: white; + padding: 2px 8px; + border-radius: 12px; + font-size: 11px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.content-type { + font-size: 11px; + color: #6c757d; + font-family: monospace; +} + +.code-actions { + display: flex; + gap: 8px; +} + +.copy-button { + background: #28a745; + color: white; + border: none; + padding: 4px 8px; + border-radius: 4px; + font-size: 11px; + cursor: pointer; + transition: background-color 0.2s; +} + +.copy-button:hover { + background: #218838; +} + +.code-container { + display: flex; + background: white; + max-height: 600px; + overflow: auto; +} + +.code-renderer--preview .code-container { + max-height: 400px; +} + +.code-renderer--minimal .code-container { + max-height: 200px; +} + +.line-numbers { + background: #f8f9fa; + border-right: 1px solid #e9ecef; + padding: 12px 8px; + text-align: right; + user-select: none; + min-width: 40px; + flex-shrink: 0; +} + +.line-number { + line-height: 1.4; + color: #6c757d; + font-size: 12px; + padding-right: 8px; +} + +.code-content { + flex: 1; + overflow: auto; +} + +.code-content pre { + margin: 0; + padding: 12px; + background: transparent; + overflow: visible; +} + +.code-content code { + font-family: inherit; + font-size: 13px; + line-height: 1.4; + color: #333; + white-space: pre; +} + +/* Syntax highlighting styles */ +.keyword { + color: #0066cc; + font-weight: 600; +} + +.type { + color: #007f7f; + font-weight: 500; +} + +.comment { + color: #998; + font-style: italic; +} + +.string { + color: #d14; +} + +.url { + color: #3498db; + text-decoration: underline; +} + +.path { + color: #8e44ad; +} + +.number { + color: #009999; +} + +/* Language-specific badge colors */ +.language-badge:has-text("go") { + background: #00add8; +} + +.language-badge:has-text("rust") { + background: #ce422b; +} + +.language-badge:has-text("python") { + background: #3776ab; +} + +.language-badge:has-text("javascript") { + background: #f7df1e; + color: #333; +} + +.language-badge:has-text("typescript") { + background: #3178c6; +} + +.content-truncated { + text-align: center; + padding: 12px; + background: #f8f9fa; + border-top: 1px solid #e9ecef; + color: #6c757d; + font-style: italic; + font-size: 12px; +} + +/* Minimal mode styling */ +.code-renderer--minimal .code-header { + padding: 4px 8px; +} + +.code-renderer--minimal .language-badge { + font-size: 10px; + padding: 1px 6px; +} + +.code-renderer--minimal .code-content pre { + padding: 8px; +} + +.code-renderer--minimal .code-content code { + font-size: 12px; +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .code-renderer { + background: #2d2d2d; + border-color: #404040; + } + + .code-header { + background: #404040; + border-color: #555; + } + + .content-type { + color: #adb5bd; + } + + .code-container { + background: #1e1e1e; + } + + .line-numbers { + background: #2d2d2d; + border-color: #404040; + } + + .line-number { + color: #888; + } + + .code-content code { + color: #e0e0e0; + } + + .keyword { + color: #569cd6; + } + + .type { + color: #4ec9b0; + } + + .comment { + color: #6a9955; + } + + .string { + color: #ce9178; + } + + .url { + color: #74c0fc; + } + + .path { + color: #c678dd; + } + + .number { + color: #b5cea8; + } + + .content-truncated { + background: #2d2d2d; + border-color: #404040; + color: #adb5bd; + } +} \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/CodeRenderer.tsx b/rustle/frontend/src/components/renderers/CodeRenderer.tsx new file mode 100644 index 0000000..7d2c597 --- /dev/null +++ b/rustle/frontend/src/components/renderers/CodeRenderer.tsx @@ -0,0 +1,216 @@ +import React, { useMemo } from 'react'; +import './CodeRenderer.css'; + +interface CodeRendererProps { + content: string; + contentType: string; + renderMode?: 'full' | 'preview' | 'minimal'; +} + +export const CodeRenderer: React.FC = ({ + content, + contentType, + renderMode = 'full' +}) => { + const getLanguageFromContentType = (contentType: string): string => { + const typeMap: Record = { + 'text/x-go': 'go', + 'text/x-rust': 'rust', + 'text/x-python': 'python', + 'text/x-javascript': 'javascript', + 'text/x-typescript': 'typescript', + 'application/javascript': 'javascript', + 'application/typescript': 'typescript', + 'text/plain': 'text' + }; + return typeMap[contentType] || 'text'; + }; + + const language = getLanguageFromContentType(contentType); + + // Basic syntax highlighting patterns + const applySyntaxHighlighting = (code: string, lang: string): string => { + let highlighted = escapeHtml(code); + + switch (lang) { + case 'go': + highlighted = highlightGo(highlighted); + break; + case 'rust': + highlighted = highlightRust(highlighted); + break; + case 'python': + highlighted = highlightPython(highlighted); + break; + case 'javascript': + case 'typescript': + highlighted = highlightJavaScript(highlighted); + break; + default: + // For plain text, just highlight URLs and file paths + highlighted = highlightGeneric(highlighted); + } + + return highlighted; + }; + + const escapeHtml = (text: string): string => { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + }; + + const highlightGo = (code: string): string => { + // Go keywords + code = code.replace(/\b(package|import|func|var|const|type|struct|interface|if|else|for|range|switch|case|default|return|defer|go|chan|select|map|make|new|len|cap|append|copy|delete|close|nil|true|false)\b/g, '$1'); + + // Go types + code = code.replace(/\b(int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|float32|float64|complex64|complex128|bool|byte|rune|string|error)\b/g, '$1'); + + // Comments + code = code.replace(/(\/\/.*$)/gm, '$1'); + code = code.replace(/(\/\*[\s\S]*?\*\/)/g, '$1'); + + // Strings + code = code.replace(/(".*?"|`.*?`)/g, '$1'); + + return code; + }; + + const highlightRust = (code: string): string => { + // Rust keywords + code = code.replace(/\b(fn|let|mut|const|static|use|mod|pub|struct|enum|impl|trait|for|if|else|match|while|loop|break|continue|return|true|false|Some|None|Ok|Err|Self|self|super|crate)\b/g, '$1'); + + // Rust types + code = code.replace(/\b(i8|i16|i32|i64|i128|isize|u8|u16|u32|u64|u128|usize|f32|f64|bool|char|str|String|Vec|Option|Result)\b/g, '$1'); + + // Comments + code = code.replace(/(\/\/.*$)/gm, '$1'); + code = code.replace(/(\/\*[\s\S]*?\*\/)/g, '$1'); + + // Strings + code = code.replace(/(".*?"|'.*?')/g, '$1'); + + return code; + }; + + const highlightPython = (code: string): string => { + // Python keywords + code = code.replace(/\b(def|class|import|from|if|elif|else|for|while|try|except|finally|with|as|return|yield|lambda|and|or|not|in|is|True|False|None|pass|break|continue|global|nonlocal)\b/g, '$1'); + + // Python built-ins + code = code.replace(/\b(str|int|float|bool|list|dict|tuple|set|len|range|enumerate|zip|map|filter|print|input|open|type|isinstance|hasattr|getattr|setattr)\b/g, '$1'); + + // Comments + code = code.replace(/(#.*$)/gm, '$1'); + + // Strings + code = code.replace(/("""[\s\S]*?"""|'''[\s\S]*?'''|".*?"|'.*?')/g, '$1'); + + return code; + }; + + const highlightJavaScript = (code: string): string => { + // JavaScript keywords + code = code.replace(/\b(function|const|let|var|if|else|for|while|do|switch|case|default|return|break|continue|try|catch|finally|throw|new|this|class|extends|import|export|from|as|async|await|yield|true|false|null|undefined)\b/g, '$1'); + + // JavaScript types and built-ins + code = code.replace(/\b(String|Number|Boolean|Array|Object|Date|RegExp|Promise|Map|Set|Symbol|console|window|document|Math|JSON)\b/g, '$1'); + + // Comments + code = code.replace(/(\/\/.*$)/gm, '$1'); + code = code.replace(/(\/\*[\s\S]*?\*\/)/g, '$1'); + + // Strings + code = code.replace(/(`.*?`|".*?"|'.*?')/g, '$1'); + + return code; + }; + + const highlightGeneric = (code: string): string => { + // URLs + code = code.replace(/(https?:\/\/[^\s]+)/g, '$1'); + + // File paths + code = code.replace(/(\/[^\s]*)/g, '$1'); + + // Numbers + code = code.replace(/\b(\d+\.?\d*)\b/g, '$1'); + + return code; + }; + + const processedContent = useMemo(() => { + if (renderMode === 'preview') { + const lines = content.split('\n'); + const previewLines = lines.slice(0, 20); + return applySyntaxHighlighting(previewLines.join('\n'), language); + } + return applySyntaxHighlighting(content, language); + }, [content, language, renderMode]); + + const getLineNumbers = (text: string): string[] => { + const lines = text.split('\n'); + return lines.map((_, index) => (index + 1).toString()); + }; + + const lineNumbers = getLineNumbers(renderMode === 'preview' ? content.split('\n').slice(0, 20).join('\n') : content); + + const handleCopyToClipboard = () => { + navigator.clipboard.writeText(content).then(() => { + // Could add a toast notification here + console.log('Code copied to clipboard'); + }); + }; + + return ( +
+
+
+ {language} + {contentType} +
+ {renderMode === 'full' && ( +
+ +
+ )} +
+ +
+ {renderMode !== 'minimal' && ( +
+ {lineNumbers.map((num, index) => ( +
+ {num} +
+ ))} +
+ )} + +
+
+            
+          
+
+
+ + {renderMode === 'preview' && content.split('\n').length > 20 && ( +
+ Showing first 20 lines. Click to view full content. +
+ )} +
+ ); +}; + +export default CodeRenderer; \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/ContextRenderer.css b/rustle/frontend/src/components/renderers/ContextRenderer.css new file mode 100644 index 0000000..a9474bd --- /dev/null +++ b/rustle/frontend/src/components/renderers/ContextRenderer.css @@ -0,0 +1,306 @@ +.context-renderer { + background: white; + border: 1px solid #e9ecef; + border-radius: 8px; + overflow: hidden; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; +} + +.context-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; +} + +.context-info { + display: flex; + align-items: center; + gap: 12px; +} + +.format-badge { + background: rgba(255, 255, 255, 0.2); + color: white; + padding: 4px 12px; + border-radius: 16px; + font-size: 12px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.generated-time { + font-size: 11px; + opacity: 0.9; + font-family: monospace; +} + +.context-content { + padding: 16px; +} + +.context-metadata { + display: flex; + flex-direction: column; + gap: 16px; +} + +.context-type { + margin-bottom: 8px; +} + +.type-badge { + background: #17a2b8; + color: white; + padding: 4px 12px; + border-radius: 20px; + font-size: 12px; + font-weight: 500; + text-transform: capitalize; +} + +.metadata-section { + border-left: 3px solid #e9ecef; + padding-left: 12px; +} + +.metadata-section h4 { + margin: 0 0 8px 0; + font-size: 14px; + font-weight: 600; + color: #495057; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.metadata-section p { + margin: 0; + line-height: 1.5; + color: #6c757d; +} + +.tag-list { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 4px; +} + +.tag { + padding: 4px 8px; + border-radius: 12px; + font-size: 11px; + font-weight: 500; + text-transform: lowercase; +} + +.tech-tag { + background: #e3f2fd; + color: #1565c0; + border: 1px solid #bbdefb; +} + +.context-tag { + background: #f3e5f5; + color: #7b1fa2; + border: 1px solid #e1bee7; +} + +.insight-list, +.dependency-list, +.file-list, +.decision-list { + margin: 4px 0 0 0; + padding-left: 16px; +} + +.insight-list li { + margin-bottom: 4px; + color: #495057; + line-height: 1.4; +} + +.dependency-item, +.decision-item { + margin-bottom: 4px; + color: #6c757d; + font-size: 14px; +} + +.file-item { + margin-bottom: 4px; +} + +.file-item code { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 3px; + padding: 2px 4px; + font-size: 12px; + color: #e83e8c; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; +} + +.confidence-indicator { + display: flex; + align-items: center; + gap: 8px; + padding: 12px; + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 6px; + margin-top: 8px; +} + +.confidence-label { + font-size: 12px; + font-weight: 500; + color: #495057; + min-width: 90px; +} + +.confidence-bar { + flex: 1; + height: 6px; + background: #e9ecef; + border-radius: 3px; + overflow: hidden; +} + +.confidence-fill { + height: 100%; + border-radius: 3px; + transition: width 0.3s ease; +} + +.confidence-value { + font-size: 11px; + font-weight: 500; + color: #495057; + min-width: 80px; + text-align: right; +} + +.content-truncated { + text-align: center; + padding: 12px; + background: #f8f9fa; + border-top: 1px solid #e9ecef; + color: #6c757d; + font-style: italic; + font-size: 12px; +} + +/* Context type specific styling */ +.context-renderer--minimal .metadata-section { + border-left-width: 2px; + padding-left: 8px; +} + +.context-renderer--minimal .metadata-section h4 { + font-size: 12px; + margin-bottom: 4px; +} + +.context-renderer--minimal .tag { + font-size: 10px; + padding: 2px 6px; +} + +.context-renderer--preview .context-content { + max-height: 300px; + overflow: hidden; +} + +/* Special styling for different context types */ +.metadata-section:has(.type-badge:contains("file")) { + border-left-color: #28a745; +} + +.metadata-section:has(.type-badge:contains("directory")) { + border-left-color: #ffc107; +} + +.metadata-section:has(.type-badge:contains("project")) { + border-left-color: #007bff; +} + +.metadata-section:has(.type-badge:contains("global")) { + border-left-color: #6f42c1; +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .context-renderer { + background: #1e1e1e; + border-color: #404040; + color: #e0e0e0; + } + + .context-content { + background: #1e1e1e; + } + + .metadata-section { + border-left-color: #555; + } + + .metadata-section h4 { + color: #f8f9fa; + } + + .metadata-section p { + color: #ced4da; + } + + .tech-tag { + background: #1a365d; + color: #90cdf4; + border-color: #2a69ac; + } + + .context-tag { + background: #44337a; + color: #d8b4fe; + border-color: #6b46c1; + } + + .insight-list li { + color: #ced4da; + } + + .dependency-item, + .decision-item { + color: #adb5bd; + } + + .file-item code { + background: #2d2d2d; + border-color: #404040; + color: #fd7e14; + } + + .confidence-indicator { + background: #2d2d2d; + border-color: #404040; + } + + .confidence-label, + .confidence-value { + color: #ced4da; + } + + .confidence-bar { + background: #404040; + } + + .content-truncated { + background: #2d2d2d; + border-color: #404040; + color: #adb5bd; + } +} \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/ContextRenderer.tsx b/rustle/frontend/src/components/renderers/ContextRenderer.tsx new file mode 100644 index 0000000..1bed58a --- /dev/null +++ b/rustle/frontend/src/components/renderers/ContextRenderer.tsx @@ -0,0 +1,239 @@ +import React, { useMemo } from 'react'; +import { Envelope } from '../../types/Envelope'; +import { MarkdownRenderer } from './MarkdownRenderer'; +import { JSONRenderer } from './JSONRenderer'; +import './ContextRenderer.css'; + +interface ContextRendererProps { + envelope: Envelope; + renderMode?: 'full' | 'preview' | 'minimal'; +} + +interface UCXLContext { + summary?: string; + purpose?: string; + technologies?: string[]; + tags?: string[]; + insights?: string[]; + dependencies?: string[]; + related_files?: string[]; + decision_records?: string[]; + rag_confidence?: number; + generated_at?: string; + context_type?: 'file' | 'directory' | 'project' | 'global'; +} + +export const ContextRenderer: React.FC = ({ + envelope, + renderMode = 'full' +}) => { + const contextData = useMemo((): UCXLContext | null => { + try { + const content = envelope.content.raw; + + // Try to parse as JSON first + if (envelope.content.content_type === 'application/json' || + envelope.content.content_type === 'application/ucxl-context') { + return JSON.parse(content); + } + + // If it's markdown with front matter, try to extract context + if (envelope.content.content_type === 'text/markdown') { + const frontMatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); + if (frontMatterMatch) { + const frontMatter = frontMatterMatch[1]; + // Simple YAML-like parsing for common fields + const context: UCXLContext = {}; + + frontMatter.split('\n').forEach(line => { + const match = line.match(/^(\w+):\s*(.+)$/); + if (match) { + const [, key, value] = match; + if (key === 'technologies' || key === 'tags' || key === 'insights') { + context[key as keyof UCXLContext] = value.split(',').map(s => s.trim()) as any; + } else { + (context as any)[key] = value; + } + } + }); + + return context; + } + } + + return null; + } catch { + return null; + } + }, [envelope.content]); + + const renderContextMetadata = (context: UCXLContext) => ( +
+ {context.context_type && ( +
+ {context.context_type} +
+ )} + + {context.summary && ( +
+

Summary

+

{context.summary}

+
+ )} + + {context.purpose && ( +
+

Purpose

+

{context.purpose}

+
+ )} + + {context.technologies && context.technologies.length > 0 && ( +
+

Technologies

+
+ {context.technologies.map((tech, index) => ( + {tech} + ))} +
+
+ )} + + {context.tags && context.tags.length > 0 && ( +
+

Tags

+
+ {context.tags.map((tag, index) => ( + {tag} + ))} +
+
+ )} + + {context.insights && context.insights.length > 0 && ( +
+

Key Insights

+
    + {context.insights.map((insight, index) => ( +
  • {insight}
  • + ))} +
+
+ )} + + {context.dependencies && context.dependencies.length > 0 && ( +
+

Dependencies

+
    + {context.dependencies.map((dep, index) => ( +
  • {dep}
  • + ))} +
+
+ )} + + {context.related_files && context.related_files.length > 0 && ( +
+

Related Files

+
    + {context.related_files.map((file, index) => ( +
  • + {file} +
  • + ))} +
+
+ )} + + {context.decision_records && context.decision_records.length > 0 && ( +
+

Decision Records

+
    + {context.decision_records.map((record, index) => ( +
  • {record}
  • + ))} +
+
+ )} +
+ ); + + const renderConfidenceIndicator = (confidence?: number) => { + if (!confidence) return null; + + const getConfidenceColor = (score: number): string => { + if (score >= 0.8) return '#28a745'; + if (score >= 0.6) return '#ffc107'; + return '#dc3545'; + }; + + const getConfidenceLabel = (score: number): string => { + if (score >= 0.8) return 'High'; + if (score >= 0.6) return 'Medium'; + return 'Low'; + }; + + return ( +
+ RAG Confidence: +
+
+
+ + {getConfidenceLabel(confidence)} ({(confidence * 100).toFixed(1)}%) + +
+ ); + }; + + // If we can't parse the context, fall back to other renderers + if (!contextData) { + if (envelope.content.content_type === 'application/json') { + return ; + } else { + return ; + } + } + + return ( +
+ {renderMode === 'full' && ( +
+
+ UCXL Context + {contextData.generated_at && ( + + Generated: {new Date(contextData.generated_at).toLocaleString()} + + )} +
+
+ )} + +
+ {renderContextMetadata(contextData)} + + {contextData.rag_confidence && renderMode !== 'minimal' && ( +
+ {renderConfidenceIndicator(contextData.rag_confidence)} +
+ )} +
+ + {renderMode === 'preview' && ( +
+ Context preview - click to view full details +
+ )} +
+ ); +}; + +export default ContextRenderer; \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/ErrorRenderer.css b/rustle/frontend/src/components/renderers/ErrorRenderer.css new file mode 100644 index 0000000..d7efbb0 --- /dev/null +++ b/rustle/frontend/src/components/renderers/ErrorRenderer.css @@ -0,0 +1,259 @@ +.error-renderer { + background: #fff5f5; + border: 1px solid #fed7d7; + border-radius: 8px; + overflow: hidden; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; +} + +.error-header { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; + background: #fed7d7; + border-bottom: 1px solid #feb2b2; +} + +.error-icon { + font-size: 2rem; + opacity: 0.7; +} + +.error-title h3 { + margin: 0 0 4px 0; + color: #c53030; + font-size: 1.1rem; + font-weight: 600; +} + +.error-title p { + margin: 0; + color: #744210; + font-size: 0.9rem; +} + +.error-title code { + background: rgba(255, 255, 255, 0.5); + padding: 2px 4px; + border-radius: 3px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.8rem; +} + +.error-details { + padding: 16px; +} + +.error-section { + margin-bottom: 20px; +} + +.error-section:last-child { + margin-bottom: 0; +} + +.error-section h4 { + margin: 0 0 8px 0; + color: #c53030; + font-size: 0.9rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.error-info, +.tech-info { + display: flex; + flex-direction: column; + gap: 6px; +} + +.error-field, +.tech-field { + display: flex; + align-items: flex-start; + gap: 8px; +} + +.field-label { + font-weight: 500; + color: #744210; + min-width: 80px; + font-size: 0.85rem; +} + +.field-value { + color: #2d3748; + font-size: 0.85rem; + word-break: break-word; +} + +.field-value code { + background: #f7fafc; + border: 1px solid #e2e8f0; + border-radius: 3px; + padding: 2px 4px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.75rem; + color: #e53e3e; +} + +.stack-trace { + background: #f7fafc; + border: 1px solid #e2e8f0; + border-radius: 4px; + padding: 12px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.75rem; + color: #4a5568; + overflow-x: auto; + max-height: 200px; + overflow-y: auto; + white-space: pre; + line-height: 1.4; +} + +.suggestion-list { + margin: 0; + padding-left: 20px; +} + +.suggestion-item { + margin-bottom: 6px; + color: #4a5568; + font-size: 0.85rem; + line-height: 1.4; +} + +.suggestion-item::marker { + color: #e53e3e; +} + +.error-actions { + display: flex; + gap: 8px; + padding: 16px; + background: #f7fafc; + border-top: 1px solid #e2e8f0; +} + +.retry-button, +.report-button { + padding: 8px 12px; + border: none; + border-radius: 4px; + font-size: 0.85rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s; +} + +.retry-button { + background: #38a169; + color: white; +} + +.retry-button:hover { + background: #2f855a; +} + +.report-button { + background: #4299e1; + color: white; +} + +.report-button:hover { + background: #3182ce; +} + +/* Responsive design */ +@media (max-width: 768px) { + .error-header { + flex-direction: column; + align-items: flex-start; + text-align: left; + } + + .error-icon { + font-size: 1.5rem; + } + + .error-field, + .tech-field { + flex-direction: column; + gap: 2px; + } + + .field-label { + min-width: auto; + font-weight: 600; + } + + .error-actions { + flex-direction: column; + } +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .error-renderer { + background: #3d1a1b; + border-color: #842029; + } + + .error-header { + background: #842029; + border-color: #6c1f28; + } + + .error-title h3 { + color: #ea868f; + } + + .error-title p { + color: #f7d794; + } + + .error-title code { + background: rgba(0, 0, 0, 0.3); + color: #fd7e14; + } + + .error-section h4 { + color: #ea868f; + } + + .field-label { + color: #f7d794; + } + + .field-value { + color: #e9ecef; + } + + .field-value code { + background: #495057; + border-color: #6c757d; + color: #fd7e14; + } + + .stack-trace { + background: #495057; + border-color: #6c757d; + color: #ced4da; + } + + .suggestion-item { + color: #adb5bd; + } + + .suggestion-item::marker { + color: #ea868f; + } + + .error-actions { + background: #495057; + border-color: #6c757d; + } +} \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/ErrorRenderer.tsx b/rustle/frontend/src/components/renderers/ErrorRenderer.tsx new file mode 100644 index 0000000..f77a9d5 --- /dev/null +++ b/rustle/frontend/src/components/renderers/ErrorRenderer.tsx @@ -0,0 +1,168 @@ +import React from 'react'; +import './ErrorRenderer.css'; + +interface ErrorRendererProps { + error: any; + contentType: string; +} + +export const ErrorRenderer: React.FC = ({ + error, + contentType +}) => { + const getErrorDetails = (error: any) => { + if (error instanceof Error) { + return { + name: error.name, + message: error.message, + stack: error.stack + }; + } + + if (typeof error === 'string') { + return { + name: 'Rendering Error', + message: error, + stack: null + }; + } + + return { + name: 'Unknown Error', + message: 'An unexpected error occurred while rendering this content', + stack: null + }; + }; + + const errorDetails = getErrorDetails(error); + + const getErrorIcon = (contentType: string): string => { + if (contentType.startsWith('image/')) return '🖼️'; + if (contentType.includes('json')) return '📄'; + if (contentType.includes('markdown')) return '📝'; + if (contentType.startsWith('text/')) return '📄'; + return '⚠️'; + }; + + const getSuggestions = (contentType: string): string[] => { + const suggestions: string[] = []; + + if (contentType.includes('json')) { + suggestions.push('Check if the JSON syntax is valid'); + suggestions.push('Verify there are no trailing commas or unescaped quotes'); + } + + if (contentType.includes('markdown')) { + suggestions.push('Check for malformed markdown syntax'); + suggestions.push('Verify code blocks are properly closed'); + } + + if (contentType.startsWith('image/')) { + suggestions.push('Verify the image data is not corrupted'); + suggestions.push('Check if the image format is supported'); + suggestions.push('Ensure base64 encoding is correct if applicable'); + } + + if (contentType.startsWith('text/')) { + suggestions.push('Check for special characters or encoding issues'); + suggestions.push('Verify the content matches the declared content type'); + } + + suggestions.push('Try refreshing the content'); + suggestions.push('Contact support if the problem persists'); + + return suggestions; + }; + + const suggestions = getSuggestions(contentType); + + return ( +
+
+
{getErrorIcon(contentType)}
+
+

Content Rendering Error

+

Unable to display content of type: {contentType}

+
+
+ +
+
+

Error Details

+
+
+ Type: + {errorDetails.name} +
+
+ Message: + {errorDetails.message} +
+
+
+ + {errorDetails.stack && ( +
+

Stack Trace

+
{errorDetails.stack}
+
+ )} + +
+

Suggestions

+
    + {suggestions.map((suggestion, index) => ( +
  • + {suggestion} +
  • + ))} +
+
+ +
+

Technical Information

+
+
+ Content Type: + {contentType} +
+
+ Error Time: + {new Date().toLocaleString()} +
+
+ Browser: + {navigator.userAgent.split(' ')[0]} +
+
+
+
+ +
+ + +
+
+ ); +}; + +export default ErrorRenderer; \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/ImageRenderer.css b/rustle/frontend/src/components/renderers/ImageRenderer.css new file mode 100644 index 0000000..fa6f710 --- /dev/null +++ b/rustle/frontend/src/components/renderers/ImageRenderer.css @@ -0,0 +1,262 @@ +.image-renderer { + background: white; + border: 1px solid #e9ecef; + border-radius: 8px; + overflow: hidden; +} + +.image-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background: #f8f9fa; + border-bottom: 1px solid #e9ecef; +} + +.image-info { + display: flex; + align-items: center; + gap: 8px; +} + +.format-badge { + background: #17a2b8; + color: white; + padding: 2px 8px; + border-radius: 12px; + font-size: 11px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.content-type, +.file-size { + font-size: 11px; + color: #6c757d; + font-family: monospace; +} + +.image-actions { + display: flex; + gap: 8px; +} + +.zoom-button { + background: #28a745; + color: white; + border: none; + padding: 4px 8px; + border-radius: 4px; + font-size: 11px; + cursor: pointer; + transition: background-color 0.2s; +} + +.zoom-button:hover { + background: #218838; +} + +.image-container { + position: relative; + display: flex; + justify-content: center; + align-items: center; + background: #f8f9fa; + min-height: 200px; +} + +.image-renderer--preview .image-container { + max-height: 300px; +} + +.image-renderer--minimal .image-container { + min-height: 100px; + max-height: 150px; +} + +.loading-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + background: rgba(248, 249, 250, 0.9); + z-index: 1; +} + +.loading-spinner { + color: #6c757d; + font-size: 14px; +} + +.image-content { + max-width: 100%; + max-height: 600px; + object-fit: contain; + border-radius: 4px; + transition: transform 0.2s; +} + +.image-content:hover { + transform: scale(1.02); +} + +.image-content.preview { + max-height: 300px; +} + +.image-content.minimal { + max-height: 150px; +} + +.zoom-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.9); + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + cursor: pointer; +} + +.zoomed-image { + max-width: 95vw; + max-height: 95vh; + object-fit: contain; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +} + +.image-footer { + background: #f8f9fa; + border-top: 1px solid #e9ecef; + padding: 8px 12px; + text-align: center; +} + +.image-stats { + font-size: 12px; + color: #6c757d; + font-style: italic; +} + +/* Error state */ +.image-error { + background: #f8d7da; + border-color: #f5c6cb; +} + +.error-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 2rem; + text-align: center; + color: #721c24; +} + +.error-icon { + font-size: 3rem; + margin-bottom: 1rem; + opacity: 0.5; +} + +.error-content h4 { + margin: 0 0 1rem 0; + color: #721c24; +} + +.error-content p { + margin: 0.25rem 0; + font-size: 14px; + color: #856404; +} + +/* Format-specific badge colors */ +.format-badge:has-text("JPG"), +.format-badge:has-text("JPEG") { + background: #fd7e14; +} + +.format-badge:has-text("PNG") { + background: #20c997; +} + +.format-badge:has-text("GIF") { + background: #e83e8c; +} + +.format-badge:has-text("WEBP") { + background: #6f42c1; +} + +.format-badge:has-text("SVG") { + background: #ffc107; + color: #212529; +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .image-renderer { + background: #1e1e1e; + border-color: #404040; + } + + .image-header { + background: #2d2d2d; + border-color: #404040; + } + + .content-type, + .file-size { + color: #adb5bd; + } + + .image-container { + background: #2d2d2d; + } + + .loading-overlay { + background: rgba(45, 45, 45, 0.9); + } + + .loading-spinner { + color: #adb5bd; + } + + .image-footer { + background: #2d2d2d; + border-color: #404040; + } + + .image-stats { + color: #adb5bd; + } + + .image-error { + background: #3d1a1b; + border-color: #842029; + } + + .error-content { + color: #ea868f; + } + + .error-content h4 { + color: #ea868f; + } + + .error-content p { + color: #f7d794; + } +} \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/ImageRenderer.tsx b/rustle/frontend/src/components/renderers/ImageRenderer.tsx new file mode 100644 index 0000000..d131cc1 --- /dev/null +++ b/rustle/frontend/src/components/renderers/ImageRenderer.tsx @@ -0,0 +1,149 @@ +import React, { useState } from 'react'; +import './ImageRenderer.css'; + +interface ImageRendererProps { + content: string; // Base64 encoded or URL + contentType: string; + renderMode?: 'full' | 'preview' | 'minimal'; +} + +export const ImageRenderer: React.FC = ({ + content, + contentType, + renderMode = 'full' +}) => { + const [isLoading, setIsLoading] = useState(true); + const [hasError, setHasError] = useState(false); + const [isZoomed, setIsZoomed] = useState(false); + + // Determine if content is base64 or URL + const getImageSrc = (): string => { + if (content.startsWith('data:')) { + return content; + } else if (content.startsWith('http')) { + return content; + } else { + // Assume base64 without data prefix + return `data:${contentType};base64,${content}`; + } + }; + + const handleImageLoad = () => { + setIsLoading(false); + setHasError(false); + }; + + const handleImageError = () => { + setIsLoading(false); + setHasError(true); + }; + + const toggleZoom = () => { + if (renderMode === 'full') { + setIsZoomed(!isZoomed); + } + }; + + const getFileExtension = (mimeType: string): string => { + const typeMap: Record = { + 'image/jpeg': 'jpg', + 'image/jpg': 'jpg', + 'image/png': 'png', + 'image/gif': 'gif', + 'image/webp': 'webp', + 'image/svg+xml': 'svg' + }; + return typeMap[mimeType] || 'img'; + }; + + const getImageDimensions = (element: HTMLImageElement): { width: number; height: number } => { + return { + width: element.naturalWidth, + height: element.naturalHeight + }; + }; + + const formatFileSize = (base64String: string): string => { + // Rough estimation of file size from base64 + const bytes = (base64String.length * 3) / 4; + if (bytes < 1024) return `${bytes.toFixed(0)} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; + }; + + if (hasError) { + return ( +
+
+
🖼️
+

Unable to display image

+

Content Type: {contentType}

+

The image data may be corrupted or in an unsupported format.

+
+
+ ); + } + + return ( +
+ {renderMode === 'full' && ( +
+
+ {getFileExtension(contentType).toUpperCase()} + {contentType} + {formatFileSize(content)} +
+ {!hasError && ( +
+ +
+ )} +
+ )} + +
+ {isLoading && ( +
+
Loading image...
+
+ )} + + UCXL content + + {isZoomed && ( +
+ UCXL content (zoomed) +
+ )} +
+ + {renderMode === 'full' && !hasError && !isLoading && ( +
+
+ Click to zoom +
+
+ )} +
+ ); +}; + +export default ImageRenderer; \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/JSONRenderer.css b/rustle/frontend/src/components/renderers/JSONRenderer.css new file mode 100644 index 0000000..82a0013 --- /dev/null +++ b/rustle/frontend/src/components/renderers/JSONRenderer.css @@ -0,0 +1,324 @@ +.json-renderer { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; + overflow: hidden; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; +} + +.json-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background: #e9ecef; + border-bottom: 1px solid #dee2e6; +} + +.json-info { + display: flex; + align-items: center; + gap: 8px; +} + +.format-badge { + background: #6f42c1; + color: white; + padding: 2px 8px; + border-radius: 12px; + font-size: 11px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.size-info { + font-size: 11px; + color: #6c757d; + font-family: monospace; +} + +.json-actions { + display: flex; + gap: 8px; +} + +.action-button { + background: #007bff; + color: white; + border: none; + padding: 4px 8px; + border-radius: 4px; + font-size: 11px; + cursor: pointer; + transition: background-color 0.2s; +} + +.action-button:hover { + background: #0056b3; +} + +.json-content { + background: white; + padding: 12px; + max-height: 600px; + overflow: auto; +} + +.json-renderer--preview .json-content { + max-height: 400px; +} + +.json-renderer--minimal .json-content { + max-height: 200px; + padding: 8px; +} + +.json-node { + margin: 0; +} + +.json-line { + display: flex; + align-items: center; + padding: 2px 0; + line-height: 1.4; +} + +.collapse-toggle { + background: none; + border: none; + cursor: pointer; + font-size: 10px; + color: #6c757d; + margin-right: 6px; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 2px; + transition: background-color 0.2s; +} + +.collapse-toggle:hover { + background: #f8f9fa; +} + +.json-key { + color: #0066cc; + font-weight: 500; + margin-right: 6px; +} + +.json-value { + font-weight: normal; +} + +.json-value--string { + color: #d14; +} + +.json-value--number { + color: #009999; +} + +.json-value--boolean { + color: #0066cc; + font-weight: 600; +} + +.json-value--null { + color: #999; + font-style: italic; +} + +.json-value--object, +.json-value--array { + color: #333; + font-weight: 500; +} + +.json-preview { + color: #6c757d; + font-style: italic; + margin-left: 8px; + font-size: 12px; +} + +.json-children { + margin-left: 20px; + border-left: 1px solid #e9ecef; + padding-left: 8px; +} + +.json-truncated { + padding: 4px 0; + color: #6c757d; + font-style: italic; +} + +.json-ellipsis { + background: #f8f9fa; + padding: 2px 6px; + border-radius: 3px; + border: 1px solid #e9ecef; +} + +/* Level-based indentation */ +.level-0 { } +.level-1 { } +.level-2 { } +.level-3 { opacity: 0.9; } +.level-4 { opacity: 0.8; } +.level-5 { opacity: 0.7; } + +/* Error state */ +.json-error { + background: #f8d7da; + border-color: #f5c6cb; +} + +.error-header { + background: #f5c6cb; + padding: 8px 12px; + border-bottom: 1px solid #f1aeb5; +} + +.error-badge { + background: #dc3545; + color: white; + padding: 2px 8px; + border-radius: 12px; + font-size: 11px; + font-weight: 500; + text-transform: uppercase; +} + +.error-content { + padding: 12px; +} + +.error-content p { + margin: 0 0 12px 0; + color: #721c24; +} + +.raw-content { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 4px; + padding: 12px; + margin: 0; + max-height: 200px; + overflow: auto; + font-size: 12px; + color: #495057; +} + +.content-truncated { + text-align: center; + padding: 12px; + background: #f8f9fa; + border-top: 1px solid #e9ecef; + color: #6c757d; + font-style: italic; + font-size: 12px; +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .json-renderer { + background: #2d2d2d; + border-color: #404040; + } + + .json-header { + background: #404040; + border-color: #555; + } + + .size-info { + color: #adb5bd; + } + + .json-content { + background: #1e1e1e; + } + + .collapse-toggle { + color: #888; + } + + .collapse-toggle:hover { + background: #404040; + } + + .json-key { + color: #569cd6; + } + + .json-value--string { + color: #ce9178; + } + + .json-value--number { + color: #b5cea8; + } + + .json-value--boolean { + color: #569cd6; + } + + .json-value--null { + color: #888; + } + + .json-value--object, + .json-value--array { + color: #e0e0e0; + } + + .json-preview { + color: #888; + } + + .json-children { + border-color: #404040; + } + + .json-truncated { + color: #888; + } + + .json-ellipsis { + background: #404040; + border-color: #555; + } + + .json-error { + background: #3d1a1b; + border-color: #842029; + } + + .error-header { + background: #842029; + border-color: #6c1f28; + } + + .error-content p { + color: #ea868f; + } + + .raw-content { + background: #2d2d2d; + border-color: #404040; + color: #e0e0e0; + } + + .content-truncated { + background: #2d2d2d; + border-color: #404040; + color: #888; + } +} \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/JSONRenderer.tsx b/rustle/frontend/src/components/renderers/JSONRenderer.tsx new file mode 100644 index 0000000..9964201 --- /dev/null +++ b/rustle/frontend/src/components/renderers/JSONRenderer.tsx @@ -0,0 +1,212 @@ +import React, { useState, useMemo } from 'react'; +import './JSONRenderer.css'; + +interface JSONRendererProps { + content: string; + renderMode?: 'full' | 'preview' | 'minimal'; +} + +interface JSONNode { + key?: string; + value: any; + type: 'object' | 'array' | 'string' | 'number' | 'boolean' | 'null'; + level: number; + isExpanded?: boolean; +} + +export const JSONRenderer: React.FC = ({ + content, + renderMode = 'full' +}) => { + const [expandedNodes, setExpandedNodes] = useState>(new Set()); + + const parsedJSON = useMemo(() => { + try { + return JSON.parse(content); + } catch (error) { + return null; + } + }, [content]); + + const toggleNode = (path: string) => { + const newExpanded = new Set(expandedNodes); + if (newExpanded.has(path)) { + newExpanded.delete(path); + } else { + newExpanded.add(path); + } + setExpandedNodes(newExpanded); + }; + + const getValueType = (value: any): JSONNode['type'] => { + if (value === null) return 'null'; + if (Array.isArray(value)) return 'array'; + if (typeof value === 'object') return 'object'; + return typeof value as JSONNode['type']; + }; + + const formatValue = (value: any, type: JSONNode['type']): string => { + switch (type) { + case 'string': + return `"${value}"`; + case 'number': + case 'boolean': + return String(value); + case 'null': + return 'null'; + case 'array': + return `Array(${value.length})`; + case 'object': + const keys = Object.keys(value); + return `Object{${keys.length}}`; + default: + return String(value); + } + }; + + const renderJSONNode = ( + value: any, + key?: string, + level: number = 0, + path: string = '' + ): React.ReactNode => { + const type = getValueType(value); + const currentPath = path ? `${path}.${key}` : key || ''; + const isExpanded = expandedNodes.has(currentPath); + const isCollapsible = type === 'object' || type === 'array'; + + // In preview mode, limit depth and number of items + if (renderMode === 'preview' && level > 2) { + return ( +
+ ... +
+ ); + } + + return ( +
+
+ {isCollapsible && ( + + )} + + {key && ( + + "{key}": + + )} + + + {formatValue(value, type)} + + + {isCollapsible && !isExpanded && ( + + {type === 'array' ? `[${value.length} items]` : `{${Object.keys(value).length} keys}`} + + )} +
+ + {isCollapsible && isExpanded && ( +
+ {type === 'array' ? ( + value.map((item: any, index: number) => + renderJSONNode(item, String(index), level + 1, currentPath) + ) + ) : ( + Object.entries(value).map(([childKey, childValue]) => + renderJSONNode(childValue, childKey, level + 1, currentPath) + ) + )} +
+ )} +
+ ); + }; + + const handleCopyToClipboard = () => { + navigator.clipboard.writeText(content).then(() => { + console.log('JSON copied to clipboard'); + }); + }; + + const handleExpandAll = () => { + const getAllPaths = (obj: any, prefix: string = ''): string[] => { + const paths: string[] = []; + if (typeof obj === 'object' && obj !== null) { + Object.keys(obj).forEach(key => { + const currentPath = prefix ? `${prefix}.${key}` : key; + paths.push(currentPath); + if (typeof obj[key] === 'object' && obj[key] !== null) { + paths.push(...getAllPaths(obj[key], currentPath)); + } + }); + } + return paths; + }; + + if (parsedJSON) { + setExpandedNodes(new Set(getAllPaths(parsedJSON))); + } + }; + + const handleCollapseAll = () => { + setExpandedNodes(new Set()); + }; + + if (!parsedJSON) { + return ( +
+
+ Invalid JSON +
+
+

Unable to parse JSON content. Displaying as raw text:

+
{content}
+
+
+ ); + } + + return ( +
+ {renderMode === 'full' && ( +
+
+ JSON + {content.length} characters +
+
+ + + +
+
+ )} + +
+ {renderJSONNode(parsedJSON)} +
+ + {renderMode === 'preview' && ( +
+ Preview mode - some nested content may be hidden +
+ )} +
+ ); +}; + +export default JSONRenderer; \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/MarkdownRenderer.css b/rustle/frontend/src/components/renderers/MarkdownRenderer.css new file mode 100644 index 0000000..3d3148d --- /dev/null +++ b/rustle/frontend/src/components/renderers/MarkdownRenderer.css @@ -0,0 +1,247 @@ +.markdown-renderer { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; + line-height: 1.6; + color: #333; +} + +.markdown-content h1 { + font-size: 2rem; + margin: 0 0 1rem 0; + padding-bottom: 0.5rem; + border-bottom: 2px solid #e0e0e0; + color: #2c3e50; +} + +.markdown-content h2 { + font-size: 1.5rem; + margin: 1.5rem 0 1rem 0; + color: #34495e; + border-bottom: 1px solid #e0e0e0; + padding-bottom: 0.25rem; +} + +.markdown-content h3 { + font-size: 1.25rem; + margin: 1rem 0 0.75rem 0; + color: #34495e; +} + +.markdown-content p { + margin: 0 0 1rem 0; +} + +.markdown-content ul { + margin: 0 0 1rem 0; + padding-left: 1.5rem; +} + +.markdown-content li { + margin-bottom: 0.25rem; +} + +.markdown-content strong { + font-weight: 600; + color: #2c3e50; +} + +.markdown-content em { + font-style: italic; + color: #555; +} + +.markdown-content a { + color: #3498db; + text-decoration: none; + border-bottom: 1px solid transparent; + transition: border-color 0.2s; +} + +.markdown-content a:hover { + border-bottom-color: #3498db; +} + +.markdown-content .inline-code { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 3px; + padding: 2px 4px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.9em; + color: #e83e8c; +} + +.markdown-content .code-block { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 6px; + padding: 1rem; + margin: 1rem 0; + overflow-x: auto; + position: relative; +} + +.markdown-content .code-block::before { + content: attr(data-lang); + position: absolute; + top: 0.5rem; + right: 0.5rem; + font-size: 0.75rem; + color: #6c757d; + background: #e9ecef; + padding: 2px 6px; + border-radius: 3px; + text-transform: uppercase; +} + +.markdown-content .code-block code { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.9em; + line-height: 1.4; + color: #333; +} + +/* Preview mode styling */ +.markdown-content.preview { + max-height: 300px; + overflow: hidden; + position: relative; +} + +.markdown-content.preview::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 2rem; + background: linear-gradient(transparent, white); +} + +/* Minimal mode styling */ +.markdown-content.minimal h1, +.markdown-content.minimal h2, +.markdown-content.minimal h3 { + margin: 0.5rem 0; + font-size: 1.1em; +} + +.markdown-content.minimal .code-block { + margin: 0.5rem 0; + padding: 0.5rem; +} + +/* Decision Record specific styling */ +.markdown-renderer--decision-record { + border-left: 4px solid #28a745; + padding-left: 1rem; +} + +.decision-record .front-matter { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 6px; + padding: 1rem; + margin-bottom: 1rem; +} + +.decision-record .front-matter h4 { + margin: 0 0 0.5rem 0; + color: #495057; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.decision-record .front-matter pre { + margin: 0; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.8rem; + color: #6c757d; + white-space: pre-wrap; +} + +.decision-content { + border-top: 1px solid #e9ecef; + padding-top: 1rem; +} + +.content-truncated { + text-align: center; + padding: 1rem; + color: #6c757d; + font-style: italic; + border-top: 1px solid #e9ecef; + margin-top: 1rem; +} + +/* Dark mode support */ +@media (prefers-color-scheme: dark) { + .markdown-renderer { + color: #e0e0e0; + } + + .markdown-content h1, + .markdown-content h2, + .markdown-content h3 { + color: #f8f9fa; + border-color: #495057; + } + + .markdown-content strong { + color: #f8f9fa; + } + + .markdown-content em { + color: #ced4da; + } + + .markdown-content a { + color: #74c0fc; + } + + .markdown-content a:hover { + border-bottom-color: #74c0fc; + } + + .markdown-content .inline-code { + background: #495057; + border-color: #6c757d; + color: #fd7e14; + } + + .markdown-content .code-block { + background: #495057; + border-color: #6c757d; + } + + .markdown-content .code-block::before { + background: #6c757d; + color: #ced4da; + } + + .markdown-content .code-block code { + color: #e9ecef; + } + + .decision-record .front-matter { + background: #495057; + border-color: #6c757d; + } + + .decision-record .front-matter h4 { + color: #ced4da; + } + + .decision-record .front-matter pre { + color: #adb5bd; + } + + .decision-content { + border-color: #6c757d; + } + + .content-truncated { + color: #adb5bd; + border-color: #6c757d; + } +} \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/MarkdownRenderer.tsx b/rustle/frontend/src/components/renderers/MarkdownRenderer.tsx new file mode 100644 index 0000000..51d1a07 --- /dev/null +++ b/rustle/frontend/src/components/renderers/MarkdownRenderer.tsx @@ -0,0 +1,116 @@ +import React, { useMemo } from 'react'; +import './MarkdownRenderer.css'; + +interface MarkdownRendererProps { + content: string; + renderMode?: 'full' | 'preview' | 'minimal'; + specialType?: 'decision-record' | 'context-metadata' | 'standard'; +} + +export const MarkdownRenderer: React.FC = ({ + content, + renderMode = 'full', + specialType = 'standard' +}) => { + // Simple markdown parsing for common elements + const parseMarkdown = (text: string): string => { + let html = text; + + // Headers + html = html.replace(/^### (.*$)/gim, '

$1

'); + html = html.replace(/^## (.*$)/gim, '

$1

'); + html = html.replace(/^# (.*$)/gim, '

$1

'); + + // Bold and italic + html = html.replace(/\*\*(.*?)\*\*/g, '$1'); + html = html.replace(/\*(.*?)\*/g, '$1'); + + // Code blocks + html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => { + return `
${escapeHtml(code.trim())}
`; + }); + + // Inline code + html = html.replace(/`([^`]+)`/g, '$1'); + + // Lists + html = html.replace(/^\s*\* (.+)$/gm, '
  • $1
  • '); + html = html.replace(/(
  • .*<\/li>)/s, '
      $1
    '); + + // Links + html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); + + // Line breaks + html = html.replace(/\n/g, '
    '); + + return html; + }; + + const escapeHtml = (text: string): string => { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + }; + + const processedContent = useMemo(() => { + if (renderMode === 'preview') { + // For preview mode, truncate content + const lines = content.split('\n'); + const previewLines = lines.slice(0, 10); + return parseMarkdown(previewLines.join('\n')); + } + return parseMarkdown(content); + }, [content, renderMode]); + + const extractFrontMatter = (text: string) => { + const frontMatterMatch = text.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); + if (frontMatterMatch) { + try { + const frontMatter = frontMatterMatch[1]; + const content = frontMatterMatch[2]; + return { frontMatter, content }; + } catch { + return { frontMatter: null, content: text }; + } + } + return { frontMatter: null, content: text }; + }; + + const { frontMatter, content: mainContent } = extractFrontMatter(content); + + const renderDecisionRecord = () => ( +
    + {frontMatter && ( +
    +

    Decision Metadata

    +
    {frontMatter}
    +
    + )} +
    +
    + ); + + const renderStandardMarkdown = () => ( +
    + ); + + return ( +
    + {specialType === 'decision-record' ? renderDecisionRecord() : renderStandardMarkdown()} + + {renderMode === 'preview' && content.split('\n').length > 10 && ( +
    + Content truncated for preview... +
    + )} +
    + ); +}; + +export default MarkdownRenderer; \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/MetadataRenderer.css b/rustle/frontend/src/components/renderers/MetadataRenderer.css new file mode 100644 index 0000000..52c8a55 --- /dev/null +++ b/rustle/frontend/src/components/renderers/MetadataRenderer.css @@ -0,0 +1,290 @@ +.metadata-renderer { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border-radius: 8px 8px 0 0; + padding: 12px 16px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; +} + +.metadata-renderer--preview { + border-radius: 8px; + margin-bottom: 8px; +} + +.metadata-renderer--minimal { + background: #f8f9fa; + color: #495057; + border: 1px solid #e9ecef; + border-radius: 6px; + padding: 8px 12px; + margin-bottom: 8px; +} + +.metadata-header { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + margin-bottom: 12px; +} + +.uri-section h4, +.metadata-info h4 { + margin: 0 0 8px 0; + font-size: 0.9rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + opacity: 0.9; +} + +.uri-breakdown { + display: flex; + flex-direction: column; + gap: 4px; +} + +.uri-part { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.85rem; +} + +.uri-label { + font-weight: 500; + opacity: 0.8; + min-width: 50px; + font-size: 0.75rem; + text-transform: uppercase; +} + +.uri-value { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + background: rgba(255, 255, 255, 0.15); + padding: 2px 6px; + border-radius: 3px; + font-size: 0.8rem; + font-weight: 500; +} + +.uri-value.agent { + background: rgba(52, 152, 219, 0.3); +} + +.uri-value.role { + background: rgba(155, 89, 182, 0.3); +} + +.uri-value.project { + background: rgba(46, 204, 113, 0.3); +} + +.uri-value.task { + background: rgba(230, 126, 34, 0.3); +} + +.uri-value.path { + background: rgba(241, 196, 15, 0.3); + word-break: break-all; +} + +.metadata-info { + display: flex; + flex-direction: column; + gap: 6px; +} + +.info-row { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.85rem; +} + +.info-label { + font-weight: 500; + opacity: 0.8; + min-width: 60px; + font-size: 0.75rem; + text-transform: uppercase; +} + +.info-value { + background: rgba(255, 255, 255, 0.1); + padding: 2px 6px; + border-radius: 3px; + font-size: 0.8rem; +} + +.info-value.title { + font-weight: 600; + background: rgba(255, 255, 255, 0.2); +} + +.info-value.author { + font-style: italic; +} + +.info-value.timestamp { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.75rem; +} + +.info-value.version { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + background: rgba(255, 255, 255, 0.15); +} + +.info-value.source { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + color: #ffd700; +} + +.tags-section { + margin-bottom: 12px; +} + +.tags-section h5 { + margin: 0 0 6px 0; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + opacity: 0.9; +} + +.tags-list { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.tag { + background: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.3); + color: white; + padding: 2px 8px; + border-radius: 12px; + font-size: 0.7rem; + font-weight: 500; + text-transform: lowercase; +} + +.context-data { + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid rgba(255, 255, 255, 0.2); +} + +.context-data h5 { + margin: 0 0 6px 0; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + opacity: 0.9; +} + +.context-entries { + display: flex; + flex-direction: column; + gap: 4px; +} + +.context-entry { + display: flex; + gap: 8px; + font-size: 0.75rem; +} + +.context-key { + font-weight: 500; + opacity: 0.8; + min-width: 80px; +} + +.context-value { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + background: rgba(255, 255, 255, 0.1); + padding: 2px 4px; + border-radius: 3px; + flex: 1; + overflow-wrap: break-word; +} + +/* Minimal mode specific styles */ +.minimal-info { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.uri-badge { + background: #007bff; + color: white; + padding: 2px 8px; + border-radius: 12px; + font-size: 0.7rem; + font-weight: 500; + font-family: monospace; +} + +.title-minimal { + font-weight: 600; + color: #495057; + font-size: 0.85rem; +} + +.author-minimal { + color: #6c757d; + font-size: 0.75rem; + font-style: italic; +} + +/* Responsive design */ +@media (max-width: 768px) { + .metadata-header { + grid-template-columns: 1fr; + gap: 12px; + } + + .uri-part, + .info-row { + flex-direction: column; + align-items: flex-start; + gap: 2px; + } + + .uri-label, + .info-label { + min-width: auto; + } + + .minimal-info { + flex-direction: column; + align-items: flex-start; + gap: 4px; + } +} + +/* Dark mode specific adjustments */ +@media (prefers-color-scheme: dark) { + .metadata-renderer--minimal { + background: #2d2d2d; + color: #e0e0e0; + border-color: #404040; + } + + .title-minimal { + color: #e0e0e0; + } + + .author-minimal { + color: #adb5bd; + } + + .uri-badge { + background: #0d6efd; + } +} \ No newline at end of file diff --git a/rustle/frontend/src/components/renderers/MetadataRenderer.tsx b/rustle/frontend/src/components/renderers/MetadataRenderer.tsx new file mode 100644 index 0000000..1a497b9 --- /dev/null +++ b/rustle/frontend/src/components/renderers/MetadataRenderer.tsx @@ -0,0 +1,189 @@ +import React from 'react'; +import './MetadataRenderer.css'; + +interface EnvelopeMetadata { + author?: string; + title?: string; + tags: string[]; + source?: string; + context_data: Record; +} + +interface UCXLUri { + toString(): string; + // Add other UCXL URI properties as needed +} + +interface MetadataRendererProps { + metadata: EnvelopeMetadata; + uri: UCXLUri; + timestamp: string; + version: string; + renderMode?: 'full' | 'preview' | 'minimal'; +} + +export const MetadataRenderer: React.FC = ({ + metadata, + uri, + timestamp, + version, + renderMode = 'full' +}) => { + const formatTimestamp = (timestamp: string): string => { + try { + const date = new Date(timestamp); + return date.toLocaleString(); + } catch { + return timestamp; + } + }; + + const formatURI = (uri: UCXLUri): { agent: string; role: string; project: string; task: string; path: string } => { + const uriString = uri.toString(); + + // Parse UCXL URI format: ucxl://agent:role@project:task/temporal/path + const match = uriString.match(/^ucxl:\/\/([^:]+):([^@]+)@([^:]+):([^\/]+)(.*)$/); + + if (match) { + const [, agent, role, project, task, pathPart] = match; + // Extract temporal and path parts + const pathMatch = pathPart.match(/^\/([^\/]*)(.*)$/); + const temporal = pathMatch ? pathMatch[1] : ''; + const path = pathMatch ? pathMatch[2] : pathPart; + + return { + agent, + role, + project, + task, + path: temporal ? `${temporal}${path}` : path + }; + } + + // Fallback for non-standard format + return { + agent: 'unknown', + role: 'unknown', + project: 'unknown', + task: 'unknown', + path: uriString + }; + }; + + const uriParts = formatURI(uri); + + const renderContextData = (data: Record) => { + const entries = Object.entries(data); + if (entries.length === 0) return null; + + return ( +
    +
    Context Data
    +
    + {entries.map(([key, value]) => ( +
    + {key}: + + {typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value)} + +
    + ))} +
    +
    + ); + }; + + if (renderMode === 'minimal') { + return ( +
    +
    + {uriParts.project}/{uriParts.task} + {metadata.title && {metadata.title}} + {metadata.author && by {metadata.author}} +
    +
    + ); + } + + return ( +
    +
    +
    +

    UCXL Address

    +
    +
    + Agent: + {uriParts.agent} +
    +
    + Role: + {uriParts.role} +
    +
    + Project: + {uriParts.project} +
    +
    + Task: + {uriParts.task} +
    + {uriParts.path && ( +
    + Path: + {uriParts.path} +
    + )} +
    +
    + +
    + {metadata.title && ( +
    + Title: + {metadata.title} +
    + )} + + {metadata.author && ( +
    + Author: + {metadata.author} +
    + )} + +
    + Created: + {formatTimestamp(timestamp)} +
    + +
    + Version: + {version.substring(0, 8)}... +
    + + {metadata.source && ( +
    + Source: + {metadata.source} +
    + )} +
    +
    + + {metadata.tags.length > 0 && ( +
    +
    Tags
    +
    + {metadata.tags.map((tag, index) => ( + {tag} + ))} +
    +
    + )} + + {renderMode === 'full' && renderContextData(metadata.context_data)} +
    + ); +}; + +export default MetadataRenderer; \ No newline at end of file diff --git a/rustle/frontend/src/main.tsx b/rustle/frontend/src/main.tsx new file mode 100644 index 0000000..3626ff3 --- /dev/null +++ b/rustle/frontend/src/main.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import {createRoot} from 'react-dom/client' +import './style.css' +import App from './App' + +const container = document.getElementById('root') + +const root = createRoot(container!) + +root.render( + + + +) diff --git a/rustle/frontend/src/style.css b/rustle/frontend/src/style.css new file mode 100644 index 0000000..3940d6c --- /dev/null +++ b/rustle/frontend/src/style.css @@ -0,0 +1,26 @@ +html { + background-color: rgba(27, 38, 54, 1); + text-align: center; + color: white; +} + +body { + margin: 0; + color: white; + font-family: "Nunito", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", + "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", + sans-serif; +} + +@font-face { + font-family: "Nunito"; + font-style: normal; + font-weight: 400; + src: local(""), + url("assets/fonts/nunito-v16-latin-regular.woff2") format("woff2"); +} + +#app { + height: 100vh; + text-align: center; +} diff --git a/rustle/frontend/src/types/Envelope.ts b/rustle/frontend/src/types/Envelope.ts new file mode 100644 index 0000000..2a7a77e --- /dev/null +++ b/rustle/frontend/src/types/Envelope.ts @@ -0,0 +1,101 @@ +// Type definitions for UCXL content rendering + +export interface EnvelopeContent { + raw: string; + content_type: string; + encoding: string; +} + +export interface EnvelopeMetadata { + author?: string; + title?: string; + tags: string[]; + source?: string; + context_data: Record; +} + +export interface UCXLUri { + toString(): string; +} + +export interface Envelope { + id: string; + ucxl_uri: UCXLUri; + content: EnvelopeContent; + metadata: EnvelopeMetadata; + version: string; + parent_version?: string; + timestamp: string; + content_hash: string; +} + +// Content type categories for easier handling +export type ContentCategory = + | 'markdown' + | 'code' + | 'json' + | 'image' + | 'context' + | 'decision' + | 'unknown'; + +export const getContentCategory = (contentType: string): ContentCategory => { + if (contentType === 'text/markdown' || contentType === 'text/x-markdown') { + return 'markdown'; + } + + if (contentType.startsWith('text/x-') || + contentType === 'text/plain' || + contentType.startsWith('application/javascript') || + contentType.startsWith('application/typescript')) { + return 'code'; + } + + if (contentType === 'application/json' || contentType === 'text/json') { + return 'json'; + } + + if (contentType.startsWith('image/')) { + return 'image'; + } + + if (contentType === 'application/ucxl-context' || + contentType === 'application/ucxl-metadata') { + return 'context'; + } + + if (contentType === 'application/ucxl-decision' || + contentType === 'application/ucxl-report') { + return 'decision'; + } + + return 'unknown'; +}; + +// Utility functions for working with envelopes +export const createMockEnvelope = ( + uri: string, + content: string, + contentType: string, + metadata: Partial = {} +): Envelope => { + return { + id: `envelope-${Date.now()}`, + ucxl_uri: { toString: () => uri }, + content: { + raw: content, + content_type: contentType, + encoding: 'utf-8' + }, + metadata: { + author: metadata.author, + title: metadata.title, + tags: metadata.tags || [], + source: metadata.source, + context_data: metadata.context_data || {} + }, + version: `v${Date.now()}`, + timestamp: new Date().toISOString(), + content_hash: `hash-${Date.now()}` + }; +}; \ No newline at end of file diff --git a/rustle/frontend/src/vite-env.d.ts b/rustle/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/rustle/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/rustle/frontend/src/wails/go/main/App.d.ts b/rustle/frontend/src/wails/go/main/App.d.ts new file mode 100644 index 0000000..cb80ccb --- /dev/null +++ b/rustle/frontend/src/wails/go/main/App.d.ts @@ -0,0 +1,44 @@ +// Type definitions for Wails App bindings + +export interface UCXLValidationResult { + valid: boolean; + error: string; + components: { + agent: string; + role: string; + project: string; + task: string; + temporal: string; + path: string; + }; +} + +export interface BZZZStatus { + connected: boolean; + mode: string; + peers: number; + agent: string; + role: string; + project: string; + capabilities: string[]; +} + +export interface UCXLContent { + ucxl_uri: string; + content: { + raw: string; + content_type: string; + encoding: string; + }; + metadata: { [key: string]: string }; + timestamp: string; + version: string; + content_hash: string; +} + +export declare function GetBZZZStatus(): Promise; +export declare function GetUCXLContent(uri: string): Promise; +export declare function Greet(name: string): Promise; +export declare function PostUCXLContent(uri: string, content: string, contentType: string, metadata: { [key: string]: string }): Promise; +export declare function SearchUCXLContent(query: string, tags: string[], limit: number): Promise; +export declare function ValidateUCXLAddress(uri: string): Promise; \ No newline at end of file diff --git a/rustle/frontend/src/wails/go/main/App.js b/rustle/frontend/src/wails/go/main/App.js new file mode 100644 index 0000000..45cc2a2 --- /dev/null +++ b/rustle/frontend/src/wails/go/main/App.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function GetBZZZStatus() { + return window['go']['main']['App']['GetBZZZStatus'](); +} + +export function GetUCXLContent(arg1) { + return window['go']['main']['App']['GetUCXLContent'](arg1); +} + +export function Greet(arg1) { + return window['go']['main']['App']['Greet'](arg1); +} + +export function PostUCXLContent(arg1, arg2, arg3, arg4) { + return window['go']['main']['App']['PostUCXLContent'](arg1, arg2, arg3, arg4); +} + +export function SearchUCXLContent(arg1, arg2, arg3) { + return window['go']['main']['App']['SearchUCXLContent'](arg1, arg2, arg3); +} + +export function ValidateUCXLAddress(arg1) { + return window['go']['main']['App']['ValidateUCXLAddress'](arg1); +} \ No newline at end of file diff --git a/rustle/frontend/tsconfig.json b/rustle/frontend/tsconfig.json new file mode 100644 index 0000000..823e83d --- /dev/null +++ b/rustle/frontend/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": [ + "DOM", + "DOM.Iterable", + "ESNext" + ], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/rustle/frontend/tsconfig.node.json b/rustle/frontend/tsconfig.node.json new file mode 100644 index 0000000..b8afcc8 --- /dev/null +++ b/rustle/frontend/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": [ + "vite.config.ts" + ] +} diff --git a/rustle/frontend/vite.config.ts b/rustle/frontend/vite.config.ts new file mode 100644 index 0000000..4955065 --- /dev/null +++ b/rustle/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import {defineConfig} from 'vite' +import react from '@vitejs/plugin-react' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()] +}) diff --git a/rustle/frontend/wailsjs/go/main/App.d.ts b/rustle/frontend/wailsjs/go/main/App.d.ts new file mode 100755 index 0000000..bb27b42 --- /dev/null +++ b/rustle/frontend/wailsjs/go/main/App.d.ts @@ -0,0 +1,14 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function GetBZZZStatus():Promise>; + +export function GetUCXLContent(arg1:string):Promise>; + +export function Greet(arg1:string):Promise; + +export function PostUCXLContent(arg1:string,arg2:string,arg3:string,arg4:Record):Promise; + +export function SearchUCXLContent(arg1:string,arg2:Array,arg3:number):Promise>>; + +export function ValidateUCXLAddress(arg1:string):Promise>; diff --git a/rustle/frontend/wailsjs/go/main/App.js b/rustle/frontend/wailsjs/go/main/App.js new file mode 100755 index 0000000..a4f79ed --- /dev/null +++ b/rustle/frontend/wailsjs/go/main/App.js @@ -0,0 +1,27 @@ +// @ts-check +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export function GetBZZZStatus() { + return window['go']['main']['App']['GetBZZZStatus'](); +} + +export function GetUCXLContent(arg1) { + return window['go']['main']['App']['GetUCXLContent'](arg1); +} + +export function Greet(arg1) { + return window['go']['main']['App']['Greet'](arg1); +} + +export function PostUCXLContent(arg1, arg2, arg3, arg4) { + return window['go']['main']['App']['PostUCXLContent'](arg1, arg2, arg3, arg4); +} + +export function SearchUCXLContent(arg1, arg2, arg3) { + return window['go']['main']['App']['SearchUCXLContent'](arg1, arg2, arg3); +} + +export function ValidateUCXLAddress(arg1) { + return window['go']['main']['App']['ValidateUCXLAddress'](arg1); +} diff --git a/rustle/frontend/wailsjs/runtime/package.json b/rustle/frontend/wailsjs/runtime/package.json new file mode 100644 index 0000000..1e7c8a5 --- /dev/null +++ b/rustle/frontend/wailsjs/runtime/package.json @@ -0,0 +1,24 @@ +{ + "name": "@wailsapp/runtime", + "version": "2.0.0", + "description": "Wails Javascript runtime library", + "main": "runtime.js", + "types": "runtime.d.ts", + "scripts": { + }, + "repository": { + "type": "git", + "url": "git+https://github.com/wailsapp/wails.git" + }, + "keywords": [ + "Wails", + "Javascript", + "Go" + ], + "author": "Lea Anthony ", + "license": "MIT", + "bugs": { + "url": "https://github.com/wailsapp/wails/issues" + }, + "homepage": "https://github.com/wailsapp/wails#readme" +} diff --git a/rustle/frontend/wailsjs/runtime/runtime.d.ts b/rustle/frontend/wailsjs/runtime/runtime.d.ts new file mode 100644 index 0000000..4445dac --- /dev/null +++ b/rustle/frontend/wailsjs/runtime/runtime.d.ts @@ -0,0 +1,249 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export interface Position { + x: number; + y: number; +} + +export interface Size { + w: number; + h: number; +} + +export interface Screen { + isCurrent: boolean; + isPrimary: boolean; + width : number + height : number +} + +// Environment information such as platform, buildtype, ... +export interface EnvironmentInfo { + buildType: string; + platform: string; + arch: string; +} + +// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit) +// emits the given event. Optional data may be passed with the event. +// This will trigger any event listeners. +export function EventsEmit(eventName: string, ...data: any): void; + +// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. +export function EventsOn(eventName: string, callback: (...data: any) => void): () => void; + +// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) +// sets up a listener for the given event name, but will only trigger a given number times. +export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void; + +// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) +// sets up a listener for the given event name, but will only trigger once. +export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void; + +// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff) +// unregisters the listener for the given event name. +export function EventsOff(eventName: string, ...additionalEventNames: string[]): void; + +// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) +// unregisters all listeners. +export function EventsOffAll(): void; + +// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) +// logs the given message as a raw message +export function LogPrint(message: string): void; + +// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace) +// logs the given message at the `trace` log level. +export function LogTrace(message: string): void; + +// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug) +// logs the given message at the `debug` log level. +export function LogDebug(message: string): void; + +// [LogError](https://wails.io/docs/reference/runtime/log#logerror) +// logs the given message at the `error` log level. +export function LogError(message: string): void; + +// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal) +// logs the given message at the `fatal` log level. +// The application will quit after calling this method. +export function LogFatal(message: string): void; + +// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo) +// logs the given message at the `info` log level. +export function LogInfo(message: string): void; + +// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning) +// logs the given message at the `warning` log level. +export function LogWarning(message: string): void; + +// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload) +// Forces a reload by the main application as well as connected browsers. +export function WindowReload(): void; + +// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp) +// Reloads the application frontend. +export function WindowReloadApp(): void; + +// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop) +// Sets the window AlwaysOnTop or not on top. +export function WindowSetAlwaysOnTop(b: boolean): void; + +// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme) +// *Windows only* +// Sets window theme to system default (dark/light). +export function WindowSetSystemDefaultTheme(): void; + +// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme) +// *Windows only* +// Sets window to light theme. +export function WindowSetLightTheme(): void; + +// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme) +// *Windows only* +// Sets window to dark theme. +export function WindowSetDarkTheme(): void; + +// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter) +// Centers the window on the monitor the window is currently on. +export function WindowCenter(): void; + +// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle) +// Sets the text in the window title bar. +export function WindowSetTitle(title: string): void; + +// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen) +// Makes the window full screen. +export function WindowFullscreen(): void; + +// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen) +// Restores the previous window dimensions and position prior to full screen. +export function WindowUnfullscreen(): void; + +// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen) +// Returns the state of the window, i.e. whether the window is in full screen mode or not. +export function WindowIsFullscreen(): Promise; + +// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) +// Sets the width and height of the window. +export function WindowSetSize(width: number, height: number): void; + +// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) +// Gets the width and height of the window. +export function WindowGetSize(): Promise; + +// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize) +// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions. +// Setting a size of 0,0 will disable this constraint. +export function WindowSetMaxSize(width: number, height: number): void; + +// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize) +// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions. +// Setting a size of 0,0 will disable this constraint. +export function WindowSetMinSize(width: number, height: number): void; + +// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition) +// Sets the window position relative to the monitor the window is currently on. +export function WindowSetPosition(x: number, y: number): void; + +// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition) +// Gets the window position relative to the monitor the window is currently on. +export function WindowGetPosition(): Promise; + +// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide) +// Hides the window. +export function WindowHide(): void; + +// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow) +// Shows the window, if it is currently hidden. +export function WindowShow(): void; + +// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise) +// Maximises the window to fill the screen. +export function WindowMaximise(): void; + +// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise) +// Toggles between Maximised and UnMaximised. +export function WindowToggleMaximise(): void; + +// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise) +// Restores the window to the dimensions and position prior to maximising. +export function WindowUnmaximise(): void; + +// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised) +// Returns the state of the window, i.e. whether the window is maximised or not. +export function WindowIsMaximised(): Promise; + +// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) +// Minimises the window. +export function WindowMinimise(): void; + +// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise) +// Restores the window to the dimensions and position prior to minimising. +export function WindowUnminimise(): void; + +// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised) +// Returns the state of the window, i.e. whether the window is minimised or not. +export function WindowIsMinimised(): Promise; + +// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal) +// Returns the state of the window, i.e. whether the window is normal or not. +export function WindowIsNormal(): Promise; + +// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) +// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. +export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; + +// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall) +// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system. +export function ScreenGetAll(): Promise; + +// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl) +// Opens the given URL in the system browser. +export function BrowserOpenURL(url: string): void; + +// [Environment](https://wails.io/docs/reference/runtime/intro#environment) +// Returns information about the environment +export function Environment(): Promise; + +// [Quit](https://wails.io/docs/reference/runtime/intro#quit) +// Quits the application. +export function Quit(): void; + +// [Hide](https://wails.io/docs/reference/runtime/intro#hide) +// Hides the application. +export function Hide(): void; + +// [Show](https://wails.io/docs/reference/runtime/intro#show) +// Shows the application. +export function Show(): void; + +// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext) +// Returns the current text stored on clipboard +export function ClipboardGetText(): Promise; + +// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) +// Sets a text on the clipboard +export function ClipboardSetText(text: string): Promise; + +// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop) +// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. +export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void + +// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff) +// OnFileDropOff removes the drag and drop listeners and handlers. +export function OnFileDropOff() :void + +// Check if the file path resolver is available +export function CanResolveFilePaths(): boolean; + +// Resolves file paths for an array of files +export function ResolveFilePaths(files: File[]): void \ No newline at end of file diff --git a/rustle/frontend/wailsjs/runtime/runtime.js b/rustle/frontend/wailsjs/runtime/runtime.js new file mode 100644 index 0000000..623397b --- /dev/null +++ b/rustle/frontend/wailsjs/runtime/runtime.js @@ -0,0 +1,238 @@ +/* + _ __ _ __ +| | / /___ _(_) /____ +| | /| / / __ `/ / / ___/ +| |/ |/ / /_/ / / (__ ) +|__/|__/\__,_/_/_/____/ +The electron alternative for Go +(c) Lea Anthony 2019-present +*/ + +export function LogPrint(message) { + window.runtime.LogPrint(message); +} + +export function LogTrace(message) { + window.runtime.LogTrace(message); +} + +export function LogDebug(message) { + window.runtime.LogDebug(message); +} + +export function LogInfo(message) { + window.runtime.LogInfo(message); +} + +export function LogWarning(message) { + window.runtime.LogWarning(message); +} + +export function LogError(message) { + window.runtime.LogError(message); +} + +export function LogFatal(message) { + window.runtime.LogFatal(message); +} + +export function EventsOnMultiple(eventName, callback, maxCallbacks) { + return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); +} + +export function EventsOn(eventName, callback) { + return EventsOnMultiple(eventName, callback, -1); +} + +export function EventsOff(eventName, ...additionalEventNames) { + return window.runtime.EventsOff(eventName, ...additionalEventNames); +} + +export function EventsOnce(eventName, callback) { + return EventsOnMultiple(eventName, callback, 1); +} + +export function EventsEmit(eventName) { + let args = [eventName].slice.call(arguments); + return window.runtime.EventsEmit.apply(null, args); +} + +export function WindowReload() { + window.runtime.WindowReload(); +} + +export function WindowReloadApp() { + window.runtime.WindowReloadApp(); +} + +export function WindowSetAlwaysOnTop(b) { + window.runtime.WindowSetAlwaysOnTop(b); +} + +export function WindowSetSystemDefaultTheme() { + window.runtime.WindowSetSystemDefaultTheme(); +} + +export function WindowSetLightTheme() { + window.runtime.WindowSetLightTheme(); +} + +export function WindowSetDarkTheme() { + window.runtime.WindowSetDarkTheme(); +} + +export function WindowCenter() { + window.runtime.WindowCenter(); +} + +export function WindowSetTitle(title) { + window.runtime.WindowSetTitle(title); +} + +export function WindowFullscreen() { + window.runtime.WindowFullscreen(); +} + +export function WindowUnfullscreen() { + window.runtime.WindowUnfullscreen(); +} + +export function WindowIsFullscreen() { + return window.runtime.WindowIsFullscreen(); +} + +export function WindowGetSize() { + return window.runtime.WindowGetSize(); +} + +export function WindowSetSize(width, height) { + window.runtime.WindowSetSize(width, height); +} + +export function WindowSetMaxSize(width, height) { + window.runtime.WindowSetMaxSize(width, height); +} + +export function WindowSetMinSize(width, height) { + window.runtime.WindowSetMinSize(width, height); +} + +export function WindowSetPosition(x, y) { + window.runtime.WindowSetPosition(x, y); +} + +export function WindowGetPosition() { + return window.runtime.WindowGetPosition(); +} + +export function WindowHide() { + window.runtime.WindowHide(); +} + +export function WindowShow() { + window.runtime.WindowShow(); +} + +export function WindowMaximise() { + window.runtime.WindowMaximise(); +} + +export function WindowToggleMaximise() { + window.runtime.WindowToggleMaximise(); +} + +export function WindowUnmaximise() { + window.runtime.WindowUnmaximise(); +} + +export function WindowIsMaximised() { + return window.runtime.WindowIsMaximised(); +} + +export function WindowMinimise() { + window.runtime.WindowMinimise(); +} + +export function WindowUnminimise() { + window.runtime.WindowUnminimise(); +} + +export function WindowSetBackgroundColour(R, G, B, A) { + window.runtime.WindowSetBackgroundColour(R, G, B, A); +} + +export function ScreenGetAll() { + return window.runtime.ScreenGetAll(); +} + +export function WindowIsMinimised() { + return window.runtime.WindowIsMinimised(); +} + +export function WindowIsNormal() { + return window.runtime.WindowIsNormal(); +} + +export function BrowserOpenURL(url) { + window.runtime.BrowserOpenURL(url); +} + +export function Environment() { + return window.runtime.Environment(); +} + +export function Quit() { + window.runtime.Quit(); +} + +export function Hide() { + window.runtime.Hide(); +} + +export function Show() { + window.runtime.Show(); +} + +export function ClipboardGetText() { + return window.runtime.ClipboardGetText(); +} + +export function ClipboardSetText(text) { + return window.runtime.ClipboardSetText(text); +} + +/** + * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. + * + * @export + * @callback OnFileDropCallback + * @param {number} x - x coordinate of the drop + * @param {number} y - y coordinate of the drop + * @param {string[]} paths - A list of file paths. + */ + +/** + * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. + * + * @export + * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. + * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) + */ +export function OnFileDrop(callback, useDropTarget) { + return window.runtime.OnFileDrop(callback, useDropTarget); +} + +/** + * OnFileDropOff removes the drag and drop listeners and handlers. + */ +export function OnFileDropOff() { + return window.runtime.OnFileDropOff(); +} + +export function CanResolveFilePaths() { + return window.runtime.CanResolveFilePaths(); +} + +export function ResolveFilePaths(files) { + return window.runtime.ResolveFilePaths(files); +} \ No newline at end of file diff --git a/rustle/go.mod b/rustle/go.mod new file mode 100644 index 0000000..e5c42d9 --- /dev/null +++ b/rustle/go.mod @@ -0,0 +1,37 @@ +module rustle + +go 1.23.0 + +toolchain go1.24.5 + +require github.com/wailsapp/wails/v2 v2.10.2 + +require ( + github.com/bep/debounce v1.2.1 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect + github.com/labstack/echo/v4 v4.13.3 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/leaanthony/go-ansi-parser v1.6.1 // indirect + github.com/leaanthony/gosod v1.0.4 // indirect + github.com/leaanthony/slicer v1.6.0 // indirect + github.com/leaanthony/u v1.1.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/tkrajina/go-reflector v0.5.8 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/wailsapp/go-webview2 v1.0.19 // indirect + github.com/wailsapp/mimetype v1.4.1 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect +) diff --git a/rustle/go.sum b/rustle/go.sum new file mode 100644 index 0000000..b1e0229 --- /dev/null +++ b/rustle/go.sum @@ -0,0 +1,81 @@ +github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= +github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= +github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= +github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= +github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= +github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= +github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= +github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= +github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI= +github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw= +github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= +github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= +github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= +github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= +github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/wailsapp/go-webview2 v1.0.19 h1:7U3QcDj1PrBPaxJNCui2k1SkWml+Q5kvFUFyTImA6NU= +github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= +github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= +github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= +github.com/wailsapp/wails/v2 v2.10.2 h1:29U+c5PI4K4hbx8yFbFvwpCuvqK9VgNv8WGobIlKlXk= +github.com/wailsapp/wails/v2 v2.10.2/go.mod h1:XuN4IUOPpzBrHUkEd7sCU5ln4T/p1wQedfxP7fKik+4= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/rustle/main.go b/rustle/main.go new file mode 100644 index 0000000..7fc6d46 --- /dev/null +++ b/rustle/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "embed" + + "github.com/wailsapp/wails/v2" + "github.com/wailsapp/wails/v2/pkg/options" + "github.com/wailsapp/wails/v2/pkg/options/assetserver" +) + +//go:embed all:frontend/dist +var assets embed.FS + +func main() { + // Create an instance of the app structure + app := NewApp() + + // Create application with options + err := wails.Run(&options.App{ + Title: "rustle", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + OnStartup: app.startup, + Bind: []interface{}{ + app, + }, + }) + + if err != nil { + println("Error:", err.Error()) + } +} diff --git a/rustle/wails.json b/rustle/wails.json new file mode 100644 index 0000000..b308533 --- /dev/null +++ b/rustle/wails.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://wails.io/schemas/config.v2.json", + "name": "rustle", + "outputfilename": "rustle", + "frontend:install": "npm install", + "frontend:build": "npm run build", + "frontend:dev:watcher": "npm run dev", + "frontend:dev:serverUrl": "auto", + "author": { + "name": "anthonyrawlins", + "email": "anthonyrawlins@users.noreply.github.com" + } +}