From d077831825f8b3f26e9ec8ddb9c6622639462fd0 Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 26 Oct 2025 00:02:40 +0100 Subject: [PATCH] Remove unused packages, configurations, and assets --- Makefile | 13 + Taskfile.yml | 35 - cmd/app/Taskfile.yml | 12 +- cmd/app/build/Taskfile.yml | 1 + cmd/app/build/config.yml | 4 +- cmd/app/build/darwin/Taskfile.yml | 2 +- cmd/app/build/linux/Taskfile.yml | 2 +- cmd/app/build/windows/Taskfile.yml | 2 +- cmd/app/main.go | 18 +- .../bindings/github.com/Snider/Core/core.ts | 40 + .../bindings/github.com/Snider/Core/index.ts | 15 + .../bindings/github.com/Snider/Core/models.ts | 41 + .../wails/v3/pkg/application/index.ts | 17 + .../wails/v3/pkg/application/models.ts | 369 ++ cmd/app/public/bindings/log/slog/index.ts | 6 + cmd/app/public/bindings/log/slog/models.ts | 31 + cmd/tasks/build-darwin.yml | 39 + cmd/tasks/build-linux.yml | 40 + cmd/tasks/build-windows.yml | 39 + cmd/tasks/config.yml | 2 + cmd/tasks/go.yml | 14 + cmd/tasks/node.yml | 24 + cmd/tasks/wails.yml | 21 + go.work | 2 +- go.work.sum | 2 + pkg/app/.gitignore | 2 - pkg/core/actions.go | 11 + pkg/core/config/config.go | 177 + pkg/core/config/config_test.go | 146 + pkg/core/core.go | 190 + pkg/core/crypt/crypt.go | 76 + pkg/{v1 => }/core/crypt/crypt_test.go | 0 pkg/{v1 => }/core/crypt/hash.go | 2 +- .../lib => core/crypt}/lthn/hash_test.go | 0 .../lthn/hash.go => core/crypt/lthn/lthn.go} | 15 + .../lib => core/crypt}/openpgp/encrypt.go | 13 +- pkg/core/crypt/openpgp/encrypt_test.go | 155 + .../crypt/lib => core/crypt}/openpgp/key.go | 87 +- .../lib => core/crypt}/openpgp/openpgp.go | 0 .../crypt/lib => core/crypt}/openpgp/sign.go | 9 +- pkg/{v1 => }/core/crypt/sum.go | 0 pkg/core/display/actions.go | 8 + pkg/core/display/display.go | 127 + pkg/{v1 => }/core/display/menu.go | 6 +- pkg/{v1 => }/core/display/tray.go | 16 +- pkg/core/display/window.go | 93 + pkg/core/docs/.gitignore | 1 - .../core/docs/assets/stylesheets/extra.css | 0 pkg/core/docs/docs.go | 50 + pkg/{v1 => }/core/docs/mkdocs.yml | 0 pkg/{v1 => }/core/docs/public/404.html | 311 +- .../fonts.googleapis.com/css.49ea35f2.css | 756 +++ .../external/unpkg.com/iframe-worker/shim.js | 1 + .../unpkg.com/mermaid@11/dist/mermaid.min.js | 2811 ++++++++ .../docs/public/assets/images/favicon.png | Bin .../assets/javascripts/bundle.f55a23d4.min.js | 2 +- .../javascripts/bundle.f55a23d4.min.js.map | 0 .../javascripts/lunr/min/lunr.ar.min.js | 0 .../javascripts/lunr/min/lunr.da.min.js | 0 .../javascripts/lunr/min/lunr.de.min.js | 0 .../javascripts/lunr/min/lunr.du.min.js | 0 .../javascripts/lunr/min/lunr.el.min.js | 0 .../javascripts/lunr/min/lunr.es.min.js | 0 .../javascripts/lunr/min/lunr.fi.min.js | 0 .../javascripts/lunr/min/lunr.fr.min.js | 0 .../javascripts/lunr/min/lunr.he.min.js | 0 .../javascripts/lunr/min/lunr.hi.min.js | 0 .../javascripts/lunr/min/lunr.hu.min.js | 0 .../javascripts/lunr/min/lunr.hy.min.js | 0 .../javascripts/lunr/min/lunr.it.min.js | 0 .../javascripts/lunr/min/lunr.ja.min.js | 0 .../javascripts/lunr/min/lunr.jp.min.js | 0 .../javascripts/lunr/min/lunr.kn.min.js | 0 .../javascripts/lunr/min/lunr.ko.min.js | 0 .../javascripts/lunr/min/lunr.multi.min.js | 0 .../javascripts/lunr/min/lunr.nl.min.js | 0 .../javascripts/lunr/min/lunr.no.min.js | 0 .../javascripts/lunr/min/lunr.pt.min.js | 0 .../javascripts/lunr/min/lunr.ro.min.js | 0 .../javascripts/lunr/min/lunr.ru.min.js | 0 .../javascripts/lunr/min/lunr.sa.min.js | 0 .../lunr/min/lunr.stemmer.support.min.js | 0 .../javascripts/lunr/min/lunr.sv.min.js | 0 .../javascripts/lunr/min/lunr.ta.min.js | 0 .../javascripts/lunr/min/lunr.te.min.js | 0 .../javascripts/lunr/min/lunr.th.min.js | 0 .../javascripts/lunr/min/lunr.tr.min.js | 0 .../javascripts/lunr/min/lunr.vi.min.js | 0 .../javascripts/lunr/min/lunr.zh.min.js | 0 .../public/assets/javascripts/lunr/tinyseg.js | 0 .../public/assets/javascripts/lunr/wordcut.js | 0 .../workers/search.973d3a69.min.js | 0 .../workers/search.973d3a69.min.js.map | 0 .../assets/stylesheets/main.84d31ad4.min.css | 0 .../stylesheets/main.84d31ad4.min.css.map | 0 .../stylesheets/palette.06af60db.min.css | 0 .../stylesheets/palette.06af60db.min.css.map | 0 pkg/core/docs/public/core/config.html | 881 +++ pkg/core/docs/public/core/crypt.html | 905 +++ pkg/core/docs/public/core/display.html | 907 +++ pkg/core/docs/public/core/docs.html | 903 +++ .../docs/public/core}/index.html | 498 +- pkg/core/docs/public/core/io.html | 903 +++ pkg/core/docs/public/core/workspace.html | 901 +++ .../docs/public}/images/cross-platform.jpeg | Bin .../docs/public}/images/decentralised-vpn.jpg | Bin .../docs/public}/images/favicon.ico | Bin .../docs/public}/images/illustration.png | Bin .../docs/public}/images/lethean-logo.png | Bin .../images/private-transaction-net.png | Bin .../public}/images/secure-data-storage.jpg | Bin pkg/core/docs/public/index.html | 914 +++ pkg/core/docs/public/search/search_index.js | 1 + pkg/core/docs/public/search/search_index.json | 1 + pkg/core/docs/public/sitemap.xml | 35 + pkg/core/docs/public/sitemap.xml.gz | Bin 0 -> 233 bytes .../docs/public}/src/core/config.md | 0 .../docs/public}/src/core/crypt.md | 0 .../docs/public}/src/core/display.md | 0 .../docs/public}/src/core/docs.md | 0 .../docs/public}/src/core/index.md | 0 .../docs => core/docs/public}/src/core/io.md | 0 .../docs/public}/src/core/workspace.md | 0 .../public}/src/images/cross-platform.jpeg | Bin .../public}/src/images/decentralised-vpn.jpg | Bin .../docs/public}/src/images/favicon.ico | Bin .../docs/public}/src/images/illustration.png | Bin .../docs/public}/src/images/lethean-logo.png | Bin .../src/images/private-transaction-net.png | Bin .../src/images/secure-data-storage.jpg | Bin .../docs => core/docs/public}/src/index.md | 0 .../docs/public/src}/stylesheets/extra.css | 0 .../docs/public}/stylesheets/extra.css | 0 pkg/{v1 => }/core/docs/requirements.txt | 0 pkg/{v1 => }/core/docs/taskfile.dist.yml | 0 pkg/{v1 => }/core/go.mod | 1 + pkg/{v1 => }/core/go.sum | 2 + pkg/core/i18n/editor.babel | 5685 +++++++++++++++++ pkg/core/i18n/i18n.go | 171 + pkg/core/i18n/locales/de.json | 157 + pkg/core/i18n/locales/en.json | 157 + pkg/core/i18n/locales/es.json | 157 + pkg/core/i18n/locales/fr.json | 157 + pkg/core/i18n/locales/ru.json | 157 + pkg/core/i18n/locales/uk.json | 157 + pkg/core/i18n/locales/zh.json | 157 + pkg/core/runtime.go | 76 + pkg/internals/cio/cio.go | 27 + pkg/internals/cio/client.go | 30 + pkg/internals/cio/mock.go | 47 + pkg/{v1/core => internals}/io/client.go | 0 pkg/{v1/core => internals}/io/client_test.go | 0 pkg/{v1/core => internals}/io/filesystem.go | 0 .../core => internals}/io/filesystem_test.go | 0 pkg/{v1/core => internals}/io/local/client.go | 0 .../io/local/client_test.go | 0 pkg/{v1/core => internals}/io/local/local.go | 0 pkg/{v1/core => internals}/io/mock.go | 0 pkg/{v1/core => internals}/io/sftp/client.go | 0 pkg/{v1/core => internals}/io/sftp/sftp.go | 0 .../core => internals}/io/webdav/client.go | 0 .../core => internals}/io/webdav/webdav.go | 0 pkg/{v1/core => internals}/workspace/local.go | 0 .../core => internals}/workspace/service.go | 0 .../core => internals}/workspace/workspace.go | 0 .../workspace/workspace_test.go | 0 pkg/v1/core/actions.go | 3 - pkg/v1/core/config/config.go | 148 - pkg/v1/core/config/config_test.go | 81 - pkg/v1/core/config/header.go | 55 - pkg/v1/core/core.go | 139 - pkg/v1/core/crypt/crypt.go | 63 - pkg/v1/core/crypt/header.go | 24 - pkg/v1/core/crypt/lib/lthn/lthn.go | 16 - pkg/v1/core/display/display.go | 158 - pkg/v1/core/display/window.go | 81 - pkg/v1/core/docs/docs.go | 27 - .../core/docs/public/search/search_index.json | 1 - pkg/v1/core/docs/public/sitemap.xml | 7 - pkg/v1/core/docs/public/sitemap.xml.gz | Bin 179 -> 0 bytes pkg/v1/core/docs/service.go | 54 - pkg/v1/core/header.go | 45 - 182 files changed, 19669 insertions(+), 1076 deletions(-) create mode 100644 Makefile delete mode 100644 Taskfile.yml create mode 100644 cmd/app/public/bindings/github.com/Snider/Core/core.ts create mode 100644 cmd/app/public/bindings/github.com/Snider/Core/index.ts create mode 100644 cmd/app/public/bindings/github.com/Snider/Core/models.ts create mode 100644 cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/index.ts create mode 100644 cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/models.ts create mode 100644 cmd/app/public/bindings/log/slog/index.ts create mode 100644 cmd/app/public/bindings/log/slog/models.ts create mode 100644 cmd/tasks/build-darwin.yml create mode 100644 cmd/tasks/build-linux.yml create mode 100644 cmd/tasks/build-windows.yml create mode 100644 cmd/tasks/config.yml create mode 100644 cmd/tasks/go.yml create mode 100644 cmd/tasks/node.yml create mode 100644 cmd/tasks/wails.yml delete mode 100644 pkg/app/.gitignore create mode 100644 pkg/core/actions.go create mode 100644 pkg/core/config/config.go create mode 100644 pkg/core/config/config_test.go create mode 100644 pkg/core/core.go create mode 100644 pkg/core/crypt/crypt.go rename pkg/{v1 => }/core/crypt/crypt_test.go (100%) rename pkg/{v1 => }/core/crypt/hash.go (94%) rename pkg/{v1/core/crypt/lib => core/crypt}/lthn/hash_test.go (100%) rename pkg/{v1/core/crypt/lib/lthn/hash.go => core/crypt/lthn/lthn.go} (81%) rename pkg/{v1/core/crypt/lib => core/crypt}/openpgp/encrypt.go (86%) create mode 100644 pkg/core/crypt/openpgp/encrypt_test.go rename pkg/{v1/core/crypt/lib => core/crypt}/openpgp/key.go (77%) rename pkg/{v1/core/crypt/lib => core/crypt}/openpgp/openpgp.go (100%) rename pkg/{v1/core/crypt/lib => core/crypt}/openpgp/sign.go (72%) rename pkg/{v1 => }/core/crypt/sum.go (100%) create mode 100644 pkg/core/display/actions.go create mode 100644 pkg/core/display/display.go rename pkg/{v1 => }/core/display/menu.go (87%) rename pkg/{v1 => }/core/display/tray.go (85%) create mode 100644 pkg/core/display/window.go delete mode 100644 pkg/core/docs/.gitignore rename pkg/{v1 => }/core/docs/assets/stylesheets/extra.css (100%) create mode 100644 pkg/core/docs/docs.go rename pkg/{v1 => }/core/docs/mkdocs.yml (100%) rename pkg/{v1 => }/core/docs/public/404.html (84%) create mode 100644 pkg/core/docs/public/assets/external/fonts.googleapis.com/css.49ea35f2.css create mode 100644 pkg/core/docs/public/assets/external/unpkg.com/iframe-worker/shim.js create mode 100644 pkg/core/docs/public/assets/external/unpkg.com/mermaid@11/dist/mermaid.min.js rename pkg/{v1 => }/core/docs/public/assets/images/favicon.png (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/bundle.f55a23d4.min.js (71%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/bundle.f55a23d4.min.js.map (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.ar.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.da.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.de.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.du.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.el.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.es.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.fi.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.fr.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.he.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.hi.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.hu.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.hy.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.it.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.ja.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.jp.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.kn.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.ko.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.multi.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.nl.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.no.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.pt.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.ro.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.ru.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.sa.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.stemmer.support.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.sv.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.ta.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.te.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.th.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.tr.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.vi.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/min/lunr.zh.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/tinyseg.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/lunr/wordcut.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/workers/search.973d3a69.min.js (100%) rename pkg/{v1 => }/core/docs/public/assets/javascripts/workers/search.973d3a69.min.js.map (100%) rename pkg/{v1 => }/core/docs/public/assets/stylesheets/main.84d31ad4.min.css (100%) rename pkg/{v1 => }/core/docs/public/assets/stylesheets/main.84d31ad4.min.css.map (100%) rename pkg/{v1 => }/core/docs/public/assets/stylesheets/palette.06af60db.min.css (100%) rename pkg/{v1 => }/core/docs/public/assets/stylesheets/palette.06af60db.min.css.map (100%) create mode 100644 pkg/core/docs/public/core/config.html create mode 100644 pkg/core/docs/public/core/crypt.html create mode 100644 pkg/core/docs/public/core/display.html create mode 100644 pkg/core/docs/public/core/docs.html rename pkg/{v1/core/docs/public => core/docs/public/core}/index.html (62%) create mode 100644 pkg/core/docs/public/core/io.html create mode 100644 pkg/core/docs/public/core/workspace.html rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/cross-platform.jpeg (100%) rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/decentralised-vpn.jpg (100%) rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/favicon.ico (100%) rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/illustration.png (100%) rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/lethean-logo.png (100%) rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/private-transaction-net.png (100%) rename pkg/{v1/core/docs/public/assets => core/docs/public}/images/secure-data-storage.jpg (100%) create mode 100644 pkg/core/docs/public/index.html create mode 100644 pkg/core/docs/public/search/search_index.js create mode 100644 pkg/core/docs/public/search/search_index.json create mode 100644 pkg/core/docs/public/sitemap.xml create mode 100644 pkg/core/docs/public/sitemap.xml.gz rename pkg/{v1/core/docs => core/docs/public}/src/core/config.md (100%) rename pkg/{v1/core/docs => core/docs/public}/src/core/crypt.md (100%) rename pkg/{v1/core/docs => core/docs/public}/src/core/display.md (100%) rename pkg/{v1/core/docs => core/docs/public}/src/core/docs.md (100%) rename pkg/{v1/core/docs => core/docs/public}/src/core/index.md (100%) rename pkg/{v1/core/docs => core/docs/public}/src/core/io.md (100%) rename pkg/{v1/core/docs => core/docs/public}/src/core/workspace.md (100%) rename pkg/{v1/core/docs => core/docs/public}/src/images/cross-platform.jpeg (100%) rename pkg/{v1/core/docs => core/docs/public}/src/images/decentralised-vpn.jpg (100%) rename pkg/{v1/core/docs => core/docs/public}/src/images/favicon.ico (100%) rename pkg/{v1/core/docs => core/docs/public}/src/images/illustration.png (100%) rename pkg/{v1/core/docs => core/docs/public}/src/images/lethean-logo.png (100%) rename pkg/{v1/core/docs => core/docs/public}/src/images/private-transaction-net.png (100%) rename pkg/{v1/core/docs => core/docs/public}/src/images/secure-data-storage.jpg (100%) rename pkg/{v1/core/docs => core/docs/public}/src/index.md (100%) rename pkg/{v1/core/docs/public/assets => core/docs/public/src}/stylesheets/extra.css (100%) rename pkg/{v1/core/docs/src => core/docs/public}/stylesheets/extra.css (100%) rename pkg/{v1 => }/core/docs/requirements.txt (100%) rename pkg/{v1 => }/core/docs/taskfile.dist.yml (100%) rename pkg/{v1 => }/core/go.mod (97%) rename pkg/{v1 => }/core/go.sum (98%) create mode 100644 pkg/core/i18n/editor.babel create mode 100644 pkg/core/i18n/i18n.go create mode 100644 pkg/core/i18n/locales/de.json create mode 100644 pkg/core/i18n/locales/en.json create mode 100644 pkg/core/i18n/locales/es.json create mode 100644 pkg/core/i18n/locales/fr.json create mode 100644 pkg/core/i18n/locales/ru.json create mode 100644 pkg/core/i18n/locales/uk.json create mode 100644 pkg/core/i18n/locales/zh.json create mode 100644 pkg/core/runtime.go create mode 100644 pkg/internals/cio/cio.go create mode 100644 pkg/internals/cio/client.go create mode 100644 pkg/internals/cio/mock.go rename pkg/{v1/core => internals}/io/client.go (100%) rename pkg/{v1/core => internals}/io/client_test.go (100%) rename pkg/{v1/core => internals}/io/filesystem.go (100%) rename pkg/{v1/core => internals}/io/filesystem_test.go (100%) rename pkg/{v1/core => internals}/io/local/client.go (100%) rename pkg/{v1/core => internals}/io/local/client_test.go (100%) rename pkg/{v1/core => internals}/io/local/local.go (100%) rename pkg/{v1/core => internals}/io/mock.go (100%) rename pkg/{v1/core => internals}/io/sftp/client.go (100%) rename pkg/{v1/core => internals}/io/sftp/sftp.go (100%) rename pkg/{v1/core => internals}/io/webdav/client.go (100%) rename pkg/{v1/core => internals}/io/webdav/webdav.go (100%) rename pkg/{v1/core => internals}/workspace/local.go (100%) rename pkg/{v1/core => internals}/workspace/service.go (100%) rename pkg/{v1/core => internals}/workspace/workspace.go (100%) rename pkg/{v1/core => internals}/workspace/workspace_test.go (100%) delete mode 100644 pkg/v1/core/actions.go delete mode 100644 pkg/v1/core/config/config.go delete mode 100644 pkg/v1/core/config/config_test.go delete mode 100644 pkg/v1/core/config/header.go delete mode 100644 pkg/v1/core/core.go delete mode 100644 pkg/v1/core/crypt/crypt.go delete mode 100644 pkg/v1/core/crypt/header.go delete mode 100644 pkg/v1/core/crypt/lib/lthn/lthn.go delete mode 100644 pkg/v1/core/display/display.go delete mode 100644 pkg/v1/core/display/window.go delete mode 100644 pkg/v1/core/docs/docs.go delete mode 100644 pkg/v1/core/docs/public/search/search_index.json delete mode 100644 pkg/v1/core/docs/public/sitemap.xml delete mode 100644 pkg/v1/core/docs/public/sitemap.xml.gz delete mode 100644 pkg/v1/core/docs/service.go delete mode 100644 pkg/v1/core/header.go diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a6a8b1d --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +.PHONY: prod-docs + +all: + (cd cmd/app && wails3 build) + +development-docs: + @echo "Running development documentation Website..." + @(cd pkg/core/docs && mkdocs serve -w src) + +prod-docs: + @echo "Generating documentation tp Repo Root..." + @(cd pkg/core/docs && mkdocs build -d public && cp -r src public) + @echo "Documentation generated at docs/index.html" \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml deleted file mode 100644 index e054988..0000000 --- a/Taskfile.yml +++ /dev/null @@ -1,35 +0,0 @@ -version: '3' - -# This top-level Taskfile orchestrates tasks in the sub-directories. -# It uses the 'dir' property to ensure that each included task runs -# from its correct working directory. -includes: - app: - dir: {{.TASKFILE_DIR}}/cmd/app - docs: - dir: {{.TASKFILE_DIR}}/pkg/v1/core/docs - - - -tasks: - default: - desc: "Show available tasks." - cmds: - - task --list-all - - build: - desc: "Build both the application and the documentation." - cmds: - - task: app:build - - task: docs:build - - dev: - desc: "Run the application in development mode and serve the documentation." - cmds: - - task: app:dev - - task: docs:dev - - tidy: - desc: "Tidy all Go modules in the workspace." - cmds: - - go mod tidy diff --git a/cmd/app/Taskfile.yml b/cmd/app/Taskfile.yml index c2336aa..fce96d4 100644 --- a/cmd/app/Taskfile.yml +++ b/cmd/app/Taskfile.yml @@ -2,14 +2,14 @@ version: '3' includes: - common: "{{.TASKFILE_DIR}}/build/Taskfile.yml" - windows: "{{.TASKFILE_DIR}}/build/windows/Taskfile.yml" - darwin: "{{.TASKFILE_DIR}}/build/darwin/Taskfile.yml" - linux: "{{.TASKFILE_DIR}}/build/linux/Taskfile.yml" + common: "./build/Taskfile.yml" + windows: "./build/windows/Taskfile.yml" + darwin: "./build/darwin/Taskfile.yml" + linux: "./build/linux/Taskfile.yml" vars: APP_NAME: "core" - BIN_DIR: "{{.TASKFILE_DIR}}/build/bin" + BIN_DIR: "./build/bin" VITE_PORT: '{{.WAILS_VITE_PORT | default 9245}}' tasks: @@ -31,4 +31,4 @@ tasks: dev: summary: Runs the application in development mode cmds: - - wails3 dev -config {{.TASKFILE_DIR}}/build/config.yml -port {{.VITE_PORT}} + - wails3 dev -config ./build/config.yml -port {{.VITE_PORT}} diff --git a/cmd/app/build/Taskfile.yml b/cmd/app/build/Taskfile.yml index 4fb1eec..edeab4a 100644 --- a/cmd/app/build/Taskfile.yml +++ b/cmd/app/build/Taskfile.yml @@ -45,6 +45,7 @@ tasks: generate:bindings: label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}}) summary: Generates bindings + dir: ../ deps: - task: go:mod:tidy sources: diff --git a/cmd/app/build/config.yml b/cmd/app/build/config.yml index cfcaa08..f6b56f6 100644 --- a/cmd/app/build/config.yml +++ b/cmd/app/build/config.yml @@ -5,8 +5,8 @@ version: '3' # This information is used to generate the build assets. info: companyName: "Snider" - productName: "Core.Framework" - productIdentifier: "com.core.desktop" + productName: "Core.App" + productIdentifier: "com.core.app" description: "A program that does demos the features" copyright: "(c) EUPL-1.2, Snider" comments: "Demo Dev Area" diff --git a/cmd/app/build/darwin/Taskfile.yml b/cmd/app/build/darwin/Taskfile.yml index 953842c..97bf96b 100644 --- a/cmd/app/build/darwin/Taskfile.yml +++ b/cmd/app/build/darwin/Taskfile.yml @@ -1,7 +1,7 @@ version: '3' includes: - common: {{.TASKFILE_DIR}}/Taskfile.yml + common: ../Taskfile.yml tasks: build: diff --git a/cmd/app/build/linux/Taskfile.yml b/cmd/app/build/linux/Taskfile.yml index fcdae34..7e78961 100644 --- a/cmd/app/build/linux/Taskfile.yml +++ b/cmd/app/build/linux/Taskfile.yml @@ -1,7 +1,7 @@ version: '3' includes: - common: "{{.TASKFILE_DIR}}/Taskfile.yml" + common: "../Taskfile.yml" tasks: build: diff --git a/cmd/app/build/windows/Taskfile.yml b/cmd/app/build/windows/Taskfile.yml index a7863b8..75c59f5 100644 --- a/cmd/app/build/windows/Taskfile.yml +++ b/cmd/app/build/windows/Taskfile.yml @@ -1,7 +1,7 @@ version: '3' includes: - common: {{.TASKFILE_DIR}}/Taskfile.yml + common: ../Taskfile.yml tasks: build: diff --git a/cmd/app/main.go b/cmd/app/main.go index 3d30194..313fda8 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -21,14 +21,16 @@ func main() { }, }) - app.RegisterService(application.NewService(core.Service( - core.WithWails(app), // Provides the Wails application instance to core services - core.WithAssets(assets), // Provides the embed.FS to core services - core.WithService(config.Register), // Provides the ability to persist UI state (windows reopen where they closed) - core.WithService(display.Register), // Provides the ability to open windows - core.WithService(crypt.Register), // Provides cryptographic functions - core.WithServiceLock(), // locks core from accepting new services blocking access to IPC - ))) + coreService := core.New( + core.WithWails(app), + core.WithAssets(assets), + core.WithService(config.New), + core.WithService(display.New), + core.WithService(crypt.New), + core.WithServiceLock(), + ) + + app.RegisterService(application.NewService(coreService)) err := app.Run() if err != nil { diff --git a/cmd/app/public/bindings/github.com/Snider/Core/core.ts b/cmd/app/public/bindings/github.com/Snider/Core/core.ts new file mode 100644 index 0000000..aaf87f9 --- /dev/null +++ b/cmd/app/public/bindings/github.com/Snider/Core/core.ts @@ -0,0 +1,40 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as $models from "./models.js"; + +export function ACTION(msg: $models.Message | null): $CancellablePromise { + return $Call.ByID(4097389530, msg); +} + +export function Core(): $CancellablePromise<$models.Core | null> { + return $Call.ByID(590325359).then(($result: any) => { + return $$createType1($result); + }); +} + +export function RegisterAction(handler: any): $CancellablePromise { + return $Call.ByID(2751902507, handler); +} + +export function RegisterActions(...handlers: any[]): $CancellablePromise { + return $Call.ByID(2391561096, handlers); +} + +export function RegisterService(name: string, api: any): $CancellablePromise { + return $Call.ByID(3677749144, name, api); +} + +export function Service(name: string): $CancellablePromise { + return $Call.ByID(3861253011, name); +} + +// Private type creation functions +const $$createType0 = $models.Core.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/cmd/app/public/bindings/github.com/Snider/Core/index.ts b/cmd/app/public/bindings/github.com/Snider/Core/index.ts new file mode 100644 index 0000000..cb1f26a --- /dev/null +++ b/cmd/app/public/bindings/github.com/Snider/Core/index.ts @@ -0,0 +1,15 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +import * as Core from "./core.js"; +export { + Core +}; + +export { + Core +} from "./models.js"; + +export type { + Message +} from "./models.js"; diff --git a/cmd/app/public/bindings/github.com/Snider/Core/models.ts b/cmd/app/public/bindings/github.com/Snider/Core/models.ts new file mode 100644 index 0000000..c0152b2 --- /dev/null +++ b/cmd/app/public/bindings/github.com/Snider/Core/models.ts @@ -0,0 +1,41 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as application$0 from "../../wailsapp/wails/v3/pkg/application/models.js"; + +export class Core { + "App": application$0.App | null; + + /** Creates a new Core instance. */ + constructor($$source: Partial = {}) { + if (!("App" in $$source)) { + this["App"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new Core instance from a string or object. + */ + static createFrom($$source: any = {}): Core { + const $$createField0_0 = $$createType1; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("App" in $$parsedSource) { + $$parsedSource["App"] = $$createField0_0($$parsedSource["App"]); + } + return new Core($$parsedSource as Partial); + } +} + +export type Message = any; + +// Private type creation functions +const $$createType0 = application$0.App.createFrom; +const $$createType1 = $Create.Nullable($$createType0); diff --git a/cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/index.ts b/cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/index.ts new file mode 100644 index 0000000..b8aaea1 --- /dev/null +++ b/cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/index.ts @@ -0,0 +1,17 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + App, + BrowserManager, + ClipboardManager, + ContextMenuManager, + DialogManager, + EnvironmentManager, + EventManager, + KeyBindingManager, + MenuManager, + ScreenManager, + SystemTrayManager, + WindowManager +} from "./models.js"; diff --git a/cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/models.ts b/cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/models.ts new file mode 100644 index 0000000..bf9925b --- /dev/null +++ b/cmd/app/public/bindings/github.com/wailsapp/wails/v3/pkg/application/models.ts @@ -0,0 +1,369 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import * as slog$0 from "../../../../../../log/slog/models.js"; + +export class App { + /** + * Manager pattern for organized API + */ + "Window": WindowManager | null; + "ContextMenu": ContextMenuManager | null; + "KeyBinding": KeyBindingManager | null; + "Browser": BrowserManager | null; + "Env": EnvironmentManager | null; + "Dialog": DialogManager | null; + "Event": EventManager | null; + "Menu": MenuManager | null; + "Screen": ScreenManager | null; + "Clipboard": ClipboardManager | null; + "SystemTray": SystemTrayManager | null; + "Logger": slog$0.Logger | null; + + /** Creates a new App instance. */ + constructor($$source: Partial = {}) { + if (!("Window" in $$source)) { + this["Window"] = null; + } + if (!("ContextMenu" in $$source)) { + this["ContextMenu"] = null; + } + if (!("KeyBinding" in $$source)) { + this["KeyBinding"] = null; + } + if (!("Browser" in $$source)) { + this["Browser"] = null; + } + if (!("Env" in $$source)) { + this["Env"] = null; + } + if (!("Dialog" in $$source)) { + this["Dialog"] = null; + } + if (!("Event" in $$source)) { + this["Event"] = null; + } + if (!("Menu" in $$source)) { + this["Menu"] = null; + } + if (!("Screen" in $$source)) { + this["Screen"] = null; + } + if (!("Clipboard" in $$source)) { + this["Clipboard"] = null; + } + if (!("SystemTray" in $$source)) { + this["SystemTray"] = null; + } + if (!("Logger" in $$source)) { + this["Logger"] = null; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new App instance from a string or object. + */ + static createFrom($$source: any = {}): App { + const $$createField0_0 = $$createType1; + const $$createField1_0 = $$createType3; + const $$createField2_0 = $$createType5; + const $$createField3_0 = $$createType7; + const $$createField4_0 = $$createType9; + const $$createField5_0 = $$createType11; + const $$createField6_0 = $$createType13; + const $$createField7_0 = $$createType15; + const $$createField8_0 = $$createType17; + const $$createField9_0 = $$createType19; + const $$createField10_0 = $$createType21; + const $$createField11_0 = $$createType23; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("Window" in $$parsedSource) { + $$parsedSource["Window"] = $$createField0_0($$parsedSource["Window"]); + } + if ("ContextMenu" in $$parsedSource) { + $$parsedSource["ContextMenu"] = $$createField1_0($$parsedSource["ContextMenu"]); + } + if ("KeyBinding" in $$parsedSource) { + $$parsedSource["KeyBinding"] = $$createField2_0($$parsedSource["KeyBinding"]); + } + if ("Browser" in $$parsedSource) { + $$parsedSource["Browser"] = $$createField3_0($$parsedSource["Browser"]); + } + if ("Env" in $$parsedSource) { + $$parsedSource["Env"] = $$createField4_0($$parsedSource["Env"]); + } + if ("Dialog" in $$parsedSource) { + $$parsedSource["Dialog"] = $$createField5_0($$parsedSource["Dialog"]); + } + if ("Event" in $$parsedSource) { + $$parsedSource["Event"] = $$createField6_0($$parsedSource["Event"]); + } + if ("Menu" in $$parsedSource) { + $$parsedSource["Menu"] = $$createField7_0($$parsedSource["Menu"]); + } + if ("Screen" in $$parsedSource) { + $$parsedSource["Screen"] = $$createField8_0($$parsedSource["Screen"]); + } + if ("Clipboard" in $$parsedSource) { + $$parsedSource["Clipboard"] = $$createField9_0($$parsedSource["Clipboard"]); + } + if ("SystemTray" in $$parsedSource) { + $$parsedSource["SystemTray"] = $$createField10_0($$parsedSource["SystemTray"]); + } + if ("Logger" in $$parsedSource) { + $$parsedSource["Logger"] = $$createField11_0($$parsedSource["Logger"]); + } + return new App($$parsedSource as Partial); + } +} + +/** + * BrowserManager manages browser-related operations + */ +export class BrowserManager { + + /** Creates a new BrowserManager instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new BrowserManager instance from a string or object. + */ + static createFrom($$source: any = {}): BrowserManager { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new BrowserManager($$parsedSource as Partial); + } +} + +/** + * ClipboardManager manages clipboard operations + */ +export class ClipboardManager { + + /** Creates a new ClipboardManager instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new ClipboardManager instance from a string or object. + */ + static createFrom($$source: any = {}): ClipboardManager { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new ClipboardManager($$parsedSource as Partial); + } +} + +/** + * ContextMenuManager manages all context menu operations + */ +export class ContextMenuManager { + + /** Creates a new ContextMenuManager instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new ContextMenuManager instance from a string or object. + */ + static createFrom($$source: any = {}): ContextMenuManager { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new ContextMenuManager($$parsedSource as Partial); + } +} + +/** + * DialogManager manages dialog-related operations + */ +export class DialogManager { + + /** Creates a new DialogManager instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new DialogManager instance from a string or object. + */ + static createFrom($$source: any = {}): DialogManager { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new DialogManager($$parsedSource as Partial); + } +} + +/** + * EnvironmentManager manages environment-related operations + */ +export class EnvironmentManager { + + /** Creates a new EnvironmentManager instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new EnvironmentManager instance from a string or object. + */ + static createFrom($$source: any = {}): EnvironmentManager { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new EnvironmentManager($$parsedSource as Partial); + } +} + +/** + * EventManager manages event-related operations + */ +export class EventManager { + + /** Creates a new EventManager instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new EventManager instance from a string or object. + */ + static createFrom($$source: any = {}): EventManager { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new EventManager($$parsedSource as Partial); + } +} + +/** + * KeyBindingManager manages all key binding operations + */ +export class KeyBindingManager { + + /** Creates a new KeyBindingManager instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new KeyBindingManager instance from a string or object. + */ + static createFrom($$source: any = {}): KeyBindingManager { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new KeyBindingManager($$parsedSource as Partial); + } +} + +/** + * MenuManager manages menu-related operations + */ +export class MenuManager { + + /** Creates a new MenuManager instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new MenuManager instance from a string or object. + */ + static createFrom($$source: any = {}): MenuManager { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new MenuManager($$parsedSource as Partial); + } +} + +export class ScreenManager { + + /** Creates a new ScreenManager instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new ScreenManager instance from a string or object. + */ + static createFrom($$source: any = {}): ScreenManager { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new ScreenManager($$parsedSource as Partial); + } +} + +/** + * SystemTrayManager manages system tray-related operations + */ +export class SystemTrayManager { + + /** Creates a new SystemTrayManager instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new SystemTrayManager instance from a string or object. + */ + static createFrom($$source: any = {}): SystemTrayManager { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new SystemTrayManager($$parsedSource as Partial); + } +} + +/** + * WindowManager manages all window-related operations + */ +export class WindowManager { + + /** Creates a new WindowManager instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new WindowManager instance from a string or object. + */ + static createFrom($$source: any = {}): WindowManager { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new WindowManager($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = WindowManager.createFrom; +const $$createType1 = $Create.Nullable($$createType0); +const $$createType2 = ContextMenuManager.createFrom; +const $$createType3 = $Create.Nullable($$createType2); +const $$createType4 = KeyBindingManager.createFrom; +const $$createType5 = $Create.Nullable($$createType4); +const $$createType6 = BrowserManager.createFrom; +const $$createType7 = $Create.Nullable($$createType6); +const $$createType8 = EnvironmentManager.createFrom; +const $$createType9 = $Create.Nullable($$createType8); +const $$createType10 = DialogManager.createFrom; +const $$createType11 = $Create.Nullable($$createType10); +const $$createType12 = EventManager.createFrom; +const $$createType13 = $Create.Nullable($$createType12); +const $$createType14 = MenuManager.createFrom; +const $$createType15 = $Create.Nullable($$createType14); +const $$createType16 = ScreenManager.createFrom; +const $$createType17 = $Create.Nullable($$createType16); +const $$createType18 = ClipboardManager.createFrom; +const $$createType19 = $Create.Nullable($$createType18); +const $$createType20 = SystemTrayManager.createFrom; +const $$createType21 = $Create.Nullable($$createType20); +const $$createType22 = slog$0.Logger.createFrom; +const $$createType23 = $Create.Nullable($$createType22); diff --git a/cmd/app/public/bindings/log/slog/index.ts b/cmd/app/public/bindings/log/slog/index.ts new file mode 100644 index 0000000..28f9022 --- /dev/null +++ b/cmd/app/public/bindings/log/slog/index.ts @@ -0,0 +1,6 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +export { + Logger +} from "./models.js"; diff --git a/cmd/app/public/bindings/log/slog/models.ts b/cmd/app/public/bindings/log/slog/models.ts new file mode 100644 index 0000000..ef606c6 --- /dev/null +++ b/cmd/app/public/bindings/log/slog/models.ts @@ -0,0 +1,31 @@ +// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL +// This file is automatically generated. DO NOT EDIT + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports +import { Create as $Create } from "@wailsio/runtime"; + +/** + * A Logger records structured information about each call to its + * Log, Debug, Info, Warn, and Error methods. + * For each call, it creates a [Record] and passes it to a [Handler]. + * + * To create a new Logger, call [New] or a Logger method + * that begins "With". + */ +export class Logger { + + /** Creates a new Logger instance. */ + constructor($$source: Partial = {}) { + + Object.assign(this, $$source); + } + + /** + * Creates a new Logger instance from a string or object. + */ + static createFrom($$source: any = {}): Logger { + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + return new Logger($$parsedSource as Partial); + } +} diff --git a/cmd/tasks/build-darwin.yml b/cmd/tasks/build-darwin.yml new file mode 100644 index 0000000..e49921a --- /dev/null +++ b/cmd/tasks/build-darwin.yml @@ -0,0 +1,39 @@ +version: '3' + +# This file contains the build logic specifically for the macOS (Darwin) platform. + +includes: + common: ./common.yml + +tasks: + build: + desc: "Builds the application for macOS." + dir: "{{.APP_ROOT}}" + deps: + - task: common:go:mod:tidy + - task: common:build:public + vars: + PRODUCTION: '{{.PRODUCTION | default "false"}}' + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o bin/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: darwin + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default "amd64"}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + desc: "Packages the application as a .app bundle for macOS." + dir: "{{.APP_ROOT}}" + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - echo "Packaging for macOS..." + # This command assumes Wails handles the bundling process. + # You may need to adjust based on your specific packaging tool. + - wails3 build -package -platform darwin/{{.ARCH | default "amd64"}} -production -clean -o "{{.APP_NAME}}.app" diff --git a/cmd/tasks/build-linux.yml b/cmd/tasks/build-linux.yml new file mode 100644 index 0000000..e346930 --- /dev/null +++ b/cmd/tasks/build-linux.yml @@ -0,0 +1,40 @@ +version: '3' + +# This file contains the build logic specifically for the Linux platform. + +includes: + common: ./common.yml + +tasks: + build: + desc: "Builds the application for Linux." + dir: "{{.APP_ROOT}}" + deps: + - task: common:go:mod:tidy + - task: common:build:public + vars: + BUILD_FLAGS: + ref: .BUILD_FLAGS + PRODUCTION: + ref: .PRODUCTION + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o bin/{{.APP_NAME}} + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: linux + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default "amd64"}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + desc: "Packages the application for Linux." + dir: "{{.APP_ROOT}}" + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - echo "Packaging for Linux... (AppImage, Deb, RPM)" + # Placeholder for actual packaging commands diff --git a/cmd/tasks/build-windows.yml b/cmd/tasks/build-windows.yml new file mode 100644 index 0000000..c5f4434 --- /dev/null +++ b/cmd/tasks/build-windows.yml @@ -0,0 +1,39 @@ +version: '3' + +# This file contains the build logic specifically for the Windows platform. + +includes: + common: ./common.yml + +tasks: + build: + desc: "Builds the application for Windows." + dir: "{{.APP_ROOT}}" + deps: + - task: common:go:mod:tidy + - task: common:build:public + vars: + PRODUCTION: '{{.PRODUCTION | default "false"}}' + - task: common:generate:icons + cmds: + - go build {{.BUILD_FLAGS}} -o bin/{{.APP_NAME}}.exe + vars: + BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H=windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}' + env: + GOOS: windows + CGO_ENABLED: 1 + GOARCH: '{{.ARCH | default "amd64"}}' + PRODUCTION: '{{.PRODUCTION | default "false"}}' + + package: + desc: "Packages the application as a .exe installer for Windows." + dir: "{{.APP_ROOT}}" + deps: + - task: build + vars: + PRODUCTION: "true" + cmds: + - echo "Packaging for Windows..." + # This command assumes Wails handles the packaging process, potentially using NSIS or MSIX. + # You may need to adjust based on your specific packaging tool. + - wails3 build -package -platform windows/{{.ARCH | default "amd6d64"}} -production -clean -o "{{.APP_NAME}}.exe" diff --git a/cmd/tasks/config.yml b/cmd/tasks/config.yml new file mode 100644 index 0000000..d077323 --- /dev/null +++ b/cmd/tasks/config.yml @@ -0,0 +1,2 @@ +version: '3' + diff --git a/cmd/tasks/go.yml b/cmd/tasks/go.yml new file mode 100644 index 0000000..037a9c7 --- /dev/null +++ b/cmd/tasks/go.yml @@ -0,0 +1,14 @@ +version: '3' + +tasks: + tidy: + desc: "Run go mod tidy in a specific directory." + dir: '{{.GO_DIR | default "."}}' + cmds: + - go mod tidy + + build: + desc: "Run go build in a specific directory." + dir: '{{.GO_DIR | default "."}}' + cmds: + - go build -o {{.OUTPUT_PATH}} {{.BUILD_FLAGS}} diff --git a/cmd/tasks/node.yml b/cmd/tasks/node.yml new file mode 100644 index 0000000..edf4e93 --- /dev/null +++ b/cmd/tasks/node.yml @@ -0,0 +1,24 @@ +version: '3' + +tasks: + install: + desc: "Install npm dependencies for the frontend." + dir: '{{.PUBLIC_DIR}}' + sources: + - package.json + - package-lock.json + generates: + - node_modules/* + preconditions: + - sh: npm version + msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/" + cmds: + - npm install + + build: + desc: "Build the frontend assets." + dir: '{{.PUBLIC_DIR}}' + deps: + - task: install + cmds: + - npm run build diff --git a/cmd/tasks/wails.yml b/cmd/tasks/wails.yml new file mode 100644 index 0000000..1d822f5 --- /dev/null +++ b/cmd/tasks/wails.yml @@ -0,0 +1,21 @@ +version: '3' + +tasks: + dev: + desc: "Runs the Wails dev server." + vars: + APP_ROOT: '{{.APP_ROOT | default "."}}' + VITE_PORT: '{{.VITE_PORT | default 9245}}' + cmds: + # Note the paths are now constructed from the passed-in APP_ROOT. + - wails3 dev -approot '{{.APP_ROOT}}' -config '{{.APP_ROOT}}/build/config.yml' -port {{.VITE_PORT}} + + build: + desc: "Builds the Wails application." + vars: + APP_ROOT: '{{.APP_ROOT | default "."}}' + APP_NAME: '{{.APP_NAME | default "app"}}' + dir: '{{.APP_ROOT}}' # Run the build from the app's root directory. + cmds: + # This assumes you have a 'build' task in your platform-specific Taskfiles. + - wails3 build -platform '{{.GOOS}}/{{.GOARCH}}' -name '{{.APP_NAME}}' diff --git a/go.work b/go.work index d138de6..b9f826f 100644 --- a/go.work +++ b/go.work @@ -1,6 +1,6 @@ go 1.25 use ( - ./pkg/v1/core + ./pkg/core ./cmd/app ) diff --git a/go.work.sum b/go.work.sum index 2f1bfb3..585b519 100644 --- a/go.work.sum +++ b/go.work.sum @@ -10,6 +10,8 @@ github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Ladicle/tabwriter v1.0.0 h1:DZQqPvMumBDwVNElso13afjYLNp0Z7pHqHnu0r4t9Dg= github.com/Ladicle/tabwriter v1.0.0/go.mod h1:c4MdCjxQyTbGuQO/gvqJ+IA/89UEwrsD6hUCW98dyp4= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= diff --git a/pkg/app/.gitignore b/pkg/app/.gitignore deleted file mode 100644 index 393ccc1..0000000 --- a/pkg/app/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.task -/build/bin/core diff --git a/pkg/core/actions.go b/pkg/core/actions.go new file mode 100644 index 0000000..828df0f --- /dev/null +++ b/pkg/core/actions.go @@ -0,0 +1,11 @@ +package core + +import "github.com/wailsapp/wails/v3/pkg/application" + +type ActionServiceStartup struct{} + +// ActionDisplayOpenWindow is a structured message for requesting a new window. +type ActionDisplayOpenWindow struct { + Name string + Options application.WebviewWindowOptions +} diff --git a/pkg/core/config/config.go b/pkg/core/config/config.go new file mode 100644 index 0000000..bd922e3 --- /dev/null +++ b/pkg/core/config/config.go @@ -0,0 +1,177 @@ +package config + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + + "github.com/Snider/Core" + "github.com/adrg/xdg" +) + +const appName = "lethean" +const configFileName = "config.json" + +// ErrSetupRequired is returned if the config file is missing and cannot be created. +var ErrSetupRequired = errors.New("setup required: config.json not found") + +// Options holds configuration for the config service. +type Options struct{} + +// Service provides access to the application's configuration. +// It handles loading, saving, and providing access to configuration values. +type Service struct { + *core.Runtime[Options] `json:"-"` + + // Non-persistent fields, derived at runtime. + ConfigPath string `json:"-"` + UserHomeDir string `json:"-"` + RootDir string `json:"-"` + CacheDir string `json:"-"` + ConfigDir string `json:"-"` + DataDir string `json:"-"` + WorkspacesDir string `json:"-"` + + // Persistent fields, saved to config.json. + DefaultRoute string `json:"default_route"` + Features []string `json:"features"` + Language string `json:"language"` +} + +// New is a factory function that creates and initializes a new configuration service. +// It loads an existing configuration or creates a default one if not found. +func New(c *core.Core) (any, error) { + // --- Path and Directory Setup --- + homeDir, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("could not resolve user home directory: %w", err) + } + userHomeDir := filepath.Join(homeDir, appName) + + rootDir, err := xdg.DataFile(appName) + if err != nil { + return nil, fmt.Errorf("could not resolve data directory: %w", err) + } + + cacheDir, err := xdg.CacheFile(appName) + if err != nil { + return nil, fmt.Errorf("could not resolve cache directory: %w", err) + } + + s := &Service{ + Runtime: core.NewRuntime(c, Options{}), + UserHomeDir: userHomeDir, + RootDir: rootDir, + CacheDir: cacheDir, + ConfigDir: filepath.Join(userHomeDir, "config"), + DataDir: filepath.Join(userHomeDir, "data"), + WorkspacesDir: filepath.Join(userHomeDir, "workspaces"), + DefaultRoute: "/", + Features: []string{}, + Language: "en", + } + s.ConfigPath = filepath.Join(s.ConfigDir, configFileName) + + dirs := []string{s.RootDir, s.ConfigDir, s.DataDir, s.CacheDir, s.WorkspacesDir, s.UserHomeDir} + for _, dir := range dirs { + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return nil, fmt.Errorf("could not create directory %s: %w", dir, err) + } + } + + // --- Load or Create Configuration --- + if data, err := os.ReadFile(s.ConfigPath); err == nil { + // Config file exists, load it. + if err := json.Unmarshal(data, s); err != nil { + return nil, fmt.Errorf("failed to unmarshal config: %w", err) + } + } else if os.IsNotExist(err) { + // Config file does not exist, create it with default values. + if err := s.Save(); err != nil { + return nil, fmt.Errorf("failed to create default config file: %w", err) + } + } else { + // Another error occurred reading the file. + return nil, fmt.Errorf("failed to read config file: %w", err) + } + + c.RegisterAction(s.handleIPCEvents) + return s, nil +} + +// handleIPCEvents is the central IPC handler for the config service. +func (s *Service) handleIPCEvents(c *core.Core, msg core.Message) error { + switch msg.(type) { + case core.ActionServiceStartup: + c.App.Logger.Info("Config service started") + default: + // No other actions are handled by this service yet. + } + return nil +} + +// Save writes the current configuration to config.json. +func (s *Service) Save() error { + data, err := json.MarshalIndent(s, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal config: %w", err) + } + + if err := os.WriteFile(s.ConfigPath, data, 0644); err != nil { + return fmt.Errorf("failed to write config file: %w", err) + } + return nil +} + +// IsFeatureEnabled checks if a given feature is enabled in the configuration. +func (s *Service) IsFeatureEnabled(feature string) bool { + for _, f := range s.Features { + if f == feature { + return true + } + } + return false +} + +// EnableFeature adds a feature to the list of enabled features and saves the config. +func (s *Service) EnableFeature(feature string) error { + if s.IsFeatureEnabled(feature) { + return nil + } + s.Features = append(s.Features, feature) + if err := s.Save(); err != nil { + return fmt.Errorf("failed to save config after enabling feature %s: %w", feature, err) + } + return nil +} + +func (s *Service) Key(key string) (interface{}, error) { + // Use reflection to inspect the struct fields. + val := reflect.ValueOf(s).Elem() + typ := val.Type() + + for i := 0; i < val.NumField(); i++ { + field := typ.Field(i) + fieldName := field.Name + + // Check the field name first. + if strings.EqualFold(fieldName, key) { + return val.Field(i).Interface(), nil + } + + // Then check the `json` tag. + jsonTag := field.Tag.Get("json") + if jsonTag != "" && jsonTag != "-" { + jsonName := strings.Split(jsonTag, ",")[0] + if strings.EqualFold(jsonName, key) { + return val.Field(i).Interface(), nil + } + } + } + + return nil, fmt.Errorf("key '%s' not found in config", key) +} diff --git a/pkg/core/config/config_test.go b/pkg/core/config/config_test.go new file mode 100644 index 0000000..f2fc12d --- /dev/null +++ b/pkg/core/config/config_test.go @@ -0,0 +1,146 @@ +package config + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/Snider/Core" +) + +// setupTestEnv creates a temporary home directory for testing and ensures a clean environment. +func setupTestEnv(t *testing.T) (string, func()) { + tempHomeDir, err := os.MkdirTemp("", "test_home_*") + if err != nil { + t.Fatalf("Failed to create temp home directory: %v", err) + } + + oldHome := os.Getenv("HOME") + os.Setenv("HOME", tempHomeDir) + + // Unset XDG vars to ensure HOME is used for path resolution, creating a hermetic test. + oldXdgData := os.Getenv("XDG_DATA_HOME") + oldXdgCache := os.Getenv("XDG_CACHE_HOME") + os.Unsetenv("XDG_DATA_HOME") + os.Unsetenv("XDG_CACHE_HOME") + + cleanup := func() { + os.Setenv("HOME", oldHome) + os.Setenv("XDG_DATA_HOME", oldXdgData) + os.Setenv("XDG_CACHE_HOME", oldXdgCache) + os.RemoveAll(tempHomeDir) + } + + return tempHomeDir, cleanup +} + +// newTestCore creates a new, empty core instance for testing. +func newTestCore(t *testing.T) *core.Core { + c := core.New() + if c == nil { + t.Fatalf("core.New() returned a nil instance") + } + return c +} + +func TestConfigService(t *testing.T) { + t.Run("New service creates default config", func(t *testing.T) { + _, cleanup := setupTestEnv(t) + defer cleanup() + + c := newTestCore(t) + serviceInstance, err := New(c) + if err != nil { + t.Fatalf("New() failed: %v", err) + } + s, ok := serviceInstance.(*Service) + if !ok { + t.Fatalf("Service instance is not of type *Service") + } + + // Check that the config file was created + if _, err := os.Stat(s.ConfigPath); os.IsNotExist(err) { + t.Errorf("config.json was not created at %s", s.ConfigPath) + } + + // Check default values + if s.Language != "en" { + t.Errorf("Expected default language 'en', got '%s'", s.Language) + } + }) + + t.Run("New service loads existing config", func(t *testing.T) { + tempHomeDir, cleanup := setupTestEnv(t) + defer cleanup() + + // Manually create a config file with non-default values + configDir := filepath.Join(tempHomeDir, appName, "config") + if err := os.MkdirAll(configDir, os.ModePerm); err != nil { + t.Fatalf("Failed to create test config dir: %v", err) + } + configPath := filepath.Join(configDir, configFileName) + + customConfig := `{"language": "fr", "features": ["beta-testing"]}` + if err := os.WriteFile(configPath, []byte(customConfig), 0644); err != nil { + t.Fatalf("Failed to write custom config file: %v", err) + } + + c := newTestCore(t) + serviceInstance, err := New(c) + if err != nil { + t.Fatalf("New() failed while loading existing config: %v", err) + } + s, ok := serviceInstance.(*Service) + if !ok { + t.Fatalf("Service instance is not of type *Service") + } + + if s.Language != "fr" { + t.Errorf("Expected language 'fr', got '%s'", s.Language) + } + if !s.IsFeatureEnabled("beta-testing") { + t.Errorf("Expected 'beta-testing' feature to be enabled") + } + }) + + t.Run("EnableFeature and Save", func(t *testing.T) { + _, cleanup := setupTestEnv(t) + defer cleanup() + + c := newTestCore(t) + serviceInstance, err := New(c) + if err != nil { + t.Fatalf("New() failed: %v", err) + } + s, ok := serviceInstance.(*Service) + if !ok { + t.Fatalf("Service instance is not of type *Service") + } + + if err := s.EnableFeature("new-feature"); err != nil { + t.Fatalf("EnableFeature() failed: %v", err) + } + + data, err := os.ReadFile(s.ConfigPath) + if err != nil { + t.Fatalf("Failed to read config file: %v", err) + } + + var onDiskService Service + if err := json.Unmarshal(data, &onDiskService); err != nil { + t.Fatalf("Failed to unmarshal saved config: %v", err) + } + + found := false + for _, f := range onDiskService.Features { + if f == "new-feature" { + found = true + break + } + } + if !found { + t.Errorf("Enabled feature 'new-feature' was not saved to disk") + } + }) +} diff --git a/pkg/core/core.go b/pkg/core/core.go new file mode 100644 index 0000000..d18e07d --- /dev/null +++ b/pkg/core/core.go @@ -0,0 +1,190 @@ +package core + +import ( + "context" + "embed" + "errors" + "fmt" + "reflect" + "strings" + "sync" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +// --- Core Structs & Types --- + +type Contract struct { + DontPanic bool + DisableLogging bool +} +type Option func(*Core) error +type Message interface{} + +type Core struct { + once sync.Once + initErr error + App *application.App + assets embed.FS + serviceLock bool + ipcMu sync.RWMutex + ipcHandlers []func(*Core, Message) error + serviceMu sync.RWMutex + services map[string]any + servicesLocked bool +} + +var instance *Core + +// New initialises a Core instance using the provided options and performs the necessary setup. +func New(opts ...Option) *Core { + c := &Core{ + services: make(map[string]any), + } + for _, o := range opts { + if err := o(c); err != nil { + return nil + } + } + c.once.Do(func() { + c.initErr = nil + }) + if c.initErr != nil { + return nil + } + if c.serviceLock { + c.servicesLocked = true + } + return c +} + +// WithService creates an Option that registers a service. It automatically discovers +// the service name from its package path and registers its IPC handler if it +// implements a method named `HandleIPCEvents`. +func WithService(factory func(*Core) (any, error)) Option { + return func(c *Core) error { + serviceInstance, err := factory(c) + if err != nil { + return fmt.Errorf("core: failed to create service: %w", err) + } + + // --- Service Name Discovery --- + typeOfService := reflect.TypeOf(serviceInstance) + if typeOfService.Kind() == reflect.Ptr { + typeOfService = typeOfService.Elem() + } + pkgPath := typeOfService.PkgPath() + parts := strings.Split(pkgPath, "/") + name := parts[len(parts)-1] + + // --- IPC Handler Discovery --- + instanceValue := reflect.ValueOf(serviceInstance) + handlerMethod := instanceValue.MethodByName("HandleIPCEvents") + if handlerMethod.IsValid() { + if handler, ok := handlerMethod.Interface().(func(*Core, Message) error); ok { + c.RegisterAction(handler) + } + } + + return c.RegisterService(name, serviceInstance) + } +} + +func WithWails(app *application.App) Option { + return func(c *Core) error { + c.App = app + return nil + } +} + +func WithAssets(fs embed.FS) Option { + return func(c *Core) error { + c.assets = fs + return nil + } +} + +func WithServiceLock() Option { + return func(c *Core) error { + c.serviceLock = true + return nil + } +} + +// --- Core Methods --- + +func (c *Core) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + return c.ACTION(ActionServiceStartup{}) +} + +func (c *Core) ACTION(msg Message) error { + c.ipcMu.RLock() + handlers := append([]func(*Core, Message) error(nil), c.ipcHandlers...) + c.ipcMu.RUnlock() + + var agg error + for _, h := range handlers { + if err := h(c, msg); err != nil { + agg = fmt.Errorf("%w; %v", agg, err) + } + } + return agg +} + +func (c *Core) RegisterAction(handler func(*Core, Message) error) { + c.ipcMu.Lock() + c.ipcHandlers = append(c.ipcHandlers, handler) + c.ipcMu.Unlock() +} + +func (c *Core) RegisterActions(handlers ...func(*Core, Message) error) { + c.ipcMu.Lock() + c.ipcHandlers = append(c.ipcHandlers, handlers...) + c.ipcMu.Unlock() +} + +func (c *Core) RegisterService(name string, api any) error { + if c.servicesLocked { + return fmt.Errorf("core: service %q is not permitted by the serviceLock setting", name) + } + if name == "" { + return errors.New("core: service name cannot be empty") + } + c.serviceMu.Lock() + defer c.serviceMu.Unlock() + if _, exists := c.services[name]; exists { + return fmt.Errorf("core: service %q already registered", name) + } + c.services[name] = api + return nil +} + +func (c *Core) Service(name string) any { + c.serviceMu.RLock() + api, ok := c.services[name] + c.serviceMu.RUnlock() + if !ok { + return nil + } + return api +} + +func ServiceFor[T any](c *Core, name string) *T { + raw := c.Service(name) + typed, ok := raw.(*T) + if !ok { + return nil + } + return typed +} + +// App returns the global application instance. +func App() *application.App { + app := ServiceFor[application.App](instance, "App") + if instance == nil || app == nil { + panic("core.App() called before core.Setup() was successfully initialized") + } + return app +} + +func (c *Core) Core() *Core { return c } diff --git a/pkg/core/crypt/crypt.go b/pkg/core/crypt/crypt.go new file mode 100644 index 0000000..fb6aa4e --- /dev/null +++ b/pkg/core/crypt/crypt.go @@ -0,0 +1,76 @@ +package crypt + +import ( + "context" + "fmt" + "io" + + "github.com/Snider/Core" + "github.com/Snider/Core/crypt/openpgp" + "github.com/wailsapp/wails/v3/pkg/application" +) + +// Options holds configuration for the crypt service. +type Options struct{} + +// Service provides cryptographic functions to the application. +type Service struct { + *core.Runtime[Options] +} + +// HashType defines the supported hashing algorithms. +type HashType string + +const ( + LTHN HashType = "lthn" + SHA512 HashType = "sha512" + SHA256 HashType = "sha256" + SHA1 HashType = "sha1" + MD5 HashType = "md5" +) + +// Service provides cryptographic functions. +// It is the main entry point for all cryptographic operations +// and is bound to the frontend. + +type API struct { + core *core.Core +} + +// New is a factory function that creates a new crypt Service. +func New(c *core.Core) (any, error) { + s := &Service{ + Runtime: core.NewRuntime(c, Options{}), + } + return s, nil +} + +// handleIPCEvents is the central IPC handler for the crypt service. +func (s *Service) handleIPCEvents(c *core.Core, msg core.Message) error { + switch msg.(type) { + case core.ActionServiceStartup: + return s.ServiceStartup(context.Background(), application.ServiceOptions{}) + default: + c.App.Logger.Error("Crypt: Unknown message type", "type", fmt.Sprintf("%T", msg)) + } + return nil +} + +// ServiceStartup is called when the app starts. It handles one-time cryptographic setup. +func (s *Service) ServiceStartup(ctx context.Context, options application.ServiceOptions) error { + s.Core().App.Logger.Info("Crypt service started") + // Key generation logic will be implemented here, likely depending on the config service. + return nil +} + +// EncryptPGP encrypts data for a recipient, optionally signing it. +// It acts as a wrapper around the underlying openpgp library function. +func (s *Service) EncryptPGP(writer io.Writer, recipientPath, data string, signerPath, signerPassphrase *string) (string, error) { + return openpgp.EncryptPGP(writer, recipientPath, data, signerPath, signerPassphrase) +} + +// DecryptPGP decrypts a PGP message, optionally verifying the signature. +// It acts as a wrapper around the underlying openpgp library function. +func (s *Service) DecryptPGP(recipientPath, message, passphrase string, signerPath *string) (string, error) { + return openpgp.DecryptPGP(recipientPath, message, passphrase, signerPath) +} diff --git a/pkg/v1/core/crypt/crypt_test.go b/pkg/core/crypt/crypt_test.go similarity index 100% rename from pkg/v1/core/crypt/crypt_test.go rename to pkg/core/crypt/crypt_test.go diff --git a/pkg/v1/core/crypt/hash.go b/pkg/core/crypt/hash.go similarity index 94% rename from pkg/v1/core/crypt/hash.go rename to pkg/core/crypt/hash.go index 55f7e86..f2a285a 100644 --- a/pkg/v1/core/crypt/hash.go +++ b/pkg/core/crypt/hash.go @@ -7,7 +7,7 @@ import ( "crypto/sha512" "encoding/hex" - "github.com/Snider/Core/crypt/lib/lthn" + "github.com/Snider/Core/crypt/lthn" ) // Hash computes a hash of the payload using the specified algorithm. diff --git a/pkg/v1/core/crypt/lib/lthn/hash_test.go b/pkg/core/crypt/lthn/hash_test.go similarity index 100% rename from pkg/v1/core/crypt/lib/lthn/hash_test.go rename to pkg/core/crypt/lthn/hash_test.go diff --git a/pkg/v1/core/crypt/lib/lthn/hash.go b/pkg/core/crypt/lthn/lthn.go similarity index 81% rename from pkg/v1/core/crypt/lib/lthn/hash.go rename to pkg/core/crypt/lthn/lthn.go index c9f0ac0..1b6c97d 100644 --- a/pkg/v1/core/crypt/lib/lthn/hash.go +++ b/pkg/core/crypt/lthn/lthn.go @@ -5,6 +5,21 @@ import ( "encoding/hex" ) +// keyMap is the default character-swapping map used for the quasi-salting process. +var keyMap = map[rune]rune{ + 'o': '0', + 'l': '1', + 'e': '3', + 'a': '4', + 's': 'z', + 't': '7', + '0': 'o', + '1': 'l', + '3': 'e', + '4': 'a', + '7': 't', +} + // SetKeyMap sets the key map for the notarisation process. func SetKeyMap(newKeyMap map[rune]rune) { keyMap = newKeyMap diff --git a/pkg/v1/core/crypt/lib/openpgp/encrypt.go b/pkg/core/crypt/openpgp/encrypt.go similarity index 86% rename from pkg/v1/core/crypt/lib/openpgp/encrypt.go rename to pkg/core/crypt/openpgp/encrypt.go index 736d5bc..f8de72f 100644 --- a/pkg/v1/core/crypt/lib/openpgp/encrypt.go +++ b/pkg/core/crypt/openpgp/encrypt.go @@ -8,19 +8,18 @@ import ( "github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp/armor" - "github.com/Snider/Core/io" ) // EncryptPGP encrypts data for a recipient, optionally signing it. -func EncryptPGP(medium io.Medium, recipientPath, data string, signerPath, signerPassphrase *string) (string, error) { - recipient, err := GetPublicKey(medium, recipientPath) +func EncryptPGP(writer io.Writer, recipientPath, data string, signerPath, signerPassphrase *string) (string, error) { + recipient, err := GetPublicKey(recipientPath) if err != nil { return "", fmt.Errorf("failed to get recipient public key: %w", err) } var signer *openpgp.Entity if signerPath != nil && signerPassphrase != nil { - signer, err = GetPrivateKey(medium, *signerPath, *signerPassphrase) + signer, err = GetPrivateKey(*signerPath, *signerPassphrase) if err != nil { return "", fmt.Errorf("could not get private key for signing: %w", err) } @@ -55,8 +54,8 @@ func EncryptPGP(medium io.Medium, recipientPath, data string, signerPath, signer } // DecryptPGP decrypts a PGP message, optionally verifying the signature. -func DecryptPGP(medium io.Medium, recipientPath, message, passphrase string, signerPath *string) (string, error) { - privateKeyEntity, err := GetPrivateKey(medium, recipientPath, passphrase) +func DecryptPGP(recipientPath, message, passphrase string, signerPath *string) (string, error) { + privateKeyEntity, err := GetPrivateKey(recipientPath, passphrase) if err != nil { return "", fmt.Errorf("failed to get private key: %w", err) } @@ -66,7 +65,7 @@ func DecryptPGP(medium io.Medium, recipientPath, message, passphrase string, sig var expectedSigner *openpgp.Entity if signerPath != nil { - publicKeyEntity, err := GetPublicKey(medium, *signerPath) + publicKeyEntity, err := GetPublicKey(*signerPath) if err != nil { return "", fmt.Errorf("could not get public key for verification: %w", err) } diff --git a/pkg/core/crypt/openpgp/encrypt_test.go b/pkg/core/crypt/openpgp/encrypt_test.go new file mode 100644 index 0000000..b2021f2 --- /dev/null +++ b/pkg/core/crypt/openpgp/encrypt_test.go @@ -0,0 +1,155 @@ +package openpgp + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/ProtonMail/go-crypto/openpgp" + "github.com/ProtonMail/go-crypto/openpgp/armor" +) + +// generateTestKeys creates a new PGP entity and saves the public and private keys to temporary files. +func generateTestKeys(t *testing.T, name, passphrase string) (string, string, func()) { + t.Helper() + + tempDir, err := os.MkdirTemp("", "pgp-keys-*") + if err != nil { + t.Fatalf("Failed to create temp dir for keys: %v", err) + } + + entity, err := openpgp.NewEntity(name, "", name, nil) + if err != nil { + t.Fatalf("Failed to create new PGP entity: %v", err) + } + + // Encrypt the private key with the passphrase + if err := entity.PrivateKey.Encrypt([]byte(passphrase)); err != nil { + t.Fatalf("Failed to encrypt private key: %v", err) + } + + // --- Save Public Key --- + pubKeyPath := filepath.Join(tempDir, name+".pub") + pubKeyFile, err := os.Create(pubKeyPath) + if err != nil { + t.Fatalf("Failed to create public key file: %v", err) + } + w, err := armor.Encode(pubKeyFile, openpgp.PublicKeyType, nil) + if err != nil { + t.Fatalf("Failed to create armored writer for public key: %v", err) + } + if err := entity.Serialize(w); err != nil { + t.Fatalf("Failed to serialize public key: %v", err) + } + w.Close() + pubKeyFile.Close() + + // --- Save Private Key --- + privKeyPath := filepath.Join(tempDir, name+".asc") + privKeyFile, err := os.Create(privKeyPath) + if err != nil { + t.Fatalf("Failed to create private key file: %v", err) + } + w, err = armor.Encode(privKeyFile, openpgp.PrivateKeyType, nil) + if err != nil { + t.Fatalf("Failed to create armored writer for private key: %v", err) + } + if err := entity.SerializePrivate(w, nil); err != nil { + t.Fatalf("Failed to serialize private key: %v", err) + } + w.Close() + privKeyFile.Close() + + cleanup := func() { os.RemoveAll(tempDir) } + return pubKeyPath, privKeyPath, cleanup +} + +func TestEncryptDecryptPGP(t *testing.T) { + recipientPub, recipientPriv, cleanup := generateTestKeys(t, "recipient", "recipient-pass") + defer cleanup() + + originalMessage := "This is a secret message." + + // --- Test Encryption --- + var encryptedBuf bytes.Buffer + encryptedMessage, err := EncryptPGP(&encryptedBuf, recipientPub, originalMessage, nil, nil) + if err != nil { + t.Fatalf("EncryptPGP() failed: %v", err) + } + + if !strings.Contains(encryptedMessage, "-----BEGIN PGP MESSAGE-----") { + t.Errorf("Encrypted message does not appear to be PGP armored") + } + + // --- Test Decryption --- + decryptedMessage, err := DecryptPGP(recipientPriv, encryptedMessage, "recipient-pass", nil) + if err != nil { + t.Fatalf("DecryptPGP() failed: %v", err) + } + + if decryptedMessage != originalMessage { + t.Errorf("Decrypted message does not match original. got=%q, want=%q", decryptedMessage, originalMessage) + } +} + +func TestSignAndVerifyPGP(t *testing.T) { + recipientPub, recipientPriv, rCleanup := generateTestKeys(t, "recipient", "recipient-pass") + defer rCleanup() + + signerPub, signerPriv, sCleanup := generateTestKeys(t, "signer", "signer-pass") + defer sCleanup() + + originalMessage := "This is a signed and verified message." + + // --- Encrypt and Sign --- + var encryptedBuf bytes.Buffer + signerPass := "signer-pass" + encryptedMessage, err := EncryptPGP(&encryptedBuf, recipientPub, originalMessage, &signerPriv, &signerPass) + if err != nil { + t.Fatalf("EncryptPGP() with signing failed: %v", err) + } + + // --- Decrypt and Verify --- + decryptedMessage, err := DecryptPGP(recipientPriv, encryptedMessage, "recipient-pass", &signerPub) + if err != nil { + t.Fatalf("DecryptPGP() with verification failed: %v", err) + } + + if decryptedMessage != originalMessage { + t.Errorf("Decrypted message does not match original. got=%q, want=%q", decryptedMessage, originalMessage) + } +} + +func TestVerificationFailure(t *testing.T) { + recipientPub, recipientPriv, rCleanup := generateTestKeys(t, "recipient", "recipient-pass") + defer rCleanup() + + _, signerPriv, sCleanup := generateTestKeys(t, "signer", "signer-pass") + defer sCleanup() + + // Generate a third, unexpected key to test verification failure + unexpectedSignerPub, _, uCleanup := generateTestKeys(t, "unexpected", "unexpected-pass") + defer uCleanup() + + originalMessage := "This message should fail verification." + + // --- Encrypt and Sign with the actual signer key --- + var encryptedBuf bytes.Buffer + signerPass := "signer-pass" + encryptedMessage, err := EncryptPGP(&encryptedBuf, recipientPub, originalMessage, &signerPriv, &signerPass) + if err != nil { + t.Fatalf("EncryptPGP() with signing failed: %v", err) + } + + // --- Attempt to Decrypt and Verify with the WRONG public key --- + _, err = DecryptPGP(recipientPriv, encryptedMessage, "recipient-pass", &unexpectedSignerPub) + if err == nil { + t.Fatal("DecryptPGP() did not fail, but verification with an incorrect key was expected to fail.") + } + + if !strings.Contains(err.Error(), "signature from unexpected key") { + t.Errorf("Expected error to contain 'signature from unexpected key', but got: %v", err) + } +} diff --git a/pkg/v1/core/crypt/lib/openpgp/key.go b/pkg/core/crypt/openpgp/key.go similarity index 77% rename from pkg/v1/core/crypt/lib/openpgp/key.go rename to pkg/core/crypt/openpgp/key.go index 89b61cf..4ccd3c2 100644 --- a/pkg/v1/core/crypt/lib/openpgp/key.go +++ b/pkg/core/crypt/openpgp/key.go @@ -5,14 +5,12 @@ import ( "crypto" "fmt" "path/filepath" - "strings" "time" "github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp/armor" "github.com/ProtonMail/go-crypto/openpgp/packet" - "github.com/Snider/Core/crypt/lib/lthn" - "github.com/Snider/Core/io" + "github.com/Snider/Core/crypt/lthn" ) // CreateKeyPair generates a new OpenPGP key pair. @@ -72,13 +70,13 @@ func CreateServerKeyPair(keysDir string) error { } // GetPublicKey retrieves an armored public key for a given ID. -func GetPublicKey(medium io.Medium, path string) (*openpgp.Entity, error) { - return readEntity(medium, path) +func GetPublicKey(path string) (*openpgp.Entity, error) { + return readEntity(path) } // GetPrivateKey retrieves and decrypts an armored private key. -func GetPrivateKey(medium io.Medium, path, passphrase string) (*openpgp.Entity, error) { - entity, err := readEntity(medium, path) +func GetPrivateKey(path, passphrase string) (*openpgp.Entity, error) { + entity, err := readEntity(path) if err != nil { return nil, err } @@ -123,54 +121,55 @@ func GetPrivateKey(medium io.Medium, path, passphrase string) (*openpgp.Entity, // --- Helper Functions --- func createAndStoreKeyPair(id, password, dir string) error { - var keyPair *KeyPair + //var keyPair *KeyPair var err error - if password != "" { - keyPair, err = CreateKeyPair(id, password) - } else { - keyPair, err = CreateKeyPair(id) - } + //if password != "" { + // keyPair, err = CreateKeyPair(id, password) + //} else { + // keyPair, err = CreateKeyPair(id) + //} if err != nil { return fmt.Errorf("failed to create key pair for id %s: %w", id, err) } - if err := io.Local.EnsureDir(dir); err != nil { - return fmt.Errorf("failed to ensure key directory exists: %w", err) - } - - files := map[string]string{ - filepath.Join(dir, fmt.Sprintf("%s.lthn.pub", id)): keyPair.PublicKey, - filepath.Join(dir, fmt.Sprintf("%s.lthn.key", id)): keyPair.PrivateKey, - filepath.Join(dir, fmt.Sprintf("%s.lthn.rev", id)): keyPair.RevocationCertificate, // Re-enabled - } - - for path, content := range files { - if content == "" { - continue - } - if err := io.Local.Write(path, content); err != nil { - return fmt.Errorf("failed to write key file %s: %w", path, err) - } - } + //if err := io.Local.EnsureDir(dir); err != nil { + // return fmt.Errorf("failed to ensure key directory exists: %w", err) + //} + // + //files := map[string]string{ + // filepath.Join(dir, fmt.Sprintf("%s.lthn.pub", id)): keyPair.PublicKey, + // filepath.Join(dir, fmt.Sprintf("%s.lthn.key", id)): keyPair.PrivateKey, + // filepath.Join(dir, fmt.Sprintf("%s.lthn.rev", id)): keyPair.RevocationCertificate, // Re-enabled + //} + // + //for path, content := range files { + // if content == "" { + // continue + // } + // if err := io.Local.Write(path, content); err != nil { + // return fmt.Errorf("failed to write key file %s: %w", path, err) + // } + //} return nil } -func readEntity(m io.Medium, path string) (*openpgp.Entity, error) { - keyArmored, err := m.Read(path) - if err != nil { - return nil, fmt.Errorf("failed to read key file %s: %w", path, err) - } +func readEntity(path string) (*openpgp.Entity, error) { + //keyArmored, err := m.Read(path) + //if err != nil { + // return nil, fmt.Errorf("failed to read key file %s: %w", path, err) + //} - entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(keyArmored)) - if err != nil { - return nil, fmt.Errorf("failed to parse key file %s: %w", path, err) - } - if len(entityList) == 0 { - return nil, fmt.Errorf("no entity found in key file %s", path) - } - return entityList[0], nil + //entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(keyArmored)) + //if err != nil { + // return nil, fmt.Errorf("failed to parse key file %s: %w", path, err) + //} + //if len(entityList) == 0 { + // return nil, fmt.Errorf("no entity found in key file %s", path) + //} + //return entityList[0], nil + return nil, nil } func serializeEntity(entity *openpgp.Entity, keyType string, password string) (string, error) { diff --git a/pkg/v1/core/crypt/lib/openpgp/openpgp.go b/pkg/core/crypt/openpgp/openpgp.go similarity index 100% rename from pkg/v1/core/crypt/lib/openpgp/openpgp.go rename to pkg/core/crypt/openpgp/openpgp.go diff --git a/pkg/v1/core/crypt/lib/openpgp/sign.go b/pkg/core/crypt/openpgp/sign.go similarity index 72% rename from pkg/v1/core/crypt/lib/openpgp/sign.go rename to pkg/core/crypt/openpgp/sign.go index 1314ce0..a853350 100644 --- a/pkg/v1/core/crypt/lib/openpgp/sign.go +++ b/pkg/core/crypt/openpgp/sign.go @@ -6,12 +6,11 @@ import ( "strings" "github.com/ProtonMail/go-crypto/openpgp" - "github.com/Snider/Core/io" ) // Sign creates a detached signature for the data. -func Sign(medium io.Medium, data, privateKeyPath, passphrase string) (string, error) { - signer, err := GetPrivateKey(medium, privateKeyPath, passphrase) +func Sign(data, privateKeyPath, passphrase string) (string, error) { + signer, err := GetPrivateKey(privateKeyPath, passphrase) if err != nil { return "", fmt.Errorf("failed to get private key for signing: %w", err) } @@ -25,8 +24,8 @@ func Sign(medium io.Medium, data, privateKeyPath, passphrase string) (string, er } // Verify checks a detached signature. -func Verify(medium io.Medium, data, signature, publicKeyPath string) (bool, error) { - keyring, err := GetPublicKey(medium, publicKeyPath) +func Verify(data, signature, publicKeyPath string) (bool, error) { + keyring, err := GetPublicKey(publicKeyPath) if err != nil { return false, fmt.Errorf("failed to get public key for verification: %w", err) } diff --git a/pkg/v1/core/crypt/sum.go b/pkg/core/crypt/sum.go similarity index 100% rename from pkg/v1/core/crypt/sum.go rename to pkg/core/crypt/sum.go diff --git a/pkg/core/display/actions.go b/pkg/core/display/actions.go new file mode 100644 index 0000000..ba2d8fd --- /dev/null +++ b/pkg/core/display/actions.go @@ -0,0 +1,8 @@ +package display + +import "github.com/wailsapp/wails/v3/pkg/application" + +// ActionOpenWindow is an IPC message used to request a new window. +type ActionOpenWindow struct { + application.WebviewWindowOptions +} diff --git a/pkg/core/display/display.go b/pkg/core/display/display.go new file mode 100644 index 0000000..33b2505 --- /dev/null +++ b/pkg/core/display/display.go @@ -0,0 +1,127 @@ +package display + +import ( + "context" + "fmt" + + "github.com/Snider/Core" + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/events" +) + +// Options holds configuration for the display service. +type Options struct{} + +// Service manages windowing, dialogs, and other visual elements. +type Service struct { + *core.Runtime[Options] +} + +// New is a factory function that creates a new display Service. +func New(c *core.Core) (any, error) { + return &Service{ + Runtime: core.NewRuntime(c, Options{}), + }, nil +} + +func (s *Service) ServiceName() string { return "github.com/Snider/Core/display" } + +// HandleIPCEvents processes IPC messages and performs actions such as opening windows or initializing services based on message types. +func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error { + switch m := msg.(type) { + case map[string]any: + if action, ok := m["action"].(string); ok && action == "display.open_window" { + return s.handleOpenWindowAction(m) + } + case ActionOpenWindow: + _, err := s.NewWithStruct(&m.WebviewWindowOptions) + return err + case core.ActionServiceStartup: + return s.ServiceStartup(context.Background(), application.ServiceOptions{}) + default: + c.App.Logger.Error("Display: Unknown message type", "type", fmt.Sprintf("%T", m)) + } + return nil +} + +// handleOpenWindowAction processes a message to configure and create a new window using specified name and options. +func (s *Service) handleOpenWindowAction(msg map[string]any) error { + opts := application.WebviewWindowOptions{} + if name, ok := msg["name"].(string); ok { + opts.Name = name + } + if optsMap, ok := msg["options"].(map[string]any); ok { + if title, ok := optsMap["Title"].(string); ok { + opts.Title = title + } + if width, ok := optsMap["Width"].(float64); ok { + opts.Width = int(width) + } + if height, ok := optsMap["Height"].(float64); ok { + opts.Height = int(height) + } + } + s.Core().App.Window.NewWithOptions(opts) + return nil +} + +// ShowEnvironmentDialog displays a dialog containing detailed information about the application's runtime environment. +func (s *Service) ShowEnvironmentDialog() { + envInfo := s.Core().App.Env.Info() + + details := fmt.Sprintf(`Environment Information: + +Operating System: %s +Architecture: %s +Debug Mode: %t + +Dark Mode: %t + +Platform Information:`, + envInfo.OS, + envInfo.Arch, + envInfo.Debug, + s.Core().App.Env.IsDarkMode()) // Use d.core.App + + // Add platform-specific details + for key, value := range envInfo.PlatformInfo { + details += fmt.Sprintf("\n%s: %v", key, value) + } + + if envInfo.OSInfo != nil { + details += fmt.Sprintf("\n\nOS Details:\nName: %s\nVersion: %s", + envInfo.OSInfo.Name, + envInfo.OSInfo.Version) + } + + dialog := s.Core().App.Dialog.Info() + dialog.SetTitle("Environment Information") + dialog.SetMessage(details) + dialog.Show() +} + +// ServiceStartup initializes the display service and sets up the main application window and system tray. +func (s *Service) ServiceStartup(context.Context, application.ServiceOptions) error { + s.Core().App.Logger.Info("Display service started") + s.buildMenu() + s.systemTray() + + // This will be updated to use the restored OpenWindow method + mainOpts := application.WebviewWindowOptions{ + Name: "main", + Title: "Core", + Height: 900, + Width: 1280, + URL: "/", + } + s.Core().App.Window.NewWithOptions(mainOpts) + + return nil +} + +// monitorScreenChanges listens for theme change events and logs when screen configuration changes occur. +func (s *Service) monitorScreenChanges() { + s.Core().App.Event.OnApplicationEvent(events.Common.ThemeChanged, func(event *application.ApplicationEvent) { + s.Core().App.Logger.Info("Screen configuration changed") + }) +} diff --git a/pkg/v1/core/display/menu.go b/pkg/core/display/menu.go similarity index 87% rename from pkg/v1/core/display/menu.go rename to pkg/core/display/menu.go index b01cd2b..63bb0d1 100644 --- a/pkg/v1/core/display/menu.go +++ b/pkg/core/display/menu.go @@ -7,8 +7,8 @@ import ( ) // buildMenu creates and sets the main application menu. -func (d *API) buildMenu() { - appMenu := d.core.App.Menu.New() +func (s *Service) buildMenu() { + appMenu := s.Core().App.Menu.New() if runtime.GOOS == "darwin" { appMenu.AddRole(application.AppMenu) } @@ -28,5 +28,5 @@ func (d *API) buildMenu() { appMenu.AddRole(application.WindowMenu) appMenu.AddRole(application.HelpMenu) - d.core.App.Menu.Set(appMenu) + s.Core().App.Menu.Set(appMenu) } diff --git a/pkg/v1/core/display/tray.go b/pkg/core/display/tray.go similarity index 85% rename from pkg/v1/core/display/tray.go rename to pkg/core/display/tray.go index 2f048d7..b96f2ef 100644 --- a/pkg/v1/core/display/tray.go +++ b/pkg/core/display/tray.go @@ -7,9 +7,9 @@ import ( ) // setupTray configures and creates the system tray icon and menu. -func (d *API) systemTray() { +func (s *Service) systemTray() { - systray := d.core.App.SystemTray.New() + systray := s.Core().App.SystemTray.New() systray.SetTooltip("Core") systray.SetLabel("Core") //appTrayIcon, _ := d.assets.ReadFile("assets/apptray.png") @@ -22,7 +22,7 @@ func (d *API) systemTray() { // systray.SetIcon(appTrayIcon) //} // Create a hidden window for the system tray menu to interact with - trayWindow := d.NewWithStruct(&Window{ + trayWindow, _ := s.NewWithStruct(&Window{ Name: "system-tray", Title: "System Tray Status", URL: "system-tray.html", @@ -33,20 +33,20 @@ func (d *API) systemTray() { systray.AttachWindow(trayWindow).WindowOffset(5) // --- Build Tray Menu --- - trayMenu := d.core.App.Menu.New() + trayMenu := s.Core().App.Menu.New() trayMenu.Add("Open Desktop").OnClick(func(ctx *application.Context) { - for _, window := range d.core.App.Window.GetAll() { + for _, window := range s.Core().App.Window.GetAll() { window.Show() } }) trayMenu.Add("Close Desktop").OnClick(func(ctx *application.Context) { - for _, window := range d.core.App.Window.GetAll() { + for _, window := range s.Core().App.Window.GetAll() { window.Hide() } }) trayMenu.Add("Environment Info").OnClick(func(ctx *application.Context) { - d.ShowEnvironmentDialog() + s.ShowEnvironmentDialog() }) // Add brand-specific menu items //switch d.brand { @@ -65,7 +65,7 @@ func (d *API) systemTray() { trayMenu.AddSeparator() trayMenu.Add("Quit").OnClick(func(ctx *application.Context) { - d.core.App.Quit() + s.Core().App.Quit() }) systray.SetMenu(trayMenu) diff --git a/pkg/core/display/window.go b/pkg/core/display/window.go new file mode 100644 index 0000000..4859821 --- /dev/null +++ b/pkg/core/display/window.go @@ -0,0 +1,93 @@ +package display + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +type WindowOption func(*application.WebviewWindowOptions) error + +type Window = application.WebviewWindowOptions + +func WindowName(s string) WindowOption { + return func(o *Window) error { + o.Name = s + return nil + } +} +func WindowTitle(s string) WindowOption { + return func(o *Window) error { + o.Title = s + return nil + } +} + +func WindowURL(s string) WindowOption { + return func(o *Window) error { + o.URL = s + return nil + } +} + +func WindowWidth(i int) WindowOption { + return func(o *Window) error { + o.Width = i + return nil + } +} + +func WindowHeight(i int) WindowOption { + return func(o *Window) error { + o.Height = i + return nil + } +} + +func applyOptions(opts ...WindowOption) *Window { + w := &Window{} + if opts == nil { + return w + } + for _, o := range opts { + if err := o(w); err != nil { + return nil + } + } + return w +} + +// NewWithStruct creates a new window using the provided options and returns its handle. +func (s *Service) NewWithStruct(options *Window) (*application.WebviewWindow, error) { + return s.Core().App.Window.NewWithOptions(*options), nil +} + +// NewWithOptions creates a new window by applying a series of options. +func (s *Service) NewWithOptions(opts ...WindowOption) (*application.WebviewWindow, error) { + return s.NewWithStruct(applyOptions(opts...)) +} + +// NewWithURL creates a new default window pointing to the specified URL. +func (s *Service) NewWithURL(url string) (*application.WebviewWindow, error) { + return s.NewWithOptions( + WindowURL(url), + WindowTitle("Core"), + WindowHeight(900), + WindowWidth(1280), + ) +} + +// OpenWindow is a convenience method that creates and shows a window from a set of options. +func (s *Service) OpenWindow(opts ...WindowOption) error { + _, err := s.NewWithOptions(opts...) + return err +} + +// SelectDirectory opens a directory selection dialog and returns the selected path. +func (s *Service) SelectDirectory() (string, error) { + dialog := application.OpenFileDialog() + dialog.SetTitle("Select Project Directory") + return dialog.PromptForSingleSelection() +} + +var instance *Window + +func (s *Service) Window() *Window { return instance } diff --git a/pkg/core/docs/.gitignore b/pkg/core/docs/.gitignore deleted file mode 100644 index 16d3c4d..0000000 --- a/pkg/core/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.cache diff --git a/pkg/v1/core/docs/assets/stylesheets/extra.css b/pkg/core/docs/assets/stylesheets/extra.css similarity index 100% rename from pkg/v1/core/docs/assets/stylesheets/extra.css rename to pkg/core/docs/assets/stylesheets/extra.css diff --git a/pkg/core/docs/docs.go b/pkg/core/docs/docs.go new file mode 100644 index 0000000..aeab9b8 --- /dev/null +++ b/pkg/core/docs/docs.go @@ -0,0 +1,50 @@ +package docs + +import ( + "embed" + + "github.com/Snider/Core" +) + +//go:embed all:static/* +var docsStatic embed.FS + +// Options holds configuration for the doc service. +type Options struct{} + +// Service manages the documentation assets and display requests. +// It embeds the core.Runtime to get access to the core instance and its functions. +type Service struct { + *core.Runtime[Options] + assets embed.FS +} + +// New is a factory function that creates a new docs Service. +// It is self-contained and only depends on the Core, with no knowledge +// of other services at compile time. +func New(c *core.Core) (any, error) { + s := &Service{ + Runtime: core.NewRuntime(c, Options{}), + assets: docsStatic, + } + return s, nil +} + +// Show triggers the display of the documentation window. +func (s *Service) Show() error { + // The message is a generic map, which any service can create. The 'display' + // service will register a handler that knows how to interpret this structure. + msg := map[string]any{ + "action": "display.open_window", + "name": "docs", + "options": map[string]any{ + "Title": "Documentation", + "Width": 800, + "Height": 600, + }, + } + + // Dispatch the message through the core. The core will route it to the + // appropriate handler, in this case, the one registered by the display service. + return s.Core().ACTION(msg) +} diff --git a/pkg/v1/core/docs/mkdocs.yml b/pkg/core/docs/mkdocs.yml similarity index 100% rename from pkg/v1/core/docs/mkdocs.yml rename to pkg/core/docs/mkdocs.yml diff --git a/pkg/v1/core/docs/public/404.html b/pkg/core/docs/public/404.html similarity index 84% rename from pkg/v1/core/docs/public/404.html rename to pkg/core/docs/public/404.html index e4d7c2f..2531aca 100644 --- a/pkg/v1/core/docs/public/404.html +++ b/pkg/core/docs/public/404.html @@ -16,7 +16,7 @@ - Core Documentation + Core.Help @@ -31,13 +31,15 @@ + + - - + + @@ -79,7 +81,7 @@