feat(spa): port community sub-screens + drop PrimeVue dependency (Wave 7+8)

Wave 7: rewrite all community/* views (404, Detail, Discover, Offline,
Profile, Upload) to use src/manager/components/ui/* primitives + the local
useToast composable instead of PrimeVue Button/InputText/Select/Checkbox/
Textarea/useToast.

Wave 8: rename toast.add() → toast.push() at remaining list-screen
callsites (Combines, Constraints, Derivations, FixedValues, Pipelines,
Wildcards) and migrate the imports to ../composables/useToast. Drop the
PrimeVue plugin install from src/manager/main.ts, delete styles/preset.ts
and the now-empty styles/theme.css (legacy compat aliases inlined into
tokens.css), simplify tailwind.css (no more layer ordering), and remove
primevue, @primevue/themes, @primevue/forms from package.json. Strip
PrimeVue/ToastService/ConfirmationService plugin entries from all
__tests__ mounts.

Result: zero `from "primevue"` / `from "@primevue"` imports in src/.
338 vitest tests pass, 289 pytest tests pass, vue-tsc clean,
extension bundle within budget.
This commit is contained in:
DumiFlex
2026-04-27 19:55:27 +03:00
parent 1d13048982
commit e4ab2186e3
43 changed files with 284 additions and 608 deletions
+1 -4
View File
@@ -26,15 +26,12 @@
"vue": "^3.5.12",
"vue-router": "^4.4.5",
"pinia": "^2.2.6",
"primevue": "^4.2.5",
"primeicons": "^7.0.0",
"@primevue/themes": "^4.2.5"
"primeicons": "^7.0.0"
},
"devDependencies": {
"tailwindcss": "^3.4.14",
"postcss": "^8.4.47",
"autoprefixer": "^10.4.20",
"@primevue/forms": "^4.2.5",
"@comfyorg/comfyui-frontend-types": "^1.42.15",
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
-104
View File
@@ -8,18 +8,12 @@ importers:
.:
dependencies:
'@primevue/themes':
specifier: ^4.2.5
version: 4.5.4
pinia:
specifier: ^2.2.6
version: 2.3.1(typescript@5.9.3)(vue@3.5.33(typescript@5.9.3))
primeicons:
specifier: ^7.0.0
version: 7.0.0
primevue:
specifier: ^4.2.5
version: 4.5.5(vue@3.5.33(typescript@5.9.3))
vue:
specifier: ^3.5.12
version: 3.5.33(typescript@5.9.3)
@@ -36,9 +30,6 @@ importers:
'@commitlint/config-conventional':
specifier: ^19.5.0
version: 19.8.1
'@primevue/forms':
specifier: ^4.2.5
version: 4.5.5(vue@3.5.33(typescript@5.9.3))
'@semantic-release/changelog':
specifier: ^6.0.3
version: 6.0.3(semantic-release@25.0.3(typescript@5.9.3))
@@ -597,43 +588,6 @@ packages:
resolution: {integrity: sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==}
engines: {node: '>=12'}
'@primeuix/forms@0.1.0':
resolution: {integrity: sha512-LctcQidb+B5PuvAFWH24YH/SIzmHlOabLHpaTeGY/k51iBv1WyCp+5w9JMYuMB/BplSvV0ZGySxQVkN5Azr/aQ==}
engines: {node: '>=12.11.0'}
'@primeuix/styled@0.7.4':
resolution: {integrity: sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ==}
engines: {node: '>=12.11.0'}
'@primeuix/styles@2.0.3':
resolution: {integrity: sha512-2ykAB6BaHzR/6TwF8ShpJTsZrid6cVIEBVlookSdvOdmlWuevGu5vWOScgIwqWwlZcvkFYAGR/SUV3OHCTBMdw==}
'@primeuix/themes@2.0.3':
resolution: {integrity: sha512-3fS1883mtCWhgUgNf/feiaaDSOND4EBIOu9tZnzJlJ8QtYyL6eFLcA6V3ymCWqLVXQ1+lTVEZv1gl47FIdXReg==}
'@primeuix/utils@0.6.4':
resolution: {integrity: sha512-pZ5f+vj7wSzRhC7KoEQRU5fvYAe+RP9+m39CTscZ3UywCD1Y2o6Fe1rRgklMPSkzUcty2jzkA0zMYkiJBD1hgg==}
engines: {node: '>=12.11.0'}
'@primevue/core@4.5.5':
resolution: {integrity: sha512-JpkXhq1ddc70JdsC3CC4dM+UbeeWuCW/8DpS9dNBfrOk824TLSlRlMEGFyVKqRMn5WPQvYLiy3xXfLQeNdSqhQ==}
engines: {node: '>=12.11.0'}
peerDependencies:
vue: ^3.5.0
'@primevue/forms@4.5.5':
resolution: {integrity: sha512-LUeIt6oItwCZyBreQ7ycErE8aEjPmBWOTq1VPLhyaATcnzMBOZRIiwclaYk8s/tf5VYMqN9N4B80XnXoO7OdvQ==}
engines: {node: '>=12.11.0'}
'@primevue/icons@4.5.5':
resolution: {integrity: sha512-eteOhTdAOXEYE9qW1AOrBBgDxQ2szHJxSkEK1XVdV2TKxGM5FQf03Ovms0VDyZTc16XBIgvwYjXJQS0BPbhPaA==}
engines: {node: '>=12.11.0'}
'@primevue/themes@4.5.4':
resolution: {integrity: sha512-rUFZxMHLanTZdvZq4zgZPk+KRBZ3s7fE3bBK32OrZBkHQhEJmkJ7Ftd4w4QFlXyz1B7c+k5invZiOOCjwHXg9Q==}
engines: {node: '>=12.11.0'}
deprecated: 'Deprecated. This package is no longer maintained. Please migrate to @primeuix/themes: https://www.npmjs.com/package/@primeuix/themes'
'@rollup/rollup-android-arm-eabi@4.60.2':
resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==}
cpu: [arm]
@@ -2525,10 +2479,6 @@ packages:
primeicons@7.0.0:
resolution: {integrity: sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw==}
primevue@4.5.5:
resolution: {integrity: sha512-Kv5REIewCdP806QaoU+4nBXfmpzOGFKkZ9qH4KsL6MjiAQVc4PUzypt8erl4r3Vzh3nr3aWZIxkxYRRsLGiX2A==}
engines: {node: '>=12.11.0'}
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
@@ -3644,50 +3594,6 @@ snapshots:
'@pnpm/network.ca-file': 1.0.2
config-chain: 1.1.13
'@primeuix/forms@0.1.0':
dependencies:
'@primeuix/utils': 0.6.4
'@primeuix/styled@0.7.4':
dependencies:
'@primeuix/utils': 0.6.4
'@primeuix/styles@2.0.3':
dependencies:
'@primeuix/styled': 0.7.4
'@primeuix/themes@2.0.3':
dependencies:
'@primeuix/styled': 0.7.4
'@primeuix/utils@0.6.4': {}
'@primevue/core@4.5.5(vue@3.5.33(typescript@5.9.3))':
dependencies:
'@primeuix/styled': 0.7.4
'@primeuix/utils': 0.6.4
vue: 3.5.33(typescript@5.9.3)
'@primevue/forms@4.5.5(vue@3.5.33(typescript@5.9.3))':
dependencies:
'@primeuix/forms': 0.1.0
'@primeuix/utils': 0.6.4
'@primevue/core': 4.5.5(vue@3.5.33(typescript@5.9.3))
transitivePeerDependencies:
- vue
'@primevue/icons@4.5.5(vue@3.5.33(typescript@5.9.3))':
dependencies:
'@primeuix/utils': 0.6.4
'@primevue/core': 4.5.5(vue@3.5.33(typescript@5.9.3))
transitivePeerDependencies:
- vue
'@primevue/themes@4.5.4':
dependencies:
'@primeuix/styled': 0.7.4
'@primeuix/themes': 2.0.3
'@rollup/rollup-android-arm-eabi@4.60.2':
optional: true
@@ -5534,16 +5440,6 @@ snapshots:
primeicons@7.0.0: {}
primevue@4.5.5(vue@3.5.33(typescript@5.9.3)):
dependencies:
'@primeuix/styled': 0.7.4
'@primeuix/styles': 2.0.3
'@primeuix/utils': 0.6.4
'@primevue/core': 4.5.5(vue@3.5.33(typescript@5.9.3))
'@primevue/icons': 4.5.5(vue@3.5.33(typescript@5.9.3))
transitivePeerDependencies:
- vue
process-nextick-args@2.0.1: {}
proto-list@1.2.4: {}
+4 -7
View File
@@ -1,9 +1,6 @@
import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
vi.mock("../api/client", () => ({
api: {
categories: {
@@ -39,7 +36,7 @@ describe("Categories.vue", () => {
it("renders the page header and 'New category' card", async () => {
apiCat.list.mockResolvedValue({ items: [] });
const wrap = mount(Categories, {
global: { plugins: [PrimeVue, ToastService] },
global: { plugins: [] },
});
await flushPromises();
expect(wrap.text()).toContain("Categories");
@@ -56,7 +53,7 @@ describe("Categories.vue", () => {
],
});
const wrap = mount(Categories, {
global: { plugins: [PrimeVue, ToastService] },
global: { plugins: [] },
});
await flushPromises();
expect(wrap.find('[data-test="cat-row-cat_a"]').exists()).toBe(true);
@@ -80,7 +77,7 @@ describe("Categories.vue", () => {
total: 3,
});
const wrap = mount(Categories, {
global: { plugins: [PrimeVue, ToastService] },
global: { plugins: [] },
});
await flushPromises();
expect(wrap.get('[data-test="cat-count-cat_a"]').text()).toBe("2");
@@ -92,7 +89,7 @@ describe("Categories.vue", () => {
id: "cat_new", name: "Mood", color: "#fbbf24", icon: null, sort_order: 0,
});
const wrap = mount(Categories, {
global: { plugins: [PrimeVue, ToastService] },
global: { plugins: [] },
});
await flushPromises();
await wrap.get('[data-test="new-cat-name"]').setValue("Mood");
+3 -4
View File
@@ -1,12 +1,11 @@
import { mount } from "@vue/test-utils";
import { describe, expect, it } from "vitest";
import PrimeVue from "primevue/config";
import ColorPicker from "../components/ColorPicker.vue";
function makeWrapper(modelValue = "#7c3aed") {
return mount(ColorPicker, {
props: { modelValue },
global: { plugins: [PrimeVue] },
global: { plugins: [] },
});
}
@@ -56,7 +55,7 @@ describe("ColorPicker.vue", () => {
it("uses provided ariaLabel on the swatch button", () => {
const wrap = mount(ColorPicker, {
props: { modelValue: "#abcdef", ariaLabel: "Pick brand color" },
global: { plugins: [PrimeVue] },
global: { plugins: [] },
});
expect(wrap.get('[data-test="color-swatch"]').attributes("aria-label"))
.toBe("Pick brand color");
@@ -65,7 +64,7 @@ describe("ColorPicker.vue", () => {
it("supports custom presets prop", async () => {
const wrap = mount(ColorPicker, {
props: { modelValue: "#000000", presets: ["#111111", "#222222"] },
global: { plugins: [PrimeVue] },
global: { plugins: [] },
});
await openPopover(wrap);
expect(wrap.findAll('[data-test="color-preset"]').length).toBe(2);
+5 -8
View File
@@ -2,9 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -47,7 +44,7 @@ function makeRouter() {
describe("CombineEditor.vue", () => {
it("renders 'New combine' heading when no id", async () => {
const wrap = mount(CombineEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.text()).toContain("New combine");
@@ -66,7 +63,7 @@ describe("CombineEditor.vue", () => {
});
const wrap = mount(CombineEditor, {
props: { id: "cb_a" },
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.text()).toContain("Edit combine");
@@ -77,7 +74,7 @@ describe("CombineEditor.vue", () => {
it("save without name does not call api", async () => {
const wrap = mount(CombineEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const saveBtn = wrap.find('[data-test="save-btn"]');
@@ -94,7 +91,7 @@ describe("CombineEditor.vue", () => {
version: 1, created_at: "", updated_at: "",
});
const wrap = mount(CombineEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const nameInput = wrap.find('[data-test="identity-name"]');
@@ -116,7 +113,7 @@ describe("CombineEditor.vue", () => {
it("detected inputs panel shows the $vars from the template", async () => {
const wrap = mount(CombineEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const ta = wrap.find('[data-test="cb-template"] textarea');
+1 -5
View File
@@ -2,10 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
import ConfirmationService from "primevue/confirmationservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -45,7 +41,7 @@ function makeRouter() {
function mountView() {
return mount(Combines, {
global: { plugins: [makeRouter(), PrimeVue, ToastService, ConfirmationService] },
global: { plugins: [makeRouter()] },
});
}
@@ -9,9 +9,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
vi.mock("../community/mockApi", async () => {
const actual = await vi.importActual<typeof import("../community/mockApi")>("../community/mockApi");
return {
@@ -79,7 +76,7 @@ function makeRouter() {
function mountView() {
return mount(CommunityDiscover, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
}
@@ -8,9 +8,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
vi.mock("../community/mockApi", async () => {
const actual = await vi.importActual<typeof import("../community/mockApi")>("../community/mockApi");
return {
@@ -44,7 +41,7 @@ function makeRouter() {
function mountView() {
return mount(CommunityUpload, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
}
@@ -2,9 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -72,7 +69,7 @@ function makeRouter() {
describe("ConstraintEditor.vue", () => {
it("renders 'New constraint' heading when no id", async () => {
const wrap = mount(ConstraintEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.text()).toContain("New constraint");
@@ -99,7 +96,7 @@ describe("ConstraintEditor.vue", () => {
});
const wrap = mount(ConstraintEditor, {
props: { id: "cn_a" },
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.text()).toContain("Edit constraint");
@@ -128,7 +125,7 @@ describe("ConstraintEditor.vue", () => {
version: 1, created_at: "", updated_at: "",
});
const wrap = mount(ConstraintEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const nameInput = wrap.find('[data-test="identity-name"]');
@@ -157,7 +154,7 @@ describe("ConstraintEditor.vue", () => {
it("save without source/target shows warn and does not call api", async () => {
const wrap = mount(ConstraintEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const nameInput = wrap.find('[data-test="identity-name"]');
@@ -1,6 +1,5 @@
import { mount } from "@vue/test-utils";
import { describe, expect, it } from "vitest";
import PrimeVue from "primevue/config";
import ConstraintMatrix from "../components/ConstraintMatrix.vue";
import type { ConstraintMatrix as Matrix } from "../api/types";
@@ -15,7 +14,7 @@ function lastEmitted(
function mountGrid(modelValue: Matrix = {}, rows = ["red", "blue"], cols = ["warm", "cool"]) {
return mount(ConstraintMatrix, {
props: { rows, cols, modelValue },
global: { plugins: [PrimeVue] },
global: { plugins: [] },
});
}
+1 -5
View File
@@ -2,10 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
import ConfirmationService from "primevue/confirmationservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -45,7 +41,7 @@ function makeRouter() {
function mountView() {
return mount(Constraints, {
global: { plugins: [makeRouter(), PrimeVue, ToastService, ConfirmationService] },
global: { plugins: [makeRouter()] },
});
}
+2 -6
View File
@@ -2,10 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
import ConfirmationService from "primevue/confirmationservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -59,7 +55,7 @@ function makeRouter() {
function mountView() {
return mount(Dashboard, {
global: { plugins: [makeRouter(), PrimeVue, ToastService, ConfirmationService] },
global: { plugins: [makeRouter()] },
});
}
@@ -102,7 +98,7 @@ describe("Dashboard.vue", () => {
const router = makeRouter();
const push = vi.spyOn(router, "push");
const wrap = mount(Dashboard, {
global: { plugins: [router, PrimeVue, ToastService, ConfirmationService] },
global: { plugins: [router] },
});
await flushPromises();
const stats = wrap.findAll(".dashboard__stat");
@@ -2,9 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -47,7 +44,7 @@ function makeRouter() {
describe("DerivationEditor.vue", () => {
it("renders 'New derivation' heading when no id", async () => {
const wrap = mount(DerivationEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.text()).toContain("New derivation");
@@ -56,7 +53,7 @@ describe("DerivationEditor.vue", () => {
it("starts with no rules and shows the empty-state card", async () => {
const wrap = mount(DerivationEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.find('[data-test="rules-empty"]').exists()).toBe(true);
@@ -66,7 +63,7 @@ describe("DerivationEditor.vue", () => {
it("Add rule increments the rules length", async () => {
const wrap = mount(DerivationEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const vm = wrap.vm as unknown as { rules: DerivationRule[] };
@@ -85,7 +82,7 @@ describe("DerivationEditor.vue", () => {
payload: { rules: [] }, version: 1, created_at: "", updated_at: "",
});
const wrap = mount(DerivationEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const nameInput = wrap.find('[data-test="identity-name"]');
@@ -133,7 +130,7 @@ describe("DerivationEditor.vue", () => {
});
const wrap = mount(DerivationEditor, {
props: { id: "dv_a" },
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.text()).toContain("Edit derivation");
@@ -1,7 +1,5 @@
import { mount } from "@vue/test-utils";
import { describe, expect, it } from "vitest";
import PrimeVue from "primevue/config";
import DerivationRuleCard from "../components/DerivationRuleCard.vue";
import type { DerivationRule } from "../api/types";
@@ -21,7 +19,7 @@ function makeRule(overrides: Partial<DerivationRule> = {}): DerivationRule {
function mountCard(rule: DerivationRule, index = 0) {
return mount(DerivationRuleCard, {
props: { modelValue: rule, index },
global: { plugins: [PrimeVue] },
global: { plugins: [] },
});
}
+1 -5
View File
@@ -2,10 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
import ConfirmationService from "primevue/confirmationservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -45,7 +41,7 @@ function makeRouter() {
function mountView() {
return mount(Derivations, {
global: { plugins: [makeRouter(), PrimeVue, ToastService, ConfirmationService] },
global: { plugins: [makeRouter()] },
});
}
+5 -8
View File
@@ -2,9 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -46,7 +43,7 @@ function makeRouter() {
describe("FixedEditor.vue", () => {
it("renders 'New fixed values' heading when no id", async () => {
const wrap = mount(FixedEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.text()).toContain("New fixed values");
@@ -61,7 +58,7 @@ describe("FixedEditor.vue", () => {
});
const wrap = mount(FixedEditor, {
props: { id: "fv_a" },
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.text()).toContain("Edit fixed values");
@@ -72,7 +69,7 @@ describe("FixedEditor.vue", () => {
it("save without name shows warn toast and does not call api", async () => {
const wrap = mount(FixedEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const saveBtn = wrap.find('[data-test="save-btn"]');
@@ -83,7 +80,7 @@ describe("FixedEditor.vue", () => {
it("flags invalid identifier rows with inline error", async () => {
const wrap = mount(FixedEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const nameInput = wrap.find('[data-test="identity-name"]');
@@ -105,7 +102,7 @@ describe("FixedEditor.vue", () => {
version: 1, created_at: "", updated_at: "",
});
const wrap = mount(FixedEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const nameInput = wrap.find('[data-test="identity-name"]');
+1 -5
View File
@@ -2,10 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
import ConfirmationService from "primevue/confirmationservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -45,7 +41,7 @@ function makeRouter() {
function mountView() {
return mount(FixedValues, {
global: { plugins: [makeRouter(), PrimeVue, ToastService, ConfirmationService] },
global: { plugins: [makeRouter()] },
});
}
+4 -5
View File
@@ -1,6 +1,5 @@
import { mount } from "@vue/test-utils";
import { describe, expect, it } from "vitest";
import PrimeVue from "primevue/config";
import HistoryPanel from "../components/HistoryPanel.vue";
import type { ModuleHistoryEntry } from "../api/types";
@@ -20,7 +19,7 @@ describe("HistoryPanel.vue", () => {
it("does not render when closed", () => {
const wrap = mount(HistoryPanel, {
props: { open: false, entries: [] },
global: { plugins: [PrimeVue] },
global: { plugins: [] },
attachTo: document.body,
});
// Teleport puts the contents on body when open=true; closed = nothing.
@@ -31,7 +30,7 @@ describe("HistoryPanel.vue", () => {
it("renders empty state when no entries", async () => {
const wrap = mount(HistoryPanel, {
props: { open: true, entries: [] },
global: { plugins: [PrimeVue] },
global: { plugins: [] },
attachTo: document.body,
});
const empty = document.body.querySelector('[data-test="history-empty"]');
@@ -47,7 +46,7 @@ describe("HistoryPanel.vue", () => {
];
const wrap = mount(HistoryPanel, {
props: { open: true, entries },
global: { plugins: [PrimeVue] },
global: { plugins: [] },
attachTo: document.body,
});
const list = document.body.querySelector('[data-test="history-list"]');
@@ -72,7 +71,7 @@ describe("HistoryPanel.vue", () => {
it("emits update:open=false when close button clicked", async () => {
const wrap = mount(HistoryPanel, {
props: { open: true, entries: [makeEntry()] },
global: { plugins: [PrimeVue] },
global: { plugins: [] },
attachTo: document.body,
});
const closeBtn = document.body.querySelector(
+1 -4
View File
@@ -1,9 +1,6 @@
import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -60,7 +57,7 @@ afterEach(() => vi.clearAllMocks());
function mountView() {
return mount(ImportExport, {
global: { plugins: [PrimeVue, ToastService] },
global: { plugins: [] },
});
}
+4 -7
View File
@@ -2,9 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -46,7 +43,7 @@ function makeRouter() {
describe("PipelineEditor.vue", () => {
it("renders 'New pipeline' heading when no id", async () => {
const wrap = mount(PipelineEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.text()).toContain("New pipeline");
@@ -61,7 +58,7 @@ describe("PipelineEditor.vue", () => {
});
const wrap = mount(PipelineEditor, {
props: { id: "pl_a" },
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.text()).toContain("Edit pipeline");
@@ -70,7 +67,7 @@ describe("PipelineEditor.vue", () => {
it("save without name shows warn toast and skips API", async () => {
const wrap = mount(PipelineEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const saveBtn = wrap.find('[data-test="save-btn"]');
@@ -86,7 +83,7 @@ describe("PipelineEditor.vue", () => {
payload: { steps: [] }, version: 1, created_at: "", updated_at: "",
});
const wrap = mount(PipelineEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const nameInput = wrap.find('[data-test="identity-name"]');
+1 -2
View File
@@ -1,6 +1,5 @@
import { mount } from "@vue/test-utils";
import { describe, expect, it } from "vitest";
import PrimeVue from "primevue/config";
import PipelineSteps from "../components/PipelineSteps.vue";
import type { ModuleRow, ModuleType, PipelineStep } from "../api/types";
@@ -42,7 +41,7 @@ function mountSteps(steps: PipelineStep[], modules: ModuleRow[]) {
modulesById,
modulesByKind: buildKindGroups(modules),
},
global: { plugins: [PrimeVue] },
global: { plugins: [] },
});
}
+1 -5
View File
@@ -2,10 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
import ConfirmationService from "primevue/confirmationservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -45,7 +41,7 @@ function makeRouter() {
function mountView() {
return mount(Pipelines, {
global: { plugins: [makeRouter(), PrimeVue, ToastService, ConfirmationService] },
global: { plugins: [makeRouter()] },
});
}
+1 -3
View File
@@ -1,8 +1,6 @@
import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
import type { ModuleRow } from "../api/types";
vi.mock("../api/client", () => {
@@ -105,7 +103,7 @@ afterEach(() => {
});
function mountRunner() {
return mount(TestRunner, { global: { plugins: [PrimeVue, ToastService] } });
return mount(TestRunner, { global: { plugins: [] } });
}
describe("TestRunner.vue", () => {
+10 -13
View File
@@ -2,9 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -50,7 +47,7 @@ function findByText(wrap: ReturnType<typeof mount>, text: string) {
describe("WildcardEditor.vue", () => {
it("renders 'New wildcard' heading when no id", async () => {
const wrap = mount(WildcardEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.text()).toContain("New wildcard");
@@ -64,7 +61,7 @@ describe("WildcardEditor.vue", () => {
});
const wrap = mount(WildcardEditor, {
props: { id: "wc_a" },
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
expect(wrap.text()).toContain("Edit wildcard");
@@ -78,7 +75,7 @@ describe("WildcardEditor.vue", () => {
payload: { options: [] }, version: 1, created_at: "", updated_at: "",
});
const wrap = mount(WildcardEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const nameInput = wrap.find('[data-test="identity-name"]');
@@ -94,7 +91,7 @@ describe("WildcardEditor.vue", () => {
it("save without name shows warn toast and does not call api", async () => {
const wrap = mount(WildcardEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const saveBtn = wrap.find('[data-test="save-btn"]');
@@ -105,7 +102,7 @@ describe("WildcardEditor.vue", () => {
it("auto-derives var_binding from name while untouched", async () => {
const wrap = mount(WildcardEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const nameInput = wrap.find('[data-test="identity-name"]');
@@ -124,7 +121,7 @@ describe("WildcardEditor.vue", () => {
version: 1, created_at: "", updated_at: "",
});
const wrap = mount(WildcardEditor, {
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const nameInput = wrap.find('[data-test="identity-name"]');
@@ -146,7 +143,7 @@ describe("WildcardEditor.vue", () => {
});
const wrap = mount(WildcardEditor, {
props: { id: "wc_a" },
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const rti = wrap.findComponent({ name: "RichTextInput" });
@@ -163,7 +160,7 @@ describe("WildcardEditor.vue", () => {
});
const wrap = mount(WildcardEditor, {
props: { id: "wc_a" },
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const varInput = wrap.find('[data-test="identity-var-binding"]')
@@ -195,7 +192,7 @@ describe("WildcardEditor.vue", () => {
const wrap = mount(WildcardEditor, {
props: { id: "wc_a" },
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
@@ -219,7 +216,7 @@ describe("WildcardEditor.vue", () => {
}));
const wrap = mount(WildcardEditor, {
props: { id: "wc_a" },
global: { plugins: [makeRouter(), PrimeVue, ToastService] },
global: { plugins: [makeRouter()] },
});
await flushPromises();
const nameInput = wrap.find('[data-test="identity-name"]');
+1 -5
View File
@@ -2,10 +2,6 @@ import { mount, flushPromises } from "@vue/test-utils";
import { setActivePinia, createPinia } from "pinia";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { createMemoryHistory, createRouter } from "vue-router";
import PrimeVue from "primevue/config";
import ToastService from "primevue/toastservice";
import ConfirmationService from "primevue/confirmationservice";
vi.mock("../api/client", () => ({
api: {
modules: {
@@ -45,7 +41,7 @@ function makeRouter() {
function mountView() {
return mount(Wildcards, {
global: { plugins: [makeRouter(), PrimeVue, ToastService, ConfirmationService] },
global: { plugins: [makeRouter()] },
});
}
+1 -16
View File
@@ -1,11 +1,7 @@
import { createApp } from "vue";
import { createPinia } from "pinia";
import PrimeVue from "primevue/config";
import { WildcardPreset } from "./styles/preset";
import ToastService from "primevue/toastservice";
import ConfirmationService from "primevue/confirmationservice";
import "primeicons/primeicons.css";
import "./styles/theme.css";
import "./styles/tokens.css";
import "./styles/community.css";
import "./styles/tailwind.css";
import App from "./App.vue";
@@ -20,15 +16,4 @@ app.use(router);
// Apply persisted theme before mount so the first paint is correct.
// `useUiStore` reads localStorage and listens to OS preference changes.
useUiStore(pinia).initializeTheme();
app.use(PrimeVue, {
theme: {
preset: WildcardPreset,
options: {
darkModeSelector: ".wp-dark",
cssLayer: { name: "primevue", order: "tailwind-base, primevue, tailwind-utilities" },
},
},
});
app.use(ToastService);
app.use(ConfirmationService);
app.mount("#app");
-49
View File
@@ -1,49 +0,0 @@
import { definePreset } from "@primevue/themes";
import Aura from "@primevue/themes/aura";
/** Aura derivative driven by the SPA `--wp-*` token vocabulary so PrimeVue
* components (Button, DataTable, Tag, Toast) render with project colors
* out of the box.
*
* The `primary` palette mirrors the prototype's violet accent scale (see
* src/manager/styles/tokens.css). 950 is extrapolated as accent-900 mixed
* ~30% with #000 (no 950 stop is defined in the prototype).
*
* Surface colors point at `--wp-bg*` so flipping the theme tokens at runtime
* also flips the PrimeVue surface scope.
*/
export const WildcardPreset = definePreset(Aura, {
semantic: {
primary: {
50: "#f5f3ff",
100: "#ede9fe",
200: "#ddd6fe",
300: "#c4b5fd",
400: "#a78bfa",
500: "#8b5cf6",
600: "#7c3aed",
700: "#6d28d9",
800: "#5b21b6",
900: "#4c1d95",
950: "#3b1480",
},
colorScheme: {
dark: {
surface: {
0: "var(--wp-text)",
50: "var(--wp-bg-4)",
100: "var(--wp-bg-4)",
200: "var(--wp-text-dim)",
300: "var(--wp-text-muted)",
400: "var(--wp-text-muted)",
500: "var(--wp-text-muted)",
600: "var(--wp-border-strong)",
700: "var(--wp-bg-4)",
800: "var(--wp-bg-3)",
900: "var(--wp-bg-2)",
950: "var(--wp-bg-1)",
},
},
},
},
});
+3 -52
View File
@@ -1,52 +1,3 @@
/* Declare layer order so PrimeVue's `primevue` layer sits between Tailwind's
* base reset and Tailwind's utilities. Matches the cssLayer config in main.ts.
* Without layers, PrimeVue + Tailwind utility specificity collide.
*/
@layer tailwind-base, primevue, tailwind-utilities;
@layer tailwind-base {
@tailwind base;
}
@layer tailwind-utilities {
@tailwind components;
@tailwind utilities;
}
/* Compact button + form-control density for the SPA. PrimeVue Aura defaults
* are sized for large touch targets; this app is keyboard-first so we use
* a tighter rhythm (matches the old project). */
.p-button {
padding: 0.4rem 0.75rem;
font-size: 0.875rem;
}
.p-button-sm,
.p-button[data-pc-section="root"][size="small"] {
padding: 0.3rem 0.6rem;
font-size: 0.8125rem;
}
.p-button-icon-only {
width: 2rem;
height: 2rem;
padding: 0;
}
.p-button-icon-only.p-button-sm {
width: 1.75rem;
height: 1.75rem;
}
/* Tighter inputs, dropdowns, selects to match button density. */
.p-inputtext,
.p-select,
.p-inputnumber-input {
padding: 0.4rem 0.6rem;
font-size: 0.875rem;
}
.p-textarea {
padding: 0.5rem 0.6rem;
font-size: 0.875rem;
}
/* DataTable rows */
.p-datatable-tbody > tr > td,
.p-datatable-thead > tr > th {
padding: 0.55rem 0.75rem;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
-59
View File
@@ -1,59 +0,0 @@
/* Centralized stylesheet entry for the SPA Manager.
*
* Re-exports the design-token vocabulary extracted from the React/HTML
* prototype at docs/design-handoff/wildcardpipeline/project/styles.css,
* plus a small compatibility layer that maps the legacy `--wp-*` token
* names used by existing .vue components onto the new vocabulary.
*
* Imported by src/manager/main.ts before tailwind.css so utilities can
* reference any --wp-* token.
*/
@import "./tokens.css";
/* PrimeVue 4 uses `darkModeSelector: ".wp-dark"` (see src/manager/main.ts).
* Mark the dark scope explicitly. The class is applied eagerly on <html>
* for the v1 dark-only experience. */
html.wp-dark {
color-scheme: dark;
}
/* ---------- Legacy compatibility aliases ----------
* Existing .vue components reference an older flat set of token names
* (--wp-bg2, --wp-text2, --wp-accent, etc.). Map them to the new tokens
* so the screen-by-screen port can roll out without a flag-day rewrite. */
:root {
/* Surfaces */
--wp-bg2: var(--wp-bg-2);
--wp-bg3: var(--wp-bg-3);
--wp-bg4: var(--wp-bg-4);
/* Text tiers */
--wp-text2: var(--wp-text-muted);
--wp-text3: var(--wp-text-dim);
--wp-text4: var(--wp-text-dim);
/* Borders */
--wp-border2: var(--wp-border-strong);
/* Accent — old flat aliases */
--wp-accent: var(--wp-accent-500);
--wp-accent2: var(--wp-accent-400);
--wp-accent-glow: rgba(139, 92, 246, 0.15);
/* Module-kind aliases (legacy single-color tokens) */
--wp-violet: var(--wp-kind-wildcard);
--wp-violet-bg: var(--wp-kind-wildcard-bg);
--wp-teal: var(--wp-kind-fixed);
--wp-teal-bg: var(--wp-kind-fixed-bg);
--wp-green: var(--wp-kind-combine);
--wp-green-bg: var(--wp-kind-combine-bg);
--wp-amber: var(--wp-kind-derivation);
--wp-amber-bg: var(--wp-kind-derivation-bg);
--wp-rose: var(--wp-kind-constraint);
--wp-rose-bg: var(--wp-kind-constraint-bg);
--wp-red: var(--wp-danger);
--wp-red-bg: rgba(239, 68, 68, 0.12);
/* Type alias */
--wp-font-sans: var(--wp-font);
}
+49
View File
@@ -2221,3 +2221,52 @@ input, textarea, select { font: inherit; }
:root {
--wp-brand-gradient: linear-gradient(135deg, var(--wp-brand-1), var(--wp-brand-2), var(--wp-brand-3));
}
/* ---------- Color-scheme hint ----------
* The app applies `wp-dark` / `wp-theme-light` classes on <html> from
* stores/uiStore.ts. Mark the dark scope explicitly so form controls (date
* pickers, scrollbars) respect the theme. */
html.wp-dark {
color-scheme: dark;
}
/* ---------- Legacy compatibility aliases ----------
* Existing .vue components reference an older flat set of token names
* (--wp-bg2, --wp-text2, --wp-accent, etc.). Map them to the new tokens
* so the screen-by-screen port can roll out without a flag-day rewrite. */
:root {
/* Surfaces */
--wp-bg2: var(--wp-bg-2);
--wp-bg3: var(--wp-bg-3);
--wp-bg4: var(--wp-bg-4);
/* Text tiers */
--wp-text2: var(--wp-text-muted);
--wp-text3: var(--wp-text-dim);
--wp-text4: var(--wp-text-dim);
/* Borders */
--wp-border2: var(--wp-border-strong);
/* Accent — old flat aliases */
--wp-accent: var(--wp-accent-500);
--wp-accent2: var(--wp-accent-400);
--wp-accent-glow: rgba(139, 92, 246, 0.15);
/* Module-kind aliases (legacy single-color tokens) */
--wp-violet: var(--wp-kind-wildcard);
--wp-violet-bg: var(--wp-kind-wildcard-bg);
--wp-teal: var(--wp-kind-fixed);
--wp-teal-bg: var(--wp-kind-fixed-bg);
--wp-green: var(--wp-kind-combine);
--wp-green-bg: var(--wp-kind-combine-bg);
--wp-amber: var(--wp-kind-derivation);
--wp-amber-bg: var(--wp-kind-derivation-bg);
--wp-rose: var(--wp-kind-constraint);
--wp-rose-bg: var(--wp-kind-constraint-bg);
--wp-red: var(--wp-danger);
--wp-red-bg: rgba(239, 68, 68, 0.12);
/* Type alias */
--wp-font-sans: var(--wp-font);
}
+9 -9
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useToast } from "primevue/usetoast";
import { useToast } from "../composables/useToast";
import ModuleListView from "../components/ModuleListView.vue";
import Button from "../components/ui/Button.vue";
import Checkbox from "../components/ui/Checkbox.vue";
@@ -42,7 +42,7 @@ async function fetch() {
try {
await store.fetchAll();
} catch (e) {
toast.add({ severity: "error", summary: "Load failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Load failed", detail: String(e), life: 4000 });
}
}
@@ -53,32 +53,32 @@ function edit(row: ModuleRow) {
async function copyId(id: string) {
try {
await navigator.clipboard.writeText(id);
toast.add({ severity: "info", summary: "ID copied", detail: id, life: 1500 });
toast.push({ severity: "info", summary: "ID copied", detail: id, life: 1500 });
} catch {
toast.add({ severity: "error", summary: "Copy failed", life: 2000 });
toast.push({ severity: "error", summary: "Copy failed", life: 2000 });
}
}
async function dup(row: ModuleRow) {
try {
await store.duplicate(row.id);
toast.add({ severity: "success", summary: "Duplicated", detail: row.name, life: 2000 });
toast.push({ severity: "success", summary: "Duplicated", detail: row.name, life: 2000 });
} catch (e) {
toast.add({ severity: "error", summary: "Duplicate failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Duplicate failed", detail: String(e), life: 4000 });
}
}
async function fav(row: ModuleRow) {
try { await store.toggleFavorite(row.id); }
catch (e) { toast.add({ severity: "error", summary: "Favorite failed", detail: String(e), life: 4000 }); }
catch (e) { toast.push({ severity: "error", summary: "Favorite failed", detail: String(e), life: 4000 }); }
}
async function del(row: ModuleRow) {
try {
await store.remove(row.id);
toast.add({ severity: "success", summary: "Deleted", detail: row.name, life: 2000 });
toast.push({ severity: "success", summary: "Deleted", detail: row.name, life: 2000 });
} catch (e) {
toast.add({ severity: "error", summary: "Delete failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Delete failed", detail: String(e), life: 4000 });
}
}
+9 -9
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useToast } from "primevue/usetoast";
import { useToast } from "../composables/useToast";
import ModuleListView from "../components/ModuleListView.vue";
import Button from "../components/ui/Button.vue";
import Checkbox from "../components/ui/Checkbox.vue";
@@ -56,7 +56,7 @@ async function fetch() {
try {
await store.fetchAll();
} catch (e) {
toast.add({ severity: "error", summary: "Load failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Load failed", detail: String(e), life: 4000 });
}
}
@@ -67,32 +67,32 @@ function edit(row: ModuleRow) {
async function copyId(id: string) {
try {
await navigator.clipboard.writeText(id);
toast.add({ severity: "info", summary: "ID copied", detail: id, life: 1500 });
toast.push({ severity: "info", summary: "ID copied", detail: id, life: 1500 });
} catch {
toast.add({ severity: "error", summary: "Copy failed", life: 2000 });
toast.push({ severity: "error", summary: "Copy failed", life: 2000 });
}
}
async function dup(row: ModuleRow) {
try {
await store.duplicate(row.id);
toast.add({ severity: "success", summary: "Duplicated", detail: row.name, life: 2000 });
toast.push({ severity: "success", summary: "Duplicated", detail: row.name, life: 2000 });
} catch (e) {
toast.add({ severity: "error", summary: "Duplicate failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Duplicate failed", detail: String(e), life: 4000 });
}
}
async function fav(row: ModuleRow) {
try { await store.toggleFavorite(row.id); }
catch (e) { toast.add({ severity: "error", summary: "Favorite failed", detail: String(e), life: 4000 }); }
catch (e) { toast.push({ severity: "error", summary: "Favorite failed", detail: String(e), life: 4000 }); }
}
async function del(row: ModuleRow) {
try {
await store.remove(row.id);
toast.add({ severity: "success", summary: "Deleted", detail: row.name, life: 2000 });
toast.push({ severity: "success", summary: "Deleted", detail: row.name, life: 2000 });
} catch (e) {
toast.add({ severity: "error", summary: "Delete failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Delete failed", detail: String(e), life: 4000 });
}
}
+9 -9
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useToast } from "primevue/usetoast";
import { useToast } from "../composables/useToast";
import ModuleListView from "../components/ModuleListView.vue";
import Button from "../components/ui/Button.vue";
import Checkbox from "../components/ui/Checkbox.vue";
@@ -49,7 +49,7 @@ async function fetch() {
try {
await store.fetchAll();
} catch (e) {
toast.add({ severity: "error", summary: "Load failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Load failed", detail: String(e), life: 4000 });
}
}
@@ -60,32 +60,32 @@ function edit(row: ModuleRow) {
async function copyId(id: string) {
try {
await navigator.clipboard.writeText(id);
toast.add({ severity: "info", summary: "ID copied", detail: id, life: 1500 });
toast.push({ severity: "info", summary: "ID copied", detail: id, life: 1500 });
} catch {
toast.add({ severity: "error", summary: "Copy failed", life: 2000 });
toast.push({ severity: "error", summary: "Copy failed", life: 2000 });
}
}
async function dup(row: ModuleRow) {
try {
await store.duplicate(row.id);
toast.add({ severity: "success", summary: "Duplicated", detail: row.name, life: 2000 });
toast.push({ severity: "success", summary: "Duplicated", detail: row.name, life: 2000 });
} catch (e) {
toast.add({ severity: "error", summary: "Duplicate failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Duplicate failed", detail: String(e), life: 4000 });
}
}
async function fav(row: ModuleRow) {
try { await store.toggleFavorite(row.id); }
catch (e) { toast.add({ severity: "error", summary: "Favorite failed", detail: String(e), life: 4000 }); }
catch (e) { toast.push({ severity: "error", summary: "Favorite failed", detail: String(e), life: 4000 }); }
}
async function del(row: ModuleRow) {
try {
await store.remove(row.id);
toast.add({ severity: "success", summary: "Deleted", detail: row.name, life: 2000 });
toast.push({ severity: "success", summary: "Deleted", detail: row.name, life: 2000 });
} catch (e) {
toast.add({ severity: "error", summary: "Delete failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Delete failed", detail: String(e), life: 4000 });
}
}
+9 -9
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useToast } from "primevue/usetoast";
import { useToast } from "../composables/useToast";
import ModuleListView from "../components/ModuleListView.vue";
import Button from "../components/ui/Button.vue";
import Checkbox from "../components/ui/Checkbox.vue";
@@ -42,7 +42,7 @@ async function fetch() {
try {
await store.fetchAll();
} catch (e) {
toast.add({ severity: "error", summary: "Load failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Load failed", detail: String(e), life: 4000 });
}
}
@@ -53,32 +53,32 @@ function edit(row: ModuleRow) {
async function copyId(id: string) {
try {
await navigator.clipboard.writeText(id);
toast.add({ severity: "info", summary: "ID copied", detail: id, life: 1500 });
toast.push({ severity: "info", summary: "ID copied", detail: id, life: 1500 });
} catch {
toast.add({ severity: "error", summary: "Copy failed", life: 2000 });
toast.push({ severity: "error", summary: "Copy failed", life: 2000 });
}
}
async function dup(row: ModuleRow) {
try {
await store.duplicate(row.id);
toast.add({ severity: "success", summary: "Duplicated", detail: row.name, life: 2000 });
toast.push({ severity: "success", summary: "Duplicated", detail: row.name, life: 2000 });
} catch (e) {
toast.add({ severity: "error", summary: "Duplicate failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Duplicate failed", detail: String(e), life: 4000 });
}
}
async function fav(row: ModuleRow) {
try { await store.toggleFavorite(row.id); }
catch (e) { toast.add({ severity: "error", summary: "Favorite failed", detail: String(e), life: 4000 }); }
catch (e) { toast.push({ severity: "error", summary: "Favorite failed", detail: String(e), life: 4000 }); }
}
async function del(row: ModuleRow) {
try {
await store.remove(row.id);
toast.add({ severity: "success", summary: "Deleted", detail: row.name, life: 2000 });
toast.push({ severity: "success", summary: "Deleted", detail: row.name, life: 2000 });
} catch (e) {
toast.add({ severity: "error", summary: "Delete failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Delete failed", detail: String(e), life: 4000 });
}
}
+9 -9
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useToast } from "primevue/usetoast";
import { useToast } from "../composables/useToast";
import ModuleListView from "../components/ModuleListView.vue";
import Button from "../components/ui/Button.vue";
import Checkbox from "../components/ui/Checkbox.vue";
@@ -54,7 +54,7 @@ async function fetch() {
try {
await store.fetchAll();
} catch (e) {
toast.add({ severity: "error", summary: "Load failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Load failed", detail: String(e), life: 4000 });
}
}
@@ -65,32 +65,32 @@ function edit(row: ModuleRow) {
async function copyId(id: string) {
try {
await navigator.clipboard.writeText(id);
toast.add({ severity: "info", summary: "ID copied", detail: id, life: 1500 });
toast.push({ severity: "info", summary: "ID copied", detail: id, life: 1500 });
} catch {
toast.add({ severity: "error", summary: "Copy failed", life: 2000 });
toast.push({ severity: "error", summary: "Copy failed", life: 2000 });
}
}
async function dup(row: ModuleRow) {
try {
await store.duplicate(row.id);
toast.add({ severity: "success", summary: "Duplicated", detail: row.name, life: 2000 });
toast.push({ severity: "success", summary: "Duplicated", detail: row.name, life: 2000 });
} catch (e) {
toast.add({ severity: "error", summary: "Duplicate failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Duplicate failed", detail: String(e), life: 4000 });
}
}
async function fav(row: ModuleRow) {
try { await store.toggleFavorite(row.id); }
catch (e) { toast.add({ severity: "error", summary: "Favorite failed", detail: String(e), life: 4000 }); }
catch (e) { toast.push({ severity: "error", summary: "Favorite failed", detail: String(e), life: 4000 }); }
}
async function del(row: ModuleRow) {
try {
await store.remove(row.id);
toast.add({ severity: "success", summary: "Deleted", detail: row.name, life: 2000 });
toast.push({ severity: "success", summary: "Deleted", detail: row.name, life: 2000 });
} catch (e) {
toast.add({ severity: "error", summary: "Delete failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Delete failed", detail: String(e), life: 4000 });
}
}
+9 -9
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useToast } from "primevue/usetoast";
import { useToast } from "../composables/useToast";
import ModuleListView from "../components/ModuleListView.vue";
import RichTextPreview from "../components/RichTextPreview.vue";
import Button from "../components/ui/Button.vue";
@@ -91,7 +91,7 @@ async function fetch() {
try {
await store.fetchAll();
} catch (e) {
toast.add({ severity: "error", summary: "Load failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Load failed", detail: String(e), life: 4000 });
}
}
@@ -102,32 +102,32 @@ function edit(row: ModuleRow) {
async function copyId(id: string) {
try {
await navigator.clipboard.writeText(id);
toast.add({ severity: "info", summary: "ID copied", detail: id, life: 1500 });
toast.push({ severity: "info", summary: "ID copied", detail: id, life: 1500 });
} catch {
toast.add({ severity: "error", summary: "Copy failed", life: 2000 });
toast.push({ severity: "error", summary: "Copy failed", life: 2000 });
}
}
async function dup(row: ModuleRow) {
try {
await store.duplicate(row.id);
toast.add({ severity: "success", summary: "Duplicated", detail: row.name, life: 2000 });
toast.push({ severity: "success", summary: "Duplicated", detail: row.name, life: 2000 });
} catch (e) {
toast.add({ severity: "error", summary: "Duplicate failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Duplicate failed", detail: String(e), life: 4000 });
}
}
async function fav(row: ModuleRow) {
try { await store.toggleFavorite(row.id); }
catch (e) { toast.add({ severity: "error", summary: "Favorite failed", detail: String(e), life: 4000 }); }
catch (e) { toast.push({ severity: "error", summary: "Favorite failed", detail: String(e), life: 4000 }); }
}
async function del(row: ModuleRow) {
try {
await store.remove(row.id);
toast.add({ severity: "success", summary: "Deleted", detail: row.name, life: 2000 });
toast.push({ severity: "success", summary: "Deleted", detail: row.name, life: 2000 });
} catch (e) {
toast.add({ severity: "error", summary: "Delete failed", detail: String(e), life: 4000 });
toast.push({ severity: "error", summary: "Delete failed", detail: String(e), life: 4000 });
}
}
+2 -2
View File
@@ -3,7 +3,7 @@
* Community → 404 state.
*/
import { useRouter } from "vue-router";
import Button from "primevue/button";
import Button from "../../components/ui/Button.vue";
const router = useRouter();
@@ -18,7 +18,7 @@ function back() {
<div class="big">404</div>
<h3>Module not found</h3>
<p>It might have been unpublished, renamed, or moved.</p>
<Button label="Back to Discover" icon="pi pi-arrow-left" class="mt-3" @click="back" />
<Button icon="arrow-left" class="mt-3" @click="back">Back to Discover</Button>
</div>
</div>
</template>
+21 -26
View File
@@ -7,8 +7,8 @@
*/
import { computed, onMounted, ref, watch } from "vue";
import { useRouter } from "vue-router";
import Button from "primevue/button";
import { useToast } from "primevue/usetoast";
import Button from "../../components/ui/Button.vue";
import { useToast } from "../../composables/useToast";
import { useCommunityStore } from "../../stores/communityStore";
import { engineSatisfies } from "../../community/mockApi";
import { renderMarkdown } from "../../community/markdown";
@@ -68,9 +68,9 @@ async function onInstall() {
installing.value = true;
try {
await store.install(atom.value.id);
toast.add({ severity: "success", summary: "Installed", detail: atom.value.name, life: 2500 });
toast.push({ severity: "success", summary: "Installed", detail: atom.value.name, life: 2500 });
} catch {
toast.add({ severity: "error", summary: "Install failed", detail: "Registry unreachable.", life: 3000 });
toast.push({ severity: "error", summary: "Install failed", detail: "Registry unreachable.", life: 3000 });
} finally {
installing.value = false;
}
@@ -80,16 +80,16 @@ async function onUninstall() {
if (!atom.value) return;
try {
await store.uninstall(atom.value.id);
toast.add({ severity: "info", summary: "Removed", detail: atom.value.name, life: 2500 });
toast.push({ severity: "info", summary: "Removed", detail: atom.value.name, life: 2500 });
} catch {
toast.add({ severity: "error", summary: "Could not remove", life: 3000 });
toast.push({ severity: "error", summary: "Could not remove", life: 3000 });
}
}
async function onStar() {
if (!atom.value) return;
if (!store.currentUser) {
toast.add({ severity: "info", summary: "Sign in to star modules.", life: 2500 });
toast.push({ severity: "info", summary: "Sign in to star modules.", life: 2500 });
return;
}
await store.toggleStar(atom.value.id);
@@ -137,13 +137,11 @@ onMounted(() => load());
<template>
<div class="wp-comm-page">
<Button
label="Back to Community"
icon="pi pi-arrow-left"
severity="secondary"
text
variant="ghost"
icon="arrow-left"
class="self-start"
@click="onBack"
/>
>Back to Community</Button>
<div v-if="loading" class="wp-comm-empty">Loading...</div>
@@ -180,29 +178,26 @@ onMounted(() => load());
<div class="wp-comm-detail-hero__actions">
<Button
v-if="installed"
label="Installed — Remove"
icon="pi pi-check"
severity="secondary"
variant="secondary"
icon="check"
@click="onUninstall"
/>
>Installed — Remove</Button>
<Button
v-else
:label="atom.type === 'bundle' ? 'Install pack' : 'Install module'"
icon="pi pi-download"
variant="primary"
icon="download"
:disabled="!compatible || installing"
@click="onInstall"
/>
>{{ atom.type === 'bundle' ? 'Install pack' : 'Install module' }}</Button>
<Button
:label="starred ? 'Starred' : 'Star'"
:icon="starred ? 'pi pi-star-fill' : 'pi pi-star'"
severity="secondary"
variant="secondary"
:icon="starred ? 'star-fill' : 'star'"
@click="onStar"
/>
>{{ starred ? 'Starred' : 'Star' }}</Button>
<Button
icon="pi pi-flag"
variant="ghost"
icon="flag"
aria-label="Report module"
severity="secondary"
text
/>
</div>
</div>
@@ -7,10 +7,10 @@
*/
import { computed, onMounted, ref, watch } from "vue";
import { useRouter } from "vue-router";
import Button from "primevue/button";
import InputText from "primevue/inputtext";
import Select from "primevue/select";
import Checkbox from "primevue/checkbox";
import Button from "../../components/ui/Button.vue";
import Input from "../../components/ui/Input.vue";
import Select from "../../components/ui/Select.vue";
import Checkbox from "../../components/ui/Checkbox.vue";
import { useCommunityStore } from "../../stores/communityStore";
import CommunityCard from "../../community/CommunityCard.vue";
import { KIND_ICON, KIND_LABEL, fmtNumber } from "../../community/format";
@@ -102,17 +102,15 @@ onMounted(async () => {
</div>
<div class="wp-comm-page__actions">
<Button
label="My profile"
icon="pi pi-user"
severity="secondary"
outlined
variant="outline"
icon="user"
@click="router.push('/community/profile')"
/>
>My profile</Button>
<Button
label="Publish"
icon="pi pi-upload"
variant="primary"
icon="upload"
@click="router.push('/community/upload')"
/>
>Publish</Button>
</div>
</header>
@@ -152,41 +150,38 @@ onMounted(async () => {
<!-- Filter bar -->
<section class="wp-comm-filters">
<span class="wp-comm-filters__search p-input-icon-left">
<i class="pi pi-search" />
<InputText
<div class="wp-comm-filters__search">
<Input
v-model="q"
icon="search"
placeholder="Search modules, tags, authors..."
class="w-full"
aria-label="Search community"
/>
</span>
<Select
v-model="kind"
:options="KIND_OPTIONS"
option-label="label"
option-value="value"
placeholder="Kind"
class="w-40"
aria-label="Filter by kind"
/>
<Select
v-model="sort"
:options="SORT_OPTIONS"
option-label="label"
option-value="value"
placeholder="Sort"
class="w-44"
aria-label="Sort modules"
/>
</div>
<div class="w-40">
<Select
v-model="kind"
:options="KIND_OPTIONS"
placeholder="Kind"
aria-label="Filter by kind"
/>
</div>
<div class="w-44">
<Select
v-model="sort"
:options="SORT_OPTIONS"
placeholder="Sort"
aria-label="Sort modules"
/>
</div>
<label class="wp-comm-filters__toggle">
<Checkbox v-model="verifiedOnly" :binary="true" /> Verified only
<Checkbox v-model="verifiedOnly" /> Verified only
</label>
<label class="wp-comm-filters__toggle">
<Checkbox v-model="compatibleOnly" :binary="true" /> Compatible (engine {{ store.engineVersion }})
<Checkbox v-model="compatibleOnly" /> Compatible (engine {{ store.engineVersion }})
</label>
<label class="wp-comm-filters__toggle wp-comm-filters__toggle--nsfw">
<Checkbox v-model="includeNsfw" :binary="true" /> Show 18+
<Checkbox v-model="includeNsfw" /> Show 18+
</label>
</section>
@@ -223,12 +218,10 @@ onMounted(async () => {
<p>Try clearing filters or broaden your search.</p>
<Button
class="mt-3"
label="Clear filters"
icon="pi pi-filter-slash"
severity="secondary"
outlined
variant="outline"
icon="filter-slash"
@click="clearFilters"
/>
>Clear filters</Button>
</div>
<div v-else class="wp-comm-grid">
<CommunityCard v-for="atom in store.feed" :key="atom.id" :atom="atom" />
@@ -7,7 +7,7 @@
*/
import { ref } from "vue";
import { useRouter } from "vue-router";
import Button from "primevue/button";
import Button from "../../components/ui/Button.vue";
import { useCommunityStore } from "../../stores/communityStore";
import { isForceOffline, setForceOffline } from "../../community/mockApi";
@@ -46,23 +46,19 @@ function forceOffline() {
Community registry is offline. We'll keep retrying.
</p>
<div class="actions">
<Button label="Try again" icon="pi pi-refresh" @click="retry" />
<Button variant="primary" icon="refresh" @click="retry">Try again</Button>
<Button
v-if="forced"
label="Force online (demo)"
icon="pi pi-check"
severity="secondary"
outlined
variant="outline"
icon="check"
@click="forceOnline"
/>
>Force online (demo)</Button>
<Button
v-else
label="Force offline (demo)"
icon="pi pi-times-circle"
severity="secondary"
outlined
variant="outline"
icon="times-circle"
@click="forceOffline"
/>
>Force offline (demo)</Button>
</div>
</div>
</div>
@@ -7,7 +7,7 @@
*/
import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import Button from "primevue/button";
import Button from "../../components/ui/Button.vue";
import { useCommunityStore } from "../../stores/communityStore";
import CommunityCard from "../../community/CommunityCard.vue";
import { findInCatalog } from "../../community/mockApi";
@@ -54,13 +54,11 @@ onMounted(() => {
<template>
<div class="wp-comm-page">
<Button
label="Back to Community"
icon="pi pi-arrow-left"
severity="secondary"
text
variant="ghost"
icon="arrow-left"
class="self-start"
@click="back"
/>
>Back to Community</Button>
<div v-if="!store.currentUser" class="wp-comm-empty">
<i class="pi pi-user wp-comm-empty__icon" aria-hidden="true" />
@@ -86,8 +84,8 @@ onMounted(() => {
</div>
</div>
<div style="display: flex; gap: 8px;">
<Button label="Publish" icon="pi pi-upload" @click="router.push('/community/upload')" />
<Button label="Sign out" icon="pi pi-sign-out" severity="secondary" outlined @click="store.signOut()" />
<Button variant="primary" icon="upload" @click="router.push('/community/upload')">Publish</Button>
<Button variant="outline" icon="sign-out" @click="store.signOut()">Sign out</Button>
</div>
</section>
@@ -108,7 +106,7 @@ onMounted(() => {
<div v-if="!uploadAtoms.length" class="wp-comm-empty">
<i class="pi pi-upload wp-comm-empty__icon" aria-hidden="true" />
<h3>You haven't published anything yet.</h3>
<Button class="mt-3" label="Publish a module" icon="pi pi-upload" @click="router.push('/community/upload')" />
<Button class="mt-3" variant="primary" icon="upload" @click="router.push('/community/upload')">Publish a module</Button>
</div>
<div v-else class="wp-comm-grid">
<CommunityCard v-for="atom in uploadAtoms" :key="atom.id" :atom="atom" />
@@ -158,11 +156,9 @@ onMounted(() => {
<td style="color: var(--wp-text-muted);">{{ relativeTime(entry.at) }}</td>
<td style="text-align: right;">
<Button
label="Open"
severity="secondary"
text
variant="ghost"
@click="router.push({ name: 'community-detail', params: { id: entry.id } })"
/>
>Open</Button>
</td>
</tr>
</tbody>
+39 -47
View File
@@ -7,12 +7,12 @@
*/
import { computed, reactive, ref } from "vue";
import { useRouter } from "vue-router";
import Button from "primevue/button";
import InputText from "primevue/inputtext";
import Textarea from "primevue/textarea";
import Select from "primevue/select";
import Checkbox from "primevue/checkbox";
import { useToast } from "primevue/usetoast";
import Button from "../../components/ui/Button.vue";
import Input from "../../components/ui/Input.vue";
import Textarea from "../../components/ui/Textarea.vue";
import Select from "../../components/ui/Select.vue";
import Checkbox from "../../components/ui/Checkbox.vue";
import { useToast } from "../../composables/useToast";
import { useCommunityStore } from "../../stores/communityStore";
import { KIND_LABEL } from "../../community/format";
import type { CommunityUploadPayload } from "../../community/types";
@@ -72,7 +72,7 @@ const detailsValid = computed(
async function submit() {
if (!detailsValid.value) return;
if (!store.currentUser) {
toast.add({ severity: "warn", summary: "Sign in to publish.", life: 2500 });
toast.push({ severity: "warn", summary: "Sign in to publish.", life: 2500 });
return;
}
submitting.value = true;
@@ -80,9 +80,9 @@ async function submit() {
const result = await store.upload(form);
newId.value = result.id;
step.value = 4;
toast.add({ severity: "success", summary: "Published", detail: result.name, life: 2500 });
toast.push({ severity: "success", summary: "Published", detail: result.name, life: 2500 });
} catch {
toast.add({ severity: "error", summary: "Publish failed", detail: "Registry unreachable.", life: 3000 });
toast.push({ severity: "error", summary: "Publish failed", detail: "Registry unreachable.", life: 3000 });
} finally {
submitting.value = false;
}
@@ -108,13 +108,11 @@ function onTagKey(event: KeyboardEvent) {
<template>
<div class="wp-comm-page">
<Button
label="Back"
icon="pi pi-arrow-left"
severity="secondary"
text
variant="ghost"
icon="arrow-left"
class="self-start"
@click="back"
/>
>Back</Button>
<header class="wp-comm-page__header">
<div>
@@ -177,18 +175,17 @@ function onTagKey(event: KeyboardEvent) {
<div v-if="form.atom === 'single'" class="form-row">
<label class="form-label">Module kind</label>
<Select
v-model="form.type"
:options="KIND_OPTIONS"
option-label="label"
option-value="value"
class="select-narrow"
aria-label="Module kind"
/>
<div class="select-narrow">
<Select
v-model="form.type"
:options="KIND_OPTIONS"
aria-label="Module kind"
/>
</div>
</div>
<div class="form-actions">
<Button label="Next" icon="pi pi-arrow-right" icon-pos="right" @click="step = 2" />
<Button variant="primary" icon-right="arrow-right" @click="step = 2">Next</Button>
</div>
</div>
@@ -196,11 +193,11 @@ function onTagKey(event: KeyboardEvent) {
<div v-if="step === 2" class="wp-card-body">
<div class="form-row">
<label class="form-label">Name *</label>
<InputText v-model="form.name" placeholder="Hair Color Pro" aria-label="Module name" />
<Input v-model="form.name" placeholder="Hair Color Pro" aria-label="Module name" />
</div>
<div class="form-row">
<label class="form-label">Tagline *</label>
<InputText v-model="form.tagline" placeholder="One sentence about what this does." aria-label="Tagline" />
<Input v-model="form.tagline" placeholder="One sentence about what this does." aria-label="Tagline" />
</div>
<div class="form-grid">
<div>
@@ -208,8 +205,6 @@ function onTagKey(event: KeyboardEvent) {
<Select
v-model="form.category"
:options="CATEGORY_OPTIONS"
option-label="label"
option-value="value"
aria-label="Category"
/>
</div>
@@ -218,30 +213,28 @@ function onTagKey(event: KeyboardEvent) {
<Select
v-model="form.license"
:options="LICENSE_OPTIONS"
option-label="label"
option-value="value"
aria-label="License"
/>
</div>
<div>
<label class="form-label">Min engine</label>
<InputText v-model="form.engine_min_version" aria-label="Minimum engine version" />
<Input v-model="form.engine_min_version" aria-label="Minimum engine version" />
</div>
<div>
<label class="form-label">Version</label>
<InputText v-model="form.version" placeholder="1.0.0" aria-label="Version" />
<Input v-model="form.version" placeholder="1.0.0" aria-label="Version" />
</div>
</div>
<div class="form-row">
<label class="form-label">Tags *</label>
<div class="tag-row">
<InputText
<Input
v-model="tagInput"
placeholder="portrait, fashion, vibrant..."
aria-label="Tag input"
@keydown="onTagKey"
/>
<Button label="Add" severity="secondary" outlined @click="addTag" />
<Button variant="outline" @click="addTag">Add</Button>
</div>
<div v-if="form.tags.length" class="tag-list">
<span v-for="t in form.tags" :key="t" class="wp-comm-card__tag">
@@ -259,7 +252,7 @@ function onTagKey(event: KeyboardEvent) {
<label class="form-label">README (Markdown)</label>
<Textarea
v-model="form.readme"
rows="8"
:rows="8"
class="mono"
placeholder="# What's inside&#10;&#10;Describe the module..."
aria-label="README"
@@ -267,18 +260,17 @@ function onTagKey(event: KeyboardEvent) {
</div>
<div class="form-row">
<label class="wp-comm-filters__toggle">
<Checkbox v-model="form.nsfw" :binary="true" /> Mark as 18+ (NSFW)
<Checkbox v-model="form.nsfw" /> Mark as 18+ (NSFW)
</label>
</div>
<div class="form-actions form-actions--split">
<Button label="Back" icon="pi pi-arrow-left" severity="secondary" @click="step = 1" />
<Button variant="secondary" icon="arrow-left" @click="step = 1">Back</Button>
<Button
label="Review"
icon="pi pi-arrow-right"
icon-pos="right"
variant="primary"
icon-right="arrow-right"
:disabled="!detailsValid"
@click="step = 3"
/>
>Review</Button>
</div>
</div>
@@ -300,13 +292,13 @@ function onTagKey(event: KeyboardEvent) {
By publishing, you agree to the registry's content guidelines. You can edit metadata or unpublish at any time from your profile.
</p>
<div class="form-actions form-actions--split">
<Button label="Back" icon="pi pi-arrow-left" severity="secondary" @click="step = 2" />
<Button variant="secondary" icon="arrow-left" @click="step = 2">Back</Button>
<Button
label="Publish"
icon="pi pi-upload"
variant="primary"
icon="upload"
:loading="submitting"
@click="submit"
/>
>Publish</Button>
</div>
</div>
@@ -316,8 +308,8 @@ function onTagKey(event: KeyboardEvent) {
<h3>Published!</h3>
<p>Your module is live with id <code>{{ newId }}</code>.</p>
<div class="form-actions" style="justify-content: center;">
<Button label="View on Community" icon="pi pi-arrow-right" @click="viewModule" />
<Button label="Back to Discover" severity="secondary" outlined @click="back" />
<Button variant="primary" icon-right="arrow-right" @click="viewModule">View on Community</Button>
<Button variant="outline" @click="back">Back to Discover</Button>
</div>
</div>
</template>
@@ -384,5 +376,5 @@ function onTagKey(event: KeyboardEvent) {
.review-grid > div strong { color: var(--wp-text); font-size: 13px; }
.review-grid__full { grid-column: 1 / -1; }
.select-narrow { max-width: 240px; }
.mono :deep(textarea) { font-family: var(--wp-font-mono); font-size: 12px; }
.mono :deep(.wp-textarea) { font-family: var(--wp-font-mono); font-size: 12px; }
</style>
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="icon" type="image/svg+xml" href="/wp/images/favicon.svg" />
<link rel="apple-touch-icon" href="/wp/images/apple-touch-icon.png" />
<link rel="manifest" href="/wp/images/site.webmanifest" />
<script type="module" crossorigin src="/wp/assets/manager-Da1TVId8.js"></script>
<link rel="stylesheet" crossorigin href="/wp/assets/manager-Bh6ktjk4.css">
<script type="module" crossorigin src="/wp/assets/manager-D-gQtvLK.js"></script>
<link rel="stylesheet" crossorigin href="/wp/assets/manager-DyPxejbp.css">
</head>
<body>
<div id="app"></div>