Compare commits
10 Commits
31eff22d95
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1f19b9464 | ||
|
|
b05827712b | ||
|
|
78a1be39eb | ||
|
|
5e71721f4d | ||
|
|
d66602cc7d | ||
|
|
63cf089d27 | ||
|
|
03e95a2c69 | ||
|
|
f86b48f43e | ||
|
|
afe555f51e | ||
|
|
7a567292ae |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
auto-imports.d.ts
|
||||||
|
components.d.ts
|
||||||
3
app/.gitignore
vendored
Normal file
3
app/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
build/bin
|
||||||
|
node_modules
|
||||||
|
frontend/dist
|
||||||
19
app/README.md
Normal file
19
app/README.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# README
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
This is the official Wails Vue-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`.
|
||||||
7
app/Taskfile.yml
Normal file
7
app/Taskfile.yml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
dev:
|
||||||
|
cmd: wails dev -tags webkit2_41
|
||||||
|
# env:
|
||||||
|
# WEBKIT_DISABLE_DMABUF_RENDERER: "1"
|
||||||
27
app/app.go
Normal file
27
app/app.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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. The context is saved
|
||||||
|
// so we can call the runtime methods
|
||||||
|
func (a *App) startup(ctx context.Context) {
|
||||||
|
a.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greet returns a greeting for the given name
|
||||||
|
func (a *App) Greet(name string) string {
|
||||||
|
return fmt.Sprintf("Hello %s, It's show time!", name)
|
||||||
|
}
|
||||||
35
app/build/README.md
Normal file
35
app/build/README.md
Normal file
@@ -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.
|
||||||
BIN
app/build/appicon.png
Normal file
BIN
app/build/appicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
68
app/build/darwin/Info.dev.plist
Normal file
68
app/build/darwin/Info.dev.plist
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>{{.Info.ProductName}}</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>{{.OutputFilename}}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.wails.{{.Name}}</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>{{.Info.ProductVersion}}</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>{{.Info.Comments}}</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>{{.Info.ProductVersion}}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>iconfile</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.13.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>true</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>{{.Info.Copyright}}</string>
|
||||||
|
{{if .Info.FileAssociations}}
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
{{range .Info.FileAssociations}}
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>{{.Ext}}</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>{{.Name}}</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>{{.Role}}</string>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>{{.IconName}}</string>
|
||||||
|
</dict>
|
||||||
|
{{end}}
|
||||||
|
</array>
|
||||||
|
{{end}}
|
||||||
|
{{if .Info.Protocols}}
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
{{range .Info.Protocols}}
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>com.wails.{{.Scheme}}</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>{{.Scheme}}</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>{{.Role}}</string>
|
||||||
|
</dict>
|
||||||
|
{{end}}
|
||||||
|
</array>
|
||||||
|
{{end}}
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
63
app/build/darwin/Info.plist
Normal file
63
app/build/darwin/Info.plist
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>{{.Info.ProductName}}</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>{{.OutputFilename}}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.wails.{{.Name}}</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>{{.Info.ProductVersion}}</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>{{.Info.Comments}}</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>{{.Info.ProductVersion}}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>iconfile</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.13.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>true</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>{{.Info.Copyright}}</string>
|
||||||
|
{{if .Info.FileAssociations}}
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
{{range .Info.FileAssociations}}
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>{{.Ext}}</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>{{.Name}}</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>{{.Role}}</string>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>{{.IconName}}</string>
|
||||||
|
</dict>
|
||||||
|
{{end}}
|
||||||
|
</array>
|
||||||
|
{{end}}
|
||||||
|
{{if .Info.Protocols}}
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
{{range .Info.Protocols}}
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>com.wails.{{.Scheme}}</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>{{.Scheme}}</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>{{.Role}}</string>
|
||||||
|
</dict>
|
||||||
|
{{end}}
|
||||||
|
</array>
|
||||||
|
{{end}}
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
BIN
app/build/windows/icon.ico
Normal file
BIN
app/build/windows/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
15
app/build/windows/info.json
Normal file
15
app/build/windows/info.json
Normal file
@@ -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}}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
114
app/build/windows/installer/project.nsi
Normal file
114
app/build/windows/installer/project.nsi
Normal file
@@ -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
|
||||||
249
app/build/windows/installer/wails_tools.nsh
Normal file
249
app/build/windows/installer/wails_tools.nsh
Normal file
@@ -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
|
||||||
15
app/build/windows/wails.exe.manifest
Normal file
15
app/build/windows/wails.exe.manifest
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<assemblyIdentity type="win32" name="com.wails.{{.Name}}" version="{{.Info.ProductVersion}}.0" processorArchitecture="*"/>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings>
|
||||||
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- fallback for Windows 7 and 8 -->
|
||||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to per-monitor if per-monitor v2 is not supported -->
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
</assembly>
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
"MaybeRefOrGetter": true,
|
"MaybeRefOrGetter": true,
|
||||||
"PropType": true,
|
"PropType": true,
|
||||||
"Ref": true,
|
"Ref": true,
|
||||||
|
"ShallowRef": true,
|
||||||
"Slot": true,
|
"Slot": true,
|
||||||
"Slots": true,
|
"Slots": true,
|
||||||
"VNode": true,
|
"VNode": true,
|
||||||
@@ -26,18 +27,18 @@
|
|||||||
"effectScope": true,
|
"effectScope": true,
|
||||||
"getCurrentInstance": true,
|
"getCurrentInstance": true,
|
||||||
"getCurrentScope": true,
|
"getCurrentScope": true,
|
||||||
|
"getCurrentWatcher": true,
|
||||||
"h": true,
|
"h": true,
|
||||||
"inject": true,
|
"inject": true,
|
||||||
"isProxy": true,
|
"isProxy": true,
|
||||||
"isReactive": true,
|
"isReactive": true,
|
||||||
"isReadonly": true,
|
"isReadonly": true,
|
||||||
"isRef": true,
|
"isRef": true,
|
||||||
|
"isShallow": true,
|
||||||
"markRaw": true,
|
"markRaw": true,
|
||||||
"nextTick": true,
|
"nextTick": true,
|
||||||
"onActivated": true,
|
"onActivated": true,
|
||||||
"onBeforeMount": true,
|
"onBeforeMount": true,
|
||||||
"onBeforeRouteLeave": true,
|
|
||||||
"onBeforeRouteUpdate": true,
|
|
||||||
"onBeforeUnmount": true,
|
"onBeforeUnmount": true,
|
||||||
"onBeforeUpdate": true,
|
"onBeforeUpdate": true,
|
||||||
"onDeactivated": true,
|
"onDeactivated": true,
|
||||||
@@ -70,8 +71,6 @@
|
|||||||
"useCssVars": true,
|
"useCssVars": true,
|
||||||
"useId": true,
|
"useId": true,
|
||||||
"useModel": true,
|
"useModel": true,
|
||||||
"useRoute": true,
|
|
||||||
"useRouter": true,
|
|
||||||
"useSlots": true,
|
"useSlots": true,
|
||||||
"useTemplateRef": true,
|
"useTemplateRef": true,
|
||||||
"watch": true,
|
"watch": true,
|
||||||
13
app/frontend/index.html
Normal file
13
app/frontend/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
|
<title>IrChad</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script src="./src/main.ts" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
7282
app/frontend/package-lock.json
generated
Normal file
7282
app/frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
app/frontend/package.json
Normal file
40
app/frontend/package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "irchad",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource/roboto": "^5.2.9",
|
||||||
|
"@mdi/font": "^7.4.47",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"dompurify": "^3.3.1",
|
||||||
|
"irc-framework": "^4.14.0",
|
||||||
|
"markdown-it": "^14.1.0",
|
||||||
|
"pinia": "^3.0.4",
|
||||||
|
"vite-plugin-node-polyfills": "^0.25.0",
|
||||||
|
"vue": "^3.2.37",
|
||||||
|
"vuetify": "^3.11.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/types": "^7.18.10",
|
||||||
|
"@tsconfig/node22": "^22.0.5",
|
||||||
|
"@types/dompurify": "^3.0.5",
|
||||||
|
"@types/markdown-it": "^14.1.2",
|
||||||
|
"@vitejs/plugin-vue": "^6.0.3",
|
||||||
|
"@vue/tsconfig": "^0.8.1",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
|
"eslint-config-vuetify": "^4.3.5-beta.1",
|
||||||
|
"sass-embedded": "^1.97.2",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"unplugin-auto-import": "^21.0.0",
|
||||||
|
"unplugin-vue-components": "^31.0.0",
|
||||||
|
"vite": "^7.3.1",
|
||||||
|
"vite-plugin-vuetify": "^2.1.2",
|
||||||
|
"vue-tsc": "^3.2.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
app/frontend/package.json.md5
Executable file
1
app/frontend/package.json.md5
Executable file
@@ -0,0 +1 @@
|
|||||||
|
d86c613036f8e3460cc743ac40acec6e
|
||||||
23
app/frontend/src/App.vue
Normal file
23
app/frontend/src/App.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<template>
|
||||||
|
<v-app>
|
||||||
|
<router-view />
|
||||||
|
|
||||||
|
<!-- <Login v-if="!ircStore.connected" /> -->
|
||||||
|
<!-- <Chat v-else /> -->
|
||||||
|
<!-- <v-dialog max-width="450px" v-model="showRegistration"> -->
|
||||||
|
<!-- <Register @success="showRegistration = false" /> -->
|
||||||
|
<!-- </v-dialog> -->
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useIRCStore } from "@/stores/irc";
|
||||||
|
import Chat from "@/components/Chat.vue";
|
||||||
|
import Login from "@/components/Login.vue";
|
||||||
|
import Register from "@/components/Register.vue";
|
||||||
|
import { useAccountStore } from "./stores/accountStore";
|
||||||
|
|
||||||
|
const ircStore = useIRCStore();
|
||||||
|
const accountStore = useAccountStore();
|
||||||
|
const { showRegistration } = storeToRefs(accountStore);
|
||||||
|
</script>
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useIRCStore } from "@/stores/irc";
|
import { useBufferStore } from "@/stores/bufferStore";
|
||||||
|
const { setActiveBuffer, buffers, activeBufferName } = useBufferStore();
|
||||||
|
|
||||||
const { setActiveBuffer, buffers, activeBufferName } = useIRCStore();
|
const store = useBufferStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
63
app/frontend/src/components/Chat.vue
Normal file
63
app/frontend/src/components/Chat.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useIRCStore } from "@/stores/irc";
|
||||||
|
import { useBufferStore } from "@/stores/bufferStore";
|
||||||
|
import { useAccountStore } from "@/stores/accountStore";
|
||||||
|
|
||||||
|
const bufferStore = useBufferStore();
|
||||||
|
const ircStore = useIRCStore();
|
||||||
|
const accountStore = useAccountStore();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="d-flex flex-row" style="height: 100vh">
|
||||||
|
<router-view />
|
||||||
|
<v-sheet border class="buffers">
|
||||||
|
<UserCard />
|
||||||
|
<v-divider />
|
||||||
|
<BufferList />
|
||||||
|
</v-sheet>
|
||||||
|
<div class="messages d-flex flex-column">
|
||||||
|
<v-card-title>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="2">
|
||||||
|
{{ bufferStore.activeBufferName }}
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="3">
|
||||||
|
{{ bufferStore.activeBuffer?.topic }}
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-title>
|
||||||
|
<MessageList
|
||||||
|
:messages="bufferStore.activeBuffer?.messages"
|
||||||
|
:me="accountStore.account.nick"
|
||||||
|
/>
|
||||||
|
<v-sheet>
|
||||||
|
<InputBuffer
|
||||||
|
@raw="(txt: string) => ircStore.client.raw(txt)"
|
||||||
|
@send="ircStore.sendActiveBuffer"
|
||||||
|
/>
|
||||||
|
</v-sheet>
|
||||||
|
</div>
|
||||||
|
<v-sheet class="user-list h-100" border>
|
||||||
|
<UserList :users="bufferStore.activeBuffer?.users" />
|
||||||
|
</v-sheet>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.buffers {
|
||||||
|
height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages {
|
||||||
|
height: 100%;
|
||||||
|
flex: 3;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-list {
|
||||||
|
height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
132
app/frontend/src/components/InputBuffer.vue
Normal file
132
app/frontend/src/components/InputBuffer.vue
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useIRCStore } from "@/stores/irc";
|
||||||
|
import { useBufferStore } from "@/stores/bufferStore";
|
||||||
|
import { useAccountStore } from "@/stores/accountStore";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
const emit = defineEmits(["send", "raw"]);
|
||||||
|
|
||||||
|
const store = useIRCStore();
|
||||||
|
const accountStore = useAccountStore();
|
||||||
|
const { showRegistration } = storeToRefs(accountStore);
|
||||||
|
const router = useRouter();
|
||||||
|
const bufferStore = useBufferStore();
|
||||||
|
const text = ref();
|
||||||
|
const menu = ref({
|
||||||
|
open: false,
|
||||||
|
activator: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const menuList = ref({
|
||||||
|
density: "compact",
|
||||||
|
slim: true,
|
||||||
|
items: [] as any[],
|
||||||
|
itemTitle: "title",
|
||||||
|
itemValue: "value",
|
||||||
|
selected: [],
|
||||||
|
selectable: true,
|
||||||
|
mandatory: true,
|
||||||
|
returnObject: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cursorPos = ref(0);
|
||||||
|
const completionPos = ref(0);
|
||||||
|
|
||||||
|
function clickItem() {}
|
||||||
|
function send() {
|
||||||
|
if (!text.value) return;
|
||||||
|
|
||||||
|
if (text.value.slice(0, 2) === "//") {
|
||||||
|
emit("raw", text.value.substring(2));
|
||||||
|
} else if (text.value[0] === "/") {
|
||||||
|
router.push({ path: "/register" });
|
||||||
|
} else {
|
||||||
|
emit("send", text.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.value.open = false;
|
||||||
|
text.value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterUsers(s: string) {
|
||||||
|
if (store.activeBuffer) {
|
||||||
|
return store.activeBuffer.users.filter((u) => u.nick.startsWith(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function trigger(ev: KeyboardEvent) {
|
||||||
|
const input = ev.target;
|
||||||
|
const cursor = input.selectionStart;
|
||||||
|
cursorPos.value = cursor;
|
||||||
|
const text = input.value;
|
||||||
|
|
||||||
|
const textBefore = text.slice(0, cursor);
|
||||||
|
const mentionMatch = textBefore.match(/@(\w*)$/);
|
||||||
|
if (text[0] === "/") {
|
||||||
|
menu.value.open = true;
|
||||||
|
menuList.value.items = [
|
||||||
|
{
|
||||||
|
title: "register",
|
||||||
|
value: "register",
|
||||||
|
cmd: () => {
|
||||||
|
showRegistration.value = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else if (mentionMatch) {
|
||||||
|
menu.value.open = true;
|
||||||
|
menuList.value.items = filterUsers(mentionMatch[1]);
|
||||||
|
menuList.value.itemTitle = "nick";
|
||||||
|
menuList.value.itemValue = "nick";
|
||||||
|
} else {
|
||||||
|
menu.value.open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function tabComplete() {
|
||||||
|
if (!menu.value.open) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextSelection = 0;
|
||||||
|
if (menuList.value.selected.length) {
|
||||||
|
const currentIdx = menuList.value.items.indexOf(menuList.value.selected[0]);
|
||||||
|
const nextIdx = currentIdx + 1;
|
||||||
|
if (menuList.value.items[nextIdx]) nextSelection = nextIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
menuList.value.selected = [menuList.value.items[nextSelection]];
|
||||||
|
|
||||||
|
// hello @
|
||||||
|
|
||||||
|
const beforeCursor = text.value.splice(0, cursor);
|
||||||
|
const afterCursor = text.value.slice(cursor);
|
||||||
|
}
|
||||||
|
const rows = ref(1);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<v-menu
|
||||||
|
v-model="menu.open"
|
||||||
|
location="top"
|
||||||
|
activator="parent"
|
||||||
|
:open-on-click="false"
|
||||||
|
:open-on-focus="false"
|
||||||
|
>
|
||||||
|
<v-list v-bind="menuList" @click:select="clickItem" />
|
||||||
|
</v-menu>
|
||||||
|
<v-textarea
|
||||||
|
:rows="rows"
|
||||||
|
auto-grow
|
||||||
|
v-model="text"
|
||||||
|
autofocus
|
||||||
|
hide-details
|
||||||
|
:placeholder="`Message ${bufferStore.activeBufferName}`"
|
||||||
|
@input="trigger"
|
||||||
|
@keydown.enter.exact.prevent="send"
|
||||||
|
@keydown.tab.prevent="tabComplete"
|
||||||
|
variant="outlined"
|
||||||
|
class="ma-1"
|
||||||
|
role="irchad"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
68
app/frontend/src/components/Login.vue
Normal file
68
app/frontend/src/components/Login.vue
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useAccountStore } from "@/stores/accountStore";
|
||||||
|
import { useIRCStore } from "@/stores/irc";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const accountStore = useAccountStore();
|
||||||
|
const ircStore = useIRCStore();
|
||||||
|
|
||||||
|
const { account } = storeToRefs(accountStore);
|
||||||
|
const withAccount = ref(false);
|
||||||
|
const form = ref(false);
|
||||||
|
const connecting = ref(false);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
function login() {
|
||||||
|
ircStore.connect();
|
||||||
|
connecting.value = true;
|
||||||
|
router.push({ name: "Chat" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function required(v: any) {
|
||||||
|
return !!v || "This field is required";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main class="w-25 mt-5 ma-auto">
|
||||||
|
<v-card title="Login to IrChad">
|
||||||
|
<v-form @submit.prevent="login" v-model="form">
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field
|
||||||
|
v-model="account.nick"
|
||||||
|
label="Nickname"
|
||||||
|
:rules="[required]"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
v-if="withAccount"
|
||||||
|
v-model="account.account"
|
||||||
|
label="Username"
|
||||||
|
role="username"
|
||||||
|
:rules="[required]"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
v-model="account.password"
|
||||||
|
v-if="withAccount"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
:rules="[required]"
|
||||||
|
/>
|
||||||
|
<v-alert color="error" v-if="accountStore.authError.reason">
|
||||||
|
{{ accountStore.authError.message }}
|
||||||
|
</v-alert>
|
||||||
|
<v-checkbox v-model="withAccount" label="Login with an account" />
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn
|
||||||
|
:loading="connecting"
|
||||||
|
type="submit"
|
||||||
|
color="success"
|
||||||
|
:disabled="!form"
|
||||||
|
>Connect</v-btn
|
||||||
|
>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-form>
|
||||||
|
</v-card>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
81
app/frontend/src/components/MessageList.vue
Normal file
81
app/frontend/src/components/MessageList.vue
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, watch, useTemplateRef } from "vue";
|
||||||
|
import { useIRCStore } from "@/stores/irc";
|
||||||
|
const props = defineProps(["messages", "me"]);
|
||||||
|
|
||||||
|
const store = useIRCStore();
|
||||||
|
const messagesReverse = computed(() => {
|
||||||
|
if (props.messages) {
|
||||||
|
return [...props.messages];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeFormatter = new Intl.DateTimeFormat("en-US", {
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
function formatTime(ts: string) {
|
||||||
|
const date = new Date(ts);
|
||||||
|
return timeFormatter.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatHistory = useTemplateRef("chat-scrollback");
|
||||||
|
watch(
|
||||||
|
() => props.messages,
|
||||||
|
() =>
|
||||||
|
nextTick(() => {
|
||||||
|
chatHistory.value!.scrollTop = chatHistory.value!.scrollHeight;
|
||||||
|
}),
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-sheet ref="chat-history" class="message-list d-flex">
|
||||||
|
<div ref="chat-scrollback">
|
||||||
|
<v-virtual-scroll height="100%" :items="messagesReverse">
|
||||||
|
<template #default="{ item: msg }">
|
||||||
|
<v-list-item
|
||||||
|
density="compact"
|
||||||
|
:prepend-avatar="store.getMetadata(msg.nick, 'avatar')"
|
||||||
|
>
|
||||||
|
<v-list-item-title>
|
||||||
|
<span
|
||||||
|
class="message-nick font-weight-bold"
|
||||||
|
:class="{ 'text-primary': me === msg.nick }"
|
||||||
|
>
|
||||||
|
{{ msg.nick }}
|
||||||
|
</span>
|
||||||
|
<span class="message-time" v-if="!!msg.time">{{
|
||||||
|
formatTime(msg.time)
|
||||||
|
}}</span>
|
||||||
|
<v-chip
|
||||||
|
class="ml-2"
|
||||||
|
v-bind="props"
|
||||||
|
label
|
||||||
|
color="purple-lighten-2"
|
||||||
|
v-if="msg.kind === 'notice'"
|
||||||
|
size="x-small"
|
||||||
|
><v-icon class="mr-2">mdi-eye</v-icon>Only visible to you
|
||||||
|
</v-chip>
|
||||||
|
</v-list-item-title>
|
||||||
|
<div v-html="msg.message" /> </v-list-item
|
||||||
|
></template>
|
||||||
|
</v-virtual-scroll>
|
||||||
|
</div>
|
||||||
|
</v-sheet>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.message-list {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
.message-time {
|
||||||
|
font-size: 0.65em;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
88
app/frontend/src/components/Register.vue
Normal file
88
app/frontend/src/components/Register.vue
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { HookStatus, useIRCStore } from "@/stores/irc";
|
||||||
|
import { onBeforeMount, onBeforeUnmount } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const router = useRouter();
|
||||||
|
const ircStore = useIRCStore();
|
||||||
|
const emit = defineEmits(["success"]);
|
||||||
|
|
||||||
|
const RegistrationSuccess = new RegExp(/Account created/);
|
||||||
|
function interceptNickServMessage(message: { nick: string; message: string }) {
|
||||||
|
if (message.nick !== "NickServ") {
|
||||||
|
return HookStatus.HOOK_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
registering.value = false;
|
||||||
|
|
||||||
|
if (message.message.match(RegistrationSuccess)) {
|
||||||
|
emit("success");
|
||||||
|
}
|
||||||
|
|
||||||
|
return HookStatus.HOOK_EAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
ircStore.registerHook("message", interceptNickServMessage);
|
||||||
|
ircStore.registerHook("notice", interceptNickServMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
ircStore.unregisterHook("message", interceptNickServMessage);
|
||||||
|
ircStore.unregisterHook("notice", interceptNickServMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
const newAccount = ref({
|
||||||
|
email: "",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = ref(false);
|
||||||
|
const registering = ref(false);
|
||||||
|
|
||||||
|
function register() {
|
||||||
|
if (!form.value) return;
|
||||||
|
|
||||||
|
const { password, email } = newAccount.value;
|
||||||
|
registering.value = true;
|
||||||
|
ircStore.client.say("NickServ", `REGISTER ${password} ${email}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancel() {
|
||||||
|
router.back();
|
||||||
|
}
|
||||||
|
const show = ref(true);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<v-dialog v-model="show" max-width="400px" persistent>
|
||||||
|
<v-card title="Account Registration">
|
||||||
|
<v-form v-model="form" @submit.prevent="register">
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field
|
||||||
|
:rules="[(v) => !!v || 'Email required']"
|
||||||
|
v-model="newAccount.email"
|
||||||
|
label="Email"
|
||||||
|
role="email"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
:rules="[(v) => !!v || 'Password required']"
|
||||||
|
v-model="newAccount.password"
|
||||||
|
label="Password"
|
||||||
|
role="password"
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn
|
||||||
|
:disabled="!form"
|
||||||
|
type="submit"
|
||||||
|
:loading="registering"
|
||||||
|
text="Register"
|
||||||
|
color="success"
|
||||||
|
/>
|
||||||
|
<v-btn @click="cancel">Cancel</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-form>
|
||||||
|
</v-card></v-dialog
|
||||||
|
>
|
||||||
|
</template>
|
||||||
52
app/frontend/src/components/UserCard.vue
Normal file
52
app/frontend/src/components/UserCard.vue
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useIRCStore } from "@/stores/irc";
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import { useAccountStore } from "@/stores/accountStore";
|
||||||
|
|
||||||
|
const ircStore = useIRCStore();
|
||||||
|
const accountStore = useAccountStore();
|
||||||
|
const { selfAvatar } = storeToRefs(ircStore);
|
||||||
|
const avatarDialog = ref(false);
|
||||||
|
const newNick = ref();
|
||||||
|
const newBio = ref();
|
||||||
|
|
||||||
|
function changeAvatar() {
|
||||||
|
newNick.value = accountStore.account.nick;
|
||||||
|
avatarDialog.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitAvatar() {
|
||||||
|
ircStore.setAvatar(selfAvatar.value);
|
||||||
|
avatarDialog.value = false;
|
||||||
|
if (newNick.value && accountStore.account.nick !== newNick.value) {
|
||||||
|
ircStore.setNick(newNick.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newBio.value) {
|
||||||
|
ircStore.setBio(newBio.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-dialog v-model="avatarDialog" max-width="800px">
|
||||||
|
<v-card title="Edit Profile">
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field v-model="selfAvatar" label="Avatar URL" />
|
||||||
|
<v-text-field v-model="newNick" label="Nick" />
|
||||||
|
<v-text-field v-model="newBio" label="Bio" />
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn text="OK" @click="submitAvatar" />
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
<v-avatar @click="changeAvatar" v-if="selfAvatar" :image="selfAvatar" />
|
||||||
|
{{ accountStore.account.nick }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text> </v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
37
app/frontend/src/components/UserList.vue
Normal file
37
app/frontend/src/components/UserList.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useBufferStore } from "@/stores/bufferStore";
|
||||||
|
import { useIRCStore } from "@/stores/irc";
|
||||||
|
import { computed } from "vue";
|
||||||
|
const props = defineProps(["users"]);
|
||||||
|
const store = useIRCStore();
|
||||||
|
const bufferStore = useBufferStore();
|
||||||
|
|
||||||
|
const sortedUsers = computed(() => {
|
||||||
|
if (!bufferStore.activeBuffer || !bufferStore.activeBuffer.users) return [];
|
||||||
|
const u = [...bufferStore.activeBuffer.users];
|
||||||
|
u.sort((a, b) => a.nick.localeCompare(b.nick));
|
||||||
|
return u;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-list density="compact">
|
||||||
|
<v-list-item
|
||||||
|
v-for="user in sortedUsers"
|
||||||
|
:prepend-avatar="store.getMetadata(user.nick, 'avatar')"
|
||||||
|
:title="user.nick"
|
||||||
|
>
|
||||||
|
<v-menu activator="parent">
|
||||||
|
<v-card :title="user.nick">
|
||||||
|
<v-card-text>
|
||||||
|
<p v-text="store.metadata[user.nick]?.bio"></p>
|
||||||
|
</v-card-text>
|
||||||
|
<v-list density="compact">
|
||||||
|
<v-list-item title="Ident"> {{ user.ident }}</v-list-item>
|
||||||
|
<v-list-item title="Hostname"> {{ user.hostname }}</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-menu>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</template>
|
||||||
51
app/frontend/src/lib/buffer.ts
Normal file
51
app/frontend/src/lib/buffer.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import IrcChannel from "irc-framework/src/channel";
|
||||||
|
import IrcUser from "irc-framework/src/user";
|
||||||
|
|
||||||
|
export interface BufferOptions {
|
||||||
|
channel: typeof IrcChannel;
|
||||||
|
name: string;
|
||||||
|
metadata?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Buffer {
|
||||||
|
name: string;
|
||||||
|
options: BufferOptions;
|
||||||
|
channel: IrcChannel;
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
lastSeenIdx: number;
|
||||||
|
messages: any[];
|
||||||
|
typing: string[];
|
||||||
|
users: IrcUser[];
|
||||||
|
topic: string | null;
|
||||||
|
|
||||||
|
constructor(options: BufferOptions) {
|
||||||
|
this.options = options || null;
|
||||||
|
|
||||||
|
this.name = options.name;
|
||||||
|
|
||||||
|
this.channel = options.channel || null;
|
||||||
|
|
||||||
|
this.metadata = options.metadata || {};
|
||||||
|
this.messages = [];
|
||||||
|
this.lastSeenIdx = 0;
|
||||||
|
|
||||||
|
this.typing = [];
|
||||||
|
this.topic = options.topic || null;
|
||||||
|
|
||||||
|
if (this.channel) this.syncUsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
syncUsers() {
|
||||||
|
this.users = [...this.channel.users];
|
||||||
|
}
|
||||||
|
|
||||||
|
kind() {
|
||||||
|
return this.name.startsWith("#") ? "channel" : "pm";
|
||||||
|
}
|
||||||
|
|
||||||
|
resetLastSeen() {
|
||||||
|
this.lastSeenIdx = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/frontend/src/main.ts
Normal file
16
app/frontend/src/main.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { registerPlugins } from "@/plugins";
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import App from "./App.vue";
|
||||||
|
|
||||||
|
// Composables
|
||||||
|
import { createApp } from "vue";
|
||||||
|
|
||||||
|
// Styles
|
||||||
|
// import "unfonts.css";
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
registerPlugins(app);
|
||||||
|
|
||||||
|
app.mount("#app");
|
||||||
9
app/frontend/src/plugins/index.ts
Normal file
9
app/frontend/src/plugins/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import vuetify from "./vuetify";
|
||||||
|
import pinia from "@/stores";
|
||||||
|
import router from "./router.ts";
|
||||||
|
|
||||||
|
import type { App } from "vue";
|
||||||
|
|
||||||
|
export function registerPlugins(app: App) {
|
||||||
|
app.use(vuetify).use(router).use(pinia);
|
||||||
|
}
|
||||||
37
app/frontend/src/plugins/router.ts
Normal file
37
app/frontend/src/plugins/router.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { createMemoryHistory, createRouter } from "vue-router";
|
||||||
|
import Chat from "@/components/Chat.vue";
|
||||||
|
import { useIRCStore } from "@/stores/irc";
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
name: "Chat",
|
||||||
|
component: Chat,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "register",
|
||||||
|
name: "Register",
|
||||||
|
component: () => import("@/components/Register.vue"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: "Login",
|
||||||
|
component: () => import("@/components/Login.vue"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createMemoryHistory(),
|
||||||
|
routes,
|
||||||
|
});
|
||||||
|
|
||||||
|
router.beforeEach(async (to, from) => {
|
||||||
|
if (!useIRCStore().connected && to.name !== "Login") {
|
||||||
|
return { name: "Login" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
10
app/frontend/src/plugins/vuetify.ts
Normal file
10
app/frontend/src/plugins/vuetify.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import "@mdi/font/css/materialdesignicons.css";
|
||||||
|
import "vuetify/styles";
|
||||||
|
|
||||||
|
import { createVuetify } from "vuetify";
|
||||||
|
|
||||||
|
export default createVuetify({
|
||||||
|
theme: {
|
||||||
|
defaultTheme: "system",
|
||||||
|
},
|
||||||
|
});
|
||||||
34
app/frontend/src/stores/accountStore.ts
Normal file
34
app/frontend/src/stores/accountStore.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
export const useAccountStore = defineStore("accountStore", () => {
|
||||||
|
const authenticated = ref(false);
|
||||||
|
const showRegistration = ref(false);
|
||||||
|
const account = ref({
|
||||||
|
nick: "",
|
||||||
|
account: "",
|
||||||
|
password: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const authError = ref({
|
||||||
|
reason: "",
|
||||||
|
message: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
function setAuthenticated(v: boolean) {
|
||||||
|
authenticated.value = v;
|
||||||
|
}
|
||||||
|
function setNick(v: string) {
|
||||||
|
account.value.nick = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
account,
|
||||||
|
authError,
|
||||||
|
authenticated,
|
||||||
|
showRegistration,
|
||||||
|
setAuthenticated,
|
||||||
|
setNick,
|
||||||
|
showRegistration,
|
||||||
|
};
|
||||||
|
});
|
||||||
47
app/frontend/src/stores/bufferStore.ts
Normal file
47
app/frontend/src/stores/bufferStore.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Buffer, type BufferOptions } from "@/lib/buffer.ts";
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
|
export const useBufferStore = defineStore("bufferStore", () => {
|
||||||
|
const buffers = ref({} as Record<string, Buffer>);
|
||||||
|
|
||||||
|
const activeBufferName = ref(null as string | null);
|
||||||
|
|
||||||
|
function setActiveBuffer(bufferName: string) {
|
||||||
|
const buffer = getBuffer(bufferName);
|
||||||
|
if (!buffer) return;
|
||||||
|
activeBufferName.value = bufferName;
|
||||||
|
buffer.resetLastSeen();
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBuffer(bufferName: string, options: BufferOptions) {
|
||||||
|
buffers.value[bufferName] = new Buffer(options);
|
||||||
|
return buffers.value[bufferName];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBuffer(bufferName: string) {
|
||||||
|
return buffers.value[bufferName];
|
||||||
|
}
|
||||||
|
|
||||||
|
function delBuffer(bufferName: string) {
|
||||||
|
if (buffers.value[bufferName]) {
|
||||||
|
delete buffers.value[bufferName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeBuffer = computed(() => {
|
||||||
|
if (activeBufferName.value) {
|
||||||
|
return buffers.value[activeBufferName.value];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
buffers,
|
||||||
|
activeBufferName,
|
||||||
|
activeBuffer,
|
||||||
|
addBuffer,
|
||||||
|
getBuffer,
|
||||||
|
delBuffer,
|
||||||
|
setActiveBuffer,
|
||||||
|
};
|
||||||
|
});
|
||||||
3
app/frontend/src/stores/index.ts
Normal file
3
app/frontend/src/stores/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { createPinia } from "pinia";
|
||||||
|
|
||||||
|
export default createPinia();
|
||||||
473
app/frontend/src/stores/irc.ts
Normal file
473
app/frontend/src/stores/irc.ts
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
import { defineStore, storeToRefs } from "pinia";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { Client } from "irc-framework";
|
||||||
|
import { useBufferStore } from "./bufferStore";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useAccountStore } from "./accountStore";
|
||||||
|
import MarkdownIt from "markdown-it";
|
||||||
|
import DOMPurify from "dompurify";
|
||||||
|
|
||||||
|
export type HookFunction = (event: any) => HookStatus;
|
||||||
|
|
||||||
|
export enum HookStatus {
|
||||||
|
HOOK_OK,
|
||||||
|
HOOK_EAT,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Batch {
|
||||||
|
type: string;
|
||||||
|
target: string;
|
||||||
|
messages: any[];
|
||||||
|
params: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const md = new MarkdownIt({
|
||||||
|
html: false,
|
||||||
|
linkify: true,
|
||||||
|
typographer: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
function renderMessage(msgIn: string) {
|
||||||
|
if (!msgIn) return "";
|
||||||
|
const dirty = md.render(msgIn);
|
||||||
|
return DOMPurify.sanitize(dirty, {
|
||||||
|
ALLOWED_TAGS: [
|
||||||
|
"li",
|
||||||
|
"ul",
|
||||||
|
"ol",
|
||||||
|
"b",
|
||||||
|
"i",
|
||||||
|
"em",
|
||||||
|
"strong",
|
||||||
|
"a",
|
||||||
|
"code",
|
||||||
|
"pre",
|
||||||
|
"br",
|
||||||
|
"p",
|
||||||
|
"h1",
|
||||||
|
"h2",
|
||||||
|
"h3",
|
||||||
|
"h4",
|
||||||
|
"h5",
|
||||||
|
],
|
||||||
|
ALLOWED_ATTR: ["href", "target", "class"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useIRCStore = defineStore("ircStore", () => {
|
||||||
|
const bufferStore = useBufferStore();
|
||||||
|
const accountStore = useAccountStore();
|
||||||
|
const connected = ref(false);
|
||||||
|
const { authError } = storeToRefs(accountStore);
|
||||||
|
const batches = new Map<string, Batch>();
|
||||||
|
const hooks = {} as Record<string, HookFunction[]>;
|
||||||
|
|
||||||
|
function registerHook(event: string, f: HookFunction) {
|
||||||
|
if (hooks[event]) hooks[event].push(f);
|
||||||
|
else hooks[event] = [f];
|
||||||
|
}
|
||||||
|
|
||||||
|
function runHook(eventName: string, eventArgs: any): HookStatus {
|
||||||
|
if (!hooks[eventName]) return HookStatus.HOOK_OK;
|
||||||
|
let lastRetVal = HookStatus.HOOK_OK;
|
||||||
|
|
||||||
|
for (const hookFunction of hooks[eventName]) {
|
||||||
|
const retVal = hookFunction(eventArgs);
|
||||||
|
if (retVal === HookStatus.HOOK_EAT) return retVal;
|
||||||
|
lastRetVal = retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastRetVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unregisterHook(eventName: string, f: HookFunction) {
|
||||||
|
if (!hooks[eventName]) return;
|
||||||
|
const idx = hooks[eventName].findIndex((item) => item === f);
|
||||||
|
if (idx === -1) return;
|
||||||
|
hooks[eventName].splice(idx, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selfAvatar = ref("https://placekittens.com/128/128");
|
||||||
|
const bio = ref();
|
||||||
|
|
||||||
|
loadPrefs();
|
||||||
|
|
||||||
|
function setAvatar(v: string) {
|
||||||
|
selfAvatar.value = v;
|
||||||
|
client.raw(`METADATA * SET avatar ${selfAvatar.value}`);
|
||||||
|
storePrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadPrefs() {
|
||||||
|
// const v = localStorage.getItem("prefs");
|
||||||
|
// if (v === null) return;
|
||||||
|
//
|
||||||
|
// const prefs = JSON.parse(v);
|
||||||
|
// if (!prefs) return;
|
||||||
|
//
|
||||||
|
// if (prefs.avatar) {
|
||||||
|
// selfAvatar.value = prefs.avatar;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (prefs.nick) {
|
||||||
|
// clientInfo.value.nick = prefs.nick;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (prefs.bio) {
|
||||||
|
// bio.value = prefs.bio;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function storePrefs() {
|
||||||
|
// localStorage.setItem(
|
||||||
|
// "prefs",
|
||||||
|
// JSON.stringify({
|
||||||
|
// nick: clientInfo.value.nick,
|
||||||
|
// avatar: selfAvatar.value,
|
||||||
|
// bio: bio.value,
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNick(v: string) {
|
||||||
|
client.changeNick(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = ref({} as Record<string, any>);
|
||||||
|
|
||||||
|
function getMetadata(subject: string, key: string) {
|
||||||
|
if (metadata.value[subject]) return metadata.value[subject][key];
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = markRaw(new Client());
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
client.requestCap("draft/metadata-2");
|
||||||
|
client.requestCap("echo-message");
|
||||||
|
client.requestCap("chathistory");
|
||||||
|
client.requestCap("draft/multiline");
|
||||||
|
const tls = location.protocol === "https:";
|
||||||
|
const connectParams = {
|
||||||
|
host: location.hostname,
|
||||||
|
port: location.port,
|
||||||
|
tls,
|
||||||
|
version: "irchad on irc-framework",
|
||||||
|
path: "/ws",
|
||||||
|
account: accountStore.account.account
|
||||||
|
? {
|
||||||
|
account: accountStore.account.account,
|
||||||
|
password: accountStore.account.password,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
sasl_disconect_on_fail: true,
|
||||||
|
nick: accountStore.account.nick,
|
||||||
|
};
|
||||||
|
|
||||||
|
client.connect(connectParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendActiveBuffer(message: string) {
|
||||||
|
if (!bufferStore.activeBuffer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
send(bufferStore.activeBuffer?.name, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
function genBatchId(): string {
|
||||||
|
return Math.random().toString(36).substring(2, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(target: string, message: string) {
|
||||||
|
if (!message.includes("\n")) {
|
||||||
|
client.say(target, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const split = message.split("\n");
|
||||||
|
|
||||||
|
const batchId = genBatchId();
|
||||||
|
client.raw(`BATCH +${batchId} draft/multiline ${target}`);
|
||||||
|
split.forEach((line) => {
|
||||||
|
client.raw(`@batch=${batchId} PRIVMSG ${target} :${line}`);
|
||||||
|
});
|
||||||
|
client.raw(`BATCH -${batchId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMe(target: string) {
|
||||||
|
return target === client.user.nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBio(v: string) {
|
||||||
|
bio.value = v;
|
||||||
|
client.raw(`METADATA * SET bio ${bio.value}`);
|
||||||
|
storePrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
client.on("socket close", () => {
|
||||||
|
batches.clear();
|
||||||
|
connected.value = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("loggedin", () => {
|
||||||
|
accountStore.setAuthenticated(true);
|
||||||
|
authError.value = {
|
||||||
|
reason: "",
|
||||||
|
message: "",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on(
|
||||||
|
"sasl failed",
|
||||||
|
({ reason, message }: { reason: string; message: string }) => {
|
||||||
|
authError.value = {
|
||||||
|
reason,
|
||||||
|
message,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
client.on("registered", function () {
|
||||||
|
connected.value = true;
|
||||||
|
router.push({ name: "Chat" });
|
||||||
|
client.list();
|
||||||
|
client.raw("METADATA * SUB avatar");
|
||||||
|
client.raw("METADATA * SUB bio");
|
||||||
|
client.raw(`METADATA * SET avatar ${selfAvatar.value}`);
|
||||||
|
client.raw(`METADATA * SET bio ${bio.value}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on(
|
||||||
|
"nick",
|
||||||
|
function ({ nick, new_nick }: { nick: string; new_nick: string }) {
|
||||||
|
if (nick === client.user.nick) {
|
||||||
|
accountStore.setNick(new_nick);
|
||||||
|
}
|
||||||
|
for (let buffName in bufferStore.buffers.value) {
|
||||||
|
const buff = bufferStore.getBuffer(buffName);
|
||||||
|
if (!buff) {
|
||||||
|
console.log(`${buffName} not found`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const idx = buff.users.findIndex((u) => u.nick === nick);
|
||||||
|
if (idx === -1) {
|
||||||
|
console.log(`${nick} not found in ${buffName}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
buff.users[idx].nick = new_nick;
|
||||||
|
}
|
||||||
|
metadata.value[new_nick] = { ...metadata.value[nick] };
|
||||||
|
delete metadata.value[nick];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
client.on(
|
||||||
|
"unknown command",
|
||||||
|
function (ircCommand: { command: string; params: string[] }) {
|
||||||
|
if (ircCommand.command === "METADATA") {
|
||||||
|
const from = ircCommand.params[0];
|
||||||
|
const target = ircCommand.params[2];
|
||||||
|
const key = ircCommand.params[1];
|
||||||
|
const value = ircCommand.params[3];
|
||||||
|
|
||||||
|
let subject = target;
|
||||||
|
if (target === "*") {
|
||||||
|
subject = from;
|
||||||
|
}
|
||||||
|
if (!subject || !key || !value) return;
|
||||||
|
|
||||||
|
if (!metadata.value[subject]) {
|
||||||
|
metadata.value[subject] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.value[subject][key] = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
client.on("channel list", (channels: { channel: string }[]) =>
|
||||||
|
channels.map((ch) => client.join(ch.channel)),
|
||||||
|
);
|
||||||
|
|
||||||
|
client.on(
|
||||||
|
"tagmsg",
|
||||||
|
({
|
||||||
|
nick,
|
||||||
|
tags,
|
||||||
|
target,
|
||||||
|
}: {
|
||||||
|
nick: string;
|
||||||
|
tags: string[];
|
||||||
|
target: string;
|
||||||
|
}) => {
|
||||||
|
console.log(nick, tags, target);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleMessage(message: any) {
|
||||||
|
let buffer;
|
||||||
|
|
||||||
|
const retVal = runHook("message", message);
|
||||||
|
if (retVal === HookStatus.HOOK_EAT) return;
|
||||||
|
|
||||||
|
if (message.nick === "HistServ") return;
|
||||||
|
if (isMe(message.target)) {
|
||||||
|
buffer = bufferStore.getBuffer(message.nick);
|
||||||
|
} else {
|
||||||
|
buffer = bufferStore.getBuffer(message.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!buffer) {
|
||||||
|
buffer = bufferStore.addBuffer(message.nick, {
|
||||||
|
name: message.nick,
|
||||||
|
channel: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
message.message = renderMessage(message.message);
|
||||||
|
buffer.messages.push(message);
|
||||||
|
|
||||||
|
if (
|
||||||
|
bufferStore.activeBuffer &&
|
||||||
|
bufferStore.activeBuffer.name === buffer.name
|
||||||
|
) {
|
||||||
|
buffer.resetLastSeen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client.on(
|
||||||
|
"message",
|
||||||
|
function (message: {
|
||||||
|
nick: string;
|
||||||
|
target: string;
|
||||||
|
tags: Record<string, string>;
|
||||||
|
}) {
|
||||||
|
const batchId = message.tags["batch"];
|
||||||
|
|
||||||
|
if (batchId && batches.has(batchId)) {
|
||||||
|
batches.get(batchId)?.messages.push(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMessage(message);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
client.on("notice", function (message) {
|
||||||
|
const retVal = runHook("notice", message);
|
||||||
|
if (retVal === HookStatus.HOOK_EAT) return;
|
||||||
|
if (bufferStore.activeBuffer) {
|
||||||
|
bufferStore.activeBuffer.messages.push({ ...message, kind: "notice" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("join", ({ nick, channel }: { nick: string; channel: string }) => {
|
||||||
|
if (isMe(nick)) {
|
||||||
|
bufferStore.addBuffer(channel, {
|
||||||
|
name: channel,
|
||||||
|
channel: client.channel(channel),
|
||||||
|
});
|
||||||
|
if (!bufferStore.activeBuffer) {
|
||||||
|
bufferStore.setActiveBuffer(channel);
|
||||||
|
}
|
||||||
|
client.raw("CHATHISTORY LATEST " + channel + " * 200");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = bufferStore.getBuffer(channel);
|
||||||
|
if (!buffer) return;
|
||||||
|
buffer.syncUsers();
|
||||||
|
buffer.users.push({
|
||||||
|
nick: nick,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("quit", function ({ nick }: { nick: string }) {
|
||||||
|
for (let buff of Object.values(bufferStore.buffers)) {
|
||||||
|
const idx = buff.users.findIndex((u) => u.nick === nick);
|
||||||
|
if (idx === -1) continue;
|
||||||
|
buff.users.splice(idx, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on(
|
||||||
|
"topic",
|
||||||
|
({ topic, channel }: { topic: string; channel: string }) => {
|
||||||
|
const buffer = bufferStore.getBuffer(channel);
|
||||||
|
if (!buffer) return;
|
||||||
|
buffer.topic = topic;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
client.on("part", ({ nick, channel }: { nick: string; channel: string }) => {
|
||||||
|
if (isMe(nick)) {
|
||||||
|
bufferStore.delBuffer(channel);
|
||||||
|
}
|
||||||
|
const buffer = bufferStore.getBuffer(channel);
|
||||||
|
if (!buffer) return;
|
||||||
|
const idx = buffer.users.findIndex((u) => u.nick === nick);
|
||||||
|
if (idx === -1) return;
|
||||||
|
|
||||||
|
buffer.users.splice(idx, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("userlist", (ev: { channel: string; users: any[] }) => {
|
||||||
|
const buffer = bufferStore.getBuffer(ev.channel);
|
||||||
|
if (!buffer) return;
|
||||||
|
buffer.users = ev.users;
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("batch start", (event: any) => {
|
||||||
|
batches.set(event.id, {
|
||||||
|
type: event.type,
|
||||||
|
target: event.params[0],
|
||||||
|
messages: [],
|
||||||
|
params: event.params,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleChatHistory(batch: Batch) {
|
||||||
|
for (const message of batch.messages) {
|
||||||
|
handleMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMultiline(batch: Batch) {
|
||||||
|
console.log(batch);
|
||||||
|
let m = "";
|
||||||
|
for (const message of batch.messages) {
|
||||||
|
m += `${message.message}\n`;
|
||||||
|
}
|
||||||
|
handleMessage({ ...batch.messages[0], message: m });
|
||||||
|
}
|
||||||
|
|
||||||
|
client.on("batch end draft/multiline", (event: any) => {
|
||||||
|
const batch = batches.get(event.id);
|
||||||
|
if (!batch) return;
|
||||||
|
|
||||||
|
handleMultiline(batch);
|
||||||
|
batches.delete(event.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("batch end chathistory", (event: any) => {
|
||||||
|
const batch = batches.get(event.id);
|
||||||
|
if (!batch) return;
|
||||||
|
handleChatHistory(batch);
|
||||||
|
batches.delete(event.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
connect,
|
||||||
|
client,
|
||||||
|
sendActiveBuffer,
|
||||||
|
getMetadata,
|
||||||
|
metadata,
|
||||||
|
selfAvatar,
|
||||||
|
setAvatar,
|
||||||
|
setNick,
|
||||||
|
setBio,
|
||||||
|
bio,
|
||||||
|
connected,
|
||||||
|
registerHook,
|
||||||
|
unregisterHook,
|
||||||
|
};
|
||||||
|
});
|
||||||
26
app/frontend/src/style.css
Normal file
26
app/frontend/src/style.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
0
app/frontend/src/styles/settings.scss
Normal file
0
app/frontend/src/styles/settings.scss
Normal file
35
app/frontend/src/types/irc-framework.d.ts
vendored
Normal file
35
app/frontend/src/types/irc-framework.d.ts
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
declare module "irc-framework" {
|
||||||
|
export interface IrcUser {
|
||||||
|
nick: string;
|
||||||
|
ident?: string;
|
||||||
|
hostname?: string;
|
||||||
|
modes?: string[];
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// export class Client {
|
||||||
|
// join(channel: string, key?: string);
|
||||||
|
// constructor();
|
||||||
|
// connect(options: any): void;
|
||||||
|
// on(event: string, callback: (event: any) => void): void;
|
||||||
|
// channel(name: string): IrcChannel;
|
||||||
|
// changeNick(newNick: string);
|
||||||
|
// say(target: string, message: string);
|
||||||
|
// raw(v: string);
|
||||||
|
// requestCap(cap: string);
|
||||||
|
// list();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// declare module "irc-framework/src/channel" {
|
||||||
|
// export class IrcChannel {
|
||||||
|
// constructor(irc_client: Client, channel_name: string, key?: string);
|
||||||
|
// name: string;
|
||||||
|
// users: IrcUser[];
|
||||||
|
// say(message: string): void;
|
||||||
|
// notice(message: string): void;
|
||||||
|
// part(message?: string): void;
|
||||||
|
// join(key?: string): void;
|
||||||
|
// mode(mode: string, param?: string): void;
|
||||||
|
// }
|
||||||
|
}
|
||||||
7
app/frontend/src/vite-env.d.ts
vendored
Normal file
7
app/frontend/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type {DefineComponent} from 'vue'
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
||||||
14
app/frontend/tsconfig.app.json
Normal file
14
app/frontend/tsconfig.app.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
|
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
|
"exclude": ["src/**/__tests__/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/frontend/tsconfig.json
Normal file
11
app/frontend/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.app.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
20
app/frontend/tsconfig.node.json
Normal file
20
app/frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/node22/tsconfig.json",
|
||||||
|
"include": [
|
||||||
|
"vite.config.*",
|
||||||
|
"vitest.config.*",
|
||||||
|
"cypress.config.*",
|
||||||
|
"nightwatch.conf.*",
|
||||||
|
"playwright.config.*"
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"types": ["node"],
|
||||||
|
"allowJs": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,65 +1,47 @@
|
|||||||
// Plugins
|
import Vue from "@vitejs/plugin-vue";
|
||||||
import AutoImport from "unplugin-auto-import/vite";
|
import AutoImport from "unplugin-auto-import/vite";
|
||||||
import Components from "unplugin-vue-components/vite";
|
import Components from "unplugin-vue-components/vite";
|
||||||
import Fonts from "unplugin-fonts/vite";
|
|
||||||
import Layouts from "vite-plugin-vue-layouts-next";
|
|
||||||
import Vue from "@vitejs/plugin-vue";
|
|
||||||
import VueRouter from "unplugin-vue-router/vite";
|
|
||||||
import { VueRouterAutoImports } from "unplugin-vue-router";
|
|
||||||
import Vuetify, { transformAssetUrls } from "vite-plugin-vuetify";
|
import Vuetify, { transformAssetUrls } from "vite-plugin-vuetify";
|
||||||
|
import { nodePolyfills } from "vite-plugin-node-polyfills";
|
||||||
|
|
||||||
// Utilities
|
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
import { fileURLToPath, URL } from "node:url";
|
import { fileURLToPath, URL } from "node:url";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
VueRouter(),
|
|
||||||
Layouts(),
|
|
||||||
Vue({
|
Vue({
|
||||||
template: { transformAssetUrls },
|
template: { transformAssetUrls },
|
||||||
}),
|
}),
|
||||||
// https://github.com/vuetifyjs/vuetify-loader/tree/master/packages/vite-plugin#readme
|
AutoImport({
|
||||||
|
imports: [
|
||||||
|
"vue",
|
||||||
|
{
|
||||||
|
pinia: ["defineStore", "storeToRefs"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dts: "src/auto-imports.d.ts",
|
||||||
|
eslintrc: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
vueTemplate: true,
|
||||||
|
}),
|
||||||
|
Components({
|
||||||
|
dts: "src/components.d.ts",
|
||||||
|
}),
|
||||||
Vuetify({
|
Vuetify({
|
||||||
autoImport: true,
|
autoImport: true,
|
||||||
styles: {
|
styles: {
|
||||||
configFile: "src/styles/settings.scss",
|
configFile: "src/styles/settings.scss",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
Components(),
|
nodePolyfills({
|
||||||
Fonts({
|
globals: {
|
||||||
google: {
|
Buffer: true,
|
||||||
families: [
|
|
||||||
{
|
|
||||||
name: "Roboto",
|
|
||||||
styles: "wght@100;300;400;500;700;900",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
AutoImport({
|
|
||||||
imports: [
|
|
||||||
"vue",
|
|
||||||
VueRouterAutoImports,
|
|
||||||
{
|
|
||||||
pinia: ["defineStore", "storeToRefs"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
eslintrc: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
vueTemplate: true,
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
exclude: [
|
exclude: ["vuetify"],
|
||||||
"vuetify",
|
|
||||||
"vue-router",
|
|
||||||
"unplugin-vue-router/runtime",
|
|
||||||
"unplugin-vue-router/data-loaders",
|
|
||||||
"unplugin-vue-router/data-loaders/basic",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
define: { "process.env": {} },
|
define: { "process.env": {} },
|
||||||
resolve: {
|
resolve: {
|
||||||
4
app/frontend/wailsjs/go/main/App.d.ts
vendored
Executable file
4
app/frontend/wailsjs/go/main/App.d.ts
vendored
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export function Greet(arg1:string):Promise<string>;
|
||||||
7
app/frontend/wailsjs/go/main/App.js
Executable file
7
app/frontend/wailsjs/go/main/App.js
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
// @ts-check
|
||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export function Greet(arg1) {
|
||||||
|
return window['go']['main']['App']['Greet'](arg1);
|
||||||
|
}
|
||||||
24
app/frontend/wailsjs/runtime/package.json
Normal file
24
app/frontend/wailsjs/runtime/package.json
Normal file
@@ -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 <lea.anthony@gmail.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/wailsapp/wails/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/wailsapp/wails#readme"
|
||||||
|
}
|
||||||
249
app/frontend/wailsjs/runtime/runtime.d.ts
vendored
Normal file
249
app/frontend/wailsjs/runtime/runtime.d.ts
vendored
Normal file
@@ -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<boolean>;
|
||||||
|
|
||||||
|
// [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<Size>;
|
||||||
|
|
||||||
|
// [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<Position>;
|
||||||
|
|
||||||
|
// [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<boolean>;
|
||||||
|
|
||||||
|
// [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<boolean>;
|
||||||
|
|
||||||
|
// [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<boolean>;
|
||||||
|
|
||||||
|
// [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<Screen[]>;
|
||||||
|
|
||||||
|
// [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<EnvironmentInfo>;
|
||||||
|
|
||||||
|
// [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<string>;
|
||||||
|
|
||||||
|
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
|
||||||
|
// Sets a text on the clipboard
|
||||||
|
export function ClipboardSetText(text: string): Promise<boolean>;
|
||||||
|
|
||||||
|
// [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
|
||||||
242
app/frontend/wailsjs/runtime/runtime.js
Normal file
242
app/frontend/wailsjs/runtime/runtime.js
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
_ __ _ __
|
||||||
|
| | / /___ _(_) /____
|
||||||
|
| | /| / / __ `/ / / ___/
|
||||||
|
| |/ |/ / /_/ / / (__ )
|
||||||
|
|__/|__/\__,_/_/_/____/
|
||||||
|
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 EventsOffAll() {
|
||||||
|
return window.runtime.EventsOffAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
37
app/go.mod
Normal file
37
app/go.mod
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
module IrChad
|
||||||
|
|
||||||
|
go 1.23
|
||||||
|
|
||||||
|
require github.com/wailsapp/wails/v2 v2.11.0
|
||||||
|
|
||||||
|
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.22 // 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
|
||||||
|
)
|
||||||
|
|
||||||
|
// replace github.com/wailsapp/wails/v2 v2.11.0 => /home/sam/go/pkg/mod
|
||||||
81
app/go.sum
Normal file
81
app/go.sum
Normal file
@@ -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.22 h1:YT61F5lj+GGaat5OB96Aa3b4QA+mybD0Ggq6NZijQ58=
|
||||||
|
github.com/wailsapp/go-webview2 v1.0.22/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.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSBtqQ=
|
||||||
|
github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k=
|
||||||
|
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=
|
||||||
41
app/main.go
Normal file
41
app/main.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"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 fixNvidiaOpenOnX11() {
|
||||||
|
_ = os.Setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create an instance of the app structure
|
||||||
|
fixNvidiaOpenOnX11()
|
||||||
|
app := NewApp()
|
||||||
|
|
||||||
|
// Create application with options
|
||||||
|
err := wails.Run(&options.App{
|
||||||
|
Title: "IrChad",
|
||||||
|
Width: 1024,
|
||||||
|
Height: 768,
|
||||||
|
AssetServer: &assetserver.Options{
|
||||||
|
Assets: assets,
|
||||||
|
},
|
||||||
|
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
|
||||||
|
OnStartup: app.startup,
|
||||||
|
Bind: []any{
|
||||||
|
app,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
println("Error:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
301
app/package-lock.json
generated
Normal file
301
app/package-lock.json
generated
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"vue-router": "^4.6.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/helper-validator-identifier": {
|
||||||
|
"version": "7.28.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
|
||||||
|
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/parser": {
|
||||||
|
"version": "7.28.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz",
|
||||||
|
"integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/types": "^7.28.6"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"parser": "bin/babel-parser.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@babel/types": {
|
||||||
|
"version": "7.28.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz",
|
||||||
|
"integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-string-parser": "^7.27.1",
|
||||||
|
"@babel/helper-validator-identifier": "^7.28.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.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==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@vue/compiler-core": {
|
||||||
|
"version": "3.5.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz",
|
||||||
|
"integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/parser": "^7.28.5",
|
||||||
|
"@vue/shared": "3.5.26",
|
||||||
|
"entities": "^7.0.0",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"source-map-js": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/compiler-dom": {
|
||||||
|
"version": "3.5.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz",
|
||||||
|
"integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/compiler-core": "3.5.26",
|
||||||
|
"@vue/shared": "3.5.26"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/compiler-sfc": {
|
||||||
|
"version": "3.5.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz",
|
||||||
|
"integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/parser": "^7.28.5",
|
||||||
|
"@vue/compiler-core": "3.5.26",
|
||||||
|
"@vue/compiler-dom": "3.5.26",
|
||||||
|
"@vue/compiler-ssr": "3.5.26",
|
||||||
|
"@vue/shared": "3.5.26",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"magic-string": "^0.30.21",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"source-map-js": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/compiler-ssr": {
|
||||||
|
"version": "3.5.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz",
|
||||||
|
"integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/compiler-dom": "3.5.26",
|
||||||
|
"@vue/shared": "3.5.26"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/devtools-api": {
|
||||||
|
"version": "6.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
|
||||||
|
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@vue/reactivity": {
|
||||||
|
"version": "3.5.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.26.tgz",
|
||||||
|
"integrity": "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/shared": "3.5.26"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/runtime-core": {
|
||||||
|
"version": "3.5.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.26.tgz",
|
||||||
|
"integrity": "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/reactivity": "3.5.26",
|
||||||
|
"@vue/shared": "3.5.26"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/runtime-dom": {
|
||||||
|
"version": "3.5.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.26.tgz",
|
||||||
|
"integrity": "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/reactivity": "3.5.26",
|
||||||
|
"@vue/runtime-core": "3.5.26",
|
||||||
|
"@vue/shared": "3.5.26",
|
||||||
|
"csstype": "^3.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/server-renderer": {
|
||||||
|
"version": "3.5.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.26.tgz",
|
||||||
|
"integrity": "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/compiler-ssr": "3.5.26",
|
||||||
|
"@vue/shared": "3.5.26"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "3.5.26"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vue/shared": {
|
||||||
|
"version": "3.5.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz",
|
||||||
|
"integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/csstype": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/estree-walker": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/magic-string": {
|
||||||
|
"version": "0.30.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
|
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nanoid": {
|
||||||
|
"version": "3.3.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
|
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||||
|
"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/picocolors": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
|
"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==",
|
||||||
|
"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/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==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue": {
|
||||||
|
"version": "3.5.26",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz",
|
||||||
|
"integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/compiler-dom": "3.5.26",
|
||||||
|
"@vue/compiler-sfc": "3.5.26",
|
||||||
|
"@vue/runtime-dom": "3.5.26",
|
||||||
|
"@vue/server-renderer": "3.5.26",
|
||||||
|
"@vue/shared": "3.5.26"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-router": {
|
||||||
|
"version": "4.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
|
||||||
|
"integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^6.6.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
app/package.json
Normal file
5
app/package.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"vue-router": "^4.6.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
app/wails.json
Normal file
13
app/wails.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://wails.io/schemas/config.v2.json",
|
||||||
|
"name": "IrChad",
|
||||||
|
"outputfilename": "IrChad",
|
||||||
|
"frontend:install": "npm install",
|
||||||
|
"frontend:build": "npm run build",
|
||||||
|
"frontend:dev:watcher": "npm run dev",
|
||||||
|
"frontend:dev:serverUrl": "auto",
|
||||||
|
"author": {
|
||||||
|
"name": "",
|
||||||
|
"email": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
ergo:
|
ergo:
|
||||||
init: true
|
init: true
|
||||||
|
|||||||
10
ircd.yaml
10
ircd.yaml
@@ -27,7 +27,7 @@ server:
|
|||||||
# 'rfc1459-strict'. we recommend leaving this value at its default;
|
# 'rfc1459-strict'. we recommend leaving this value at its default;
|
||||||
# however, note that changing it once the network is already up and running is
|
# however, note that changing it once the network is already up and running is
|
||||||
# problematic.
|
# problematic.
|
||||||
casemapping: "precis"
|
casemapping: "permissive"
|
||||||
|
|
||||||
# enforce-utf8 controls whether the server will preemptively discard non-UTF8
|
# enforce-utf8 controls whether the server will preemptively discard non-UTF8
|
||||||
# messages (since they cannot be relayed to websocket clients), or will allow
|
# messages (since they cannot be relayed to websocket clients), or will allow
|
||||||
@@ -154,7 +154,7 @@ server:
|
|||||||
max-concurrent-connections: 16
|
max-concurrent-connections: 16
|
||||||
|
|
||||||
# whether to restrict the rate of new connections per IP/CIDR
|
# whether to restrict the rate of new connections per IP/CIDR
|
||||||
throttle: true
|
throttle: false
|
||||||
# how long to keep track of connections for
|
# how long to keep track of connections for
|
||||||
window: 10m
|
window: 10m
|
||||||
# maximum number of new connections per IP/CIDR within the given duration
|
# maximum number of new connections per IP/CIDR within the given duration
|
||||||
@@ -810,17 +810,17 @@ limits:
|
|||||||
# fakelag: prevents clients from spamming commands too rapidly
|
# fakelag: prevents clients from spamming commands too rapidly
|
||||||
fakelag:
|
fakelag:
|
||||||
# whether to enforce fakelag
|
# whether to enforce fakelag
|
||||||
enabled: true
|
enabled: false
|
||||||
|
|
||||||
# time unit for counting command rates
|
# time unit for counting command rates
|
||||||
window: 1s
|
window: 1s
|
||||||
|
|
||||||
# clients can send this many commands without fakelag being imposed
|
# clients can send this many commands without fakelag being imposed
|
||||||
burst-limit: 5
|
burst-limit: 1000
|
||||||
|
|
||||||
# once clients have exceeded their burst allowance, they can send only
|
# once clients have exceeded their burst allowance, they can send only
|
||||||
# this many commands per `window`:
|
# this many commands per `window`:
|
||||||
messages-per-window: 2
|
messages-per-window: 10000
|
||||||
|
|
||||||
# client status resets to the default state if they go this long without
|
# client status resets to the default state if they go this long without
|
||||||
# sending any commands:
|
# sending any commands:
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
> 1%
|
|
||||||
last 2 versions
|
|
||||||
not dead
|
|
||||||
not ie 11
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[*.{js,jsx,ts,tsx,vue}]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
22
irchad-web/.gitignore
vendored
22
irchad-web/.gitignore
vendored
@@ -1,22 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
/dist
|
|
||||||
|
|
||||||
# local env files
|
|
||||||
.env.local
|
|
||||||
.env.*.local
|
|
||||||
|
|
||||||
# Log files
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.idea
|
|
||||||
.vscode
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<link rel="icon" href="/favicon.ico">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Welcome to Vuetify 3</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script type="module" src="/src/main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"allowJs": true,
|
|
||||||
"target": "es5",
|
|
||||||
"module": "esnext",
|
|
||||||
"baseUrl": "./",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"paths": {
|
|
||||||
"@/*": [
|
|
||||||
"src/*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"lib": [
|
|
||||||
"esnext",
|
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"scripthost"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "irchad-web",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "vite build",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"lint": "eslint . --fix"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fontsource/roboto": "5.2.7",
|
|
||||||
"@mdi/font": "7.4.47",
|
|
||||||
"irc-framework": "^4.14.0",
|
|
||||||
"pinia": "^3.0.3",
|
|
||||||
"vue": "^3.5.21",
|
|
||||||
"vue-router": "^4.5.1",
|
|
||||||
"vuetify": "^3.10.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
|
||||||
"eslint": "^9.35.0",
|
|
||||||
"eslint-config-vuetify": "^4.2.0",
|
|
||||||
"sass-embedded": "^1.92.1",
|
|
||||||
"unplugin-auto-import": "^19.3.0",
|
|
||||||
"unplugin-fonts": "^1.4.0",
|
|
||||||
"unplugin-vue-components": "^29.0.0",
|
|
||||||
"unplugin-vue-router": "^0.15.0",
|
|
||||||
"vite": "^7.1.5",
|
|
||||||
"vite-plugin-vue-layouts-next": "^1.0.0",
|
|
||||||
"vite-plugin-vuetify": "^2.1.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-app>
|
|
||||||
<router-view />
|
|
||||||
</v-app>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
//
|
|
||||||
</script>
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref, onMounted } from "vue";
|
|
||||||
import { useIRCStore } from "@/stores/irc";
|
|
||||||
|
|
||||||
const store = useIRCStore();
|
|
||||||
const inputBuffer = ref();
|
|
||||||
|
|
||||||
onMounted(store.connect);
|
|
||||||
|
|
||||||
function send() {
|
|
||||||
store.sendActiveBuffer(inputBuffer.value);
|
|
||||||
inputBuffer.value = "";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="d-flex flex-row" style="height: 100vh">
|
|
||||||
<v-sheet border class="buffers">
|
|
||||||
<BufferList />
|
|
||||||
</v-sheet>
|
|
||||||
<div class="messages d-flex flex-column">
|
|
||||||
<v-toolbar density="compact">
|
|
||||||
<v-toolbar-title>
|
|
||||||
<p>{{ store.activeBufferName }}</p>
|
|
||||||
{{ store.activeBuffer?.topic }}
|
|
||||||
</v-toolbar-title>
|
|
||||||
</v-toolbar>
|
|
||||||
<MessageList
|
|
||||||
:messages="store.activeBuffer?.messages"
|
|
||||||
:me="store.clientInfo.nick"
|
|
||||||
/>
|
|
||||||
<v-sheet>
|
|
||||||
<v-text-field
|
|
||||||
variant="outlined"
|
|
||||||
:placeholder="`Message ${store.activeBufferName}`"
|
|
||||||
v-model="inputBuffer"
|
|
||||||
hide-details
|
|
||||||
class="ma-2"
|
|
||||||
@keydown.enter.exact.prevent="send"
|
|
||||||
/>
|
|
||||||
</v-sheet>
|
|
||||||
</div>
|
|
||||||
<v-sheet class="user-list h-100" border>
|
|
||||||
<UserList :users="store.activeBuffer?.users" />
|
|
||||||
</v-sheet>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.buffers {
|
|
||||||
height: 100%;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages {
|
|
||||||
height: 100%;
|
|
||||||
flex: 3;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-list {
|
|
||||||
height: 100%;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { computed } from "vue";
|
|
||||||
import { useIRCStore } from "@/stores/irc";
|
|
||||||
const props = defineProps(["messages", "me"]);
|
|
||||||
|
|
||||||
const store = useIRCStore();
|
|
||||||
const messagesReverse = computed(() => {
|
|
||||||
if (props.messages) {
|
|
||||||
return [...props.messages];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const timeFormatter = new Intl.DateTimeFormat("en-US", {
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "2-digit",
|
|
||||||
hour12: true,
|
|
||||||
});
|
|
||||||
function formatTime(ts) {
|
|
||||||
const date = new Date(ts);
|
|
||||||
return timeFormatter.format(date);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<v-sheet class="message-list d-flex">
|
|
||||||
<v-list>
|
|
||||||
<v-list-item
|
|
||||||
v-for="msg in messagesReverse"
|
|
||||||
density="compact"
|
|
||||||
:prepend-avatar="store.getMetadata(msg.nick, 'avatar')"
|
|
||||||
>
|
|
||||||
<v-list-item-title>
|
|
||||||
<span
|
|
||||||
class="message-nick"
|
|
||||||
:class="{ 'text-primary': me === msg.nick }"
|
|
||||||
>
|
|
||||||
{{ msg.nick }}
|
|
||||||
</span>
|
|
||||||
<span class="message-time" v-if="!!msg.time">{{
|
|
||||||
formatTime(msg.time)
|
|
||||||
}}</span>
|
|
||||||
</v-list-item-title>
|
|
||||||
<v-list-item-subtitle>
|
|
||||||
{{ msg.message }}
|
|
||||||
</v-list-item-subtitle>
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-sheet>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.message-list {
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
}
|
|
||||||
.message-time {
|
|
||||||
font-size: 0.65em;
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { useIRCStore } from "@/stores/irc";
|
|
||||||
const props = defineProps(["users"]);
|
|
||||||
const store = useIRCStore();
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<v-list density="compact">
|
|
||||||
<v-list-item
|
|
||||||
v-for="user in users"
|
|
||||||
:prepend-avatar="store.getMetadata(user.nick, 'avatar')"
|
|
||||||
>
|
|
||||||
{{ user.nick }}
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</template>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-main>
|
|
||||||
<router-view />
|
|
||||||
</v-main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
//
|
|
||||||
</script>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/**
|
|
||||||
* main.js
|
|
||||||
*
|
|
||||||
* Bootstraps Vuetify and other plugins then mounts the App`
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Plugins
|
|
||||||
import { registerPlugins } from '@/plugins'
|
|
||||||
|
|
||||||
// Components
|
|
||||||
import App from './App.vue'
|
|
||||||
|
|
||||||
// Composables
|
|
||||||
import { createApp } from 'vue'
|
|
||||||
|
|
||||||
// Styles
|
|
||||||
import 'unfonts.css'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
|
||||||
|
|
||||||
registerPlugins(app)
|
|
||||||
|
|
||||||
app.mount('#app')
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Chat />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
//
|
|
||||||
</script>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* plugins/index.js
|
|
||||||
*
|
|
||||||
* Automatically included in `./src/main.js`
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Plugins
|
|
||||||
import vuetify from './vuetify'
|
|
||||||
import pinia from '@/stores'
|
|
||||||
import router from '@/router'
|
|
||||||
|
|
||||||
export function registerPlugins (app) {
|
|
||||||
app
|
|
||||||
.use(vuetify)
|
|
||||||
.use(router)
|
|
||||||
.use(pinia)
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* plugins/vuetify.js
|
|
||||||
*
|
|
||||||
* Framework documentation: https://vuetifyjs.com`
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Styles
|
|
||||||
import '@mdi/font/css/materialdesignicons.css'
|
|
||||||
import 'vuetify/styles'
|
|
||||||
|
|
||||||
// Composables
|
|
||||||
import { createVuetify } from 'vuetify'
|
|
||||||
|
|
||||||
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
|
||||||
export default createVuetify({
|
|
||||||
theme: {
|
|
||||||
defaultTheme: 'system',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/**
|
|
||||||
* router/index.ts
|
|
||||||
*
|
|
||||||
* Automatic routes for `./src/pages/*.vue`
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Composables
|
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
|
||||||
import { setupLayouts } from 'virtual:generated-layouts'
|
|
||||||
import { routes } from 'vue-router/auto-routes'
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
|
||||||
routes: setupLayouts(routes),
|
|
||||||
})
|
|
||||||
|
|
||||||
// Workaround for https://github.com/vitejs/vite/issues/11804
|
|
||||||
router.onError((err, to) => {
|
|
||||||
if (err?.message?.includes?.('Failed to fetch dynamically imported module')) {
|
|
||||||
if (localStorage.getItem('vuetify:dynamic-reload')) {
|
|
||||||
console.error('Dynamic import error, reloading page did not fix it', err)
|
|
||||||
} else {
|
|
||||||
console.log('Reloading page to fix dynamic import error')
|
|
||||||
localStorage.setItem('vuetify:dynamic-reload', 'true')
|
|
||||||
location.assign(to.fullPath)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
router.isReady().then(() => {
|
|
||||||
localStorage.removeItem('vuetify:dynamic-reload')
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// Utilities
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
export const useAppStore = defineStore('app', {
|
|
||||||
state: () => ({
|
|
||||||
//
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
// Utilities
|
|
||||||
import { createPinia } from 'pinia'
|
|
||||||
|
|
||||||
export default createPinia()
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
import { defineStore } from "pinia";
|
|
||||||
import { Client } from "irc-framework";
|
|
||||||
|
|
||||||
function autoNick() {
|
|
||||||
const discriminiator = Math.round(Math.random() * 100);
|
|
||||||
return `chad-${discriminiator}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useIRCStore = defineStore("irc", () => {
|
|
||||||
const clientInfo = ref({
|
|
||||||
nick: autoNick(),
|
|
||||||
username: "chad",
|
|
||||||
gecos: "IrChad",
|
|
||||||
});
|
|
||||||
|
|
||||||
const buffers = ref({});
|
|
||||||
const activeBufferName = ref();
|
|
||||||
const metadata = ref({});
|
|
||||||
|
|
||||||
const activeBuffer = computed(() => {
|
|
||||||
if (activeBufferName.value) {
|
|
||||||
return buffers.value[activeBufferName.value];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function getMetadata(subject, key) {
|
|
||||||
if (metadata.value[subject]) return metadata.value[subject][key];
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActiveBuffer(bufferName) {
|
|
||||||
activeBufferName.value = bufferName;
|
|
||||||
resetBufferLastSeen(bufferName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetBufferLastSeen(bufferName) {
|
|
||||||
const buffer = getBuffer(bufferName);
|
|
||||||
if (!buffer) return;
|
|
||||||
buffer.lastSeenIdx = buffer.messages.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
enable_echomessage: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
function connect() {
|
|
||||||
client.requestCap("draft/metadata-2");
|
|
||||||
client.requestCap("echo-message");
|
|
||||||
const tls = location.protocol === "https:";
|
|
||||||
client.connect({
|
|
||||||
...clientInfo.value,
|
|
||||||
host: location.hostname,
|
|
||||||
tls,
|
|
||||||
port: location.port,
|
|
||||||
version: "irchad irc-framework",
|
|
||||||
path: "/ws",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendActiveBuffer(message) {
|
|
||||||
client.say(activeBufferName.value, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isMe(target) {
|
|
||||||
return target === clientInfo.value.nick;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addBuffer(bufferName) {
|
|
||||||
buffers.value[bufferName] = {
|
|
||||||
messages: [],
|
|
||||||
topic: "",
|
|
||||||
users: [],
|
|
||||||
lastSeenIdx: 0,
|
|
||||||
};
|
|
||||||
return buffers.value[bufferName];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBuffer(bufferName) {
|
|
||||||
return buffers.value[bufferName];
|
|
||||||
}
|
|
||||||
|
|
||||||
function delBuffer(bufferName) {
|
|
||||||
if (buffers.value[bufferName]) {
|
|
||||||
delete buffers.value[bufferName];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.on("registered", function () {
|
|
||||||
client.list();
|
|
||||||
client.raw("METADATA * SUB avatar");
|
|
||||||
client.raw("METADATA * SET avatar https://placekittens.com/128/128");
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("unknown command", function (ircCommand) {
|
|
||||||
if (ircCommand.command === "METADATA") {
|
|
||||||
const from = ircCommand.params[0];
|
|
||||||
const target = ircCommand.params[2];
|
|
||||||
const key = ircCommand.params[1];
|
|
||||||
const value = ircCommand.params[3];
|
|
||||||
|
|
||||||
let subject = target;
|
|
||||||
if (target === "*") {
|
|
||||||
subject = from;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!metadata.value[subject]) {
|
|
||||||
metadata.value[subject] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata.value[subject][key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("channel list", (channels) =>
|
|
||||||
channels.map((ch) => client.join(ch.channel)),
|
|
||||||
);
|
|
||||||
|
|
||||||
client.on("message", function (message) {
|
|
||||||
let buffer;
|
|
||||||
if (isMe(message.target)) {
|
|
||||||
buffer = getBuffer(message.nick);
|
|
||||||
} else {
|
|
||||||
buffer = getBuffer(message.target);
|
|
||||||
}
|
|
||||||
buffer.messages.push(message);
|
|
||||||
|
|
||||||
if (activeBufferName.value) {
|
|
||||||
resetBufferLastSeen(activeBufferName.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("join", (ev) => {
|
|
||||||
const nick = ev.nick;
|
|
||||||
const channel = ev.channel;
|
|
||||||
if (isMe(nick)) {
|
|
||||||
addBuffer(channel);
|
|
||||||
if (!activeBufferName.value) {
|
|
||||||
activeBufferName.value = channel;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buffer = getBuffer(channel);
|
|
||||||
if (!buffer) return;
|
|
||||||
buffer.users.push({
|
|
||||||
nick: ev.nick,
|
|
||||||
gecos: ev.gecos,
|
|
||||||
hostname: ev.hostname,
|
|
||||||
ident: ev.ident,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("quit", function ({ nick }) {
|
|
||||||
for (let buff of Object.values(buffers.value)) {
|
|
||||||
const idx = buff.users.findIndex((u) => u.nick === nick);
|
|
||||||
if (idx === -1) continue;
|
|
||||||
buff.users.splice(idx, 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("topic", ({ topic, channel }) => {
|
|
||||||
const buffer = getBuffer(channel);
|
|
||||||
if (!buffer) return;
|
|
||||||
buffer.topic = topic;
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("part", ({ nick, channel }) => {
|
|
||||||
if (isMe(nick)) {
|
|
||||||
delBuffer(channel);
|
|
||||||
}
|
|
||||||
const buffer = getBuffer(channel);
|
|
||||||
if (!buffer) return;
|
|
||||||
const idx = buffer.users.findIndex((u) => u.nick === nick);
|
|
||||||
if (idx === -1) return;
|
|
||||||
|
|
||||||
buffer.users.splice(idx, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on("userlist", (ev) => {
|
|
||||||
const buffer = getBuffer(ev.channel);
|
|
||||||
if (!buffer) return;
|
|
||||||
buffer.users = ev.users;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
clientInfo,
|
|
||||||
connect,
|
|
||||||
client,
|
|
||||||
buffers,
|
|
||||||
activeBufferName,
|
|
||||||
activeBuffer,
|
|
||||||
sendActiveBuffer,
|
|
||||||
setActiveBuffer,
|
|
||||||
getMetadata,
|
|
||||||
metadata,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
/**
|
|
||||||
* src/styles/settings.scss
|
|
||||||
*
|
|
||||||
* Configures SASS variables and Vuetify overwrites
|
|
||||||
*/
|
|
||||||
|
|
||||||
// https://vuetifyjs.com/features/sass-variables/`
|
|
||||||
// @use 'vuetify/settings' with (
|
|
||||||
// $color-pack: false
|
|
||||||
// );
|
|
||||||
3081
irchad-web/yarn.lock
3081
irchad-web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user