Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
114a24a468 | ||
|
|
b776ee2af8 |
No files matched your search
@@ -0,0 +1,112 @@
|
||||
---
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
qqq-orb: kingsrook/qqq-orb@0.6
|
||||
|
||||
executors:
|
||||
playwright:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/playwright:v1.58.2-jammy
|
||||
|
||||
jobs:
|
||||
playwright_tests:
|
||||
executor: playwright
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- npm-deps-{{ checksum "package-lock.json" }}
|
||||
- npm-deps-
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: npm ci --legacy-peer-deps
|
||||
- save_cache:
|
||||
key: npm-deps-{{ checksum "package-lock.json" }}
|
||||
paths:
|
||||
- node_modules
|
||||
- run:
|
||||
name: Run Playwright visual regression tests
|
||||
command: npx playwright test
|
||||
- store_artifacts:
|
||||
path: playwright-report/
|
||||
destination: playwright-report
|
||||
- store_test_results:
|
||||
path: test-results/
|
||||
|
||||
workflows:
|
||||
test_only:
|
||||
jobs:
|
||||
- playwright_tests:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /(develop|main|release/.*|hotfix/.*)/
|
||||
tags:
|
||||
only: []
|
||||
- qqq-orb/mvn_frontend_test_only:
|
||||
context: [qqq-maven-registry-credentials]
|
||||
filters:
|
||||
branches:
|
||||
ignore: /(develop|main|release/.*|hotfix/.*)/
|
||||
tags:
|
||||
only: []
|
||||
|
||||
publish_snapshot:
|
||||
jobs:
|
||||
- playwright_tests:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- qqq-orb/mvn_frontend_publish:
|
||||
context: [qqq-maven-registry-credentials]
|
||||
branch_type: snapshot
|
||||
requires:
|
||||
- playwright_tests
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
|
||||
publish_feature:
|
||||
jobs:
|
||||
- qqq-orb/mvn_frontend_publish:
|
||||
context: [qqq-maven-registry-credentials]
|
||||
branch_type: feature
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- feature/.*
|
||||
tags:
|
||||
only: [/publish.*/]
|
||||
|
||||
publish_release_candidate:
|
||||
jobs:
|
||||
- qqq-orb/mvn_frontend_publish:
|
||||
context: [qqq-maven-registry-credentials]
|
||||
branch_type: release_candidate
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /release\/.*/
|
||||
|
||||
publish_release:
|
||||
jobs:
|
||||
- qqq-orb/mvn_frontend_publish:
|
||||
context: [qqq-maven-registry-credentials]
|
||||
branch_type: release
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: [/v.*/]
|
||||
|
||||
publish_hotfix_release:
|
||||
jobs:
|
||||
- qqq-orb/mvn_frontend_publish:
|
||||
context: [qqq-maven-registry-credentials]
|
||||
branch_type: hotfix
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- /hotfix\/.*/
|
||||
@@ -1,12 +0,0 @@
|
||||
name: build
|
||||
on:
|
||||
push:
|
||||
jobs:
|
||||
maven:
|
||||
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
|
||||
secrets: inherit
|
||||
with:
|
||||
# Selenium ITs need the Chrome-capable image; the React build is packaged into the jar.
|
||||
browsers: true
|
||||
node-version: '22.20.0'
|
||||
prebuild: bash scripts/ci-prebuild.sh
|
||||
@@ -1 +0,0 @@
|
||||
* @KofTwentyTwo
|
||||
@@ -43,7 +43,7 @@ This repository does NOT contain:
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Node.js**: LTS version (20.x or higher)
|
||||
- **Node.js**: LTS version (18.x or higher)
|
||||
- **npm**: 8.x or higher
|
||||
- **Git**: For version control
|
||||
- **QQQ Server**: Running instance for testing
|
||||
@@ -154,7 +154,7 @@ When reporting frontend-specific issues, include:
|
||||
|
||||
## 📄 License
|
||||
|
||||
By contributing to this project, you agree that your contributions will be licensed under the same license as the project (Apache License, Version 2.0).
|
||||
By contributing to this project, you agree that your contributions will be licensed under the same license as the project (GNU Affero General Public License v3.0).
|
||||
|
||||
## 🙏 Thank You
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# GitHub Release Configuration
|
||||
# Auto-generates release notes from PR labels
|
||||
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- ignore-for-release
|
||||
- duplicate
|
||||
- invalid
|
||||
authors:
|
||||
- dependabot
|
||||
- dependabot[bot]
|
||||
categories:
|
||||
- title: Breaking Changes
|
||||
labels:
|
||||
- breaking-change
|
||||
- breaking
|
||||
- title: New Features
|
||||
labels:
|
||||
- enhancement
|
||||
- feature
|
||||
- title: Bug Fixes
|
||||
labels:
|
||||
- bug
|
||||
- fix
|
||||
- title: Performance Improvements
|
||||
labels:
|
||||
- performance
|
||||
- title: Documentation
|
||||
labels:
|
||||
- documentation
|
||||
- docs
|
||||
- title: Dependencies
|
||||
labels:
|
||||
- dependencies
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
@@ -1,23 +0,0 @@
|
||||
name: CodeQL
|
||||
'on':
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
- release/**
|
||||
- hotfix/**
|
||||
pull_request: {}
|
||||
schedule:
|
||||
- cron: 41 16 * * 0
|
||||
workflow_dispatch: {}
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
security-events: write
|
||||
packages: read
|
||||
concurrency:
|
||||
group: codeql-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
jobs:
|
||||
analyze:
|
||||
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@c288a8864fe9054f68410e1d78f5863aea4c4d22
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
|
||||
# Reviewed findings committed before the Gitea migration; exact historical fingerprints only.
|
||||
267580b44babf4935c152da8fa2320d6059105f3:src/qqq/components/processes/GoogleDriveFolderPicker.tsx:gcp-api-key:43
|
||||
@@ -7,7 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
|
||||
## [0.35.0] - 2025-12-29
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -236,74 +236,36 @@ Read these files in order:
|
||||
2. `docs/TODO.md` - Active and completed tasks
|
||||
3. This file (`CLAUDE.md`) - Project context
|
||||
|
||||
### Current Status (as of 2026-01-14)
|
||||
### Current Status (as of 2026-01-24)
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Branch | `feature/fix-visual-regressions-128` |
|
||||
| Branch | `develop` |
|
||||
| Version | `0.40.0-SNAPSHOT` |
|
||||
| Latest Commit | `12934bb` - fix: align typography fallbacks with production typography.ts (#128) |
|
||||
| Status | **READY FOR REVIEW** - All typography fixes verified, waiting on Darin |
|
||||
| GitHub Issue | [#128](https://github.com/QRun-IO/qqq-frontend-material-dashboard/issues/128) |
|
||||
| Active PR | #136 (base path detection fix) |
|
||||
|
||||
### Active Work: Issue #128 Visual Regressions (Round 5 - COMPLETE)
|
||||
### Recent Activity
|
||||
|
||||
**Status:** All fixes complete and verified, waiting on Darin's approval
|
||||
|
||||
**Root Cause Identified:** The `createDynamicTheme.ts` fallback values and `DEFAULT_THEME` values didn't match the original `typography.ts` source of truth. The original `Theme.ts` used `typography: {...typography}` (direct spread), so production always used `typography.ts` values directly.
|
||||
|
||||
**What was fixed:**
|
||||
- Round 1-3: CSS scoping with `.qqq-themed` class
|
||||
- Round 4: Added CSS variable fallbacks to all `var(--qqq-*)` instances
|
||||
- Round 5: Aligned ALL 32 typography fallbacks with `typography.ts`
|
||||
|
||||
**Key Typography Corrections (verified against typography.ts):**
|
||||
| Property | Before (WRONG) | After (CORRECT) | Source |
|
||||
|----------|---------------|-----------------|--------|
|
||||
| textPrimary | #344767 | #212121 | colors.ts dark.main |
|
||||
| fontWeightMedium | 500 | 600 | typography.ts:156 |
|
||||
| H3 fontSize/weight | 1.5rem/700 | 1.75rem/600 | typography.ts:202-205 |
|
||||
| H6 fontSize/weight | 0.875rem/600 | 1.125rem/500 | typography.ts:221-224 |
|
||||
| body2 weight | 400 | 300 | typography.ts:251 |
|
||||
| button weight | 500 | 300 | typography.ts:258 |
|
||||
| caption weight | 400 | 300 | typography.ts:266 |
|
||||
|
||||
**Next steps:**
|
||||
1. Wait for Darin to test on branch `feature/fix-visual-regressions-128`
|
||||
2. If approved, create PR to merge into develop
|
||||
3. Publish snapshot after merge
|
||||
- **PR #136** - Fix for issue #134 (base path detection bug)
|
||||
- **PR #131** - Auth module enhancements (merged)
|
||||
- **Feature #364** - Customizable table action menus (merged)
|
||||
- **Feature #365** - Tables belonging to multiple apps (merged)
|
||||
- **25 unit tests** - Added for PathUtils (detectBasePath, resolveAssetUrl)
|
||||
- **18 visual regression tests** - Playwright screenshot tests
|
||||
|
||||
### Test Status
|
||||
|
||||
| Suite | Tests | Status |
|
||||
|-------|-------|--------|
|
||||
| Playwright themed | 26 | PASS |
|
||||
| Playwright unthemed | 13 | PASS |
|
||||
| Jest unit tests (PathUtils) | 25 | PASS |
|
||||
| Playwright visual regression | 18 | PASS |
|
||||
| Selenium fixture-based | ~100 | PASS |
|
||||
|
||||
### Key Files for Theme System
|
||||
### Pending Work
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/qqq/assets/theme/base/typography.ts` | **SOURCE OF TRUTH** for typography values |
|
||||
| `src/qqq/assets/theme/base/colors.ts` | Color definitions (dark.main = #212121) |
|
||||
| `src/qqq/utils/createDynamicTheme.ts` | Builds MUI theme from QThemeMetaData |
|
||||
| `src/qqq/utils/themeUtils.ts` | DEFAULT_THEME values, CSS variable injection |
|
||||
| `src/qqq/components/legacy/Theme.ts` | Original theme (spreads typography.ts directly) |
|
||||
|
||||
### Running Locally for Testing
|
||||
|
||||
```bash
|
||||
# Unthemed (original MUI styling, no CSS variable overrides)
|
||||
THEME_FIXTURE=index npm run fixture-server &
|
||||
HTTPS=true PORT=3000 REACT_APP_PROXY_LOCALHOST_PORT=8001 npm start
|
||||
|
||||
# Themed (full theme applied with CSS variables)
|
||||
THEME_FIXTURE=withFullCustomTheme npm run fixture-server &
|
||||
HTTPS=true PORT=3000 REACT_APP_PROXY_LOCALHOST_PORT=8001 npm start
|
||||
|
||||
# Run all e2e tests (themed + unthemed)
|
||||
npm run e2e:all
|
||||
```
|
||||
- Get PR #136 reviewed and merged (closes #134)
|
||||
- Review dependabot PR #135 (lodash bump)
|
||||
- Address npm audit vulnerabilities
|
||||
|
||||
### Key Documentation Files
|
||||
|
||||
@@ -311,25 +273,9 @@ npm run e2e:all
|
||||
|------|---------|
|
||||
| `docs/SESSION_STATE.md` | Current work status, branch, version, recent commits |
|
||||
| `docs/TODO.md` | Task tracking for current feature |
|
||||
| `docs/QQQ_THEMING_GUIDE.md` | Complete theming reference |
|
||||
| `docs/QQQ_CSS_SELECTORS_GUIDE.md` | CSS selector patterns reference |
|
||||
| `docs/THEME_TESTING_GUIDE.md` | Guide for testing theme features |
|
||||
|
||||
### Publishing
|
||||
|
||||
Release candidates publish automatically on `release/*` branches via CircleCI `publish_release_candidate` workflow.
|
||||
|
||||
Snapshots publish on `develop` via `publish_snapshot` workflow.
|
||||
|
||||
## Knowledge base
|
||||
|
||||
Durable cross-repo knowledge for QQQ lives in the second-brain vault
|
||||
(`$SECOND_BRAIN_VAULT`, i.e. `R:/Git.Local/KofTwentyTwo/second-brain`):
|
||||
|
||||
- Hub: `knowledge/qqq/qqq-hub.md`
|
||||
- This repo's dossier: `knowledge/qqq/repos/qqq-frontend-material-dashboard.md`
|
||||
(includes the full server-API consumption inventory, legacy vs `/qqq/v1`)
|
||||
|
||||
Dossier reviewed at commit `628e36f64e9a` on branch `develop` (2026-07-04).
|
||||
Note: the cross-repo version table earlier in this file is stale (0.36-era);
|
||||
trust `pom.xml` / `package.json` for current pins.
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
|
||||
> ℹ️ **Future direction:** The qqq admin UI is being rewritten as [qqq-frontend-next](https://github.com/QRun-IO/qqq-frontend-next) (Next.js 15 + Tailwind 4 + shadcn/ui). `qqq-frontend-material-dashboard` remains the supported choice today and will continue to receive fixes until existing qqq apps complete migration to `qqq-frontend-next`. New qqq apps starting in 2026 should evaluate `qqq-frontend-next` first.
|
||||
|
||||
React admin dashboard for QQQ applications.
|
||||
|
||||
**For:** Teams using QQQ who want a ready-to-use admin UI without writing frontend code
|
||||
**Status:** Stable (on long-term deprecation track — see banner above)
|
||||
**Status:** Stable
|
||||
|
||||
## Why This Exists
|
||||
|
||||
@@ -32,7 +30,7 @@ If you need a custom UI, use [qqq-frontend-core](https://github.com/QRun-IO/qqq-
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Prerequisites:** Node.js 20+, running QQQ backend
|
||||
**Prerequisites:** Node.js 18+, running QQQ backend
|
||||
|
||||
```bash
|
||||
git clone https://github.com/QRun-IO/qqq-frontend-material-dashboard
|
||||
@@ -81,26 +79,6 @@ npm run build
|
||||
mvn clean package -Pci
|
||||
```
|
||||
|
||||
### Verify published JAR notices
|
||||
|
||||
After staging the built UI in `src/main/resources/material-dashboard/` (as the
|
||||
publishing orb does), package with the `release` profile to produce main, sources,
|
||||
tests and Javadoc JARs. Before publication, run the archive guard with the actual
|
||||
version's archive prefix:
|
||||
|
||||
```bash
|
||||
mvn -Prelease -DskipTests -Dgpg.skip=true clean package
|
||||
./scripts/check-packaged-notices.py target/qqq-frontend-material-dashboard-0.41.0-SNAPSHOT
|
||||
```
|
||||
|
||||
This command verifies packaging only: it skips runtime tests and does not sign
|
||||
or publish. Existing runtime and hosted release gates are still required.
|
||||
The guard requires Python 3 and checks that every JAR contains exactly one
|
||||
`META-INF/LICENSE` and `META-INF/NOTICE`, byte-identical to the root files. It exits
|
||||
nonzero for missing archives, missing/duplicate entries or altered text. This is
|
||||
an explicit post-package check; the unchanged Circle orb does not invoke it
|
||||
automatically. It supplements the existing runtime and hosted release gates.
|
||||
|
||||
## Project Status
|
||||
|
||||
**Maturity:** Stable, production use
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
|
||||
## Goal
|
||||
|
||||
Add stable, semantic CSS selectors so QQQ app developers can target specific UI elements in
|
||||
`MaterialDashboardThemeMetaData.customCss`.
|
||||
Add stable, semantic CSS selectors so QQQ app developers can target specific UI elements in `QThemeMetaData.customCss`.
|
||||
|
||||
## Approach
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# PLAN: Fix Visual Regressions (Issue #128) - COMPLETE
|
||||
|
||||
**Status:** COMPLETE - Waiting on Darin to test
|
||||
**Branch:** `feature/fix-visual-regressions-128`
|
||||
**Latest Commit:** `d4251be`
|
||||
|
||||
## Goal
|
||||
Fix visual regressions caused by PR #125 pluggable themes so that unthemed apps look identical to pre-PR-125.
|
||||
|
||||
## Solution Summary
|
||||
|
||||
The key fix was **scoping CSS override rules to themed apps only**:
|
||||
1. Added `.qqq-themed` class toggle in `injectIslandVariables.ts`
|
||||
2. Scoped all CSS rules in `qqq-override-styles.css` to `.qqq-themed`
|
||||
3. Fixed e2e test infrastructure (`setupProxy.js` missing `/metaData` route)
|
||||
4. Fixed fixture structure (theme must be under `supplementalInstanceMetaData.theme`)
|
||||
|
||||
### Key Insight
|
||||
`QInstance` class in `@qrunio/qqq-frontend-core` only looks for theme at `object.supplementalInstanceMetaData["theme"]`, NOT at root level.
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/qqq/utils/injectIslandVariables.ts` | Added `.qqq-themed` class toggle |
|
||||
| `src/qqq/styles/qqq-override-styles.css` | Scoped rules to `.qqq-themed` |
|
||||
| `src/setupProxy.js` | Added `/metaData` route |
|
||||
| `src/test/resources/fixtures/metaData/withFullCustomTheme.json` | Fixed theme location |
|
||||
|
||||
## Test Results
|
||||
|
||||
| Suite | Tests | Status |
|
||||
|-------|-------|--------|
|
||||
| Playwright themed | 26 | PASS |
|
||||
| Playwright unthemed | 13 | PASS |
|
||||
| Selenium fixture-based | 115 | PASS |
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Wait for Darin's approval on issue #128
|
||||
2. Create PR to merge into develop
|
||||
3. Publish snapshot after merge
|
||||
@@ -95,10 +95,10 @@ New file: `src/qqq/utils/createDynamicTheme.ts`
|
||||
|
||||
```typescript
|
||||
import { createTheme, Theme } from "@mui/material";
|
||||
import { MaterialDashboardThemeMetaData } from "qqq/models/metadata/MaterialDashboardThemeMetaData";
|
||||
import { QThemeMetaData } from "@qrunio/qqq-frontend-core/lib/model/metaData/QThemeMetaData";
|
||||
import { DEFAULT_THEME } from "./themeUtils";
|
||||
|
||||
export function createDynamicTheme(themeData?: MaterialDashboardThemeMetaData): Theme {
|
||||
export function createDynamicTheme(themeData?: QThemeMetaData): Theme {
|
||||
// Merge with defaults
|
||||
const theme = { ...DEFAULT_THEME, ...filterUndefined(themeData) };
|
||||
|
||||
@@ -152,12 +152,12 @@ New file: `src/qqq/utils/injectIslandVariables.ts`
|
||||
This injects CSS variables ONLY for sidebar, branded header, and non-MUI components.
|
||||
|
||||
```typescript
|
||||
import { MaterialDashboardThemeMetaData } from "qqq/models/metadata/MaterialDashboardThemeMetaData";
|
||||
import { QThemeMetaData } from "@qrunio/qqq-frontend-core/lib/model/metaData/QThemeMetaData";
|
||||
import { DEFAULT_THEME } from "./themeUtils";
|
||||
|
||||
const CSS_VAR_PREFIX = "--qqq-";
|
||||
|
||||
export function injectIslandVariables(themeData?: MaterialDashboardThemeMetaData): void {
|
||||
export function injectIslandVariables(themeData?: QThemeMetaData): void {
|
||||
const root = document.documentElement;
|
||||
const theme = { ...DEFAULT_THEME, ...filterUndefined(themeData) };
|
||||
|
||||
@@ -413,10 +413,10 @@ mvn verify -Pci
|
||||
|
||||
### New Files to Create
|
||||
|
||||
| File | Purpose |
|
||||
|------|-------------------------------------------------------|
|
||||
| `src/qqq/utils/createDynamicTheme.ts` | MUI theme builder from MaterialDashboardThemeMetaData |
|
||||
| `src/qqq/utils/injectIslandVariables.ts` | CSS vars for sidebar/header islands only |
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/qqq/utils/createDynamicTheme.ts` | MUI theme builder from QThemeMetaData |
|
||||
| `src/qqq/utils/injectIslandVariables.ts` | CSS vars for sidebar/header islands only |
|
||||
|
||||
### Files to Modify
|
||||
|
||||
@@ -444,7 +444,7 @@ mvn verify -Pci
|
||||
1. All 26 Playwright tests pass
|
||||
2. CSS override file reduced from 1213 to <400 lines
|
||||
3. `!important` count reduced from 82 to <15
|
||||
4. Theme data flows directly: MaterialDashboardThemeMetaData -> MUI Theme -> Components
|
||||
4. Theme data flows directly: QThemeMetaData -> MUI Theme -> Components
|
||||
5. No visual regressions in app
|
||||
6. CI pipeline passes
|
||||
|
||||
|
||||
@@ -81,21 +81,21 @@ This document outlines the architecture changes needed to support pluggable them
|
||||
Runtime-swappable themes via CSS variables injected from backend metadata.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ QQQ Backend │
|
||||
│ MaterialDashboardThemeMetaData: palette, typography, spacing, icons, shadows │
|
||||
└─────────────────────────┬───────────────────────────────────────────────────────┘
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ QQQ Backend │
|
||||
│ QThemeMetaData: palette, typography, spacing, icons, shadows │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│ /metaData endpoint (expanded)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ qqq-frontend-core │
|
||||
│ + MaterialDashboardThemeMetaData comes along in the QInstance │
|
||||
│ as supplementalMetaData │
|
||||
│ qqq-frontend-core │
|
||||
│ + QThemeMetaData class with full design token structure │
|
||||
│ + ThemeUtils for CSS variable generation │
|
||||
└─────────────────────────┬───────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ qqq-frontend-material-dashboard │
|
||||
│ qqq-frontend-material-dashboard │
|
||||
│ ThemeProvider injects CSS variables to :root │
|
||||
│ All components consume var(--qqq-color-primary) etc. │
|
||||
│ MUI theme reads from CSS variables │
|
||||
@@ -131,7 +131,8 @@ Separate npm packages per theme, selected at build time.
|
||||
### 1. Design Token Schema
|
||||
|
||||
```typescript
|
||||
export interface MaterialDashboardThemeMetaData{
|
||||
// qqq-frontend-core: src/model/metaData/QThemeMetaData.ts
|
||||
export interface QThemeMetaData {
|
||||
name: string;
|
||||
|
||||
palette: {
|
||||
@@ -213,7 +214,7 @@ interface TypographyVariant {
|
||||
|
||||
```typescript
|
||||
// qqq-frontend-dashboard: src/qqq/theme/ThemeInjector.ts
|
||||
export function injectThemeVariables(theme: MaterialDashboardThemeMetaData): void {
|
||||
export function injectThemeVariables(theme: QThemeMetaData): void {
|
||||
const root = document.documentElement;
|
||||
|
||||
// Colors
|
||||
@@ -598,7 +599,7 @@ Create a new repository `qqq-theme-default` with the following structure:
|
||||
"type": "git",
|
||||
"url": "https://github.com/QRun-IO/qqq-theme-default.git"
|
||||
},
|
||||
"license": "Apache-2.0"
|
||||
"license": "AGPL-3.0"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# QQQ CSS Selectors Guide
|
||||
|
||||
QQQ automatically generates stable `data-qqq-id` attributes on key UI elements, enabling precise CSS targeting via `MaterialDashboardThemeMetaData.customCss`.
|
||||
QQQ automatically generates stable `data-qqq-id` attributes on key UI elements, enabling precise CSS targeting via `QThemeMetaData.customCss`.
|
||||
|
||||
## ID Format
|
||||
|
||||
|
||||
+12
-12
@@ -6,22 +6,22 @@ This document explains the QQQ pluggable theming system for AI assistants and de
|
||||
|
||||
QQQ uses a **backend-driven theming model**:
|
||||
|
||||
1. **Java Backend**: Define theme in `MaterialDashboardThemeMetaData` object
|
||||
2. **Frontend Model**: TypeScript `MaterialDashboardThemeMetaData` interface mirrors Java class
|
||||
1. **Java Backend**: Define theme in `QThemeMetaData` object
|
||||
2. **Frontend Core**: TypeScript `QThemeMetaData` interface mirrors Java class
|
||||
3. **Frontend Dashboard**: `themeUtils.ts` injects CSS custom properties at runtime
|
||||
|
||||
Theme data flows: `MaterialDashboardThemeMetaData.java` → `/metaData` API → `MaterialDashboardThemeMetaData.ts` → CSS variables (`--qqq-*`)
|
||||
Theme data flows: `QThemeMetaData.java` → `/metaData` API → `QThemeMetaData.ts` → CSS variables (`--qqq-*`)
|
||||
|
||||
## Configuring a Theme (Java Backend)
|
||||
|
||||
Create a `MaterialDashboardThemeMetaData` instance using fluent setters:
|
||||
Create a `QThemeMetaData` instance using fluent setters:
|
||||
|
||||
```java
|
||||
MaterialDashboardThemeMetaData theme = new MaterialDashboardThemeMetaData()
|
||||
QThemeMetaData theme = new QThemeMetaData()
|
||||
.withPrimaryColor("#1976D2")
|
||||
.withSidebarBackgroundColor("#1E1E2D");
|
||||
|
||||
qInstance.add(theme);
|
||||
qInstance.setTheme(theme);
|
||||
```
|
||||
|
||||
All properties are optional. Omitted properties use frontend defaults.
|
||||
@@ -251,7 +251,7 @@ The fallback value (after comma) is used if the variable is undefined.
|
||||
|
||||
## Theme Application Flow
|
||||
|
||||
1. Backend serves `MaterialDashboardThemeMetaData` via `/metaData` endpoint
|
||||
1. Backend serves `QThemeMetaData` via `/metaData` endpoint
|
||||
2. Frontend receives theme in `QInstance.theme`
|
||||
3. `themeUtils.ts` calls `injectThemeVariables(theme)`
|
||||
4. Function sets CSS custom properties on `:root`
|
||||
@@ -264,7 +264,7 @@ The fallback value (after comma) is used if the variable is undefined.
|
||||
|
||||
**Minimal** - Override only what differs from defaults:
|
||||
```java
|
||||
new MaterialDashboardThemeMetaData()
|
||||
new QThemeMetaData()
|
||||
.withPrimaryColor("#E91E63")
|
||||
.withBrandedHeaderEnabled(true);
|
||||
```
|
||||
@@ -286,10 +286,10 @@ For dark themes, invert the typical light/dark relationships:
|
||||
|
||||
## File Locations
|
||||
|
||||
| Repository | Key Files |
|
||||
|---------------------------------|-----------|
|
||||
| qqq-backend-core | `MaterialDashboardThemeMetaData.java` |
|
||||
| qqq-frontend-material-dashboard | `MaterialDashboardThemeMetaData.ts` |
|
||||
| Repository | Key Files |
|
||||
|------------|-----------|
|
||||
| qqq-backend-core | `QThemeMetaData.java` |
|
||||
| qqq-frontend-core | `QThemeMetaData.ts` |
|
||||
| qqq-frontend-material-dashboard | `themeUtils.ts`, `qqq-override-styles.css` |
|
||||
|
||||
---
|
||||
|
||||
+40
-52
@@ -1,70 +1,58 @@
|
||||
# Session State - QQQ Frontend Material Dashboard
|
||||
|
||||
**Last Updated:** 2026-01-15
|
||||
**Branch:** `feature/fix-visual-regressions-128`
|
||||
**Last Updated:** 2026-01-24
|
||||
**Branch:** `develop`
|
||||
**Version:** `0.40.0-SNAPSHOT`
|
||||
|
||||
## Current Status
|
||||
|
||||
**ROUND 6 FIXES COMPLETE (ALL 5 ISSUES)** - Ready for Darin to test.
|
||||
On `develop` branch. Just completed fix for issue #134 (base path detection bug).
|
||||
|
||||
## Active PR
|
||||
|
||||
- **PR #136** - fix(PathUtils): correct base path detection for root deployments
|
||||
- Branch: `feature/134-base-path-detection-bug`
|
||||
- Fixes issue #134
|
||||
- Adds 25 unit tests for PathUtils
|
||||
- Awaiting review/merge
|
||||
|
||||
## Recent Commits (develop)
|
||||
|
||||
```
|
||||
a969192 chore: update qqq-frontend-core to 0.40.4-SNAPSHOT
|
||||
a43b5c8 Merged feature/365-formal-support-for-tables-belonging-to-multiple-apps
|
||||
1d82bfd Merged feature/364-customizable-table-action-menus-and-additional-menu-support
|
||||
7287659 chore: bump package.json version to 0.40.0-SNAPSHOT to match pom.xml
|
||||
99a8f79 Merge pull request #131 from QRun-IO/feature/371-Anonymous-auth-module
|
||||
```
|
||||
|
||||
## What Was Done This Session
|
||||
|
||||
### Issue #128 Visual Regressions - Round 6
|
||||
1. Analyzed issue #134 - base path detection bug causing path duplication
|
||||
2. Identified two bugs in `detectBasePath()`:
|
||||
- Bug 1: `if (match && match[1])` fails for root deployments (empty string is falsy)
|
||||
- Bug 2: Strategy 3 incorrectly used current URL path as base path
|
||||
3. Fixed both bugs in `src/qqq/utils/PathUtils.ts`
|
||||
4. Created comprehensive test suite with 25 unit tests
|
||||
5. Verified all Playwright visual regression tests pass (18 tests)
|
||||
6. Created feature branch, committed, and opened PR #136
|
||||
7. Updated GitHub issue #134 with fix details
|
||||
|
||||
Fixed 5 regressions reported by Darin (compared to develop branch):
|
||||
## Next Steps
|
||||
|
||||
| Issue | Root Cause | Fix |
|
||||
|-------|------------|-----|
|
||||
| Sidenav double-highlight | CSS hover rules added in `064e86d` conflicted with JS hover | Removed CSS rules (lines 949-953) |
|
||||
| Chip border radius changed | `shape.borderRadius: 8` vs MUI default 4 | Changed default to 4 |
|
||||
| Menu padding increased | MuiMenuItem override set 8px vs menuItem.ts's ~5px | Removed override |
|
||||
| View field height reduced | Button typography changed (lineHeight 1.5→1.75, fontWeight 300→500) | Reverted to develop values |
|
||||
| Button color change | Button CSS scoped to `.qqq-themed` class (develop was unscoped) | Removed `.qqq-themed` scoping from button rules |
|
||||
|
||||
### Files Modified
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/qqq/styles/qqq-override-styles.css` | Removed sidenav hover CSS; removed `.qqq-themed` scoping from button rules |
|
||||
| `src/qqq/utils/createDynamicTheme.ts` | Fixed borderRadius default (4), removed MuiMenuItem, fixed button typography |
|
||||
| `src/qqq/utils/themeUtils.ts` | Fixed DEFAULT_THEME borderRadius and button typography |
|
||||
| `e2e/tests/round6-regressions.spec.ts` | Added 10 tests for Round 6 fixes (including Issue 5) |
|
||||
- [ ] Get PR #136 reviewed and merged
|
||||
- [ ] Review dependabot PR #135 (lodash bump)
|
||||
- [ ] Address npm audit vulnerabilities
|
||||
|
||||
## Test Status
|
||||
|
||||
| Suite | Tests | Status |
|
||||
|-------|-------|--------|
|
||||
| Playwright themed | 26 | PASS |
|
||||
| Round 6 regression tests | 10 | PASS |
|
||||
| Jest unit tests (PathUtils) | 25 | PASS |
|
||||
| Playwright visual regression | 18 | PASS |
|
||||
| Selenium fixture-based | ~100 | Not run this session |
|
||||
|
||||
## Next Steps
|
||||
## Key Files Modified This Session
|
||||
|
||||
1. Commit Round 6 fixes
|
||||
2. Darin to test on branch `feature/fix-visual-regressions-128`
|
||||
3. If approved, create PR to merge into develop
|
||||
|
||||
## GitHub Issue
|
||||
|
||||
https://github.com/QRun-IO/qqq-frontend-material-dashboard/issues/128
|
||||
|
||||
## QQQ #923 license alignment — 2026-09-30
|
||||
|
||||
Owner-approved first-party Apache-2.0 declarations now align across source headers, Maven/npm metadata and current contributor/theme documentation. Attribution, third-party notices, dependency pins and executable source are preserved. The license delta passed independent review. QQQ #937 adds bounded waits before existing saved-view background assertions; production behavior is unchanged. Browser verification requires the HTTPS frontend on port 3001 (proxy 8001), CIRCLECI=true and QQQ_SELENIUM_HEADLESS=true. Publish a new candidate only after validation, review and release gates pass. Earlier work recorded above is unchanged.
|
||||
|
||||
## QQQ #939/#940 dependency security — 2026-09-30
|
||||
|
||||
Isolated patch from `f80d76bffa1a9ba24a304a056eb5d9df606d4ab7`: Jackson BOM 2.21.7, root Axios <1 override 0.34.0 and webpack-dev-middleware override 7.4.6. The published middleware backport preserves the APIs/options used by CRA 5.0.1 and dev-server 4.15.2; its Node >=18.12/webpack 5 requirements fit this project's Node >=20 and webpack 5.105.4. Local verification passed 22 Maven units, 54 JS tests, security regressions, Material serialization and the production build (source-map warnings retained). Full dev-server tests rejected all 27 traversal probes while preserving assets, proxy/service-worker/history hooks, HMR and rebuilds; actual root/subpath `npm start` checks also passed. Evidence: `qqq-license-923/evidence/alignment/material-security-940/` in the coordinating workspace. No UI source or runtime modernization is included.
|
||||
|
||||
Fresh full and production-only npm audits both report 0 High/0 Critical; remaining totals are 15 Moderate/12 Low and 5 Moderate respectively. The old subpath disclosure baseline is retained; the middleware fix rejects traversal even though CRA still strips PUBLIC_URL's slash. The existing CRA source-overlay `_stats` mismatch reproduced identically before/after and is not fixed here. Public frontend-core 0.40.19-SNAPSHOT still pins Axios 0.33.0 outside this root override; #902 is separately coordinated. Independent review, full browser/hosted gates, GitHub alert reconciliation, RC2 integration and publication remain with the coordinator.
|
||||
|
||||
|
||||
## Public frontend-core integration — 2026-09-30
|
||||
|
||||
Pin the verified public frontend-core 0.40.20-SNAPSHOT from core PR48/snapshot job388. Its exact registry integrity, Apache LICENSE/NOTICE and library files match the reviewed package; an isolated public consumer passes four HTTP and three security checks with zero audit findings. Only the root dependency and core lock node changed in this integration. Independent pin review passed.
|
||||
|
||||
The combined Jackson/Axios/middleware/core candidate passes 54 JavaScript tests, production build and full Maven clean verify: 121 tests, zero failures/errors/skips/retries. Full audit reports zero High/Critical, 14 Moderate/12 Low; production reports zero High/Critical and five Moderate. Existing source-map/overlay and tool warnings remain recorded. Evidence is in the coordinating workspace under qqq-license-923/evidence/alignment/material-integrated-940/. Hosted checks, alert reconciliation, RC2 integration and existing publication gates remain; this is not RC/GA acceptance.
|
||||
|
||||
## QQQ #942 / #923 packaged notices — 2026-09-30
|
||||
|
||||
Bounded correction from signed PR162 merge `a37ead3b8c6b5a4819d29d853176374055dc1e8b`: Maven shared archive resources now copy the unchanged root LICENSE/NOTICE into all four published JARs. Resources 3.4.0 matches the prior integrated build. Actual release-profile packaging passed with reused verified public UI and runtime tests skipped; the executable archive guard fails on the existing public JARs and passes on all new local archives. Dependencies, Java/UI behavior and third-party notices are unchanged. Evidence: `qqq-license-923/evidence/alignment/material-notices-942/` in the coordination workspace. Independent review, signed integration, hosted required checks and verification of a newly published timestamped artifact remain; no public correction is claimed.
|
||||
- `src/qqq/utils/PathUtils.ts` - Bug fix
|
||||
- `src/qqq/utils/PathUtils.test.ts` - New test file (25 tests)
|
||||
@@ -22,7 +22,7 @@ The QQQ dashboard now supports runtime theme customization via CSS custom proper
|
||||
Add theme configuration to your QInstance:
|
||||
|
||||
```java
|
||||
MaterialDashboardThemeMetaData theme = new MaterialDashboardThemeMetaData()
|
||||
QThemeMetaData theme = new QThemeMetaData()
|
||||
// Core colors
|
||||
.withPrimaryColor("#1976D2")
|
||||
.withSecondaryColor("#9C27B0")
|
||||
@@ -144,7 +144,7 @@ Same pattern for H2-H6, Body1, Body2, Button, Caption.
|
||||
|
||||
### 1. Basic Color Test
|
||||
```java
|
||||
new MaterialDashboardThemeMetaData()
|
||||
new QThemeMetaData()
|
||||
.withPrimaryColor("#FF5722")
|
||||
.withSidebarBackgroundColor("#263238");
|
||||
```
|
||||
@@ -152,7 +152,7 @@ new MaterialDashboardThemeMetaData()
|
||||
|
||||
### 2. Typography Test
|
||||
```java
|
||||
new MaterialDashboardThemeMetaData()
|
||||
new QThemeMetaData()
|
||||
.withFontFamily("Georgia, serif")
|
||||
.withTypographyH1FontSize("3rem")
|
||||
.withTypographyH1FontWeight(800);
|
||||
@@ -161,7 +161,7 @@ new MaterialDashboardThemeMetaData()
|
||||
|
||||
### 3. Branded Header Test
|
||||
```java
|
||||
new MaterialDashboardThemeMetaData()
|
||||
new QThemeMetaData()
|
||||
.withBrandedHeaderEnabled(true)
|
||||
.withBrandedHeaderBackgroundColor("#1a237e")
|
||||
.withBrandedHeaderTextColor("#ffffff")
|
||||
@@ -172,7 +172,7 @@ new MaterialDashboardThemeMetaData()
|
||||
|
||||
### 4. Density Test
|
||||
```java
|
||||
new MaterialDashboardThemeMetaData()
|
||||
new QThemeMetaData()
|
||||
.withDensity("compact");
|
||||
```
|
||||
**Verify:** Reduced spacing throughout the UI.
|
||||
|
||||
+16
-67
@@ -1,77 +1,26 @@
|
||||
# TODO - QQQ Frontend Material Dashboard
|
||||
|
||||
**Last Updated:** 2026-01-14
|
||||
**Last Updated:** 2026-01-24
|
||||
|
||||
## In Progress
|
||||
|
||||
### Issue #128: Visual Regressions - READY FOR REVIEW
|
||||
- [ ] PR #136 - Base path detection fix (awaiting review)
|
||||
- Branch: `feature/134-base-path-detection-bug`
|
||||
- Closes issue #134
|
||||
|
||||
**Branch:** `feature/fix-visual-regressions-128`
|
||||
**Status:** All fixes complete, waiting on Darin to test
|
||||
## Open Items
|
||||
|
||||
- [x] Round 1-3: Add `.qqq-themed` class toggle, scope CSS overrides
|
||||
- [x] Round 4: Add CSS variable fallbacks to all `var(--qqq-*)` instances
|
||||
- [x] Round 5: Align ALL typography fallbacks with `typography.ts`
|
||||
- [x] Verify all 39 e2e tests pass (26 themed + 13 unthemed)
|
||||
- [x] Triple-check all 32 typography values against source of truth
|
||||
- [x] Commit typography fixes
|
||||
- [ ] **WAITING:** Darin to test and approve
|
||||
- [ ] Create PR to merge into develop
|
||||
- [ ] Publish snapshot after merge
|
||||
|
||||
---
|
||||
|
||||
### CI Playwright Timeout (Separate Issue)
|
||||
|
||||
**Branch:** `fix/ci-playwright-timeout`
|
||||
**Status:** Parked - lower priority than visual regressions
|
||||
|
||||
- [x] Diagnosed: webserver timeout (120s not enough for webpack)
|
||||
- [ ] Debug blank page issue in combined fixture server
|
||||
- [ ] Test locally until passing
|
||||
- [ ] Merge to develop
|
||||
- [ ] Review dependabot PR #135 - lodash bump
|
||||
- [ ] Address npm audit vulnerabilities (9 total: 2 critical, 1 high, 6 moderate)
|
||||
|
||||
## Completed (Recent)
|
||||
|
||||
- [x] **Issue #128 Round 5** - Typography fallbacks aligned with typography.ts
|
||||
- [x] **Issue #128 Round 4** - CSS variable fallbacks added
|
||||
- [x] **Issue #128 Round 3** - Scoped CSS to `.qqq-themed` class
|
||||
- [x] **39 Playwright tests passing** (26 themed + 13 unthemed)
|
||||
- [x] **PR #127 merged** - Playwright e2e test integration
|
||||
- [x] **PR #125 merged** - Pluggable themes + CSS selectors system
|
||||
|
||||
## Test Status
|
||||
|
||||
| Suite | Tests | Status |
|
||||
|-------|-------|--------|
|
||||
| Playwright themed | 26 | PASS |
|
||||
| Playwright unthemed | 13 | PASS |
|
||||
|
||||
## Known Issues
|
||||
|
||||
- CI Playwright tests may timeout (WIP fix on separate branch)
|
||||
- `seleniumwithqapplication` tests require full QQQ backend (hang locally)
|
||||
|
||||
## QQQ #923 license alignment — 2026-09-30
|
||||
|
||||
Owner-approved first-party Apache-2.0 declarations now align across source headers, Maven/npm metadata and current contributor/theme documentation. Attribution, third-party notices, dependency pins and executable source are preserved. The license delta passed independent review. QQQ #937 adds bounded waits before existing saved-view background assertions; production behavior is unchanged. Browser verification requires the HTTPS frontend on port 3001 (proxy 8001), CIRCLECI=true and QQQ_SELENIUM_HEADLESS=true. Publish a new candidate only after validation, review and release gates pass. Earlier work recorded above is unchanged.
|
||||
|
||||
## QQQ #939/#940 dependency security — 2026-09-30
|
||||
|
||||
- [x] Verify public Jackson 2.21.7/Axios 0.34.0 artifacts and middleware 7.4.6 backport; update BOM, overrides and their lock entries/transitive development dependencies.
|
||||
- [x] Preserve vulnerable Jackson/Axios baselines; pass security regressions, 22 Maven units, 54 JS tests, Material serialization and production build.
|
||||
- [x] Reproduce subpath disclosure on 5.3.4; verify the 7.4.6 override blocks all 27 full-server probes while preserving assets/hooks/HMR/rebuilds and actual root/subpath npm start. Rerun JS/build/audits: 0 High/0 Critical; preserve existing overlay defect evidence and remaining Moderate/Low tracking.
|
||||
- [ ] Coordinator: independently review/integrate this patch, reconcile GitHub alerts under #940/#892, coordinate frontend-core's own Axios pin under #902, and rerun RC2 browser/hosted/release gates before publication.
|
||||
|
||||
|
||||
## Public frontend-core integration — 2026-09-30
|
||||
|
||||
- [x] Verify public frontend-core 0.40.20-SNAPSHOT, pin its exact registry version/integrity and independently review the dependency delta.
|
||||
- [x] Pass the integrated 54 JavaScript tests, production build and 121 Maven/Selenium tests without failures, skips or retries; preserve remaining Moderate/Low audit findings.
|
||||
- [ ] Pass hosted PR checks, reconcile alerts and integrate the accepted changes into the RC2 draft while retaining #892 and the other release gates.
|
||||
|
||||
## QQQ #942 / #923 packaged notices
|
||||
|
||||
- [x] Include exact root LICENSE/NOTICE in main, sources, tests and Javadoc JARs using native Maven resource packaging; preserve existing archive contents.
|
||||
- [x] Prove the archive guard fails on the public baseline and altered notice bytes, and passes on all four actual local archives; document explicit post-package invocation.
|
||||
- [ ] Coordinator: independent review, signed integration and hosted checks; verify all four newly published JARs before claiming public correction, then coordinate the RC input with existing release gates.
|
||||
- [x] Issue #134 - Base path detection bug
|
||||
- Fixed `detectBasePath()` returning wrong path for root deployments
|
||||
- Removed unreliable Strategy 3 (single-segment path detection)
|
||||
- Added 25 unit tests for PathUtils
|
||||
- PR #136 created and pushed
|
||||
- [x] PR #131 - Auth module enhancements (merged)
|
||||
- [x] Feature #364 - Customizable table action menus (merged)
|
||||
- [x] Feature #365 - Tables belonging to multiple apps (merged)
|
||||
- [x] Update qqq-frontend-core to 0.40.4-SNAPSHOT
|
||||
+6
-179
@@ -47,13 +47,9 @@ const server = http.createServer((req, res) => {
|
||||
const url = new URL(req.url, `http://localhost:${PORT}`);
|
||||
const pathname = url.pathname;
|
||||
|
||||
// Support per-request fixture selection via ?fixture=name query parameter
|
||||
// This allows unthemed tests to request the 'index' fixture explicitly
|
||||
const requestedFixture = url.searchParams.get('fixture') || FIXTURE_NAME;
|
||||
|
||||
// /metaData endpoint - return main metadata with theme
|
||||
if (pathname === '/metaData' || pathname === '/metaData/' || pathname === '/api/metaData') {
|
||||
const data = loadFixture(`metaData/${requestedFixture}.json`);
|
||||
const data = loadFixture(`metaData/${FIXTURE_NAME}.json`);
|
||||
if (data) {
|
||||
serveJson(res, data);
|
||||
return;
|
||||
@@ -97,131 +93,15 @@ const server = http.createServer((req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
// /metaData/process/{processName} - process metadata (supports dots in name like person.bulkEdit)
|
||||
const processMetaMatch = pathname.match(/^\/metaData\/process\/(.+)$/);
|
||||
if (processMetaMatch) {
|
||||
const processName = processMetaMatch[1];
|
||||
const data = loadFixture(`metaData/process/${processName}.json`);
|
||||
// /processes/querySavedView/init - saved view init
|
||||
if (pathname === '/processes/querySavedView/init') {
|
||||
const data = loadFixture('processes/querySavedView/init.json');
|
||||
if (data) {
|
||||
serveJson(res, data);
|
||||
return;
|
||||
}
|
||||
// Default process metadata response (wrapped in 'process' key)
|
||||
serveJson(res, {
|
||||
process: {
|
||||
name: processName,
|
||||
label: processName.charAt(0).toUpperCase() + processName.slice(1).replace(/([A-Z])/g, ' $1'),
|
||||
tableName: 'person',
|
||||
frontendSteps: [
|
||||
{ name: 'input', label: 'Input', stepType: 'frontend', formFields: [], components: [] },
|
||||
{ name: 'review', label: 'Review', stepType: 'frontend', recordListFields: [], components: [] },
|
||||
{ name: 'result', label: 'Result', stepType: 'frontend', components: [{ type: 'PROCESS_SUMMARY_RESULTS' }] }
|
||||
]
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// /processes/{processName}/init - process init
|
||||
const processInitMatch = pathname.match(/^\/processes\/([\w.\-]+)\/init$/);
|
||||
if (processInitMatch) {
|
||||
const processName = processInitMatch[1];
|
||||
const data = loadFixture(`processes/${processName}/init.json`);
|
||||
if (data) {
|
||||
serveJson(res, data);
|
||||
return;
|
||||
}
|
||||
// Default process init response with stepper and UUID
|
||||
serveJson(res, {
|
||||
processUUID: 'test-process-uuid-12345',
|
||||
values: {},
|
||||
processMetaData: {
|
||||
name: processName,
|
||||
label: processName,
|
||||
frontendSteps: [
|
||||
{ name: 'input', label: 'Input', stepType: 'frontend', formFields: [], components: [] },
|
||||
{ name: 'review', label: 'Review', stepType: 'frontend', recordListFields: [], components: [] },
|
||||
{ name: 'result', label: 'Result', stepType: 'frontend', components: [{ type: 'PROCESS_SUMMARY_RESULTS' }] }
|
||||
]
|
||||
},
|
||||
nextStep: 'input'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// /processes/{processName}/{uuid}/records - get process records
|
||||
const processRecordsMatch = pathname.match(/^\/processes\/(\w+)\/([^\/]+)\/records$/);
|
||||
if (processRecordsMatch) {
|
||||
serveJson(res, {
|
||||
records: [],
|
||||
count: 0
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// /processes/{processName}/{uuid}/step/{stepName} - process step with UUID
|
||||
const processStepWithUuidMatch = pathname.match(/^\/processes\/(\w+)\/([^\/]+)\/step\/(\w+)$/);
|
||||
if (processStepWithUuidMatch) {
|
||||
const processName = processStepWithUuidMatch[1];
|
||||
const uuid = processStepWithUuidMatch[2];
|
||||
const stepName = processStepWithUuidMatch[3];
|
||||
// Return next step
|
||||
serveJson(res, {
|
||||
processUUID: uuid,
|
||||
values: {},
|
||||
processMetaData: {
|
||||
name: processName,
|
||||
label: processName,
|
||||
frontendSteps: [
|
||||
{ name: 'input', label: 'Input', stepType: 'frontend', formFields: [], components: [] },
|
||||
{ name: 'review', label: 'Review', stepType: 'frontend', recordListFields: [], components: [] },
|
||||
{ name: 'result', label: 'Result', stepType: 'frontend', components: [{ type: 'PROCESS_SUMMARY_RESULTS' }] }
|
||||
]
|
||||
},
|
||||
nextStep: stepName === 'input' ? 'review' : (stepName === 'review' ? 'result' : null),
|
||||
processResults: stepName === 'result' ? { message: 'Process completed successfully!' } : undefined
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// /processes/{processName}/step/{stepName} - process step (legacy, no UUID)
|
||||
const processStepMatch = pathname.match(/^\/processes\/(\w+)\/step\/(\w+)$/);
|
||||
if (processStepMatch) {
|
||||
const processName = processStepMatch[1];
|
||||
const stepName = processStepMatch[2];
|
||||
// Return next step
|
||||
serveJson(res, {
|
||||
processUUID: 'test-process-uuid-12345',
|
||||
values: {},
|
||||
processMetaData: {
|
||||
name: processName,
|
||||
label: processName,
|
||||
frontendSteps: [
|
||||
{ name: 'input', label: 'Input', stepType: 'frontend', formFields: [], components: [] },
|
||||
{ name: 'review', label: 'Review', stepType: 'frontend', recordListFields: [], components: [] },
|
||||
{ name: 'result', label: 'Result', stepType: 'frontend', components: [{ type: 'PROCESS_SUMMARY_RESULTS' }] }
|
||||
]
|
||||
},
|
||||
nextStep: stepName === 'input' ? 'review' : (stepName === 'review' ? 'result' : null),
|
||||
processResults: stepName === 'result' ? { message: 'Process completed successfully!' } : undefined
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// /widget/{widgetName} - widget data
|
||||
const widgetMatch = pathname.match(/^\/widget\/(.+)$/);
|
||||
if (widgetMatch) {
|
||||
const widgetName = widgetMatch[1];
|
||||
const data = loadFixture(`widget/${widgetName}.json`);
|
||||
if (data) {
|
||||
serveJson(res, data);
|
||||
return;
|
||||
}
|
||||
// Default empty widget response
|
||||
serveJson(res, {
|
||||
type: 'table',
|
||||
data: []
|
||||
});
|
||||
// Default response if fixture not found
|
||||
serveJson(res, { values: {} });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -277,59 +157,6 @@ const server = http.createServer((req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// /data/{tableName}/count - alternate count endpoint
|
||||
const dataCountMatch = pathname.match(/^\/data\/(\w+)\/count$/);
|
||||
if (dataCountMatch) {
|
||||
serveJson(res, { count: 2 });
|
||||
return;
|
||||
}
|
||||
|
||||
// /data/{tableName}/{id} - alternate single record lookup
|
||||
const dataRecordMatch = pathname.match(/^\/data\/(\w+)\/(\d+)$/);
|
||||
if (dataRecordMatch) {
|
||||
const tableName = dataRecordMatch[1];
|
||||
const recordId = dataRecordMatch[2];
|
||||
serveJson(res, {
|
||||
values: {
|
||||
id: parseInt(recordId),
|
||||
firstName: recordId === '1' ? 'John' : 'Jane',
|
||||
lastName: recordId === '1' ? 'Doe' : 'Smith',
|
||||
email: recordId === '1' ? 'john.doe@example.com' : 'jane.smith@example.com',
|
||||
birthDate: '1990-01-15',
|
||||
createDate: '2024-01-01T00:00:00Z',
|
||||
modifyDate: '2024-01-01T00:00:00Z'
|
||||
},
|
||||
tableName: tableName
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// /qqq/v1/table/{tableName}/{id} - single record lookup
|
||||
const recordMatch = pathname.match(/^\/qqq\/v1\/table\/(\w+)\/(\d+)$/);
|
||||
if (recordMatch) {
|
||||
const tableName = recordMatch[1];
|
||||
const recordId = recordMatch[2];
|
||||
const data = loadFixture(`qqq/v1/table/${tableName}/${recordId}.json`);
|
||||
if (data) {
|
||||
serveJson(res, data);
|
||||
return;
|
||||
}
|
||||
// Default person record response
|
||||
serveJson(res, {
|
||||
values: {
|
||||
id: parseInt(recordId),
|
||||
firstName: recordId === '1' ? 'John' : 'Jane',
|
||||
lastName: recordId === '1' ? 'Doe' : 'Smith',
|
||||
email: recordId === '1' ? 'john.doe@example.com' : 'jane.smith@example.com',
|
||||
birthDate: '1990-01-15',
|
||||
createDate: '2024-01-01T00:00:00Z',
|
||||
modifyDate: '2024-01-01T00:00:00Z'
|
||||
},
|
||||
tableName: tableName
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Default 404
|
||||
serve404(res, pathname);
|
||||
});
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import {test, expect, Page} from "@playwright/test";
|
||||
import {readFileSync} from "fs";
|
||||
import path from "path";
|
||||
|
||||
/** First-party HTTP fixtures exercise browser request and error behavior; backend authorization is tested separately. */
|
||||
async function configure(page: Page, bridgeReadable = false, denyCount = false, denyQuery = false)
|
||||
{
|
||||
const fixture = (name: string) => JSON.parse(readFileSync(path.resolve(__dirname, "../../src/test/resources/fixtures", name), "utf8"));
|
||||
const global = fixture("metaData/index.json");
|
||||
const person = fixture("qqq/v1/metaData/table/person.json");
|
||||
const toy = {name: "toy", label: "Toy", primaryKeyField: "id", readPermission: true, insertPermission: true,
|
||||
fields: {name: {name: "name", label: "Toy name", type: "STRING"}}};
|
||||
global.tables.pet = {name: "pet", label: "Pet", readPermission: bridgeReadable, insertPermission: true};
|
||||
global.tables.toy = {...toy};
|
||||
person.exposedJoins = [{label: "Toys", isMany: true, joinTable: toy, joinPath: [
|
||||
{name: "personPet", leftTable: "person", rightTable: "pet", type: "ONE_TO_MANY"},
|
||||
{name: "toyPet", leftTable: "toy", rightTable: "pet", type: "MANY_TO_ONE"}
|
||||
]}];
|
||||
const queryInputs: any[] = [];
|
||||
const countInputs: any[] = [];
|
||||
const errors: string[] = [];
|
||||
page.on("pageerror", error => errors.push(error.message));
|
||||
await page.route(/\/metaData\/?(?:\?.*)?$/, route => route.fulfill({json: global}));
|
||||
await page.route("**/metaData/table/person*", route => route.fulfill({json: person}));
|
||||
await page.route("**/qqq/v1/table/person/query*", async route =>
|
||||
{
|
||||
queryInputs.push(route.request().postDataJSON());
|
||||
if (denyQuery) await route.fulfill({status: 403, json: {error: "Permission denied."}});
|
||||
else
|
||||
{
|
||||
// Complete the successful Query after the denied Count is already displayed.
|
||||
if (denyCount) await page.getByRole("alert").filter({hasText: "Cannot count records:"}).waitFor();
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
await page.route("**/qqq/v1/table/person/count*", async route =>
|
||||
{
|
||||
countInputs.push(route.request().postDataJSON());
|
||||
if (denyCount) await route.fulfill({status: 403, json: {error: "Permission denied."}});
|
||||
else await route.continue();
|
||||
});
|
||||
return {queryInputs, countInputs, errors};
|
||||
}
|
||||
|
||||
test("restricted bridge stays out of automatic requests and field choices", async ({page}) =>
|
||||
{
|
||||
const {queryInputs, countInputs, errors} = await configure(page);
|
||||
await page.goto("/peopleApp/greetingsApp/person");
|
||||
await expect.poll(() => queryInputs.length).toBeGreaterThan(0);
|
||||
await expect.poll(() => countInputs.length).toBeGreaterThan(0);
|
||||
await expect(page.getByRole("cell", {name: "Test", exact: true})).toBeVisible();
|
||||
expect(queryInputs.every(input => !input.joins?.some((join: any) => join.joinTable === "toy"))).toBe(true);
|
||||
expect(countInputs.every(input => !input.joins?.some((join: any) => join.joinTable === "toy"))).toBe(true);
|
||||
await page.locator('[data-qqq-id="button-columns"]').click();
|
||||
await expect(page.locator(".fieldListMenuBody-columns")).toBeVisible();
|
||||
await expect(page.locator(".fieldListMenuBody-columns")).not.toContainText("Toy name");
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
test("readable bridge restores query choices and selected joins", async ({page}) =>
|
||||
{
|
||||
const {queryInputs, errors} = await configure(page, true);
|
||||
await page.goto("/peopleApp/greetingsApp/person");
|
||||
await expect.poll(() => queryInputs.length).toBeGreaterThan(0);
|
||||
await page.locator('[data-qqq-id="button-columns"]').click();
|
||||
await page.locator(".fieldListMenuBody-columns").getByText("Toy name", {exact: true}).click();
|
||||
await expect.poll(() => queryInputs.some(input => input.joins?.some((join: any) => join.joinTable === "toy"))).toBe(true);
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
test("denied Count remains visible after successful record loading", async ({page}) =>
|
||||
{
|
||||
const {queryInputs, countInputs, errors} = await configure(page, false, true);
|
||||
await page.goto("/peopleApp/greetingsApp/person");
|
||||
await expect.poll(() => queryInputs.length).toBeGreaterThan(0);
|
||||
await expect.poll(() => countInputs.length).toBeGreaterThan(0);
|
||||
await expect(page.getByRole("cell", {name: "Test", exact: true})).toBeVisible();
|
||||
await expect(page.getByRole("alert").filter({hasText: "Cannot count records:"})).toBeVisible();
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
test("saved denied criteria and ordering are retained and show an error", async ({page}) =>
|
||||
{
|
||||
const {queryInputs, countInputs, errors} = await configure(page, false, true, true);
|
||||
const filter = {criteria: [{fieldName: "toy.name", operator: "EQUALS", values: ["restricted"]}], orderBys: [{fieldName: "toy.name", isAscending: true}]};
|
||||
await page.goto("/peopleApp/greetingsApp/person?filter=" + encodeURIComponent(JSON.stringify(filter)));
|
||||
await expect.poll(() => queryInputs.length).toBeGreaterThan(0);
|
||||
await expect.poll(() => countInputs.length).toBeGreaterThan(0);
|
||||
expect(queryInputs[0].filter.criteria).toEqual(filter.criteria);
|
||||
expect(queryInputs[0].filter.orderBys).toEqual(filter.orderBys);
|
||||
expect(countInputs[0].filter.criteria).toEqual(filter.criteria);
|
||||
await expect(page.getByRole("alert").filter({hasText: "Permission denied."}).first()).toBeVisible();
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
@@ -1,345 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Round 6 Visual Regression Tests
|
||||
*
|
||||
* Tests verify fixes for issues reported by Darin:
|
||||
* 1. Sidenav double-highlight on hover
|
||||
* 2. Chip border radius changed
|
||||
* 3. Menu item padding increased
|
||||
* 4. View field height reduced (button typography)
|
||||
*/
|
||||
|
||||
test.describe('Round 6 Regression Fixes - Themed App', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Use themed fixture
|
||||
await page.goto('/testApp/person');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
|
||||
test.describe('Issue 1: Sidenav Hover - Single Highlight Only', () => {
|
||||
test('sidenav item has only JS-controlled hover styling (no CSS override)', async ({ page }) => {
|
||||
// The fix removed CSS hover rules that were causing double-highlight
|
||||
// Wait for sidebar to be visible (it may be collapsed on smaller screens)
|
||||
const drawer = page.locator('.MuiDrawer-paper');
|
||||
await expect(drawer).toBeVisible();
|
||||
|
||||
// Find any visible nav item in the sidebar
|
||||
const sidenavItem = page.locator('.MuiDrawer-paper .MuiListItem-root').first();
|
||||
await expect(sidenavItem).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Get initial background
|
||||
const initialBg = await sidenavItem.evaluate(el => getComputedStyle(el).backgroundColor);
|
||||
|
||||
// Hover and check background changes (should be controlled by JS, not CSS)
|
||||
await sidenavItem.hover();
|
||||
await page.waitForTimeout(300);
|
||||
const hoverBg = await sidenavItem.evaluate(el => getComputedStyle(el).backgroundColor);
|
||||
|
||||
// The hover background should change, but there should not be multiple overlapping backgrounds
|
||||
// The key test is that the page renders without visual artifacts
|
||||
expect(hoverBg).toBeTruthy();
|
||||
});
|
||||
|
||||
test('no redundant CSS hover rules in computed styles', async ({ page }) => {
|
||||
// Check that the removed CSS rule is not applying
|
||||
const result = await page.evaluate(() => {
|
||||
// Look for any sidenav item
|
||||
const item = document.querySelector('.MuiDrawer-paper a, .MuiDrawer-paper .MuiListItem-root');
|
||||
if (!item) return { found: false, hasInlineHover: false };
|
||||
|
||||
// Check if any external stylesheet is applying the removed hover rule
|
||||
// We removed: .MuiDrawer-paper a:hover, .MuiDrawer-paper .MuiListItem-root:hover
|
||||
const sheets = Array.from(document.styleSheets);
|
||||
let hasRemovedRule = false;
|
||||
|
||||
try {
|
||||
for (const sheet of sheets) {
|
||||
try {
|
||||
const rules = Array.from(sheet.cssRules || []);
|
||||
for (const rule of rules) {
|
||||
if (rule instanceof CSSStyleRule) {
|
||||
const selector = rule.selectorText;
|
||||
if (selector &&
|
||||
(selector.includes('.MuiDrawer-paper a:hover') ||
|
||||
selector.includes('.MuiDrawer-paper .MuiListItem-root:hover'))) {
|
||||
// Check if this rule sets background-color
|
||||
if (rule.style.backgroundColor) {
|
||||
hasRemovedRule = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Cross-origin stylesheets will throw, ignore them
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
return { found: true, hasRemovedRule };
|
||||
});
|
||||
|
||||
expect(result.found).toBe(true);
|
||||
expect(result.hasRemovedRule).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Issue 2: Chip Border Radius (4px default, not 8px)', () => {
|
||||
test('chips use borderRadius based on 4px default (2px for chip)', async ({ page }) => {
|
||||
// Navigate to a page with chips if available, or test with a chip if present
|
||||
// The default borderRadius is 4px, chips use borderRadius/2 = 2px
|
||||
const result = await page.evaluate(() => {
|
||||
// Check the shape.borderRadius from the theme via CSS variable
|
||||
const root = document.documentElement;
|
||||
const borderRadiusVar = getComputedStyle(root).getPropertyValue('--qqq-border-radius').trim();
|
||||
|
||||
// Also check if any chip is rendered
|
||||
const chip = document.querySelector('.MuiChip-root');
|
||||
let chipBorderRadius = '';
|
||||
if (chip) {
|
||||
chipBorderRadius = getComputedStyle(chip).borderRadius;
|
||||
}
|
||||
|
||||
return {
|
||||
cssVar: borderRadiusVar,
|
||||
chipBorderRadius,
|
||||
hasChip: !!chip
|
||||
};
|
||||
});
|
||||
|
||||
// The fixture uses 12px, but default should be 4px
|
||||
// If no chip found, at least verify the CSS variable is set
|
||||
if (result.hasChip) {
|
||||
// Chip borderRadius should be half of the theme borderRadius
|
||||
// With 12px theme (from fixture), chip should be 6px
|
||||
expect(result.chipBorderRadius).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Issue 3: Menu Item Padding (original values, not increased)', () => {
|
||||
test('menu items have correct padding from menuItem.ts (~5px 16px)', async ({ page }) => {
|
||||
// Find a button that opens a menu
|
||||
const menuButton = page.locator('[aria-haspopup="true"]').first();
|
||||
|
||||
if (await menuButton.isVisible().catch(() => false)) {
|
||||
await menuButton.click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
const menuItem = document.querySelector('.MuiMenuItem-root');
|
||||
if (!menuItem) return { found: false, padding: '' };
|
||||
|
||||
const styles = getComputedStyle(menuItem);
|
||||
return {
|
||||
found: true,
|
||||
padding: styles.padding,
|
||||
paddingTop: styles.paddingTop,
|
||||
paddingBottom: styles.paddingBottom,
|
||||
paddingLeft: styles.paddingLeft,
|
||||
paddingRight: styles.paddingRight,
|
||||
};
|
||||
});
|
||||
|
||||
if (result.found) {
|
||||
// Original menuItem.ts uses pxToRem(4.8) which is ~5px vertical
|
||||
// Should NOT be 8px (that was our regression)
|
||||
const topPadding = parseFloat(result.paddingTop);
|
||||
const bottomPadding = parseFloat(result.paddingBottom);
|
||||
|
||||
// Padding should be less than 7px (the regression was 8px)
|
||||
expect(topPadding).toBeLessThan(7);
|
||||
expect(bottomPadding).toBeLessThan(7);
|
||||
}
|
||||
} else {
|
||||
// Skip if no menu button found - test passes vacuously
|
||||
test.skip();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Issue 5: Button Color (Edit Filters and Columns)', () => {
|
||||
test('text primary buttons have correct color styling (not scoped to .qqq-themed)', async ({ page }) => {
|
||||
// The fix removed .qqq-themed scoping so button color applies to ALL apps
|
||||
const result = await page.evaluate(() => {
|
||||
// Look for any text primary button (like "Edit Filters and Columns")
|
||||
const button = document.querySelector('.MuiButton-textPrimary');
|
||||
if (!button) return { found: false, color: '' };
|
||||
|
||||
const styles = getComputedStyle(button);
|
||||
return {
|
||||
found: true,
|
||||
color: styles.color,
|
||||
};
|
||||
});
|
||||
|
||||
if (result.found) {
|
||||
// Button should have a color applied (not default inherited)
|
||||
expect(result.color).toBeTruthy();
|
||||
// The color should be from our CSS (--qqq-primary-color fallback is #0062FF)
|
||||
// RGB values will vary based on theme, but should not be black/inherit
|
||||
} else {
|
||||
// Skip if no text primary button found
|
||||
test.skip();
|
||||
}
|
||||
});
|
||||
|
||||
test('button CSS rules are NOT scoped to .qqq-themed class', async ({ page }) => {
|
||||
// Verify that button color CSS is not restricted to themed apps
|
||||
const result = await page.evaluate(() => {
|
||||
const sheets = Array.from(document.styleSheets);
|
||||
let hasUnscopedButtonRule = false;
|
||||
let hasScopedButtonRule = false;
|
||||
|
||||
try {
|
||||
for (const sheet of sheets) {
|
||||
try {
|
||||
const rules = Array.from(sheet.cssRules || []);
|
||||
for (const rule of rules) {
|
||||
if (rule instanceof CSSStyleRule) {
|
||||
const selector = rule.selectorText;
|
||||
if (selector) {
|
||||
// Check for unscoped button rules (what we want)
|
||||
if (selector === '.MuiButton-textPrimary' ||
|
||||
selector === '.MuiButton-containedPrimary') {
|
||||
hasUnscopedButtonRule = true;
|
||||
}
|
||||
// Check for scoped button rules (what we removed)
|
||||
if (selector.includes('.qqq-themed') &&
|
||||
selector.includes('.MuiButton-')) {
|
||||
hasScopedButtonRule = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Cross-origin stylesheets will throw, ignore them
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
return { hasUnscopedButtonRule, hasScopedButtonRule };
|
||||
});
|
||||
|
||||
// Should have unscoped button rules (matching develop behavior)
|
||||
expect(result.hasUnscopedButtonRule).toBe(true);
|
||||
// Should NOT have .qqq-themed scoped button rules (regression fix)
|
||||
expect(result.hasScopedButtonRule).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Issue 4: Button Typography (view field height)', () => {
|
||||
test('buttons have lineHeight of 1.75 (not 1.5)', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
// Find any button
|
||||
const button = document.querySelector('.MuiButton-root');
|
||||
if (!button) return { found: false, lineHeight: '' };
|
||||
|
||||
const styles = getComputedStyle(button);
|
||||
return {
|
||||
found: true,
|
||||
lineHeight: styles.lineHeight,
|
||||
fontWeight: styles.fontWeight,
|
||||
};
|
||||
});
|
||||
|
||||
if (result.found) {
|
||||
// lineHeight should be 1.75, not 1.5
|
||||
// Note: computed lineHeight may be in px, so we check it's not the smaller value
|
||||
const lineHeight = parseFloat(result.lineHeight);
|
||||
|
||||
// If it's a ratio (like 1.75), check it directly
|
||||
// If it's in px, it should reflect the larger lineHeight
|
||||
if (lineHeight < 10) {
|
||||
// It's a ratio
|
||||
expect(lineHeight).toBeGreaterThanOrEqual(1.7);
|
||||
}
|
||||
} else {
|
||||
test.skip();
|
||||
}
|
||||
});
|
||||
|
||||
test('buttons have fontWeight of 500 or configured value (not 300)', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const button = document.querySelector('.MuiButton-root');
|
||||
if (!button) return { found: false, fontWeight: '' };
|
||||
|
||||
return {
|
||||
found: true,
|
||||
fontWeight: getComputedStyle(button).fontWeight,
|
||||
};
|
||||
});
|
||||
|
||||
if (result.found) {
|
||||
// fontWeight should be >= 500 (develop value), not 300
|
||||
// The fixture sets 600, default should be 500
|
||||
const weight = parseInt(result.fontWeight);
|
||||
expect(weight).toBeGreaterThanOrEqual(500);
|
||||
} else {
|
||||
test.skip();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* NOTE: Unthemed tests are in unthemed-regression.spec.ts
|
||||
* This file tests the themed app behavior after Round 6 fixes.
|
||||
*
|
||||
* The key verifications for default values:
|
||||
* - Default borderRadius changed from 8px to 4px (in parseBorderRadius)
|
||||
* - Default button fontWeight changed from 300 to 500
|
||||
* - Default button lineHeight changed from 1.5 to 1.75
|
||||
*
|
||||
* These defaults apply when no theme is configured OR when theme
|
||||
* properties are not explicitly set.
|
||||
*/
|
||||
test.describe('Round 6 - Default Value Verification (via themed fixture override)', () => {
|
||||
test('parseBorderRadius defaults to 4 (verified via code inspection)', async ({ page }) => {
|
||||
// This test verifies our code change by checking the DEFAULT_THEME
|
||||
// The actual rendered value in themed tests uses fixture's 12px
|
||||
await page.goto('/testApp/person');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
|
||||
// Check that the CSS variable is set (fixture uses 12px)
|
||||
const result = await page.evaluate(() => {
|
||||
const cssVar = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--qqq-border-radius').trim();
|
||||
return { cssVar };
|
||||
});
|
||||
|
||||
// Fixture uses 12px, which proves the theming system works
|
||||
// The default of 4px applies when no theme is configured
|
||||
expect(result.cssVar).toBe('12px');
|
||||
});
|
||||
|
||||
test('DEFAULT_THEME button values are correct (500 weight, 1.75 lineHeight)', async ({ page }) => {
|
||||
// The fixture overrides these, but we verify the themed app works
|
||||
await page.goto('/testApp/person');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
const button = document.querySelector('.MuiButton-root');
|
||||
if (!button) return { found: false };
|
||||
|
||||
const styles = getComputedStyle(button);
|
||||
return {
|
||||
found: true,
|
||||
fontWeight: styles.fontWeight,
|
||||
lineHeight: styles.lineHeight,
|
||||
};
|
||||
});
|
||||
|
||||
if (result.found) {
|
||||
// Fixture sets typographyButtonFontWeight: 600
|
||||
// This proves the theming override works
|
||||
const weight = parseInt(result.fontWeight);
|
||||
expect(weight).toBeGreaterThanOrEqual(500);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,523 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {colorsMatch} from "../utils/color-helpers";
|
||||
|
||||
/**
|
||||
* STRICT Theme Verification Tests for QQQ MaterialDashboardThemeMetaData
|
||||
*
|
||||
* ALL tests verify ACTUAL rendered values on components.
|
||||
* Tests FAIL if:
|
||||
* 1. The expected element is not found
|
||||
* 2. The rendered value doesn't match the configured value
|
||||
*
|
||||
* Theme values from withFullCustomTheme.json fixture
|
||||
*/
|
||||
const THEME = {
|
||||
// Core colors
|
||||
primaryColor: '#E91E63',
|
||||
secondaryColor: '#9C27B0',
|
||||
backgroundColor: '#ECEFF1',
|
||||
surfaceColor: '#FAFAFA',
|
||||
textPrimary: '#263238',
|
||||
textSecondary: '#607D8B',
|
||||
// Status colors
|
||||
errorColor: '#D50000',
|
||||
warningColor: '#FF6D00',
|
||||
successColor: '#00C853',
|
||||
infoColor: '#2962FF',
|
||||
// Typography
|
||||
fontFamily: '"Inter", "Helvetica", sans-serif',
|
||||
fontSizeBase: '15px',
|
||||
fontWeightLight: 300,
|
||||
fontWeightRegular: 400,
|
||||
fontWeightMedium: 500,
|
||||
fontWeightBold: 700,
|
||||
// Button typography (from fixture - buttons use this, not fontWeightMedium)
|
||||
typographyButtonFontWeight: 600,
|
||||
// UI
|
||||
borderRadius: '12px',
|
||||
density: 'normal',
|
||||
iconStyle: 'rounded',
|
||||
// Branded header
|
||||
brandedHeaderEnabled: true,
|
||||
brandedHeaderBackgroundColor: '#1A237E',
|
||||
brandedHeaderTextColor: '#E8EAF6',
|
||||
brandedHeaderHeight: '56px',
|
||||
// Sidebar
|
||||
sidebarBackgroundColor: '#1B5E20',
|
||||
sidebarTextColor: '#C8E6C9',
|
||||
sidebarIconColor: '#A5D6A7',
|
||||
sidebarSelectedBackgroundColor: '#2E7D32',
|
||||
sidebarSelectedTextColor: '#FFFFFF',
|
||||
sidebarHoverBackgroundColor: 'rgba(255, 255, 255, 0.15)',
|
||||
sidebarDividerColor: 'rgba(255, 255, 255, 0.25)',
|
||||
// Tables
|
||||
tableHeaderBackgroundColor: '#7B1FA2',
|
||||
tableHeaderTextColor: '#FFFFFF',
|
||||
tableRowHoverColor: '#F3E5F5',
|
||||
tableBorderColor: '#E1BEE7',
|
||||
// General UI
|
||||
dividerColor: '#CFD8DC',
|
||||
borderColor: '#B0BEC5',
|
||||
};
|
||||
|
||||
// Helper to format assertion message
|
||||
function colorMismatch(property: string, expected: string, actual: string): string {
|
||||
return `${property}: expected ${expected}, got ${actual}`;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CORE COLORS - Actual Rendered Values
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Core Colors - Rendered', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/testApp/person');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(2500);
|
||||
});
|
||||
|
||||
test('primaryColor: CSS variable is set correctly', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
// Check if CSS variable is properly set - this is the authoritative source
|
||||
// Note: This app uses "info" colored buttons, not "primary", so we verify
|
||||
// the CSS variable rather than button backgrounds
|
||||
const cssVar = getComputedStyle(document.documentElement).getPropertyValue('--qqq-primary-color').trim();
|
||||
if (cssVar) {
|
||||
return { found: true, value: cssVar, debug: 'CSS variable --qqq-primary-color' };
|
||||
}
|
||||
// Fallback: Try to find primary-styled buttons
|
||||
const selectors = [
|
||||
'.MuiButton-containedPrimary',
|
||||
'button[class*="containedPrimary"]',
|
||||
'.MuiButton-root[class*="Primary"]'
|
||||
];
|
||||
for (const sel of selectors) {
|
||||
const btn = document.querySelector(sel);
|
||||
if (btn) {
|
||||
const bg = getComputedStyle(btn).backgroundColor;
|
||||
if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') {
|
||||
return { found: true, value: bg, debug: btn.className };
|
||||
}
|
||||
}
|
||||
}
|
||||
return { found: false, value: '', debug: 'No primary color CSS variable or primary button found' };
|
||||
});
|
||||
expect(result.found, `Primary color not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.primaryColor), colorMismatch('primaryColor', THEME.primaryColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('secondaryColor: links use configured color', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('a.MuiLink-root, main a:not(.MuiButton-root), .MuiTypography-root a');
|
||||
if (!el) return { found: false, value: '', debug: 'No link found' };
|
||||
return { found: true, value: getComputedStyle(el).color, debug: el.className };
|
||||
});
|
||||
expect(result.found, `Link not found: ${result.debug}`).toBe(true);
|
||||
// Accept either primary or secondary color for links
|
||||
const matchesPrimary = colorsMatch(result.value, THEME.primaryColor);
|
||||
const matchesSecondary = colorsMatch(result.value, THEME.secondaryColor);
|
||||
expect(matchesPrimary || matchesSecondary, colorMismatch('secondaryColor', THEME.secondaryColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('backgroundColor: page body uses configured background', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
return { value: getComputedStyle(document.body).backgroundColor };
|
||||
});
|
||||
expect(colorsMatch(result.value, THEME.backgroundColor), colorMismatch('backgroundColor', THEME.backgroundColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('surfaceColor: cards use configured background', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
// Find cards/paper in main content, NOT in the drawer (sidebar)
|
||||
const el = document.querySelector('main .MuiCard-root, main .MuiPaper-root, .MuiCard-root:not(.MuiDrawer-paper):not(.MuiDrawer-root *)');
|
||||
if (!el) return { found: false, value: '', debug: 'No card/paper found in main content' };
|
||||
return { found: true, value: getComputedStyle(el).backgroundColor, debug: el.className };
|
||||
});
|
||||
expect(result.found, `Card/Paper not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.surfaceColor), colorMismatch('surfaceColor', THEME.surfaceColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('textPrimary: headings use configured color', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
// Find headings NOT in the sidebar/drawer
|
||||
const allHeadings = document.querySelectorAll('h1, h2, h3, h4, h5, h6, .MuiTypography-h1, .MuiTypography-h2, .MuiTypography-h3, .MuiTypography-h4, .MuiTypography-h5, .MuiTypography-h6');
|
||||
for (const el of allHeadings) {
|
||||
// Skip headings inside drawer/sidebar
|
||||
if (el.closest('.MuiDrawer-root') || el.closest('.MuiDrawer-paper')) continue;
|
||||
return { found: true, value: getComputedStyle(el).color, debug: el.className };
|
||||
}
|
||||
return { found: false, value: '', debug: 'No heading found outside sidebar' };
|
||||
});
|
||||
expect(result.found, `Heading not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.textPrimary), colorMismatch('textPrimary', THEME.textPrimary, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('textSecondary: body2/caption use configured color', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
// Find body2/caption NOT in sidebar, drawer, or branded header
|
||||
const allElements = document.querySelectorAll('.MuiTypography-body2, .MuiTypography-caption, .MuiTypography-subtitle2');
|
||||
for (const el of allElements) {
|
||||
// Skip elements inside drawer, sidebar, or branded header
|
||||
if (el.closest('.MuiDrawer-root') || el.closest('.MuiDrawer-paper') || el.closest('.qqq-branded-header-bar')) continue;
|
||||
return { found: true, value: getComputedStyle(el).color, debug: el.className };
|
||||
}
|
||||
// Fallback: check if the CSS variable is properly set
|
||||
const cssVar = getComputedStyle(document.documentElement).getPropertyValue('--qqq-text-secondary').trim();
|
||||
if (cssVar) {
|
||||
return { found: true, value: cssVar, debug: 'CSS variable --qqq-text-secondary' };
|
||||
}
|
||||
return { found: false, value: '', debug: 'No body2/caption found outside sidebar/header and no CSS variable' };
|
||||
});
|
||||
expect(result.found, `Secondary text not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.textSecondary), colorMismatch('textSecondary', THEME.textSecondary, result.value)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// BRANDED HEADER - Actual Rendered Values
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Branded Header - Rendered', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
|
||||
test('brandedHeaderBackgroundColor: header uses configured background', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const selectors = ['.qqq-branded-header-bar', '[class*="NavBar"]', 'header', '.MuiAppBar-root'];
|
||||
for (const sel of selectors) {
|
||||
const el = document.querySelector(sel);
|
||||
if (el) {
|
||||
const bg = getComputedStyle(el).backgroundColor;
|
||||
if (bg && bg !== 'rgba(0, 0, 0, 0)') {
|
||||
return { found: true, value: bg, selector: sel };
|
||||
}
|
||||
}
|
||||
}
|
||||
return { found: false, value: '', selector: '' };
|
||||
});
|
||||
expect(result.found, 'Branded header not found').toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.brandedHeaderBackgroundColor), colorMismatch('brandedHeaderBackgroundColor', THEME.brandedHeaderBackgroundColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('brandedHeaderTextColor: header text uses configured color', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
// Specifically look for the branded header bar
|
||||
const header = document.querySelector('.qqq-branded-header-bar');
|
||||
if (!header) return { found: false, value: '', debug: 'No .qqq-branded-header-bar found' };
|
||||
|
||||
// First try to find a typography element with actual text
|
||||
const textElements = header.querySelectorAll('.MuiTypography-root, span:not(:empty)');
|
||||
for (const el of textElements) {
|
||||
const text = el.textContent?.trim();
|
||||
if (text && text.length > 0) {
|
||||
return { found: true, value: getComputedStyle(el).color, debug: `${el.className}: "${text.substring(0, 20)}"` };
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: check the CSS variable is set correctly
|
||||
const cssVar = getComputedStyle(document.documentElement).getPropertyValue('--qqq-branded-header-text-color').trim();
|
||||
if (cssVar) {
|
||||
return { found: true, value: cssVar, debug: 'CSS variable --qqq-branded-header-text-color' };
|
||||
}
|
||||
|
||||
return { found: false, value: '', debug: 'No text element in branded header' };
|
||||
});
|
||||
expect(result.found, `Header text not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.brandedHeaderTextColor), colorMismatch('brandedHeaderTextColor', THEME.brandedHeaderTextColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('brandedHeaderHeight: header uses configured height', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const header = document.querySelector('.qqq-branded-header-bar')
|
||||
|| document.querySelector('[class*="NavBar"]')
|
||||
|| document.querySelector('header');
|
||||
if (!header) return { found: false, value: '', debug: 'No header found' };
|
||||
return { found: true, value: getComputedStyle(header).height, debug: '' };
|
||||
});
|
||||
expect(result.found, `Header not found: ${result.debug}`).toBe(true);
|
||||
expect(result.value, colorMismatch('brandedHeaderHeight', THEME.brandedHeaderHeight, result.value)).toBe(THEME.brandedHeaderHeight);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// SIDEBAR - Actual Rendered Values
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Sidebar - Rendered', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
|
||||
test('sidebarBackgroundColor: drawer uses configured background', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('.MuiDrawer-paper');
|
||||
if (!el) return { found: false, value: '', debug: 'No .MuiDrawer-paper found' };
|
||||
return { found: true, value: getComputedStyle(el).backgroundColor, debug: '' };
|
||||
});
|
||||
expect(result.found, `Sidebar drawer not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.sidebarBackgroundColor), colorMismatch('sidebarBackgroundColor', THEME.sidebarBackgroundColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('sidebarTextColor: nav item text uses configured color', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('.MuiDrawer-paper .MuiListItemText-primary, .MuiDrawer-paper .MuiListItemText-root span');
|
||||
if (!el) return { found: false, value: '', debug: 'No sidebar text found' };
|
||||
return { found: true, value: getComputedStyle(el).color, debug: el.className };
|
||||
});
|
||||
expect(result.found, `Sidebar text not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.sidebarTextColor), colorMismatch('sidebarTextColor', THEME.sidebarTextColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('sidebarIconColor: nav item icons use configured color', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('.MuiDrawer-paper .MuiSvgIcon-root, .MuiDrawer-paper svg, .MuiDrawer-paper .MuiIcon-root');
|
||||
if (!el) return { found: false, value: '', debug: 'No sidebar icon found' };
|
||||
return { found: true, value: getComputedStyle(el).color, debug: el.className };
|
||||
});
|
||||
expect(result.found, `Sidebar icon not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.sidebarIconColor), colorMismatch('sidebarIconColor', THEME.sidebarIconColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('sidebarSelectedBackgroundColor: selected nav item uses configured background', async ({ page }) => {
|
||||
// Click on a nav item to select it
|
||||
await page.click('.MuiDrawer-paper .MuiListItem-root');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('.MuiDrawer-paper .qqq-sidebar-active');
|
||||
if (!el) return { found: false, value: '', debug: 'No .qqq-sidebar-active found' };
|
||||
return { found: true, value: getComputedStyle(el).backgroundColor, debug: el.className };
|
||||
});
|
||||
expect(result.found, `Selected sidebar item not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.sidebarSelectedBackgroundColor), colorMismatch('sidebarSelectedBackgroundColor', THEME.sidebarSelectedBackgroundColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('sidebarSelectedTextColor: selected nav item text uses configured color', async ({ page }) => {
|
||||
await page.click('.MuiDrawer-paper .MuiListItem-root');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
const selected = document.querySelector('.MuiDrawer-paper .qqq-sidebar-active');
|
||||
if (!selected) return { found: false, value: '', debug: 'No .qqq-sidebar-active found' };
|
||||
const text = selected.querySelector('.MuiTypography-root, span') || selected;
|
||||
return { found: true, value: getComputedStyle(text).color, debug: (text as Element).className };
|
||||
});
|
||||
expect(result.found, `Selected sidebar text not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.sidebarSelectedTextColor), colorMismatch('sidebarSelectedTextColor', THEME.sidebarSelectedTextColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('sidebarDividerColor: sidebar dividers use configured color', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('.MuiDrawer-paper .MuiDivider-root, .MuiDrawer-paper hr');
|
||||
if (!el) return { found: false, value: '', debug: 'No sidebar divider found' };
|
||||
const styles = getComputedStyle(el);
|
||||
return { found: true, value: styles.borderColor || styles.backgroundColor, debug: el.className };
|
||||
});
|
||||
expect(result.found, `Sidebar divider not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.sidebarDividerColor), colorMismatch('sidebarDividerColor', THEME.sidebarDividerColor, result.value)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// TABLES (MUI DataGrid) - Actual Rendered Values
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Tables - Rendered', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/testApp/person');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(2500);
|
||||
});
|
||||
|
||||
test('tableHeaderBackgroundColor: DataGrid header cells use configured background', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('.MuiDataGrid-columnHeader');
|
||||
if (!el) return { found: false, value: '', debug: 'No .MuiDataGrid-columnHeader found' };
|
||||
return { found: true, value: getComputedStyle(el).backgroundColor, debug: '' };
|
||||
});
|
||||
expect(result.found, `DataGrid column header not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.tableHeaderBackgroundColor), colorMismatch('tableHeaderBackgroundColor', THEME.tableHeaderBackgroundColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('tableHeaderTextColor: DataGrid header text uses configured color', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('.MuiDataGrid-columnHeaderTitle');
|
||||
if (!el) return { found: false, value: '', debug: 'No .MuiDataGrid-columnHeaderTitle found' };
|
||||
return { found: true, value: getComputedStyle(el).color, debug: '' };
|
||||
});
|
||||
expect(result.found, `DataGrid header text not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.tableHeaderTextColor), colorMismatch('tableHeaderTextColor', THEME.tableHeaderTextColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('tableBorderColor: DataGrid cells use configured border color', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const cell = document.querySelector('.MuiDataGrid-cell');
|
||||
if (!cell) return { found: false, value: '', debug: 'No .MuiDataGrid-cell found' };
|
||||
return { found: true, value: getComputedStyle(cell).borderColor, debug: '' };
|
||||
});
|
||||
expect(result.found, `DataGrid cell not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.tableBorderColor), colorMismatch('tableBorderColor', THEME.tableBorderColor, result.value)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// TYPOGRAPHY - Actual Rendered Values
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Typography - Rendered', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(2000);
|
||||
});
|
||||
|
||||
test('fontFamily: body uses configured font family', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
return { value: getComputedStyle(document.body).fontFamily };
|
||||
});
|
||||
expect(result.value, 'fontFamily should contain Inter').toMatch(/Inter/i);
|
||||
});
|
||||
|
||||
test('fontSizeBase: body uses configured font size', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
return { value: getComputedStyle(document.body).fontSize };
|
||||
});
|
||||
expect(result.value, colorMismatch('fontSizeBase', THEME.fontSizeBase, result.value)).toBe(THEME.fontSizeBase);
|
||||
});
|
||||
|
||||
test('fontWeightRegular: body text uses configured weight', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('.MuiTypography-body1, p, span');
|
||||
if (!el) return { found: false, value: '', debug: 'No body text found' };
|
||||
return { found: true, value: getComputedStyle(el).fontWeight, debug: el.className };
|
||||
});
|
||||
expect(result.found, `Body text not found: ${result.debug}`).toBe(true);
|
||||
expect(result.value, colorMismatch('fontWeightRegular', String(THEME.fontWeightRegular), result.value)).toBe(String(THEME.fontWeightRegular));
|
||||
});
|
||||
|
||||
test('typographyButtonFontWeight: buttons use configured weight', async ({ page }) => {
|
||||
await page.goto('/testApp/person');
|
||||
await page.waitForTimeout(2500);
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
const btn = document.querySelector('.MuiButton-root');
|
||||
if (!btn) return { found: false, value: '', debug: 'No button found' };
|
||||
return { found: true, value: getComputedStyle(btn).fontWeight, debug: btn.className };
|
||||
});
|
||||
expect(result.found, `Button not found: ${result.debug}`).toBe(true);
|
||||
// Buttons use typographyButtonFontWeight, NOT fontWeightMedium
|
||||
expect(result.value, colorMismatch('typographyButtonFontWeight', String(THEME.typographyButtonFontWeight), result.value)).toBe(String(THEME.typographyButtonFontWeight));
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// UI ELEMENTS - Actual Rendered Values
|
||||
// ============================================================================
|
||||
|
||||
test.describe('UI Elements - Rendered', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/testApp/person');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(2500);
|
||||
});
|
||||
|
||||
test('borderRadius: buttons use configured border radius', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const btn = document.querySelector('.MuiButton-root');
|
||||
if (!btn) return { found: false, value: '', debug: 'No button found' };
|
||||
return { found: true, value: getComputedStyle(btn).borderRadius, debug: btn.className };
|
||||
});
|
||||
expect(result.found, `Button not found: ${result.debug}`).toBe(true);
|
||||
expect(result.value, colorMismatch('borderRadius', THEME.borderRadius, result.value)).toBe(THEME.borderRadius);
|
||||
});
|
||||
|
||||
test('borderColor: inputs use configured border color', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('.MuiOutlinedInput-notchedOutline');
|
||||
if (!el) return { found: false, value: '', debug: 'No .MuiOutlinedInput-notchedOutline found' };
|
||||
return { found: true, value: getComputedStyle(el).borderColor, debug: '' };
|
||||
});
|
||||
expect(result.found, `Input border not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.borderColor), colorMismatch('borderColor', THEME.borderColor, result.value)).toBe(true);
|
||||
});
|
||||
|
||||
test('dividerColor: dividers use configured color', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
// Find a divider NOT in the drawer (sidebar has its own divider color)
|
||||
const selectors = [
|
||||
'main .MuiDivider-root',
|
||||
'.MuiDivider-root:not(.MuiDrawer-root *):not(.MuiDrawer-paper *)'
|
||||
];
|
||||
for (const sel of selectors) {
|
||||
const el = document.querySelector(sel);
|
||||
if (el) {
|
||||
const styles = getComputedStyle(el);
|
||||
return { found: true, value: styles.borderColor || styles.backgroundColor, debug: el.className };
|
||||
}
|
||||
}
|
||||
// If no divider element found, check if the CSS variable is properly set
|
||||
const cssVar = getComputedStyle(document.documentElement).getPropertyValue('--qqq-divider-color').trim();
|
||||
if (cssVar) {
|
||||
return { found: true, value: cssVar, debug: 'CSS variable --qqq-divider-color' };
|
||||
}
|
||||
return { found: false, value: '', debug: 'No divider found outside drawer' };
|
||||
});
|
||||
expect(result.found, `Divider not found: ${result.debug}`).toBe(true);
|
||||
expect(colorsMatch(result.value, THEME.dividerColor), colorMismatch('dividerColor', THEME.dividerColor, result.value)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// DIAGNOSTIC DUMP - outputs actual values for debugging
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Diagnostic Dump', () => {
|
||||
test('output all rendered theme values', async ({ page }) => {
|
||||
await page.goto('/testApp/person');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(2500);
|
||||
|
||||
const diagnostics = await page.evaluate(() => {
|
||||
const getColor = (selector: string, prop: string) => {
|
||||
const el = document.querySelector(selector);
|
||||
if (!el) return { found: false, value: 'NOT FOUND' };
|
||||
return { found: true, value: getComputedStyle(el)[prop as any] };
|
||||
};
|
||||
|
||||
return {
|
||||
primaryButton: getColor('.MuiButton-containedPrimary', 'backgroundColor'),
|
||||
link: getColor('a.MuiLink-root, main a', 'color'),
|
||||
body: getColor('body', 'backgroundColor'),
|
||||
card: getColor('.MuiCard-root, .MuiPaper-root', 'backgroundColor'),
|
||||
heading: getColor('h1, h2, h3, h4', 'color'),
|
||||
body2: getColor('.MuiTypography-body2', 'color'),
|
||||
brandedHeader: getColor('.qqq-branded-header-bar', 'backgroundColor'),
|
||||
brandedHeaderText: getColor('.qqq-branded-header-bar span', 'color'),
|
||||
sidebarBg: getColor('.MuiDrawer-paper', 'backgroundColor'),
|
||||
sidebarText: getColor('.MuiDrawer-paper .MuiListItemText-primary', 'color'),
|
||||
sidebarIcon: getColor('.MuiDrawer-paper .MuiSvgIcon-root', 'color'),
|
||||
sidebarActive: getColor('.qqq-sidebar-active', 'backgroundColor'),
|
||||
sidebarActiveText: getColor('.qqq-sidebar-active .MuiTypography-root', 'color'),
|
||||
tableHeader: getColor('.MuiDataGrid-columnHeader', 'backgroundColor'),
|
||||
tableHeaderText: getColor('.MuiDataGrid-columnHeaderTitle', 'color'),
|
||||
tableCell: getColor('.MuiDataGrid-cell', 'borderColor'),
|
||||
inputBorder: getColor('.MuiOutlinedInput-notchedOutline', 'borderColor'),
|
||||
buttonRadius: getColor('.MuiButton-root', 'borderRadius'),
|
||||
buttonWeight: getColor('.MuiButton-root', 'fontWeight'),
|
||||
bodyWeight: getColor('.MuiTypography-body1', 'fontWeight'),
|
||||
};
|
||||
});
|
||||
|
||||
console.log('=== DIAGNOSTIC OUTPUT ===');
|
||||
console.log(JSON.stringify(diagnostics, null, 2));
|
||||
console.log('=========================');
|
||||
|
||||
// This test always passes - it's just for diagnostics
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,421 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {colorsMatch, hexToRgb, isTransparentOrNearlyTransparent, parseRgb} from "../utils/color-helpers";
|
||||
|
||||
/**
|
||||
* STRICT Visual Regression Tests for UNTHEMED Apps (Issue #128)
|
||||
*
|
||||
* These tests verify that apps WITHOUT a backend theme look correct.
|
||||
* Run with: THEME_FIXTURE=index npm run e2e:unthemed
|
||||
*
|
||||
* IMPORTANT: These tests require the 'index' fixture (no theme configured).
|
||||
* They will be SKIPPED if running with a themed fixture (e.g., withFullCustomTheme).
|
||||
*
|
||||
* To run these tests:
|
||||
* npm run e2e:unthemed (uses THEME_FIXTURE=index)
|
||||
*
|
||||
* To run all tests properly:
|
||||
* npm run e2e:all (runs themed then unthemed with correct fixtures)
|
||||
*
|
||||
* Tests verify:
|
||||
* 1. Sidebar has correct DEFAULT_THEME colors (dark background, white text)
|
||||
* 2. Navbar/AppBar is NOT white (transparent, not affected by surfaceColor)
|
||||
* 3. CSS variables are injected with DEFAULT_THEME values
|
||||
* 4. Cards/Paper get surfaceColor but AppBar does not
|
||||
*/
|
||||
|
||||
// Helper function to check if themed fixture is loaded and skip if so
|
||||
async function skipIfThemedFixture(page: import('@playwright/test').Page) {
|
||||
const hasTheme = await page.evaluate(() => {
|
||||
const primaryColor = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--qqq-primary-color').trim();
|
||||
return primaryColor !== '';
|
||||
});
|
||||
|
||||
if (hasTheme) {
|
||||
test.skip(true, 'Skipping unthemed test: themed fixture detected. Run with: npm run e2e:unthemed');
|
||||
}
|
||||
}
|
||||
|
||||
// DEFAULT_THEME values from src/qqq/utils/themeUtils.ts
|
||||
// These must match the values in themeUtils.ts to restore pre-PR-125 appearance
|
||||
const DEFAULT_THEME = {
|
||||
// Core colors
|
||||
primaryColor: '#0062FF',
|
||||
backgroundColor: '#f0f2f5',
|
||||
surfaceColor: '#ffffff',
|
||||
// Sidebar (dark gray matching original gradients.dark.main)
|
||||
sidebarBackgroundColor: '#42424a',
|
||||
sidebarTextColor: '#ffffff',
|
||||
sidebarIconColor: '#ffffff',
|
||||
sidebarSelectedBackgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||
sidebarSelectedTextColor: '#ffffff',
|
||||
sidebarHoverBackgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||
// Typography (matching original typography.ts)
|
||||
fontFamily: '"SF Pro Display", "Roboto", "Helvetica", "Arial", sans-serif',
|
||||
};
|
||||
|
||||
// Helper to check if color is NOT white (surfaceColor)
|
||||
function isNotWhite(color: string): boolean {
|
||||
// Check if transparent
|
||||
if (isTransparentOrNearlyTransparent(color)) {
|
||||
return true;
|
||||
}
|
||||
// Check if NOT white (#ffffff or rgb(255,255,255))
|
||||
const actualRgb = parseRgb(color);
|
||||
if (!actualRgb) {
|
||||
if (color.startsWith('#')) {
|
||||
const hex = hexToRgb(color);
|
||||
if (!hex) {
|
||||
return true;
|
||||
}
|
||||
return !(hex.r > 250 && hex.g > 250 && hex.b > 250);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Allow some tolerance - if it's close to white, it's a problem
|
||||
return !(actualRgb.r > 250 && actualRgb.g > 250 && actualRgb.b > 250);
|
||||
}
|
||||
|
||||
// Helper to check if a background (which may be a gradient) contains a color
|
||||
function backgroundContainsColor(background: string, expectedColor: string, tolerance = 5): boolean {
|
||||
// Extract all rgb/rgba values from the background string
|
||||
const rgbMatches = background.matchAll(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/g);
|
||||
const expectedRgb = expectedColor.startsWith('#') ? hexToRgb(expectedColor) : parseRgb(expectedColor);
|
||||
if (!expectedRgb) return false;
|
||||
|
||||
for (const match of rgbMatches) {
|
||||
const r = parseInt(match[1]);
|
||||
const g = parseInt(match[2]);
|
||||
const b = parseInt(match[3]);
|
||||
if (
|
||||
Math.abs(r - expectedRgb.r) <= tolerance &&
|
||||
Math.abs(g - expectedRgb.g) <= tolerance &&
|
||||
Math.abs(b - expectedRgb.b) <= tolerance
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CSS VARIABLES - Verify they are NOT injected for unthemed apps
|
||||
// CSS variables are 100% optional - CSS uses fallback values
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Unthemed App - CSS Variables Are Optional', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(1000);
|
||||
await skipIfThemedFixture(page);
|
||||
});
|
||||
|
||||
test('no --qqq-* CSS variables are injected when no theme is configured', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const root = document.documentElement;
|
||||
const style = getComputedStyle(root);
|
||||
return {
|
||||
sidebarBg: style.getPropertyValue('--qqq-sidebar-background-color').trim(),
|
||||
sidebarText: style.getPropertyValue('--qqq-sidebar-text-color').trim(),
|
||||
surfaceColor: style.getPropertyValue('--qqq-surface-color').trim(),
|
||||
primaryColor: style.getPropertyValue('--qqq-primary-color').trim(),
|
||||
};
|
||||
});
|
||||
// All CSS variables should be empty (not injected) for unthemed apps
|
||||
expect(result.sidebarBg, 'CSS variable should not be injected for unthemed app').toBe('');
|
||||
expect(result.sidebarText, 'CSS variable should not be injected for unthemed app').toBe('');
|
||||
expect(result.surfaceColor, 'CSS variable should not be injected for unthemed app').toBe('');
|
||||
expect(result.primaryColor, 'CSS variable should not be injected for unthemed app').toBe('');
|
||||
});
|
||||
|
||||
test('body does NOT have .qqq-themed class when no theme is configured', async ({ page }) => {
|
||||
const hasClass = await page.evaluate(() => {
|
||||
return document.body.classList.contains('qqq-themed');
|
||||
});
|
||||
expect(hasClass, 'Body should not have .qqq-themed class for unthemed app').toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// SIDEBAR - Verify dark theme colors via CSS fallbacks
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Unthemed App - Sidebar Colors', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(1000);
|
||||
await skipIfThemedFixture(page);
|
||||
});
|
||||
|
||||
test('sidebar has dark background via CSS fallback', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('.MuiDrawer-paper');
|
||||
if (!el) return { found: false, value: '', debug: 'No .MuiDrawer-paper found' };
|
||||
// Use 'background' not 'backgroundColor' because gradients set background-image
|
||||
return { found: true, value: getComputedStyle(el).background, debug: '' };
|
||||
});
|
||||
expect(result.found, `Sidebar not found: ${result.debug}`).toBe(true);
|
||||
// Check that the background contains the expected dark color (gradient includes #42424a)
|
||||
expect(
|
||||
backgroundContainsColor(result.value, DEFAULT_THEME.sidebarBackgroundColor),
|
||||
`Sidebar background should contain ${DEFAULT_THEME.sidebarBackgroundColor}, got ${result.value}`
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('sidebar text is white from DEFAULT_THEME', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('.MuiDrawer-paper .MuiListItemText-primary, .MuiDrawer-paper .MuiTypography-root');
|
||||
if (!el) return { found: false, value: '', debug: 'No sidebar text found' };
|
||||
return { found: true, value: getComputedStyle(el).color, debug: el.className };
|
||||
});
|
||||
expect(result.found, `Sidebar text not found: ${result.debug}`).toBe(true);
|
||||
expect(
|
||||
colorsMatch(result.value, DEFAULT_THEME.sidebarTextColor, 5),
|
||||
`Sidebar text color: expected ${DEFAULT_THEME.sidebarTextColor}, got ${result.value}`
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('sidebar icons are white from DEFAULT_THEME', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const el = document.querySelector('.MuiDrawer-paper .MuiIcon-root, .MuiDrawer-paper .material-icons');
|
||||
if (!el) return { found: false, value: '', debug: 'No sidebar icon found' };
|
||||
return { found: true, value: getComputedStyle(el).color, debug: el.className };
|
||||
});
|
||||
expect(result.found, `Sidebar icon not found: ${result.debug}`).toBe(true);
|
||||
expect(
|
||||
colorsMatch(result.value, DEFAULT_THEME.sidebarIconColor, 5),
|
||||
`Sidebar icon color: expected ${DEFAULT_THEME.sidebarIconColor}, got ${result.value}`
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// NAVBAR/APPBAR - Verify NOT white (Issue #128 regression)
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Unthemed App - Navbar NOT White (Issue #128)', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(1000);
|
||||
await skipIfThemedFixture(page);
|
||||
});
|
||||
|
||||
test('CRITICAL: AppBar background is NOT white/surfaceColor', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const appBar = document.querySelector('.MuiAppBar-root');
|
||||
if (!appBar) return { found: false, value: '', debug: 'No .MuiAppBar-root found' };
|
||||
return { found: true, value: getComputedStyle(appBar).backgroundColor, debug: '' };
|
||||
});
|
||||
expect(result.found, `AppBar not found: ${result.debug}`).toBe(true);
|
||||
expect(
|
||||
isNotWhite(result.value),
|
||||
`REGRESSION: AppBar background is white (${result.value}). Should be transparent or non-white.`
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('CRITICAL: Breadcrumb area is NOT white', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
// Find the breadcrumb container or navbar area
|
||||
const selectors = [
|
||||
'.MuiBreadcrumbs-root',
|
||||
'.MuiToolbar-root',
|
||||
'.MuiAppBar-root .MuiBox-root'
|
||||
];
|
||||
for (const sel of selectors) {
|
||||
const el = document.querySelector(sel);
|
||||
if (el) {
|
||||
const bg = getComputedStyle(el).backgroundColor;
|
||||
return { found: true, value: bg, selector: sel };
|
||||
}
|
||||
}
|
||||
return { found: false, value: '', selector: '' };
|
||||
});
|
||||
expect(result.found, 'Breadcrumb/toolbar area not found').toBe(true);
|
||||
expect(
|
||||
isNotWhite(result.value),
|
||||
`REGRESSION: Breadcrumb area (${result.selector}) is white (${result.value}). Should not be white.`
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('AppBar does not have --qqq-surface-color applied', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const appBar = document.querySelector('.MuiAppBar-root');
|
||||
if (!appBar) return { found: false, isSurfaceColor: false, value: '' };
|
||||
const bg = getComputedStyle(appBar).backgroundColor;
|
||||
// Check if it matches surfaceColor (#ffffff)
|
||||
const isSurfaceColor = bg === 'rgb(255, 255, 255)' || bg === '#ffffff';
|
||||
return { found: true, isSurfaceColor, value: bg };
|
||||
});
|
||||
expect(result.found, 'AppBar not found').toBe(true);
|
||||
expect(
|
||||
result.isSurfaceColor,
|
||||
`REGRESSION: AppBar has surfaceColor (#ffffff) applied. Value: ${result.value}`
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// CARDS/PAPER - Verify surfaceColor IS applied (but not to AppBar)
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Unthemed App - Cards Use Surface Color', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/testApp/person');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(2500);
|
||||
await skipIfThemedFixture(page);
|
||||
});
|
||||
|
||||
test('Cards in main content use surfaceColor', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
// Find a card in main content, NOT in drawer/sidebar
|
||||
const el = document.querySelector('main .MuiCard-root, .MuiCard-root:not(.MuiDrawer-paper *)');
|
||||
if (!el) return { found: false, value: '', debug: 'No card found in main content' };
|
||||
return { found: true, value: getComputedStyle(el).backgroundColor, debug: el.className };
|
||||
});
|
||||
expect(result.found, `Card not found: ${result.debug}`).toBe(true);
|
||||
expect(
|
||||
colorsMatch(result.value, DEFAULT_THEME.surfaceColor, 5),
|
||||
`Card background: expected ${DEFAULT_THEME.surfaceColor}, got ${result.value}`
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// BRANDED HEADER - Verify NOT shown when disabled
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Unthemed App - Branded Header Disabled', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(2000);
|
||||
await skipIfThemedFixture(page);
|
||||
});
|
||||
|
||||
test('branded header is not visible when not configured', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const header = document.querySelector('.qqq-branded-header-bar');
|
||||
if (!header) return { exists: false, height: '0' };
|
||||
const height = getComputedStyle(header).height;
|
||||
const display = getComputedStyle(header).display;
|
||||
return { exists: true, height, display };
|
||||
});
|
||||
// Either the element doesn't exist, or it has 0 height, or it's hidden
|
||||
const isHidden = !result.exists ||
|
||||
result.height === '0px' ||
|
||||
result.height === '0' ||
|
||||
result.display === 'none';
|
||||
expect(isHidden, `Branded header should not be visible. Height: ${result.height}`).toBe(true);
|
||||
});
|
||||
|
||||
test('--qqq-branded-header-height is not set (CSS fallback to 0px)', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const value = getComputedStyle(document.documentElement)
|
||||
.getPropertyValue('--qqq-branded-header-height').trim();
|
||||
return { value };
|
||||
});
|
||||
// CSS variable should be empty (not injected) - CSS fallback handles it
|
||||
expect(
|
||||
result.value === '' || result.value === '0px' || result.value === '0',
|
||||
`--qqq-branded-header-height should be empty or 0px, got ${result.value}`
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('sidebar does not overhang content area', async ({ page }) => {
|
||||
const result = await page.evaluate(() => {
|
||||
const sidebar = document.querySelector('.MuiDrawer-paper');
|
||||
// Try multiple selectors for main content area
|
||||
const mainContent = document.querySelector('main') ||
|
||||
document.querySelector('[class*="content"]') ||
|
||||
document.querySelector('.MuiBox-root > .MuiBox-root');
|
||||
if (!sidebar) {
|
||||
return { found: false, sidebarTop: 0, contentTop: 0, debug: 'No sidebar found' };
|
||||
}
|
||||
const sidebarRect = sidebar.getBoundingClientRect();
|
||||
// If we can't find main content, just verify sidebar starts at top (no overhang)
|
||||
if (!mainContent) {
|
||||
// Sidebar should start at or near the top of viewport
|
||||
return {
|
||||
found: true,
|
||||
sidebarTop: sidebarRect.top,
|
||||
contentTop: 0,
|
||||
isAligned: sidebarRect.top <= 100, // Should be near top
|
||||
debug: 'Using viewport top as reference'
|
||||
};
|
||||
}
|
||||
const contentRect = mainContent.getBoundingClientRect();
|
||||
return {
|
||||
found: true,
|
||||
sidebarTop: sidebarRect.top,
|
||||
contentTop: contentRect.top,
|
||||
// Sidebar should start at same level or below content
|
||||
isAligned: sidebarRect.top <= contentRect.top + 50,
|
||||
debug: 'Found main content'
|
||||
};
|
||||
});
|
||||
expect(result.found, `Sidebar not found: ${result.debug}`).toBe(true);
|
||||
expect(
|
||||
result.isAligned,
|
||||
`Sidebar overhang: sidebar top (${result.sidebarTop}) should align with content top (${result.contentTop}). Debug: ${result.debug}`
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// DIAGNOSTIC - Output actual values for debugging
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Unthemed App - Diagnostic Dump', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.waitForSelector('[id="root"]');
|
||||
await page.waitForTimeout(2000);
|
||||
await skipIfThemedFixture(page);
|
||||
});
|
||||
|
||||
test('output all CSS variables and rendered values', async ({ page }) => {
|
||||
|
||||
const diagnostics = await page.evaluate(() => {
|
||||
const root = document.documentElement;
|
||||
const getVar = (name: string) => getComputedStyle(root).getPropertyValue(name).trim();
|
||||
const getBg = (selector: string) => {
|
||||
const el = document.querySelector(selector);
|
||||
return el ? getComputedStyle(el).backgroundColor : 'NOT FOUND';
|
||||
};
|
||||
const getColor = (selector: string) => {
|
||||
const el = document.querySelector(selector);
|
||||
return el ? getComputedStyle(el).color : 'NOT FOUND';
|
||||
};
|
||||
|
||||
return {
|
||||
cssVariables: {
|
||||
'--qqq-sidebar-background-color': getVar('--qqq-sidebar-background-color'),
|
||||
'--qqq-sidebar-text-color': getVar('--qqq-sidebar-text-color'),
|
||||
'--qqq-surface-color': getVar('--qqq-surface-color'),
|
||||
'--qqq-primary-color': getVar('--qqq-primary-color'),
|
||||
'--qqq-branded-header-height': getVar('--qqq-branded-header-height'),
|
||||
'--qqq-branded-header-enabled': getVar('--qqq-branded-header-enabled'),
|
||||
},
|
||||
renderedValues: {
|
||||
sidebarBackground: getBg('.MuiDrawer-paper'),
|
||||
sidebarText: getColor('.MuiDrawer-paper .MuiTypography-root'),
|
||||
appBarBackground: getBg('.MuiAppBar-root'),
|
||||
toolbarBackground: getBg('.MuiToolbar-root'),
|
||||
cardBackground: getBg('.MuiCard-root'),
|
||||
bodyBackground: getBg('body'),
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
console.log('=== UNTHEMED APP DIAGNOSTIC ===');
|
||||
console.log(JSON.stringify(diagnostics, null, 2));
|
||||
console.log('===============================');
|
||||
|
||||
// This test always passes - diagnostic only
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
export type RgbColor = { r: number; g: number; b: number; a?: number };
|
||||
|
||||
/**
|
||||
* Utility functions to help with assertions in theme tests
|
||||
*/
|
||||
|
||||
export function hexToRgb(hex: string): { r: number; g: number; b: number } | null
|
||||
{
|
||||
const normalized = hex.trim().replace(/^#/, "");
|
||||
if (!/^[a-f\d]+$/i.test(normalized))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
let rHex: string;
|
||||
let gHex: string;
|
||||
let bHex: string;
|
||||
|
||||
if (normalized.length === 3)
|
||||
{
|
||||
rHex = normalized[0] + normalized[0];
|
||||
gHex = normalized[1] + normalized[1];
|
||||
bHex = normalized[2] + normalized[2];
|
||||
}
|
||||
else if (normalized.length === 6 || normalized.length === 8)
|
||||
{
|
||||
// Ignore alpha channel for 8-digit hex values (#RRGGBBAA).
|
||||
rHex = normalized.slice(0, 2);
|
||||
gHex = normalized.slice(2, 4);
|
||||
bHex = normalized.slice(4, 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
r: parseInt(rHex, 16),
|
||||
g: parseInt(gHex, 16),
|
||||
b: parseInt(bHex, 16),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseRgb(rgbString: string): RgbColor | null
|
||||
{
|
||||
const rgbMatch = rgbString.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
||||
if (rgbMatch)
|
||||
{
|
||||
return {
|
||||
r: parseInt(rgbMatch[1]),
|
||||
g: parseInt(rgbMatch[2]),
|
||||
b: parseInt(rgbMatch[3]),
|
||||
a: rgbMatch[4] ? parseFloat(rgbMatch[4]) : undefined,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseColor(color: string): RgbColor | null
|
||||
{
|
||||
if (color.startsWith("rgb"))
|
||||
{
|
||||
return parseRgb(color);
|
||||
}
|
||||
if (color.startsWith("#"))
|
||||
{
|
||||
const rgb = hexToRgb(color);
|
||||
return rgb ? {...rgb, a: undefined} : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function colorsMatch(actual: string, expected: string, tolerance = 2): boolean
|
||||
{
|
||||
if (actual.startsWith("#") && expected.startsWith("#"))
|
||||
{
|
||||
return actual.toLowerCase() === expected.toLowerCase();
|
||||
}
|
||||
|
||||
const actualRgb = parseColor(actual);
|
||||
const expectedRgb = parseColor(expected);
|
||||
if (!actualRgb || !expectedRgb)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const rgbMatch = (
|
||||
Math.abs(actualRgb.r - expectedRgb.r) <= tolerance &&
|
||||
Math.abs(actualRgb.g - expectedRgb.g) <= tolerance &&
|
||||
Math.abs(actualRgb.b - expectedRgb.b) <= tolerance
|
||||
);
|
||||
|
||||
if (actualRgb.a !== undefined && expectedRgb.a !== undefined)
|
||||
{
|
||||
return rgbMatch && Math.abs(actualRgb.a - expectedRgb.a) <= 0.05;
|
||||
}
|
||||
|
||||
return rgbMatch;
|
||||
}
|
||||
|
||||
export function isTransparentOrNearlyTransparent(color: string): boolean
|
||||
{
|
||||
if (color === "transparent" || color === "rgba(0, 0, 0, 0)")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const rgb = parseRgb(color);
|
||||
return !!(rgb && rgb.a !== undefined && rgb.a < 0.1);
|
||||
}
|
||||
Generated
+792
-1361
File diff suppressed because it is too large.
Load diff
+9
-28
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "qqq-frontend-material-dashboard",
|
||||
"license": "Apache-2.0",
|
||||
"version": "0.41.0-SNAPSHOT",
|
||||
"version": "0.40.0-SNAPSHOT",
|
||||
"homepage": ".",
|
||||
"description": "QQQ Default Dashboard",
|
||||
"dependencies": {
|
||||
@@ -16,7 +15,7 @@
|
||||
"@mui/x-data-grid-pro": "5.17.23",
|
||||
"@mui/x-date-pickers": "7.1.1",
|
||||
"@mui/x-license-pro": "5.12.3",
|
||||
"@qrunio/qqq-frontend-core": "0.40.20-SNAPSHOT",
|
||||
"@qrunio/qqq-frontend-core": "0.40.7-SNAPSHOT",
|
||||
"@react-jvectormap/core": "1.0.1",
|
||||
"@react-jvectormap/unitedstates": "1.0.1",
|
||||
"@react-oauth/google": "0.2.8",
|
||||
@@ -32,13 +31,13 @@
|
||||
"dayjs": "1.11.10",
|
||||
"downshift": "3.2.10",
|
||||
"faker": "5.5.3",
|
||||
"form-data": "4.0.6",
|
||||
"form-data": "4.0.4",
|
||||
"formik": "2.2.9",
|
||||
"html-react-parser": "1.4.8",
|
||||
"html-to-text": "^9.0.5",
|
||||
"http-proxy-middleware": "2.0.9",
|
||||
"jwt-decode": "3.1.2",
|
||||
"lodash": "4.18.1",
|
||||
"lodash": "4.17.21",
|
||||
"oidc-client-ts": "2.4.1",
|
||||
"rapidoc": "9.3.4",
|
||||
"react": "18.0.0",
|
||||
@@ -63,7 +62,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "react-scripts build",
|
||||
"build-and-publish": "react-scripts build && rm -rvf src/main/resources/material-dashboard && mkdir -p src/main/resources/material-dashboard && cp -rv build/* src/main/resources/material-dashboard",
|
||||
"build-and-publish": "react-scripts build && rm -rvf src/main/resources/material-dashboard; mkdir -p src/main/resources/material-dashboard; cp -rv build/* src/main/resources/material-dashboard",
|
||||
"clean": "rm -rf node_modules package-lock.json lib",
|
||||
"eject": "react-scripts eject",
|
||||
"clean-and-install": "rm -rf node_modules/ && rm -rf package-lock.json && rm -rf lib/ && npm install --legacy-peer-deps && npm dedupe --force",
|
||||
@@ -76,11 +75,7 @@
|
||||
"test:e2e:ui": "npx playwright test --ui",
|
||||
"test:e2e:headed": "npx playwright test --headed",
|
||||
"fixture-server": "node e2e/fixture-server.js",
|
||||
"fixture-server:unthemed": "THEME_FIXTURE=index node e2e/fixture-server.js",
|
||||
"e2e": "npx playwright test",
|
||||
"e2e:themed": "THEME_FIXTURE=withFullCustomTheme npx playwright test e2e/tests/theme.spec.ts",
|
||||
"e2e:unthemed": "THEME_FIXTURE=index npx playwright test e2e/tests/unthemed-regression.spec.ts",
|
||||
"e2e:all": "npm run e2e:themed && npm run e2e:unthemed"
|
||||
"e2e": "concurrently --kill-others \"npm run fixture-server\" \"npx playwright test\""
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -106,13 +101,13 @@
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "7.21.11",
|
||||
"@playwright/test": "^1.57.0",
|
||||
"concurrently": "^8.2.0",
|
||||
"@svgr/webpack": "6.5.1",
|
||||
"@types/chroma-js": "2.1.3",
|
||||
"@types/faker": "5.5.9",
|
||||
"@types/react-table": "7.7.9",
|
||||
"@typescript-eslint/eslint-plugin": "5.10.2",
|
||||
"@typescript-eslint/parser": "5.10.2",
|
||||
"concurrently": "^8.2.0",
|
||||
"eslint": "8.8.0",
|
||||
"eslint-import-resolver-typescript": "2.5.0",
|
||||
"eslint-plugin-import": "2.25.4",
|
||||
@@ -121,26 +116,12 @@
|
||||
"typescript": "4.9.4"
|
||||
},
|
||||
"overrides": {
|
||||
"@svgr/webpack": "$@svgr/webpack",
|
||||
"axios@<1.0.0": "0.34.0",
|
||||
"form-data@^4.0.0": "$form-data",
|
||||
"resolve-url-loader": "5.0.0",
|
||||
"serialize-javascript": "7.0.3",
|
||||
"underscore": "1.13.8",
|
||||
"webpack-dev-middleware": "7.4.6"
|
||||
"@svgr/webpack": "$@svgr/webpack"
|
||||
},
|
||||
"main": "qqq-frontend-material-dashboard.js",
|
||||
"module": "lib/qqq-frontend-material-dashboard.js",
|
||||
"files": [
|
||||
"lib",
|
||||
"package.json"
|
||||
],
|
||||
"jest": {
|
||||
"transformIgnorePatterns": [
|
||||
"node_modules/(?!(@qrunio/qqq-frontend-core|@mui|@babel/runtime)/)"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
]
|
||||
}
|
||||
+7
-16
@@ -1,8 +1,5 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
const fixturePort = process.env.FIXTURE_PORT || '8001';
|
||||
const dashboardPort = process.env.DASHBOARD_PORT || '3001';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './e2e/tests',
|
||||
fullyParallel: false,
|
||||
@@ -23,36 +20,30 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
use: {
|
||||
baseURL: `https://localhost:${dashboardPort}`,
|
||||
baseURL: 'https://localhost:3001',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
ignoreHTTPSErrors: true,
|
||||
channel: process.env.PLAYWRIGHT_CHANNEL,
|
||||
// Consistent viewport for visual regression
|
||||
viewport: { width: 1280, height: 720 },
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'themed',
|
||||
testMatch: /.*(?<!unthemed-regression)\.spec\.ts$/,
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
{
|
||||
name: 'unthemed',
|
||||
testMatch: /unthemed-regression\.spec\.ts$/,
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
webServer: [
|
||||
{
|
||||
command: `THEME_FIXTURE=${process.env.THEME_FIXTURE || 'withFullCustomTheme'} node e2e/fixture-server.js`,
|
||||
url: `http://localhost:${fixturePort}/metaData`,
|
||||
// Pass THEME_FIXTURE through to fixture server for fixture selection
|
||||
command: `THEME_FIXTURE=${process.env.THEME_FIXTURE || 'index'} node e2e/fixture-server.js`,
|
||||
url: 'http://localhost:8001/metaData',
|
||||
reuseExistingServer: true,
|
||||
timeout: 60000, // 1 minute - Docker startup can be slow
|
||||
},
|
||||
{
|
||||
command: `HTTPS=true PORT=${dashboardPort} REACT_APP_PROXY_LOCALHOST_PORT=${fixturePort} npm start`,
|
||||
url: `https://localhost:${dashboardPort}`,
|
||||
command: 'HTTPS=true PORT=3001 REACT_APP_PROXY_LOCALHOST_PORT=8001 npm start',
|
||||
url: 'https://localhost:3001',
|
||||
reuseExistingServer: true,
|
||||
timeout: 300000, // 5 minutes - React compilation in Docker takes time
|
||||
ignoreHTTPSErrors: true,
|
||||
|
||||
@@ -6,17 +6,18 @@
|
||||
~ contact@kingsrook.com
|
||||
~ https://github.com/Kingsrook/
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU Affero General Public License as
|
||||
~ published by the Free Software Foundation, either version 3 of the
|
||||
~ License, or (at your option) any later version.
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU Affero General Public License for more details.
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
~ You should have received a copy of the GNU Affero General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
@@ -35,8 +36,8 @@
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache License, Version 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
|
||||
<name>GNU Affero General Public License v3.0</name>
|
||||
<url>https://www.gnu.org/licenses/agpl-3.0.html</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
@@ -57,8 +58,8 @@
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<revision>0.41.0-SNAPSHOT</revision>
|
||||
<qqq.version>4.0.0</qqq.version>
|
||||
<revision>0.40.0-SNAPSHOT</revision>
|
||||
<qqq.version>0.40.0-SNAPSHOT</qqq.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
@@ -69,13 +70,6 @@
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson</groupId>
|
||||
<artifactId>jackson-bom</artifactId>
|
||||
<version>2.21.7</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-bom-pom</artifactId>
|
||||
@@ -152,13 +146,13 @@
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.27.7</version>
|
||||
<version>3.23.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.javalin</groupId>
|
||||
<artifactId>javalin</artifactId>
|
||||
<version>7.2.3</version>
|
||||
<version>6.7.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -170,7 +164,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<version>2.25.3</version>
|
||||
<version>2.17.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -255,51 +249,7 @@
|
||||
</profiles>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>${project.build.directory}/maven-shared-archive-resources</directory>
|
||||
</resource>
|
||||
</resources>
|
||||
<testResources>
|
||||
<testResource>
|
||||
<directory>src/test/resources</directory>
|
||||
</testResource>
|
||||
<testResource>
|
||||
<directory>${project.build.directory}/maven-shared-archive-resources</directory>
|
||||
</testResource>
|
||||
</testResources>
|
||||
<plugins>
|
||||
<!-- Source and Javadoc JARs also include Maven's shared archive resources. -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>package-root-notices</id>
|
||||
<phase>generate-resources</phase>
|
||||
<goals>
|
||||
<goal>copy-resources</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/maven-shared-archive-resources/META-INF</outputDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>${project.basedir}</directory>
|
||||
<filtering>false</filtering>
|
||||
<includes>
|
||||
<include>LICENSE</include>
|
||||
<include>NOTICE</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
@@ -348,6 +298,35 @@
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- GPG signing for Maven Central -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- Sonatype Central publishing (replaces deploy to distributionManagement) -->
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.8.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<!-- Server credentials with this ID must be provided in settings.xml -->
|
||||
<publishingServerId>central</publishingServerId>
|
||||
<autoPublish>true</autoPublish>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check all four published JARs against this checkout's root license files."""
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from zipfile import BadZipFile, ZipFile
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("archive_prefix", type=Path,
|
||||
help="archive path without classifier or .jar suffix")
|
||||
args = parser.parse_args()
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
expected = {name: (root / name).read_bytes() for name in ("LICENSE", "NOTICE")}
|
||||
failures = []
|
||||
|
||||
for classifier in ("", "-sources", "-tests", "-javadoc"):
|
||||
archive = Path(f"{args.archive_prefix}{classifier}.jar")
|
||||
try:
|
||||
with ZipFile(archive) as jar:
|
||||
for name, content in expected.items():
|
||||
entry = f"META-INF/{name}"
|
||||
if jar.namelist().count(entry) != 1:
|
||||
failures.append(f"{archive}: expected exactly one {entry}")
|
||||
elif jar.read(entry) != content:
|
||||
failures.append(f"{archive}: {entry} differs from root {name}")
|
||||
except (OSError, BadZipFile) as error:
|
||||
failures.append(f"{archive}: {error}")
|
||||
|
||||
if failures:
|
||||
print("\n".join(failures), file=sys.stderr)
|
||||
return 1
|
||||
print("PASS: main, sources, tests and javadoc JARs contain exact root LICENSE/NOTICE")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
############################################################################
|
||||
## ci-prebuild.sh
|
||||
## CI prebuild for the shared ci-workflows maven.yaml (runs before Maven):
|
||||
## Jest unit tests, the React build that is packaged into the jar, and the
|
||||
## HTTPS dev server on :3001 that the Selenium ITs drive (as the old
|
||||
## qqq-orb frontend-material pipeline did). Maven then runs the ITs headless.
|
||||
############################################################################
|
||||
set -euo pipefail
|
||||
|
||||
npm ci --legacy-peer-deps
|
||||
npm test -- --watchAll=false
|
||||
npm run build-and-publish
|
||||
|
||||
HTTPS=true PORT=3001 REACT_APP_PROXY_LOCALHOST_PORT=8001 \
|
||||
nohup setsid npm run start > /tmp/react-dev-server.log 2>&1 < /dev/null &
|
||||
|
||||
for _ in $(seq 1 150); do
|
||||
if curl -ksf -o /dev/null https://localhost:3001/; then
|
||||
echo "React dev server is up on https://localhost:3001"
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "React dev server did not come up on :3001"
|
||||
cat /tmp/react-dev-server.log
|
||||
exit 1
|
||||
+22
-75
@@ -5,24 +5,25 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Avatar from "@mui/material/Avatar";
|
||||
import Box from "@mui/material/Box";
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {Theme, ThemeProvider} from "@mui/material/styles";
|
||||
import {ThemeProvider} from "@mui/material/styles";
|
||||
import {LicenseInfo} from "@mui/x-license-pro";
|
||||
import {QException} from "@qrunio/qqq-frontend-core/lib/exceptions/QException";
|
||||
import {QAppNodeType} from "@qrunio/qqq-frontend-core/lib/model/metaData/QAppNodeType";
|
||||
@@ -32,16 +33,16 @@ import {QBrandingMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QB
|
||||
import {QInstance} from "@qrunio/qqq-frontend-core/lib/model/metaData/QInstance";
|
||||
import {QProcessMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||
import {QTableMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import CommandMenu from "CommandMenu";
|
||||
import QContext from "QContext";
|
||||
import useAnonymousAuthenticationModule from "qqq/authorization/anonymous/useAnonymousAuthenticationModule";
|
||||
import useAuth0AuthenticationModule from "qqq/authorization/auth0/useAuth0AuthenticationModule";
|
||||
import useOAuth2AuthenticationModule from "qqq/authorization/oauth2/useOAuth2AuthenticationModule";
|
||||
import BrandedHeaderBar from "qqq/components/horseshoe/BrandedHeaderBar";
|
||||
import Sidenav from "qqq/components/horseshoe/sidenav/SideNav";
|
||||
import theme from "qqq/components/legacy/Theme";
|
||||
import {getBanner, getBannerClassName, getBannerStyles, makeBannerContent} from "qqq/components/misc/Banners";
|
||||
import {setMiniSidenav, useMaterialUIController} from "qqq/context";
|
||||
import {MaterialDashboardThemeMetaData} from "qqq/models/metadata/MaterialDashboardThemeMetaData";
|
||||
import AppHome from "qqq/pages/apps/Home";
|
||||
import NoApps from "qqq/pages/apps/NoApps";
|
||||
import ProcessRun from "qqq/pages/processes/ProcessRun";
|
||||
@@ -53,13 +54,11 @@ import RecordQuery from "qqq/pages/records/query/RecordQuery";
|
||||
import RecordDeveloperView from "qqq/pages/records/view/RecordDeveloperView";
|
||||
import RecordView from "qqq/pages/records/view/RecordView";
|
||||
import RecordViewByUniqueKey from "qqq/pages/records/view/RecordViewByUniqueKey";
|
||||
import {createDynamicTheme} from "qqq/utils/createDynamicTheme";
|
||||
import AnalyticsUtils, {AnalyticsModel} from "qqq/utils/analytics/AnalyticsUtils";
|
||||
import {injectIslandVariables} from "qqq/utils/injectIslandVariables";
|
||||
import GoogleAnalyticsUtils, {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUtils";
|
||||
import {detectBasePath, resolveAssetUrl} from "qqq/utils/PathUtils";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import ProcessUtils from "qqq/utils/qqq/ProcessUtils";
|
||||
import React, {JSXElementConstructor, Key, ReactElement, ReactNode, useEffect, useMemo, useState,} from "react";
|
||||
import React, {JSXElementConstructor, Key, ReactElement, useEffect, useState,} from "react";
|
||||
import {useCookies} from "react-cookie";
|
||||
import {Navigate, Route, Routes, useLocation, useParams, useSearchParams,} from "react-router-dom";
|
||||
import {Md5} from "ts-md5/dist/md5";
|
||||
@@ -67,7 +66,6 @@ import {Md5} from "ts-md5/dist/md5";
|
||||
|
||||
const qController = Client.getInstance();
|
||||
export const SESSION_UUID_COOKIE_NAME = "sessionUUID";
|
||||
const MATERIAL_DASHBOARD_THEME_META_DATA_NAME = "com.kingsrook.qqq.frontend.materialdashboard.model.metadata.MaterialDashboardThemeMetaData";
|
||||
|
||||
interface Props
|
||||
{
|
||||
@@ -81,7 +79,6 @@ export default function App({authenticationMetaData}: Props)
|
||||
const [isFullyAuthenticated, setIsFullyAuthenticated] = useState(false);
|
||||
const [profileRoutes, setProfileRoutes] = useState({});
|
||||
const [branding, setBranding] = useState({} as QBrandingMetaData);
|
||||
const [themeMetaData, setThemeMetaData] = useState(null as MaterialDashboardThemeMetaData | null);
|
||||
const [metaData, setMetaData] = useState({} as QInstance);
|
||||
const [needLicenseKey, setNeedLicenseKey] = useState(true);
|
||||
const [loggedInUser, setLoggedInUser] = useState({} as { name?: string, email?: string });
|
||||
@@ -91,7 +88,6 @@ export default function App({authenticationMetaData}: Props)
|
||||
const {setupSession: auth0SetupSession, logout: auth0Logout} = useAuth0AuthenticationModule({setIsFullyAuthenticated, setLoggedInUser, setEarlyReturnForAuth});
|
||||
const {setupSession: oauth2SetupSession, logout: oauth2Logout} = useOAuth2AuthenticationModule({setIsFullyAuthenticated, setLoggedInUser, setEarlyReturnForAuth, inOAuthContext: authenticationMetaData.type === "OAUTH2"});
|
||||
const {setupSession: anonymousSetupSession, logout: anonymousLogout} = useAnonymousAuthenticationModule({setIsFullyAuthenticated, setLoggedInUser, setEarlyReturnForAuth});
|
||||
const [analyticsUtils] = useState(new AnalyticsUtils());
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// tell the client how to do a logout if it sees a 401 //
|
||||
@@ -147,8 +143,6 @@ export default function App({authenticationMetaData}: Props)
|
||||
***************************************************************************/
|
||||
function doLogout()
|
||||
{
|
||||
analyticsUtils.reset();
|
||||
|
||||
if (authenticationMetaData?.type === "AUTH_0")
|
||||
{
|
||||
auth0Logout();
|
||||
@@ -206,7 +200,7 @@ export default function App({authenticationMetaData}: Props)
|
||||
}
|
||||
|
||||
const childList: any[] = [];
|
||||
if (app.children && !app.hideChildrenFromNavigation)
|
||||
if (app.children)
|
||||
{
|
||||
app.children.forEach((child: QAppTreeNode) =>
|
||||
{
|
||||
@@ -251,7 +245,6 @@ export default function App({authenticationMetaData}: Props)
|
||||
dropdown: true,
|
||||
icon: <Icon fontSize="medium">{app.iconName}</Icon>,
|
||||
collapse: childList,
|
||||
["data-qqq-sidenav-item-type"]: "top-level-parent-app"
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -496,35 +489,17 @@ export default function App({authenticationMetaData}: Props)
|
||||
{
|
||||
setAccentColor(metaData.branding.accentColor);
|
||||
}
|
||||
if (metaData.branding.accentColorLight)
|
||||
{
|
||||
setAccentColorLight(metaData.branding.accentColorLight);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
// Set theme metadata for dynamic theming //
|
||||
////////////////////////////////////////////
|
||||
const themeMetaDataObject = metaData.supplementalInstanceMetaData?.get(MATERIAL_DASHBOARD_THEME_META_DATA_NAME);
|
||||
if (themeMetaDataObject)
|
||||
{
|
||||
setThemeMetaData(new MaterialDashboardThemeMetaData(themeMetaDataObject));
|
||||
}
|
||||
|
||||
const gravatarBase = "https://www.gravatar.com/avatar/";
|
||||
const hash = Md5.hashStr(loggedInUser?.email || "user");
|
||||
let profilePicture = `${gravatarBase}${hash}`;
|
||||
if(metaData?.branding?.gravatarDefault)
|
||||
{
|
||||
profilePicture += `?d=${metaData?.branding?.gravatarDefault}`;
|
||||
}
|
||||
const profilePicture = `${gravatarBase}${hash}`;
|
||||
const profileRoutes = {
|
||||
type: "collapse",
|
||||
name: loggedInUser?.name ?? "Anonymous",
|
||||
key: "username",
|
||||
noCollapse: true,
|
||||
icon: <Avatar src={profilePicture} alt="{loggedInUser?.name}" />,
|
||||
["data-qqq-sidenav-item-type"]: "user-profile"
|
||||
};
|
||||
setProfileRoutes(profileRoutes);
|
||||
|
||||
@@ -674,26 +649,7 @@ export default function App({authenticationMetaData}: Props)
|
||||
},
|
||||
);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Create dynamic MUI theme from themeMetaData //
|
||||
// Theme is rebuilt when themeMetaData changes (from backend) //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
const dynamicTheme: Theme = useMemo(() =>
|
||||
{
|
||||
return createDynamicTheme(themeMetaData || undefined);
|
||||
}, [themeMetaData]);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Inject CSS variables for island components (sidebar, header, DataGrid) //
|
||||
// These components can't use MUI palette, so they need CSS variables //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
useEffect(() =>
|
||||
{
|
||||
injectIslandVariables(themeMetaData || undefined);
|
||||
}, [themeMetaData]);
|
||||
|
||||
const [pageHeader, setPageHeader] = useState("" as string | JSX.Element);
|
||||
const [pageHeaderRightContent, setPageHeaderRightContent] = useState(null as ReactNode);
|
||||
const [accentColor, setAccentColor] = useState("#0062FF");
|
||||
const [accentColorLight, setAccentColorLight] = useState("#C0D6F7");
|
||||
const [tableMetaData, setTableMetaData] = useState(null);
|
||||
@@ -710,20 +666,14 @@ export default function App({authenticationMetaData}: Props)
|
||||
}, [loggedInUser]);
|
||||
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if (isFullyAuthenticated)
|
||||
{
|
||||
analyticsUtils.initialize();
|
||||
}
|
||||
}, [isFullyAuthenticated, analyticsUtils]);
|
||||
const [googleAnalyticsUtils] = useState(new GoogleAnalyticsUtils());
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
function recordAnalytics(model: AnalyticsModel)
|
||||
{
|
||||
analyticsUtils.recordAnalytics(model);
|
||||
googleAnalyticsUtils.recordAnalytics(model);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
@@ -759,7 +709,7 @@ export default function App({authenticationMetaData}: Props)
|
||||
***************************************************************************/
|
||||
function pushModalOnStack(modalIdentifier: string): void
|
||||
{
|
||||
if (modalStack.length > 0 && modalStack[modalStack.length] == modalIdentifier)
|
||||
if(modalStack.length > 0 && modalStack[modalStack.length] == modalIdentifier)
|
||||
{
|
||||
console.warn(`Pushing a new modal on the QContext modalStack that is a duplicate of the current top-of-stack [${modalIdentifier}]`)
|
||||
}
|
||||
@@ -773,7 +723,7 @@ export default function App({authenticationMetaData}: Props)
|
||||
***************************************************************************/
|
||||
function popModalOffStack(modalIdentifier: string): void
|
||||
{
|
||||
if (modalStack.length > 0 && modalStack[modalStack.length - 1] != modalIdentifier)
|
||||
if(modalStack.length > 0 && modalStack[modalStack.length - 1] != modalIdentifier)
|
||||
{
|
||||
console.warn(`Request to pop a modal [${modalIdentifier}] off the QContext modalStack that is not currently on top [${modalStack[modalStack.length - 1]}]`)
|
||||
return;
|
||||
@@ -810,7 +760,6 @@ export default function App({authenticationMetaData}: Props)
|
||||
appRoutes && (
|
||||
<QContext.Provider value={{
|
||||
pageHeader: pageHeader,
|
||||
pageHeaderRightContent: pageHeaderRightContent,
|
||||
accentColor: accentColor,
|
||||
accentColorLight: accentColorLight,
|
||||
tableMetaData: tableMetaData,
|
||||
@@ -821,7 +770,6 @@ export default function App({authenticationMetaData}: Props)
|
||||
helpHelpActive: helpHelpActive,
|
||||
userId: userId,
|
||||
setPageHeader: (header: string | JSX.Element) => setPageHeader(header),
|
||||
setPageHeaderRightContent: (content: ReactNode) => setPageHeaderRightContent(content),
|
||||
setAccentColor: (accentColor: string) => setAccentColor(accentColor),
|
||||
setAccentColorLight: (accentColorLight: string) => setAccentColorLight(accentColorLight),
|
||||
setTableMetaData: (tableMetaData: QTableMetaData) => setTableMetaData(tableMetaData),
|
||||
@@ -835,11 +783,10 @@ export default function App({authenticationMetaData}: Props)
|
||||
pathToLabelMap: pathToLabelMap,
|
||||
branding: branding
|
||||
}}>
|
||||
<ThemeProvider theme={dynamicTheme}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<CommandMenu metaData={metaData} />
|
||||
{banner()}
|
||||
<BrandedHeaderBar theme={themeMetaData} />
|
||||
<Sidenav
|
||||
color={sidenavColor}
|
||||
icon={resolveAssetUrl(branding.icon)}
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Capability} from "@qrunio/qqq-frontend-core/lib/model/metaData/Capability";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {useAuth0} from "@auth0/auth0-react";
|
||||
|
||||
+12
-14
@@ -5,31 +5,30 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QBrandingMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QBrandingMetaData";
|
||||
import {QProcessMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QProcessMetaData";
|
||||
import {QTableMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QTableMetaData";
|
||||
import {AnalyticsModel} from "qqq/utils/analytics/AnalyticsUtils";
|
||||
import {createContext, ReactNode} from "react";
|
||||
import {AnalyticsModel} from "qqq/utils/GoogleAnalyticsUtils";
|
||||
import {createContext} from "react";
|
||||
|
||||
interface QContext
|
||||
{
|
||||
pageHeader: string | JSX.Element;
|
||||
setPageHeader?: (header: string | JSX.Element) => void;
|
||||
pageHeaderRightContent?: ReactNode;
|
||||
setPageHeaderRightContent?: (content: ReactNode) => void;
|
||||
|
||||
accentColor: string;
|
||||
setAccentColor?: (color: string) => void;
|
||||
@@ -70,7 +69,6 @@ interface QContext
|
||||
|
||||
const defaultState = {
|
||||
pageHeader: "",
|
||||
pageHeaderRightContent: null as ReactNode,
|
||||
accentColor: "#0062FF",
|
||||
accentColorLight: "#C0D6F7",
|
||||
dotMenuOpen: false,
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QAuthenticationMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster;
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster;
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster;
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster;
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster;
|
||||
|
||||
+11
-10
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster;
|
||||
@@ -30,8 +31,8 @@ import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
|
||||
import com.kingsrook.qqq.backend.javalin.QJavalinMetaData;
|
||||
import com.kingsrook.qqq.frontend.materialdashboard.model.metadata.MaterialDashboardFieldMetaData;
|
||||
import com.kingsrook.qqq.middleware.javalin.QJavalinMetaData;
|
||||
import com.kingsrook.qqq.middleware.javalin.metadata.JavalinRouteProviderMetaData;
|
||||
|
||||
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.actions.formadjuster;
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata;
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata;
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata;
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata;
|
||||
|
||||
+11
-81
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata;
|
||||
@@ -28,7 +29,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaDa
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** instance-level meta-data for this module (handled as QSupplementalTableMetaData)
|
||||
** table-level meta-data for this module (handled as QSupplementalTableMetaData)
|
||||
*******************************************************************************/
|
||||
public class MaterialDashboardInstanceMetaData implements QSupplementalInstanceMetaData
|
||||
{
|
||||
@@ -38,10 +39,6 @@ public class MaterialDashboardInstanceMetaData implements QSupplementalInstanceM
|
||||
|
||||
private Integer queryScreenCopyFullQueryColumnValuesLimit = 100_000;
|
||||
|
||||
private MaterialDashboardTableMetaData.RecordViewActionsPlacement recordViewActionsPlacement = MaterialDashboardTableMetaData.RecordViewActionsPlacement.IN_IDENTITY_SECTION;
|
||||
|
||||
private WeekdayCriteriaSettings weekdayCriteriaSettings = new WeekdayCriteriaSettings();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -158,71 +155,4 @@ public class MaterialDashboardInstanceMetaData implements QSupplementalInstanceM
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for recordViewActionsPlacement
|
||||
*******************************************************************************/
|
||||
public MaterialDashboardTableMetaData.RecordViewActionsPlacement getRecordViewActionsPlacement()
|
||||
{
|
||||
return (this.recordViewActionsPlacement);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for recordViewActionsPlacement
|
||||
*******************************************************************************/
|
||||
public void setRecordViewActionsPlacement(MaterialDashboardTableMetaData.RecordViewActionsPlacement recordViewActionsPlacement)
|
||||
{
|
||||
this.recordViewActionsPlacement = recordViewActionsPlacement;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for recordViewActionsPlacement
|
||||
*******************************************************************************/
|
||||
public MaterialDashboardInstanceMetaData withRecordViewActionsPlacement(MaterialDashboardTableMetaData.RecordViewActionsPlacement recordViewActionsPlacement)
|
||||
{
|
||||
this.recordViewActionsPlacement = recordViewActionsPlacement;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Getter for weekdayCriteriaSettings
|
||||
* @see #withWeekdayCriteriaSettings(WeekdayCriteriaSettings)
|
||||
*******************************************************************************/
|
||||
public WeekdayCriteriaSettings getWeekdayCriteriaSettings()
|
||||
{
|
||||
return (this.weekdayCriteriaSettings);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Setter for weekdayCriteriaSettings
|
||||
* @see #withWeekdayCriteriaSettings(WeekdayCriteriaSettings)
|
||||
*******************************************************************************/
|
||||
public void setWeekdayCriteriaSettings(WeekdayCriteriaSettings weekdayCriteriaSettings)
|
||||
{
|
||||
this.weekdayCriteriaSettings = weekdayCriteriaSettings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Fluent setter for weekdayCriteriaSettings
|
||||
*
|
||||
* @param weekdayCriteriaSettings
|
||||
* @return this
|
||||
*******************************************************************************/
|
||||
public MaterialDashboardInstanceMetaData withWeekdayCriteriaSettings(WeekdayCriteriaSettings weekdayCriteriaSettings)
|
||||
{
|
||||
this.weekdayCriteriaSettings = weekdayCriteriaSettings;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+10
-82
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata;
|
||||
@@ -44,12 +45,6 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
*******************************************************************************/
|
||||
public class MaterialDashboardTableMetaData extends QSupplementalTableMetaData
|
||||
{
|
||||
public enum RecordViewActionsPlacement
|
||||
{
|
||||
IN_IDENTITY_SECTION,
|
||||
INLINE_WITH_PAGE_TITLE
|
||||
}
|
||||
|
||||
private static final QLogger LOG = QLogger.getLogger(MaterialDashboardTableMetaData.class);
|
||||
|
||||
public static final String TYPE = "materialDashboard";
|
||||
@@ -57,8 +52,6 @@ public class MaterialDashboardTableMetaData extends QSupplementalTableMetaData
|
||||
private List<List<String>> gotoFieldNames;
|
||||
private List<String> defaultQuickFilterFieldNames;
|
||||
private List<FieldRule> fieldRules;
|
||||
private Boolean showRecordSidebar = true;
|
||||
private RecordViewActionsPlacement recordViewActionsPlacement = RecordViewActionsPlacement.IN_IDENTITY_SECTION;
|
||||
|
||||
private QCodeReference onLoadFormAdjuster = null;
|
||||
|
||||
@@ -269,68 +262,6 @@ public class MaterialDashboardTableMetaData extends QSupplementalTableMetaData
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for showRecordSidebar
|
||||
*******************************************************************************/
|
||||
public Boolean getShowRecordSidebar()
|
||||
{
|
||||
return (this.showRecordSidebar);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for showRecordSidebar
|
||||
*******************************************************************************/
|
||||
public void setShowRecordSidebar(Boolean showRecordSidebar)
|
||||
{
|
||||
this.showRecordSidebar = showRecordSidebar;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for showRecordSidebar
|
||||
*******************************************************************************/
|
||||
public MaterialDashboardTableMetaData withShowRecordSidebar(Boolean showRecordSidebar)
|
||||
{
|
||||
this.showRecordSidebar = showRecordSidebar;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for recordViewActionsPlacement
|
||||
*******************************************************************************/
|
||||
public RecordViewActionsPlacement getRecordViewActionsPlacement()
|
||||
{
|
||||
return (this.recordViewActionsPlacement);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for recordViewActionsPlacement
|
||||
*******************************************************************************/
|
||||
public void setRecordViewActionsPlacement(RecordViewActionsPlacement recordViewActionsPlacement)
|
||||
{
|
||||
this.recordViewActionsPlacement = recordViewActionsPlacement;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for recordViewActionsPlacement
|
||||
*******************************************************************************/
|
||||
public MaterialDashboardTableMetaData withRecordViewActionsPlacement(RecordViewActionsPlacement recordViewActionsPlacement)
|
||||
{
|
||||
this.recordViewActionsPlacement = recordViewActionsPlacement;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fieldRules
|
||||
*******************************************************************************/
|
||||
@@ -453,9 +384,6 @@ public class MaterialDashboardTableMetaData extends QSupplementalTableMetaData
|
||||
clone.onLoadFormAdjuster = onLoadFormAdjuster.clone();
|
||||
}
|
||||
|
||||
clone.showRecordSidebar = showRecordSidebar;
|
||||
clone.recordViewActionsPlacement = recordViewActionsPlacement;
|
||||
|
||||
return abstractClone;
|
||||
}
|
||||
|
||||
|
||||
-3489
File diff suppressed because it is too large.
Load diff
-156
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2026. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Material dashboard instance metadata settings - to control weekday criteria
|
||||
* on date & date-time fields. e.g., day-is-any-of or day-is-non-eof.
|
||||
*
|
||||
* <p>These criteria are on by default - but this object can turn them off with
|
||||
* the {@code enabled} setting.</p>
|
||||
*
|
||||
* <p>Also - since how time zones should apply can be a hotly contested issue,
|
||||
* values for the parameters supported by QQQ's {@code WeekdayOfDateTimeFunction}
|
||||
* can be set as {@code dateTimeFieldFunctionArguments}</p>
|
||||
*
|
||||
* <p>e.g., to always use US/Central time:
|
||||
* <pre>
|
||||
new WeekdayCriteriaSettings().withDateTimeFieldFunctionArguments(
|
||||
Map.of(WeekdayOfDateTimeFunction.PARAM_TIME_ZONE_ID, "US/Central")));</pre></p>
|
||||
*
|
||||
* <p>or to use user/session time zone:
|
||||
* <pre>
|
||||
new WeekdayCriteriaSettings().withDateTimeFieldFunctionArguments(
|
||||
Map.of(WeekdayOfDateTimeFunction.PARAM_USE_SESSION_ZONE_ID, true)));</pre></p>
|
||||
*******************************************************************************/
|
||||
public class WeekdayCriteriaSettings implements Serializable, Cloneable
|
||||
{
|
||||
private Boolean enabled = true;
|
||||
private Map<String, Serializable> dateTimeFieldFunctionArguments = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Getter for enabled
|
||||
* @see #withEnabled(Boolean)
|
||||
*******************************************************************************/
|
||||
public Boolean getEnabled()
|
||||
{
|
||||
return (this.enabled);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Setter for enabled
|
||||
* @see #withEnabled(Boolean)
|
||||
*******************************************************************************/
|
||||
public void setEnabled(Boolean enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Fluent setter for enabled
|
||||
*
|
||||
* @param enabled
|
||||
* TODO document this property
|
||||
*
|
||||
* @return this
|
||||
*******************************************************************************/
|
||||
public WeekdayCriteriaSettings withEnabled(Boolean enabled)
|
||||
{
|
||||
this.enabled = enabled;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Getter for dateTimeFieldFunctionArguments
|
||||
* @see #withDateTimeFieldFunctionArguments(Map)
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getDateTimeFieldFunctionArguments()
|
||||
{
|
||||
return (this.dateTimeFieldFunctionArguments);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Setter for dateTimeFieldFunctionArguments
|
||||
* @see #withDateTimeFieldFunctionArguments(Map)
|
||||
*******************************************************************************/
|
||||
public void setDateTimeFieldFunctionArguments(Map<String, Serializable> dateTimeFieldFunctionArguments)
|
||||
{
|
||||
this.dateTimeFieldFunctionArguments = dateTimeFieldFunctionArguments;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Fluent setter for dateTimeFieldFunctionArguments
|
||||
*
|
||||
* @param dateTimeFieldFunctionArguments
|
||||
* TODO document this property
|
||||
*
|
||||
* @return this
|
||||
*******************************************************************************/
|
||||
public WeekdayCriteriaSettings withDateTimeFieldFunctionArguments(Map<String, Serializable> dateTimeFieldFunctionArguments)
|
||||
{
|
||||
this.dateTimeFieldFunctionArguments = dateTimeFieldFunctionArguments;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public WeekdayCriteriaSettings clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
WeekdayCriteriaSettings clone = (WeekdayCriteriaSettings) super.clone();
|
||||
|
||||
if(dateTimeFieldFunctionArguments != null)
|
||||
{
|
||||
clone.dateTimeFieldFunctionArguments = new HashMap<>(dateTimeFieldFunctionArguments);
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata.fieldrules;
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata.fieldrules;
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.model.metadata.fieldrules;
|
||||
|
||||
+10
-9
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.frontend.materialdashboard.savedreports;
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
|
||||
/***************************************************************************
|
||||
* The original version of this app had the "primary" color set to a bright
|
||||
* red/fuchsia/pink tone (#E91E63). So, whenever we would try to add something
|
||||
* that might have made sense to use "primary" color, we'd see that tone that
|
||||
* we didn't like, and we'd use "info" instead - a nice blue.
|
||||
*
|
||||
* But now, with the backend-provided themes, apps might set their primary
|
||||
* color to something like, let's say teal, and the expectation is to make a
|
||||
* lot of things like the new & save buttons be that primary color.
|
||||
*
|
||||
* But, we don't want to make "info" that teal tone - it should be blue
|
||||
* (e.g,. for an info alert or icon - blue for info is the expectation).
|
||||
*
|
||||
* So the mistake we made all along I think was using "info" instead of
|
||||
* "primary" in lots of UI elements that we added.
|
||||
*
|
||||
* To preserve backward compatibility as much as possible, while also changing
|
||||
* some elements from "info" to "primary", then here's what I've come up with
|
||||
* as an approach:
|
||||
*
|
||||
* 1. Define a CSS variable on `:root`called `--qqq-prefer-info-color-to-primary-color`.
|
||||
* (which flows from MaterialDashboardThemeMetaData.preferInfoColorToPrimaryColor)
|
||||
* - If this variable is 1, then places that /should/ have used "primary"
|
||||
* all along would instead still use "info". We'd set that to 1 to
|
||||
* maintain backward-compatibility (e.g., so the create-new button would
|
||||
* still be info-blue, not primary-fuchsia).
|
||||
* - But then, an app can override that value to `0`, to say "no no, don't
|
||||
* prefer info - instead, use primary where it would have made sense
|
||||
* (but you just didn't want that fuchsia color)". So an app could set
|
||||
* MaterialDashboardThemeMetaData.preferInfoColorToPrimaryColor to false,
|
||||
* or set `--qqq-prefer-info-color-to-primary-color: 0` in their custom CSS
|
||||
* so its create new button would be primary (e.g., teal as mentioned above),
|
||||
* not info-blue.
|
||||
*
|
||||
* 2. Define a family of functions (e.g., this file) that, based on that css
|
||||
* variable, either output `"info"` or `"primary"`; or `var(--qqq-info-color)`
|
||||
* or `var(--qqq-primary-color)`, or the hard-coded fallbacks for info & primary
|
||||
*
|
||||
* 3. Update components that are using `"info"` (or `--qqq-info-color`
|
||||
* (or `var(--qqq-info-color, "#0062FF")`)) to call the appropriate
|
||||
* `preferredInfoOrPrimaryXXX` function. I don't think this is a
|
||||
* "change them all" scenario (because info should still be used where it's
|
||||
* intention is truly 'info'), but, i'm not sure entirely which ones
|
||||
* it should be... my current list looks like:
|
||||
* A. Create-New and Save buttons in DefaultButtons.tsx
|
||||
* B. icon backgrounds on app home screens (Home.tsx)
|
||||
* C. progress bar background on processes (ProcessRun.tsx usage of Stepper)
|
||||
* D. the little pagination buttons on the bottoms of like the TableData widget
|
||||
***************************************************************************/
|
||||
|
||||
/***************************************************************************
|
||||
* private function used by other functions in here to decide if the info
|
||||
* color is preferred over the primary color, based on the css variable
|
||||
* --qqq-prefer-info-color-to-primary-color being equal to 1 or not.
|
||||
*
|
||||
* 1 = prefer info color; anything else = prefer primary color
|
||||
*
|
||||
* Legacy behavior was to always prefer info color; Apps setting their own
|
||||
* primary color may want to use their primary color for more UI elements,
|
||||
* and set that css var to 0.
|
||||
***************************************************************************/
|
||||
function preferInfoColorToPrimaryColor(): boolean {
|
||||
return getComputedStyle(document.body).getPropertyValue('--qqq-prefer-info-color-to-primary-color').trim() === "1";
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* private function that, based on the value of preferInfoColorToPrimaryColor(),
|
||||
* returns the hex code for either the default info color (a blue), or the
|
||||
* default primary color (a red/fuchsia).
|
||||
***************************************************************************/
|
||||
function preferredDefaultColorInfoOrPrimary(): string {
|
||||
return preferInfoColorToPrimaryColor() ? "#0062FF" : "#E91E63";
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* public function that returns either "info" or "primary" based on the
|
||||
* value of preferInfoColorToPrimaryColor().
|
||||
*
|
||||
* So elements that should probably be displayed in the primary color (e.g.,
|
||||
* primary action buttons on screens, or some UI decorations), they can use
|
||||
* this function to get their color name - but - if the primary is undesirable,
|
||||
* then the info color (default blue) can be used instead.
|
||||
***************************************************************************/
|
||||
export function preferredColorNameInfoOrPrimary(): "info" | "primary" {
|
||||
return preferInfoColorToPrimaryColor() ? "info" : "primary";
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* public function that returns a css var(--qqq...color, #default) expression,
|
||||
* based on the value of preferInfoColorToPrimaryColor().
|
||||
*
|
||||
* For use-cases similar to preferredColorNameInfoOrPrimary(), but where we
|
||||
* need a CSS value, not a color name for MUI to interpret.
|
||||
***************************************************************************/
|
||||
export function preferredInfoOrPrimaryColorVarExpression(): string {
|
||||
return `var(--qqq-${preferredColorNameInfoOrPrimary()}-color, ${preferredDefaultColorInfoOrPrimary()})`;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {hasScope} from "./ScopeUtils";
|
||||
|
||||
describe("OAuth scopes", () =>
|
||||
{
|
||||
const driveScope = "https://www.googleapis.com/auth/drive";
|
||||
|
||||
it("accepts the exact scope among granted scopes", () =>
|
||||
{
|
||||
expect(hasScope(driveScope, driveScope)).toBe(true);
|
||||
expect(hasScope(`openid profile ${driveScope} email`, driveScope)).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects missing scopes and substring lookalikes", () =>
|
||||
{
|
||||
for (const scope of ["", "openid profile", `${driveScope}.readonly`, `https://example.com/${driveScope}`, `${driveScope}.example.com`])
|
||||
{
|
||||
expect(hasScope(scope, driveScope)).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export function hasScope(grantedScopes: string, requiredScope: string): boolean
|
||||
{
|
||||
return grantedScopes.split(/\s+/).includes(requiredScope);
|
||||
}
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QAuthenticationMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
|
||||
@@ -53,7 +54,6 @@ export default function useAnonymousAuthenticationModule({setIsFullyAuthenticate
|
||||
try
|
||||
{
|
||||
const {values} = await qController.manageSession(null, null, null);
|
||||
localStorage.setItem("sessionValues", JSON.stringify(values ?? {}));
|
||||
if (values?.user)
|
||||
{
|
||||
setLoggedInUser(values.user);
|
||||
@@ -74,7 +74,6 @@ export default function useAnonymousAuthenticationModule({setIsFullyAuthenticate
|
||||
const logout = () =>
|
||||
{
|
||||
qController.clearAuthenticationMetaDataLocalStorage();
|
||||
localStorage.removeItem("sessionValues");
|
||||
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
|
||||
};
|
||||
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {useAuth0} from "@auth0/auth0-react";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {withAuthenticationRequired} from "@auth0/auth0-react";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Auth0Provider, useAuth0} from "@auth0/auth0-react";
|
||||
import {QAuthenticationMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
|
||||
@@ -24,7 +25,6 @@ import HandleAuthorizationError from "HandleAuthorizationError";
|
||||
import jwt_decode from "jwt-decode";
|
||||
import ProtectedRoute from "qqq/authorization/auth0/ProtectedRoute";
|
||||
import {MaterialUIControllerProvider} from "qqq/context";
|
||||
import AnalyticsUtils from "qqq/utils/analytics/AnalyticsUtils";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import {useCookies} from "react-cookie";
|
||||
import {useNavigate, useSearchParams} from "react-router-dom";
|
||||
@@ -106,40 +106,6 @@ export default function useAuth0AuthenticationModule({setIsFullyAuthenticated, s
|
||||
};
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
const readStoredSessionValues = (): {[key: string]: any} | null =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const rawSessionValues = localStorage.getItem("sessionValues");
|
||||
return (rawSessionValues ? JSON.parse(rawSessionValues) : null);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.log("Error parsing sessionValues from localStorage: " + e);
|
||||
return (null);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
const hasAnalyticsIdentityValues = (sessionValues: {[key: string]: any} | null): boolean =>
|
||||
{
|
||||
const analyticsIdentityValues = AnalyticsUtils.getAnalyticsIdentityValues(sessionValues);
|
||||
return Boolean(
|
||||
analyticsIdentityValues["user_email"]
|
||||
|| sessionValues?.user?.email
|
||||
|| sessionValues?.user?.idReference
|
||||
|| sessionValues?.user?.id
|
||||
|| sessionValues?.user?.userId
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@@ -149,41 +115,26 @@ export default function useAuth0AuthenticationModule({setIsFullyAuthenticated, s
|
||||
{
|
||||
console.log("Loading token from auth0...");
|
||||
const accessToken = await auth0GetAccessTokenSilently();
|
||||
let loggedInUserForApp = auth0User;
|
||||
|
||||
const lsAccessToken = localStorage.getItem("accessToken");
|
||||
if (shouldStoreNewToken(accessToken, lsAccessToken))
|
||||
{
|
||||
console.log("Sending accessToken to backend, requesting a sessionUUID...");
|
||||
const {values} = await qController.manageSession(accessToken, null);
|
||||
const {uuid: values} = await qController.manageSession(accessToken, null);
|
||||
|
||||
localStorage.setItem("accessToken", accessToken);
|
||||
localStorage.setItem("sessionValues", JSON.stringify(values ?? {}));
|
||||
loggedInUserForApp = values?.user ?? auth0User;
|
||||
localStorage.setItem("sessionValues", JSON.stringify(values));
|
||||
console.log("Got new sessionUUID from backend, and stored new accessToken");
|
||||
}
|
||||
else
|
||||
{
|
||||
const storedSessionValues = readStoredSessionValues();
|
||||
if (!hasAnalyticsIdentityValues(storedSessionValues))
|
||||
{
|
||||
console.log("Refreshing session values from existing sessionUUID...");
|
||||
const {values} = await qController.manageSession(null, cookies[SESSION_UUID_COOKIE_NAME]);
|
||||
localStorage.setItem("sessionValues", JSON.stringify(values ?? {}));
|
||||
loggedInUserForApp = values?.user ?? auth0User;
|
||||
}
|
||||
else if (storedSessionValues?.user)
|
||||
{
|
||||
loggedInUserForApp = storedSessionValues.user;
|
||||
}
|
||||
|
||||
console.log("Using existing sessionUUID cookie");
|
||||
}
|
||||
|
||||
setIsFullyAuthenticated(true);
|
||||
Client.setGotAuthenticationInAllControllers();
|
||||
|
||||
setLoggedInUser(loggedInUserForApp);
|
||||
setLoggedInUser(auth0User);
|
||||
console.log("Token load complete.");
|
||||
}
|
||||
catch (e)
|
||||
@@ -191,7 +142,6 @@ export default function useAuth0AuthenticationModule({setIsFullyAuthenticated, s
|
||||
console.log(`Error loading token: ${JSON.stringify(e)}`);
|
||||
qController.clearAuthenticationMetaDataLocalStorage();
|
||||
localStorage.removeItem("accessToken");
|
||||
localStorage.removeItem("sessionValues");
|
||||
removeCookie(SESSION_UUID_COOKIE_NAME, {path: "/"});
|
||||
useAuth0Logout();
|
||||
return;
|
||||
@@ -205,7 +155,6 @@ export default function useAuth0AuthenticationModule({setIsFullyAuthenticated, s
|
||||
const logout = () =>
|
||||
{
|
||||
localStorage.removeItem("accessToken");
|
||||
localStorage.removeItem("sessionValues");
|
||||
useAuth0Logout({returnTo: window.location.origin});
|
||||
};
|
||||
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QAuthenticationMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QAuthenticationMetaData";
|
||||
@@ -185,7 +186,6 @@ export default function useOAuth2AuthenticationModule({setIsFullyAuthenticated,
|
||||
// ** Exchange authorization code for session UUID via backend API * //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
const {uuid: newSessionUuid, values} = await qController.manageSession(null, null, manageSessionRequestBody);
|
||||
localStorage.setItem("sessionValues", JSON.stringify(values ?? {}));
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// ** Update parent component's authentication state * //
|
||||
@@ -265,7 +265,6 @@ export default function useOAuth2AuthenticationModule({setIsFullyAuthenticated,
|
||||
// ** Validate the session UUID with backend and retrieve user data * //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
const {values} = await qController.manageSession(null, sessionUuid, null);
|
||||
localStorage.setItem("sessionValues", JSON.stringify(values ?? {}));
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// ** Update parent component's authentication state * //
|
||||
@@ -372,7 +371,6 @@ export default function useOAuth2AuthenticationModule({setIsFullyAuthenticated,
|
||||
// ** Clear authentication metadata stored in local storage by the QQQ client * //
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
qController.clearAuthenticationMetaDataLocalStorage();
|
||||
localStorage.removeItem("sessionValues");
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ** Remove session cookies from the browser * //
|
||||
@@ -431,7 +429,7 @@ export default function useOAuth2AuthenticationModule({setIsFullyAuthenticated,
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// ** Extract the OAuth2 identity provider's base URL (authority) from metadata * //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
const authority: string = authenticationMetaData.data.externalBaseUrl || authenticationMetaData.data.baseUrl;
|
||||
const authority: string = authenticationMetaData.data.baseUrl;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// ** Extract the OAuth2 client ID for this application from metadata * //
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QException} from "@qrunio/qqq-frontend-core/lib/exceptions/QException";
|
||||
|
||||
@@ -5,22 +5,22 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import {preferredColorNameInfoOrPrimary} from "qqq/assets/theme/functions/preferInfoColorToPrimaryColor";
|
||||
import {QIcon} from "@qrunio/qqq-frontend-core/lib/model/metaData/QIcon";
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
@@ -43,7 +43,7 @@ export function QCreateNewButton({tablePath}: QCreateNewButtonProps): JSX.Elemen
|
||||
return (
|
||||
<Box display="inline-block" ml={standardML} mr={0} width={standardWidth}>
|
||||
<Link to={`${tablePath}/create`}>
|
||||
<MDButton qqqId="create-new" variant="gradient" color={preferredColorNameInfoOrPrimary()} fullWidth startIcon={<Icon>add</Icon>}>
|
||||
<MDButton variant="gradient" color="info" fullWidth startIcon={<Icon>add</Icon>}>
|
||||
Create New
|
||||
</MDButton>
|
||||
</Link>
|
||||
@@ -68,7 +68,7 @@ export function QSaveButton({label, iconName, onClickHandler, disabled}: QSaveBu
|
||||
{
|
||||
return (
|
||||
<Box ml={standardML} width={standardWidth}>
|
||||
<MDButton qqqId="save" type="submit" variant="gradient" color={preferredColorNameInfoOrPrimary()} size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>{iconName}</Icon>} disabled={disabled}>
|
||||
<MDButton type="submit" variant="gradient" color="info" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>{iconName}</Icon>} disabled={disabled}>
|
||||
{label}
|
||||
</MDButton>
|
||||
</Box>
|
||||
@@ -89,7 +89,7 @@ export function QDeleteButton({onClickHandler, disabled}: QDeleteButtonProps): J
|
||||
{
|
||||
return (
|
||||
<Box ml={standardML} width={standardWidth}>
|
||||
<MDButton qqqId="delete" variant="contained" color="error" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>} disabled={disabled}>
|
||||
<MDButton variant="gradient" color="primary" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>} disabled={disabled}>
|
||||
Delete
|
||||
</MDButton>
|
||||
</Box>
|
||||
@@ -122,8 +122,7 @@ export function QActionsMenuButton({isOpen, onClickHandler, label, qIcon}: QActi
|
||||
return (
|
||||
<Box width={standardWidth} ml={1}>
|
||||
<MDButton
|
||||
qqqId="actions-menu"
|
||||
variant={"outlined"}
|
||||
variant={isOpen ? "contained" : "outlined"}
|
||||
color="dark"
|
||||
onClick={onClickHandler}
|
||||
startIcon={<Icon>{qIcon?.name ?? "games"}</Icon>}
|
||||
@@ -142,16 +141,15 @@ interface QCancelButtonProps
|
||||
disabled: boolean;
|
||||
label?: string;
|
||||
iconName?: string;
|
||||
qqqId?: string;
|
||||
}
|
||||
|
||||
export function QCancelButton({
|
||||
onClickHandler, disabled, label, iconName, qqqId
|
||||
onClickHandler, disabled, label, iconName,
|
||||
}: QCancelButtonProps): JSX.Element
|
||||
{
|
||||
return (
|
||||
<Box ml={standardML} width={standardWidth}>
|
||||
<MDButton type="button" variant="outlined" color="dark" size="small" fullWidth startIcon={<Icon>{iconName}</Icon>} onClick={onClickHandler} disabled={disabled} qqqId={qqqId}>
|
||||
<MDButton type="button" variant="outlined" color="dark" size="small" fullWidth startIcon={<Icon>{iconName}</Icon>} onClick={onClickHandler} disabled={disabled}>
|
||||
{label}
|
||||
</MDButton>
|
||||
</Box>
|
||||
@@ -161,7 +159,6 @@ export function QCancelButton({
|
||||
QCancelButton.defaultProps = {
|
||||
label: "cancel",
|
||||
iconName: "cancel",
|
||||
qqqId: "cancel"
|
||||
};
|
||||
|
||||
interface QSubmitButtonProps
|
||||
@@ -169,14 +166,13 @@ interface QSubmitButtonProps
|
||||
label?: string;
|
||||
iconName?: string;
|
||||
disabled: boolean;
|
||||
qqqId?: string;
|
||||
}
|
||||
|
||||
export function QSubmitButton({label, iconName, disabled, qqqId}: QSubmitButtonProps): JSX.Element
|
||||
export function QSubmitButton({label, iconName, disabled}: QSubmitButtonProps): JSX.Element
|
||||
{
|
||||
return (
|
||||
<Box ml={standardML} width={standardWidth}>
|
||||
<MDButton type="submit" variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>{iconName}</Icon>} disabled={disabled} qqqId={qqqId}>
|
||||
<MDButton type="submit" variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>{iconName}</Icon>} disabled={disabled}>
|
||||
{label}
|
||||
</MDButton>
|
||||
</Box>
|
||||
@@ -186,7 +182,6 @@ export function QSubmitButton({label, iconName, disabled, qqqId}: QSubmitButtonP
|
||||
QSubmitButton.defaultProps = {
|
||||
label: "Submit",
|
||||
iconName: "check",
|
||||
qqqId: "submit"
|
||||
};
|
||||
|
||||
interface QAlternateButtonProps
|
||||
@@ -194,21 +189,18 @@ interface QAlternateButtonProps
|
||||
label: string,
|
||||
iconName?: string,
|
||||
disabled: boolean,
|
||||
onClick?: () => void,
|
||||
qqqId?: string;
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export function QAlternateButton({label, iconName, disabled, onClick, qqqId}: QAlternateButtonProps): JSX.Element
|
||||
export function QAlternateButton({label, iconName, disabled, onClick}: QAlternateButtonProps): JSX.Element
|
||||
{
|
||||
return (
|
||||
<Box ml={standardML} width={standardWidth}>
|
||||
<MDButton type="button" variant="gradient" color="secondary" size="small" fullWidth startIcon={iconName && <Icon>{iconName}</Icon>} onClick={onClick} disabled={disabled} qqqId={qqqId}>
|
||||
<MDButton type="button" variant="gradient" color="secondary" size="small" fullWidth startIcon={iconName && <Icon>{iconName}</Icon>} onClick={onClick} disabled={disabled}>
|
||||
{label}
|
||||
</MDButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
QAlternateButton.defaultProps = {
|
||||
qqqId: "alternate"
|
||||
};
|
||||
QAlternateButton.defaultProps = {};
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {ClickAwayListener, Grow, MenuList, Paper, Popper} from "@mui/material";
|
||||
@@ -23,7 +24,6 @@ import Button from "@mui/material/Button/Button";
|
||||
import Icon from "@mui/material/Icon";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {generateButtonId, generateMenuItemId} from "qqq/utils/qqqIdUtils";
|
||||
|
||||
|
||||
interface Props
|
||||
@@ -95,7 +95,7 @@ function MenuButton({label, iconName, options, disabled, callback}: Props)
|
||||
const menuItems: JSX.Element[] = []
|
||||
options.map((option, index) =>
|
||||
{
|
||||
menuItems.push(<MenuItem key={index} data-qqq-id={generateMenuItemId(undefined, option)} onClick={e => menuItemClicked(e, index)}>
|
||||
menuItems.push(<MenuItem key={index} onClick={e => menuItemClicked(e, index)}>
|
||||
{option}
|
||||
</MenuItem>);
|
||||
})
|
||||
@@ -105,7 +105,6 @@ function MenuButton({label, iconName, options, disabled, callback}: Props)
|
||||
<Button
|
||||
ref={anchorRef}
|
||||
id="composition-button"
|
||||
data-qqq-id={generateButtonId(undefined, label)}
|
||||
aria-controls={open ? "composition-menu" : undefined}
|
||||
aria-expanded={open ? "true" : undefined}
|
||||
aria-haspopup="true"
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QJobError} from "@qrunio/qqq-frontend-core/lib/model/processes/QJobError";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Box, InputLabel} from "@mui/material";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Chip} from "@mui/material";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {InputAdornment, InputLabel} from "@mui/material";
|
||||
@@ -34,7 +35,6 @@ import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import React, {useMemo, useState} from "react";
|
||||
import AceEditor from "react-ace";
|
||||
import {flushSync} from "react-dom";
|
||||
import {generateInputId} from "qqq/utils/qqqIdUtils";
|
||||
|
||||
// Declaring props types for FormField
|
||||
interface Props
|
||||
@@ -281,10 +281,9 @@ function QDynamicFormField({
|
||||
}
|
||||
else
|
||||
{
|
||||
const dataQqqId = generateInputId(undefined, name, label);
|
||||
field = (
|
||||
<>
|
||||
<Field {...rest} {...onChange} onWheel={handleOnWheel} name={name} type={type} as={MDInput} variant="outlined" label={label} InputLabelProps={inputLabelProps} InputProps={inputProps} fullWidth disabled={isDisabled} data-qqq-id={dataQqqId}
|
||||
<Field {...rest} {...onChange} onWheel={handleOnWheel} name={name} type={type} as={MDInput} variant="outlined" label={label} InputLabelProps={inputLabelProps} InputProps={inputProps} fullWidth disabled={isDisabled}
|
||||
onKeyPress={(e: any) =>
|
||||
{
|
||||
if (e.key === "Enter")
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Alert, Skeleton} from "@mui/material";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {AdornmentType} from "@qrunio/qqq-frontend-core/lib/model/metaData/AdornmentType";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Checkbox, Chip, CircularProgress, FilterOptionsState, Icon} from "@mui/material";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Alert} from "@mui/material";
|
||||
@@ -52,7 +53,6 @@ import RecordGridWidget, {ChildRecordListData} from "qqq/components/widgets/misc
|
||||
import RowBuilderWidget from "qqq/components/widgets/misc/RowBuilderWidget";
|
||||
import {FieldRule, FieldRuleAction, FieldRuleTrigger} from "qqq/models/fields/FieldRules";
|
||||
import HtmlUtils from "qqq/utils/HtmlUtils";
|
||||
import {sanitizeId} from "qqq/utils/qqqIdUtils";
|
||||
import Client from "qqq/utils/qqq/Client";
|
||||
import TableUtils from "qqq/utils/qqq/TableUtils";
|
||||
import ValueUtils from "qqq/utils/qqq/ValueUtils";
|
||||
@@ -463,8 +463,8 @@ function EntityForm(props: Props): JSX.Element
|
||||
return form
|
||||
}
|
||||
|
||||
return <Card id={section.name} sx={{overflow: "visible", scrollMarginTop: "100px"}} elevation={cardElevation} data-qqq-id={`form-section-${sanitizeId(section.name)}`}>
|
||||
<MDTypography variant="h6" p={3} pb={1} data-qqq-id={`form-section-header-${sanitizeId(section.name)}`}>
|
||||
return <Card id={section.name} sx={{overflow: "visible", scrollMarginTop: "100px"}} elevation={cardElevation}>
|
||||
<MDTypography variant="h6" p={3} pb={1}>
|
||||
{section.label}
|
||||
</MDTypography>
|
||||
{getSectionHelp(section)}
|
||||
@@ -1587,8 +1587,6 @@ function EntityForm(props: Props): JSX.Element
|
||||
}
|
||||
|
||||
const formId = props.id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
|
||||
const tableNameForId = tableMetaData ? sanitizeId(tableMetaData.name) : "";
|
||||
const formMode = props.isCopy ? "copy" : (props.id != null ? "edit" : "create");
|
||||
|
||||
let body;
|
||||
|
||||
@@ -1646,7 +1644,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
else
|
||||
{
|
||||
body = (
|
||||
<Box mb={3} className="entityForm" data-qqq-id={`record-${formMode}-${tableNameForId}`}>
|
||||
<Box mb={3} className="entityForm">
|
||||
{
|
||||
(alertContent || warningContent) &&
|
||||
<Grid container spacing={3}>
|
||||
@@ -1752,9 +1750,9 @@ function EntityForm(props: Props): JSX.Element
|
||||
<ScrollToFirstError />
|
||||
|
||||
<Box pb={3} pt={0}>
|
||||
<Card id={`${t1sectionName}`} sx={{overflow: "visible", pb: 2, scrollMarginTop: "100px"}} elevation={cardElevation} data-qqq-id={`record-${formMode}-header-${tableNameForId}`}>
|
||||
<Card id={`${t1sectionName}`} sx={{overflow: "visible", pb: 2, scrollMarginTop: "100px"}} elevation={cardElevation}>
|
||||
<Box display="flex" p={3} pb={1}>
|
||||
<Box mr={1.5} data-qqq-id={`record-${formMode}-avatar-${tableNameForId}`}>
|
||||
<Box mr={1.5}>
|
||||
<Avatar sx={{bgcolor: accentColor}}>
|
||||
<Icon>
|
||||
{tableMetaData?.iconName}
|
||||
@@ -1762,7 +1760,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
</Avatar>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center">
|
||||
<MDTypography variant="h5" data-qqq-id={`record-${formMode}-title-${tableNameForId}`}>{formTitle}</MDTypography>
|
||||
<MDTypography variant="h5">{formTitle}</MDTypography>
|
||||
</Box>
|
||||
</Box>
|
||||
{t1section && getSectionHelp(t1section)}
|
||||
@@ -1782,7 +1780,7 @@ function EntityForm(props: Props): JSX.Element
|
||||
)) : null}
|
||||
|
||||
{formFieldsBySection &&
|
||||
<Box component="div" p={3} className={props.isModal ? "modalBottomButtonBar" : "stickyBottomButtonBar"} data-qqq-id={`record-${formMode}-button-bar-${tableNameForId}`}>
|
||||
<Box component="div" p={3} className={props.isModal ? "modalBottomButtonBar" : "stickyBottomButtonBar"}>
|
||||
<Grid container justifyContent="flex-end" spacing={3}>
|
||||
<QCancelButton onClickHandler={props.isModal ? props.closeModalHandler : handleCancelClicked} disabled={isSubmitting} />
|
||||
<Tooltip title={saveDisabledTooltip}>
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import {MaterialDashboardThemeMetaData} from "qqq/models/metadata/MaterialDashboardThemeMetaData";
|
||||
import React from "react";
|
||||
|
||||
interface Props
|
||||
{
|
||||
theme?: MaterialDashboardThemeMetaData | null;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** BrandedHeaderBar - renders a branded header bar when enabled via theme.
|
||||
** Uses CSS variables injected from MaterialDashboardThemeMetaData for styling.
|
||||
*******************************************************************************/
|
||||
export default function BrandedHeaderBar({theme}: Props): JSX.Element | null
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Check if branded header is enabled via theme prop //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
if (!theme?.brandedHeaderEnabled)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="header"
|
||||
data-branded-header="true"
|
||||
className="qqq-branded-header-bar"
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
padding: "0 1rem",
|
||||
backgroundColor: "var(--qqq-branded-header-background-color, #1a2035)",
|
||||
color: "var(--qqq-branded-header-text-color, #ffffff)",
|
||||
height: "var(--qqq-branded-header-height, 48px)",
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
zIndex: 1200,
|
||||
}}
|
||||
>
|
||||
<Box sx={{display: "flex", alignItems: "center", gap: 2}}>
|
||||
{theme.brandedHeaderLogoPath && (
|
||||
<Box
|
||||
component="img"
|
||||
src={theme.brandedHeaderLogoPath}
|
||||
alt={theme.brandedHeaderLogoAltText || "Logo"}
|
||||
sx={{
|
||||
height: "calc(var(--qqq-branded-header-height, 48px) - 8px)",
|
||||
maxWidth: "200px",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
onError={(e: React.SyntheticEvent<HTMLImageElement>) =>
|
||||
{
|
||||
e.currentTarget.style.display = "none";
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{theme.brandedHeaderTagline && (
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
color: "var(--qqq-branded-header-text-color, #ffffff)",
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{theme.brandedHeaderTagline}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Breadcrumbs as MuiBreadcrumbs} from "@mui/material";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Popper, InputAdornment, Box} from "@mui/material";
|
||||
@@ -64,7 +65,7 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
|
||||
const route = useLocation().pathname.split("/").slice(1);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const {pageHeader, pageHeaderRightContent, setDotMenuOpen} = useContext(QContext);
|
||||
const {pageHeader, setDotMenuOpen} = useContext(QContext);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
@@ -268,11 +269,10 @@ function NavBar({absolute, light, isMini}: Props): JSX.Element
|
||||
</Toolbar>
|
||||
{
|
||||
pageHeader &&
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" flexWrap={{xs: "wrap", md: "nowrap"}} columnGap={2} rowGap={1}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<MDTypography pb="0.5rem" variant="h3" color={light ? "white" : "dark"}>
|
||||
{pageHeader}
|
||||
</MDTypography>
|
||||
{pageHeaderRightContent && <Box display="flex">{pageHeaderRightContent}</Box>}
|
||||
</Box>
|
||||
}
|
||||
</AppBar>
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Theme} from "@mui/material/styles";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {QBrandingMetaData} from "@qrunio/qqq-frontend-core/lib/model/metaData/QBrandingMetaData";
|
||||
@@ -33,7 +34,6 @@ import sidenavLogoLabel from "qqq/components/horseshoe/sidenav/styles/SideNav";
|
||||
import MDTypography from "qqq/components/legacy/MDTypography";
|
||||
import {getBannerClassName, getBannerStyles, getBanner, makeBannerContent} from "qqq/components/misc/Banners";
|
||||
import {setMiniSidenav, setTransparentSidenav, setWhiteSidenav, useMaterialUIController,} from "qqq/context";
|
||||
import {sanitizeId} from "qqq/utils/qqqIdUtils";
|
||||
import {ReactNode, useEffect, useReducer, useState} from "react";
|
||||
import {NavLink, useLocation} from "react-router-dom";
|
||||
|
||||
@@ -209,7 +209,7 @@ function Sidenav({color, icon, logo, appName, branding, routes, logout, ...rest}
|
||||
|
||||
// Render all the routes from the routes.js (All the visible items on the Sidenav)
|
||||
const renderRoutes = routes.map(
|
||||
({type, name, icon, title, collapse, noCollapse, key, href, route, ...routeRest}: any) =>
|
||||
({type, name, icon, title, collapse, noCollapse, key, href, route}: any) =>
|
||||
{
|
||||
let returnValue;
|
||||
|
||||
@@ -230,7 +230,6 @@ function Sidenav({color, icon, logo, appName, branding, routes, logout, ...rest}
|
||||
icon={icon}
|
||||
active={key === collapseName}
|
||||
noCollapse={noCollapse}
|
||||
{...routeRest}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
@@ -261,7 +260,6 @@ function Sidenav({color, icon, logo, appName, branding, routes, logout, ...rest}
|
||||
open={openCollapse === key}
|
||||
noCollapse={noCollapse}
|
||||
onClick={() => (!noCollapse ? (openCollapse === key ? setOpenCollapse(false) : setOpenCollapse(key)) : null)}
|
||||
{...routeRest}
|
||||
>
|
||||
{collapse ? renderCollapse(collapse) : null}
|
||||
</SideNavCollapse>
|
||||
@@ -333,9 +331,8 @@ function Sidenav({color, icon, logo, appName, branding, routes, logout, ...rest}
|
||||
{...rest}
|
||||
variant="permanent"
|
||||
ownerState={{transparentSidenav, whiteSidenav, miniSidenav, darkMode}}
|
||||
data-qqq-id="sidenav-root"
|
||||
>
|
||||
<Box pt={3} mr={1} pb={0} px={4} textAlign="center" data-qqq-id="sidenav-logo-area">
|
||||
<Box pt={3} mr={1} pb={0} px={4} textAlign="center">
|
||||
<Box
|
||||
display={{xs: "block", xl: "none"}}
|
||||
position="absolute"
|
||||
@@ -373,14 +370,14 @@ function Sidenav({color, icon, logo, appName, branding, routes, logout, ...rest}
|
||||
(darkMode && !transparentSidenav && whiteSidenav)
|
||||
}
|
||||
/>
|
||||
<List data-qqq-id="sidenav-menu-list">{renderRoutes}</List>
|
||||
<List>{renderRoutes}</List>
|
||||
<Divider
|
||||
light={
|
||||
(!darkMode && !whiteSidenav && !transparentSidenav) ||
|
||||
(darkMode && !transparentSidenav && whiteSidenav)
|
||||
}
|
||||
/>
|
||||
<Button onClick={logout} data-qqq-id="sidenav-logout-button">Log Out</Button>
|
||||
<Button onClick={logout}>Log Out</Button>
|
||||
</SidenavRoot>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
@@ -27,7 +28,6 @@ import ListItemText from "@mui/material/ListItemText";
|
||||
import {ReactNode} from "react";
|
||||
import {collapseArrow, collapseIcon, collapseIconBox, collapseItem, collapseText,} from "qqq/components/horseshoe/sidenav/styles/SideNavCollapse";
|
||||
import {useMaterialUIController} from "qqq/context";
|
||||
import {generateNavItemId} from "qqq/utils/qqqIdUtils";
|
||||
|
||||
// Declaring props types for SideNavCollapse
|
||||
interface Props {
|
||||
@@ -53,14 +53,11 @@ function SideNavCollapse({
|
||||
const [controller] = useMaterialUIController();
|
||||
const {miniSidenav, transparentSidenav, whiteSidenav, darkMode} = controller;
|
||||
|
||||
const dataQqqId = generateNavItemId(undefined, name);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem component="li" data-qqq-id={dataQqqId}>
|
||||
<ListItem component="li">
|
||||
<Box
|
||||
{...rest}
|
||||
className={active ? "qqq-sidebar-active" : ""}
|
||||
sx={(theme: any) =>
|
||||
collapseItem(theme, {active, transparentSidenav, whiteSidenav, darkMode})
|
||||
}
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import List from "@mui/material/List";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Drawer from "@mui/material/Drawer";
|
||||
@@ -95,9 +96,8 @@ export default styled(Drawer)(({theme, ownerState}: { theme?: Theme | any; owner
|
||||
border: "none",
|
||||
margin: "0",
|
||||
borderRadius: "0",
|
||||
// Account for branded header height when present
|
||||
top: "var(--qqq-branded-header-height, 0px)",
|
||||
height: "calc(100vh - var(--qqq-branded-header-height, 0px))",
|
||||
height: "100%",
|
||||
top: "unset",
|
||||
|
||||
...(miniSidenav ? drawerCloseStyles() : drawerOpenStyles()),
|
||||
},
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// @mui material components
|
||||
|
||||
@@ -5,22 +5,23 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Theme} from "@mui/material/styles";
|
||||
|
||||
function collapseItem(theme: Theme, ownerState: any)
|
||||
function collapseItem(theme: Theme, ownerState: any)
|
||||
{
|
||||
const {palette, transitions, breakpoints, boxShadows, borders, functions} = theme;
|
||||
const {active, transparentSidenav, whiteSidenav, darkMode} = ownerState;
|
||||
@@ -31,30 +32,30 @@ function collapseItem(theme: Theme, ownerState: any)
|
||||
const {pxToRem, rgba} = functions;
|
||||
|
||||
return {
|
||||
background: () =>
|
||||
background: () =>
|
||||
{
|
||||
let backgroundValue;
|
||||
|
||||
if (transparentSidenav && darkMode)
|
||||
if (transparentSidenav && darkMode)
|
||||
{
|
||||
backgroundValue = active ? rgba(white.main, 0.2) : transparent.main;
|
||||
}
|
||||
else if (transparentSidenav && !darkMode)
|
||||
else if (transparentSidenav && !darkMode)
|
||||
{
|
||||
backgroundValue = active ? grey[300] : transparent.main;
|
||||
}
|
||||
else if (whiteSidenav)
|
||||
else if (whiteSidenav)
|
||||
{
|
||||
backgroundValue = active ? grey[200] : transparent.main;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
backgroundValue = active ? rgba(white.main, 0.2) : transparent.main;
|
||||
}
|
||||
|
||||
return backgroundValue;
|
||||
},
|
||||
color: "var(--qqq-sidebar-text-color, #ffffff)",
|
||||
color: (transparentSidenav && !darkMode) || whiteSidenav ? dark.main : white.main,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
@@ -78,12 +79,15 @@ function collapseItem(theme: Theme, ownerState: any)
|
||||
},
|
||||
|
||||
"&:hover, &:focus": {
|
||||
backgroundColor: "var(--qqq-sidebar-hover-background-color, rgba(255, 255, 255, 0.1))",
|
||||
backgroundColor:
|
||||
transparentSidenav && !darkMode
|
||||
? grey[300]
|
||||
: rgba(whiteSidenav ? grey[400] : white.main, 0.2),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function collapseIconBox(theme: Theme, ownerState: any)
|
||||
function collapseIconBox(theme: Theme, ownerState: any)
|
||||
{
|
||||
const {palette, transitions, borders, functions} = theme;
|
||||
const {transparentSidenav, whiteSidenav, darkMode} = ownerState;
|
||||
@@ -95,7 +99,7 @@ function collapseIconBox(theme: Theme, ownerState: any)
|
||||
return {
|
||||
minWidth: pxToRem(32),
|
||||
minHeight: pxToRem(32),
|
||||
color: "var(--qqq-sidebar-icon-color, #ffffff)",
|
||||
color: (transparentSidenav && !darkMode) || whiteSidenav ? dark.main : white.main,
|
||||
borderRadius: borderRadius.md,
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
@@ -105,13 +109,13 @@ function collapseIconBox(theme: Theme, ownerState: any)
|
||||
}),
|
||||
|
||||
"& svg, svg g": {
|
||||
color: "var(--qqq-sidebar-icon-color, #ffffff)",
|
||||
color: transparentSidenav || whiteSidenav ? dark.main : white.main,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const collapseIcon = (_theme: Theme, {active}: any) => ({
|
||||
color: active ? "var(--qqq-sidebar-selected-text-color, #ffffff)" : "var(--qqq-sidebar-icon-color, #ffffff)",
|
||||
const collapseIcon = ({palette: {white, gradients}}: Theme, {active}: any) => ({
|
||||
color: active ? white.main : gradients.dark.state,
|
||||
});
|
||||
|
||||
function collapseText(theme: any, ownerState: any)
|
||||
@@ -143,7 +147,7 @@ function collapseText(theme: any, ownerState: any)
|
||||
};
|
||||
}
|
||||
|
||||
function collapseArrow(theme: Theme, ownerState: any)
|
||||
function collapseArrow(theme: Theme, ownerState: any)
|
||||
{
|
||||
const {palette, typography, transitions, breakpoints, functions} = theme;
|
||||
const {noCollapse, transparentSidenav, whiteSidenav, miniSidenav, open, active, darkMode} =
|
||||
@@ -158,8 +162,25 @@ function collapseArrow(theme: Theme, ownerState: any)
|
||||
fontWeight: 700,
|
||||
marginBottom: pxToRem(-1),
|
||||
transform: open ? "rotate(0)" : "rotate(-180deg)",
|
||||
color: open || active ? "var(--qqq-sidebar-selected-text-color, #ffffff)" : "var(--qqq-sidebar-icon-color, #ffffff)",
|
||||
opacity: open || active ? 1 : 0.5,
|
||||
color: () =>
|
||||
{
|
||||
let colorValue;
|
||||
|
||||
if (transparentSidenav && darkMode)
|
||||
{
|
||||
colorValue = open || active ? white.main : rgba(white.main, 0.25);
|
||||
}
|
||||
else if (transparentSidenav || whiteSidenav)
|
||||
{
|
||||
colorValue = open || active ? dark.main : rgba(dark.main, 0.25);
|
||||
}
|
||||
else
|
||||
{
|
||||
colorValue = open || active ? white.main : rgba(white.main, 0.5);
|
||||
}
|
||||
|
||||
return colorValue;
|
||||
},
|
||||
transition: transitions.create(["color", "transform", "opacity"], {
|
||||
easing: transitions.easing.easeInOut,
|
||||
duration: transitions.duration.shorter,
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {Theme} from "@mui/material/styles";
|
||||
@@ -49,7 +50,11 @@ function item(theme: Theme | any, ownerState: any)
|
||||
}
|
||||
else if (active === "isParent" && transparentSidenav)
|
||||
{
|
||||
return "var(--qqq-sidebar-selected-background-color, rgba(255, 255, 255, 0.2))";
|
||||
backgroundValue = grey[300];
|
||||
}
|
||||
else if (active === "isParent" && whiteSidenav)
|
||||
{
|
||||
backgroundValue = grey[200];
|
||||
}
|
||||
else if (active)
|
||||
{
|
||||
@@ -64,7 +69,9 @@ function item(theme: Theme | any, ownerState: any)
|
||||
}),
|
||||
|
||||
"&:hover, &:focus": {
|
||||
backgroundColor: !active ? "var(--qqq-sidebar-hover-background-color, rgba(255, 255, 255, 0.1))" : undefined,
|
||||
backgroundColor:
|
||||
!active &&
|
||||
rgba((transparentSidenav && !darkMode) || whiteSidenav ? grey[400] : white.main, 0.2),
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -89,7 +96,10 @@ function itemContent(theme: Theme, ownerState: any)
|
||||
position: "relative",
|
||||
|
||||
"& span": {
|
||||
color: active ? "var(--qqq-sidebar-selected-text-color, #ffffff)" : "var(--qqq-sidebar-text-color, #ffffff)",
|
||||
color:
|
||||
((transparentSidenav && !darkMode) || whiteSidenav) && (active === "isParent" || !active)
|
||||
? dark.main
|
||||
: white.main,
|
||||
fontWeight: active ? fontWeightRegular : fontWeightLight,
|
||||
fontSize: size.sm,
|
||||
opacity: miniSidenav ? 0 : 1,
|
||||
@@ -100,7 +110,10 @@ function itemContent(theme: Theme, ownerState: any)
|
||||
},
|
||||
|
||||
"&::before": {
|
||||
color: active ? "var(--qqq-sidebar-selected-text-color, #ffffff)" : "var(--qqq-sidebar-text-color, #ffffff)",
|
||||
color:
|
||||
((transparentSidenav && !darkMode) || whiteSidenav) && (active === "isParent" || !active)
|
||||
? dark.main
|
||||
: white.main,
|
||||
fontWeight: fontWeightRegular,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
@@ -130,8 +143,25 @@ function itemArrow(theme: Theme, ownerState: any)
|
||||
fontWeight: 700,
|
||||
marginBottom: pxToRem(-1),
|
||||
transform: open ? "rotate(0)" : "rotate(-180deg)",
|
||||
color: open || active ? "var(--qqq-sidebar-selected-text-color, #ffffff)" : "var(--qqq-sidebar-icon-color, #ffffff)",
|
||||
opacity: open || active ? 1 : 0.5,
|
||||
color: () =>
|
||||
{
|
||||
let colorValue;
|
||||
|
||||
if (transparentSidenav && darkMode)
|
||||
{
|
||||
colorValue = open || active ? white.main : rgba(white.main, 0.25);
|
||||
}
|
||||
else if (transparentSidenav || whiteSidenav)
|
||||
{
|
||||
colorValue = open || active ? dark.main : rgba(dark.main, 0.25);
|
||||
}
|
||||
else
|
||||
{
|
||||
colorValue = open || active ? white.main : rgba(white.main, 0.5);
|
||||
}
|
||||
|
||||
return colorValue;
|
||||
},
|
||||
transition: transitions.create(["color", "transform", "opacity"], {
|
||||
easing: transitions.easing.easeInOut,
|
||||
duration: transitions.duration.shorter,
|
||||
|
||||
@@ -4,17 +4,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Button from "@mui/material/Button";
|
||||
@@ -166,7 +167,7 @@ export default styled(Button)(({theme, ownerState}: { theme?: Theme | any; owner
|
||||
// styles for the button with variant="gradient"
|
||||
const gradientStyles = () =>
|
||||
{
|
||||
// background value - always use gradients for consistent appearance
|
||||
// background value
|
||||
const backgroundValue =
|
||||
color === "white" || !gradients[color]
|
||||
? white.main
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {ButtonProps} from "@mui/material";
|
||||
@@ -45,7 +46,7 @@ export interface Props extends Omit<ButtonProps, "color" | "variant"> {
|
||||
}
|
||||
|
||||
const MDButton: FC<Props> = forwardRef(
|
||||
({color, variant, size, circular, iconOnly, children, qqqId, ...rest}, ref) =>
|
||||
({color, variant, size, circular, iconOnly, children, ...rest}, ref) =>
|
||||
{
|
||||
const [controller] = useMaterialUIController();
|
||||
const {darkMode} = controller;
|
||||
@@ -54,8 +55,6 @@ const MDButton: FC<Props> = forwardRef(
|
||||
<MDButtonRoot
|
||||
{...rest}
|
||||
ref={ref}
|
||||
data-qqq-id={qqqId}
|
||||
data-button-variant={variant}
|
||||
ownerState={{color, variant, size, circular, iconOnly, darkMode}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {styled, Theme} from "@mui/material/styles";
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {expect, it} from "@jest/globals";
|
||||
import {ThemeProvider} from "@mui/material/styles";
|
||||
import React from "react";
|
||||
import {createRoot} from "react-dom/client";
|
||||
import {act} from "react-dom/test-utils";
|
||||
import theme from "qqq/components/legacy/Theme";
|
||||
import MDInput from "./index";
|
||||
|
||||
/*******************************************************************************
|
||||
** A disabled field must receive native input semantics as well as styling.
|
||||
*******************************************************************************/
|
||||
it("updates native disabled state when the form metadata changes", () =>
|
||||
{
|
||||
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
const host = document.createElement("div");
|
||||
document.body.appendChild(host);
|
||||
const root = createRoot(host);
|
||||
const render = (disabled: boolean) => act(() => root.render(<ThemeProvider theme={theme}>
|
||||
<MDInput name="createDate" type="datetime-local" disabled={disabled} />
|
||||
</ThemeProvider>));
|
||||
try
|
||||
{
|
||||
render(true);
|
||||
expect(host.querySelector("input").disabled).toBe(true);
|
||||
host.querySelector("input").focus();
|
||||
expect(document.activeElement).not.toBe(host.querySelector("input"));
|
||||
render(false);
|
||||
expect(host.querySelector("input").disabled).toBe(false);
|
||||
host.querySelector("input").focus();
|
||||
expect(document.activeElement).toBe(host.querySelector("input"));
|
||||
render(true);
|
||||
expect(host.querySelector("input").disabled).toBe(true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
act(() => root.unmount());
|
||||
host.remove();
|
||||
}
|
||||
});
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {OutlinedTextFieldProps, StandardTextFieldProps} from "@mui/material";
|
||||
@@ -30,7 +31,7 @@ interface Props extends Omit<OutlinedTextFieldProps | StandardTextFieldProps, "v
|
||||
}
|
||||
|
||||
const MDInput: FC<Props | any> = forwardRef(({error, success, disabled, ...rest}, ref) => (
|
||||
<MDInputRoot {...rest} ref={ref} disabled={disabled} ownerState={{error, success, disabled}} />
|
||||
<MDInputRoot {...rest} ref={ref} ownerState={{error, success, disabled}} />
|
||||
));
|
||||
|
||||
// Declaring default props for MDInput
|
||||
|
||||
@@ -5,17 +5,18 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {styled, Theme} from "@mui/material/styles";
|
||||
|
||||
@@ -5,21 +5,21 @@
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import {preferredColorNameInfoOrPrimary} from "qqq/assets/theme/functions/preferInfoColorToPrimaryColor";
|
||||
import {createContext, FC, forwardRef, ReactNode, useContext, useMemo} from "react";
|
||||
import MDPaginationItemRoot from "qqq/components/legacy/MDPagination/MDPaginationItemRoot";
|
||||
|
||||
@@ -54,11 +54,6 @@ const MDPagination: FC<Props | any> = forwardRef(
|
||||
const context: any = useContext(Context);
|
||||
const paginationSize = context ? context.size : undefined;
|
||||
|
||||
if(!color)
|
||||
{
|
||||
color = preferredColorNameInfoOrPrimary();
|
||||
}
|
||||
|
||||
const providerValue = useMemo(
|
||||
() => ({
|
||||
variant,
|
||||
@@ -101,6 +96,7 @@ const MDPagination: FC<Props | any> = forwardRef(
|
||||
MDPagination.defaultProps = {
|
||||
item: false,
|
||||
variant: "gradient",
|
||||
color: "info",
|
||||
size: "medium",
|
||||
active: false,
|
||||
};
|
||||
|
||||
Loaded 100 of 350 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user