Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion app/angular/components/dropdownIcon.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@
<i class="fa fa-user-circle"></i>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li class="theme-menu-item">
<span class="theme-menu-label">{{ 'Theme' | translate }}</span>
<div class="theme-toggle" role="group" aria-label="{{ 'Theme' | translate }}">
<button
type="button"
ng-class="{active: $ctrl.theme === 'light'}"
ng-click="$ctrl.setTheme('light')"
>
{{ 'Light' | translate }}
</button>
<button
type="button"
ng-class="{active: $ctrl.theme === 'dark'}"
ng-click="$ctrl.setTheme('dark')"
>
{{ 'Dark' | translate }}
</button>
Comment on lines +9 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add aria-pressed so screen readers can announce the active theme.

Per the WAI-ARIA Authoring Practices Guide, if the button is a toggle button, it must carry an aria-pressed state. Currently only the visual .active class communicates which theme is selected; state changes fail completely when you rely on visual-only indicators like colour changes.

♿ Proposed fix — add aria-pressed to both buttons
 				<button
 					type="button"
 					ng-class="{active: $ctrl.theme === 'light'}"
+					ng-attr-aria-pressed="{{ $ctrl.theme === 'light' }}"
 					ng-click="$ctrl.setTheme('light')"
 				>
 					{{ 'Light' | translate }}
 				</button>
 				<button
 					type="button"
 					ng-class="{active: $ctrl.theme === 'dark'}"
+					ng-attr-aria-pressed="{{ $ctrl.theme === 'dark' }}"
 					ng-click="$ctrl.setTheme('dark')"
 				>
 					{{ 'Dark' | translate }}
 				</button>

ng-attr-aria-pressed is used instead of aria-pressed directly to emit the attribute with the correct boolean string ("true" / "false"), which AngularJS expression interpolation alone would leave as the JS boolean value.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
type="button"
ng-class="{active: $ctrl.theme === 'light'}"
ng-click="$ctrl.setTheme('light')"
>
{{ 'Light' | translate }}
</button>
<button
type="button"
ng-class="{active: $ctrl.theme === 'dark'}"
ng-click="$ctrl.setTheme('dark')"
>
{{ 'Dark' | translate }}
</button>
<button
type="button"
ng-class="{active: $ctrl.theme === 'light'}"
ng-attr-aria-pressed="{{ $ctrl.theme === 'light' }}"
ng-click="$ctrl.setTheme('light')"
>
{{ 'Light' | translate }}
</button>
<button
type="button"
ng-class="{active: $ctrl.theme === 'dark'}"
ng-attr-aria-pressed="{{ $ctrl.theme === 'dark' }}"
ng-click="$ctrl.setTheme('dark')"
>
{{ 'Dark' | translate }}
</button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/angular/components/dropdownIcon.html` around lines 9 - 22, The two theme
toggle buttons (the elements using $ctrl.setTheme(...) and ng-class that checks
$ctrl.theme) lack an ARIA pressed state; update both button elements to emit
aria-pressed reflecting whether $ctrl.theme equals 'light' or 'dark' (use
ng-attr-aria-pressed with an expression that produces "true"/"false") so screen
readers can announce the active toggle while leaving existing ng-class and
$ctrl.setTheme intact.

</div>
</li>
<li role="separator" class="divider"></li>
<li>
<a
target="_blank"
Expand Down Expand Up @@ -38,4 +58,4 @@
</a>
</li>
</ul>
</div>
</div>
10 changes: 8 additions & 2 deletions app/angular/components/dropdownIcon.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import angular from "angular";
import template from "./dropdownIcon.html";
import themeService from "../service/themeService";

const dropDownIconController = function ($element, $timeout) {
const dropDownIconController = function ($element, $timeout, ThemeService) {
const ctrl = this;
ctrl.open = false;
ctrl.theme = ThemeService.getTheme();

ctrl.toggle = (command) => {
ctrl.open = command;
};

ctrl.setTheme = (theme) => {
ctrl.theme = ThemeService.applyTheme(theme);
};

ctrl.select = (option) => {
ctrl.selected = option;
ctrl.toggle(false);
Expand All @@ -26,7 +32,7 @@ const dropDownIconController = function ($element, $timeout) {
};
};

export default angular.module("app.dropdownIcon", []).component("dropdownIcon", {
export default angular.module("app.dropdownIcon", [themeService]).component("dropdownIcon", {
template,
controller: dropDownIconController,
bindings: {
Expand Down
6 changes: 5 additions & 1 deletion app/angular/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import "oclazyload";
import sidebarControlConceptual from "./conceptual/sidebarControl";
import sidebarControlLogic from "./logic/sidebarControl";
import authService from "./service/authService";
import themeService from "./service/themeService";
import modelService from "./service/modelAPI";
import dropdownComponent from "./components/dropdown";
import dropdownIconComponent from "./components/dropdownIcon";
Expand All @@ -41,6 +42,7 @@ const app = angular.module("app", [
"ngCookies" /** textangular */,
"oc.lazyLoad",
authService,
themeService,
modelService,
logicService,
dropdownComponent,
Expand Down Expand Up @@ -249,7 +251,9 @@ app.config([
},
]);

app.run(function ($transitions, $rootScope, AuthService, $state, $window, $location) {
app.run(function ($transitions, $rootScope, AuthService, ThemeService, $state, $window, $location) {
ThemeService.applyTheme(ThemeService.getTheme());

$transitions.onStart({}, function (trans) {
const { requireLogin } = trans.to().data;
if (requireLogin) {
Expand Down
34 changes: 34 additions & 0 deletions app/angular/service/themeService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import angular from "angular";

const themeService = function ($window, $document) {
const service = this;
const storageKey = "brmw-theme";
const themes = {
light: "light",
dark: "dark",
};

const getBody = () => $document[0].body;

service.getTheme = () => {
return $window.localStorage.getItem(storageKey) || themes.light;
};
Comment on lines +13 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

getTheme() returns an unvalidated localStorage value

If localStorage["brmw-theme"] holds anything other than "light" or "dark" (e.g., manual tampering or a future migration), ctrl.theme is initialized to that arbitrary string in dropdownIcon.js line 8. The dropdown ng-class comparison will silently match nothing, leaving both options un-highlighted even though applyTheme at startup normalizes and correctly sets the body class.

🛡️ Proposed fix — validate against known themes
 service.getTheme = () => {
-  return $window.localStorage.getItem(storageKey) || themes.light;
+  const stored = $window.localStorage.getItem(storageKey);
+  return Object.values(themes).includes(stored) ? stored : themes.light;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
service.getTheme = () => {
return $window.localStorage.getItem(storageKey) || themes.light;
};
service.getTheme = () => {
const stored = $window.localStorage.getItem(storageKey);
return Object.values(themes).includes(stored) ? stored : themes.light;
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/angular/service/themeService.js` around lines 13 - 15, getTheme()
currently returns any string stored under storageKey which can initialize
ctrl.theme to invalid values; update service.getTheme to validate the retrieved
value against the allowed themes (themes.light and themes.dark) and return a
safe default (e.g., themes.light) when the stored value is not one of those two,
ensuring components like dropdownIcon.js (ctrl.theme) only receive "light" or
"dark".


service.applyTheme = (theme) => {
const selectedTheme = theme === themes.dark ? themes.dark : themes.light;
const body = getBody();

body.classList.toggle("theme-dark", selectedTheme === themes.dark);
body.classList.toggle("theme-light", selectedTheme === themes.light);
$window.localStorage.setItem(storageKey, selectedTheme);

return selectedTheme;
};

service.toggleTheme = () => {
const nextTheme = service.getTheme() === themes.dark ? themes.light : themes.dark;
return service.applyTheme(nextTheme);
};
};

export default angular.module("app.themeService", []).service("ThemeService", themeService).name;
7 changes: 5 additions & 2 deletions app/i18n/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,8 @@ export default {
'Star on Github': 'Star on Github',
'Donate': 'Donate',
'Note': 'Note',
'Color': 'Cor'
};
'Color': 'Color',
'Theme': 'Theme',
'Light': 'Light',
'Dark': 'Dark'
};
7 changes: 5 additions & 2 deletions app/i18n/languages/pt_BR.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,5 +182,8 @@ export default {
'Star on Github': 'Dê uma estrela',
'Donate': 'Doar',
'Note': 'Anotação',
'Color': 'Cor'
};
'Color': 'Cor',
'Theme': 'Tema',
'Light': 'Claro',
'Dark': 'Escuro'
};
22 changes: 21 additions & 1 deletion app/sass/colors.scss
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,24 @@
// Theme: Purple
///////////////////////////////////////////////////////////////////////////////
--theme-purple: hsl(24, 80% 60%);
}
}

body.theme-dark {
--brand-default: var(--brand-primary-40);
--border-light: hsl(220, 16%, 24%);
--border-default: hsl(220, 14%, 32%);
--border-dark: hsl(220, 12%, 46%);
--gray-95: hsl(220, 18%, 12%);
--gray-90: hsl(220, 16%, 16%);
--gray-80: hsl(220, 14%, 24%);
--gray-70: hsl(220, 12%, 36%);
--gray-50: hsl(220, 10%, 62%);
--gray-30: hsl(220, 14%, 82%);
--gray-20: hsl(220, 18%, 90%);
--gray-10: hsl(220, 22%, 96%);
--white: hsl(220, 18%, 12%);
--black: hsl(220, 22%, 96%);
--accent-light: hsl(52, 60%, 20%);
--accent-base: hsl(52, 80%, 58%);
--accent-dark: hsl(52, 90%, 72%);
}
44 changes: 44 additions & 0 deletions app/sass/joint-custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,47 @@
.joint-link.joint-theme-default .connection-wrap:hover {
display: none;
}

.theme-dark .joint-paper .joint-cell .outer,
.theme-dark .joint-paper .joint-cell .poly {
fill: hsl(220, 16%, 18%);
stroke: hsl(170, 70%, 58%);
}

.theme-dark .joint-paper .joint-cell .inner {
stroke: hsl(170, 70%, 58%);
}

.theme-dark .joint-paper .joint-cell text {
fill: hsl(220, 22%, 96%);
}

.theme-dark .joint-paper .joint-link .connection,
.theme-dark .joint-paper .joint-link .marker-source,
.theme-dark .joint-paper .joint-link .marker-target {
stroke: hsl(220, 22%, 86%);
}

.theme-dark .joint-paper .joint-link .connection[stroke-dasharray] {
stroke: hsl(45, 86%, 68%);
}

.theme-dark .joint-paper .uml-class-name-rect {
fill: hsl(170, 58%, 32%);
stroke: hsl(170, 70%, 58%);
}

.theme-dark .joint-paper .uml-class-attrs-rect,
.theme-dark .joint-paper .uml-class-methods-rect {
fill: hsl(220, 16%, 18%);
stroke: hsl(170, 34%, 48%);
}

.theme-dark .joint-paper .uml-class-name-text {
fill: hsl(0, 0%, 100%);
}

.theme-dark .joint-paper .uml-class-attrs-text,
.theme-dark .joint-paper .uml-class-methods-text {
fill: hsl(220, 22%, 94%);
}
39 changes: 38 additions & 1 deletion app/sass/mainHeader.scss
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,41 @@

.nav-icons .dropdown-menu > li > a {
color: var(--gray-20);
}
}

.theme-menu-item {
min-width: 190px;
padding: 8px 12px 10px;
}

.theme-menu-label {
display: block;
margin-bottom: 6px;
font-size: 0.85em;
font-weight: 600;
color: var(--gray-30);
}

.theme-toggle {
display: grid;
grid-template-columns: 1fr 1fr;
padding: 2px;
border: 1px solid var(--border-default);
border-radius: 4px;
background-color: var(--gray-90);
}

.theme-toggle button {
border: 0;
border-radius: 2px;
padding: 5px 8px;
font-size: 0.9em;
line-height: 1.2;
color: var(--gray-20);
background-color: transparent;
}

.theme-toggle button.active {
color: hsl(0, 0%, 100%);
background-color: var(--brand-default);
}
43 changes: 42 additions & 1 deletion app/sass/structure.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ body {
font-family: BlinkMacSystemFont,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
font-weight: 400;
color: var(--gray-20);
background-color: var(--white);
}

a,
Expand Down Expand Up @@ -288,4 +289,44 @@ a:hover,
////////////////////////////////////////////////////////////////////////////////
.page-header {
border-color: var(--border-default);
}
}

.theme-dark .dropdown-menu,
.theme-dark .modal-content {
background-color: var(--gray-95);
border-color: var(--border-default);
color: var(--gray-20);
}

.theme-dark .dropdown-menu .divider {
background-color: var(--border-default);
}

.theme-dark .dropdown-menu > li > a:hover,
.theme-dark .dropdown-menu > li > a:focus {
background-color: var(--gray-90);
color: var(--gray-10);
}

.theme-dark .table > thead > tr > th,
.theme-dark .table > tbody > tr > td {
border-color: var(--border-default);
}

.theme-dark .table-hover > tbody > tr:hover {
background-color: var(--gray-90);
}

.theme-dark .form-control {
background-color: var(--gray-90);
border-color: var(--border-default);
color: var(--gray-20);
}

.theme-dark .editor-scroller {
background-color: hsl(220, 18%, 10%);
}

.theme-dark .editor-scroller .joint-paper {
background-color: hsl(220, 18%, 13%);
}