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
43 changes: 43 additions & 0 deletions src/frontend/components/alert/lib/alertBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Hidable, Renderable } from 'util/renderable';
import { AlertType } from './types';

export abstract class AlertBase extends Hidable implements Renderable<HTMLDivElement> {
/**
* Create an instance of AlertBase.
* This class serves as a base for alert components.
* It implements the Renderable interface, which requires a render method.
* The render method should be implemented by subclasses to provide specific rendering logic.
* @implements {Renderable<HTMLDivElement>}
* @param {string} message - The message to be displayed in the alert.
* @param {AlertType} type - The type of alert, which determines its styling and behavior.
* @see Renderable
* @see Hidable
* @see AlertType
* @example
* const alert = new AlertBase('This is an alert message', AlertType.INFO);
* document.body.appendChild(alert.render());
*/
constructor(private readonly message: string, private readonly type: AlertType, private readonly transparent: boolean = false) {
super();
}

/**
* Render the alert as an HTMLDivElement.
* @returns {HTMLDivElement} The rendered HTML element representing the alert.
*/
render(): HTMLDivElement {
if(this.element) throw new Error('AlertBase.render() should not be called multiple times without resetting the element.');
const alertDiv = document.createElement('div');
alertDiv.classList.add('alert', `alert-${this.type}`);
if(this.transparent) {
alertDiv.classList.add('alert-no-bg');
}
for(const item of this.message.split('\n')) {
const pDiv = document.createElement('p');
pDiv.textContent = item;
alertDiv.appendChild(pDiv);
}
this.element = alertDiv;
return alertDiv;
}
}
18 changes: 18 additions & 0 deletions src/frontend/components/alert/lib/dangerAlert.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, it, expect } from '@jest/globals';
import { DangerAlert } from './dangerAlert';

describe('Error Alert Tests', () => {
it('should display an error alert with the correct message', () => {
const errorMessage = 'This is a test error message';
const errorAlert = new DangerAlert(errorMessage);

const alert = errorAlert.render();

document.body.appendChild(alert);

expect(alert.classList.contains('alert-danger')).toBeTruthy();
expect(alert.textContent).toContain(errorMessage);

document.body.removeChild(alert);
});
});
17 changes: 17 additions & 0 deletions src/frontend/components/alert/lib/dangerAlert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { AlertBase } from "./alertBase";

export class DangerAlert extends AlertBase {
/**
* Create an instance of InfoAlert.
* This class extends AlertBase to provide a specific implementation for info alerts.
* It uses the 'info' alert type for styling and behavior.
* @class
* @public
* @memberof alert.lib
* @constructor
* @param {string} message - The message to be displayed in the alert.
*/
constructor(message: string) {
super(message, 'danger');
}
}
18 changes: 18 additions & 0 deletions src/frontend/components/alert/lib/infoAlert.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, it, expect } from '@jest/globals';
import { InfoAlert } from './infoAlert';

describe('Info Alert Tests', () => {
it('should display an info alert with the correct message', () => {
const infoMessage = 'This is a test info message';
const infoAlert = new InfoAlert(infoMessage);

const alert = infoAlert.render();

document.body.appendChild(alert);

expect(alert.classList.contains('alert-info')).toBeTruthy();
expect(alert.textContent).toContain(infoMessage);

document.body.removeChild(alert);
});
});
17 changes: 17 additions & 0 deletions src/frontend/components/alert/lib/infoAlert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { AlertBase } from './alertBase';

export class InfoAlert extends AlertBase {
/**
* Create an instance of InfoAlert.
* This class extends AlertBase to provide a specific implementation for info alerts.
* It uses the AlertType.INFO to set the alert type.
* @class
* @public
* @memberof alert.lib
* @constructor
* @param {string} message - The message to be displayed in the info alert.
*/
constructor(message: string) {
super(message, "info");
}
}
18 changes: 18 additions & 0 deletions src/frontend/components/alert/lib/successAlert.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, it, expect } from '@jest/globals';
import { SuccessAlert } from './successAlert';

describe('Success Alert Tests', () => {
it('should create a success alert', () => {
const message = 'Operation completed successfully';
const alert = new SuccessAlert(message);

const result = alert.render();

document.body.appendChild(result);

expect(result.classList.contains('alert-success')).toBeTruthy();
expect(result.textContent).toBe(message);

document.body.removeChild(result);
});
});
17 changes: 17 additions & 0 deletions src/frontend/components/alert/lib/successAlert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { AlertBase } from "./alertBase";

export class SuccessAlert extends AlertBase {
/**
* Create an instance of InfoAlert.
* This class extends AlertBase to provide a specific implementation for info alerts.
* It uses the 'info' alert type for styling and behavior.
* @class
* @public
* @memberof alert.lib
* @constructor
* @param {string} message - The message to be displayed in the alert.
*/
constructor(message: string) {
super(message, 'success', true);
}
}
1 change: 1 addition & 0 deletions src/frontend/components/alert/lib/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type AlertType = 'info' | 'success' | 'warning' | 'danger';
18 changes: 18 additions & 0 deletions src/frontend/components/alert/lib/warningAlert.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, it, expect } from '@jest/globals';
import { WarningAlert } from './warningAlert';

describe('Warning Alert Tests', () => {
it('should create a warning alert', () => {
const message = 'This is a warning message';
const alert = new WarningAlert(message);

const result = alert.render();

document.body.appendChild(result);

expect(result.classList.contains('alert-warning')).toBe(true);
expect(result.textContent).toBe(message);

document.body.removeChild(result);
});
});
17 changes: 17 additions & 0 deletions src/frontend/components/alert/lib/warningAlert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { AlertBase } from "./alertBase";

export class WarningAlert extends AlertBase {
/**
* Create an instance of InfoAlert.
* This class extends AlertBase to provide a specific implementation for info alerts.
* It uses the AlertType.INFO to set the alert type.
* @class
* @public
* @memberof alert.lib
* @constructor
* @param {string} message - The message to be displayed in the info alert.
*/
constructor(message: string) {
super(message, "warning");
}
}
45 changes: 45 additions & 0 deletions src/frontend/components/button/lib/RenderableButton.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { describe, it, expect, jest } from '@jest/globals';
import { RenderableButton } from './RenderableButton';

describe('Renderable Button Tests', () => {
it('should create a Renderable Button', () => {
const caption = 'Test Button';
const button = new RenderableButton(caption, ()=>{});

const rendered = button.render();

document.body.appendChild(rendered);

expect(rendered.textContent).toBe('Test Button');
expect(rendered.classList.contains('btn')).toBeTruthy();
expect(rendered.classList.contains('btn-default')).toBeTruthy();

document.body.removeChild(rendered);
});

it('should handle click events', () => {
const mockCallback = jest.fn();
const button = new RenderableButton('Click Me', mockCallback);

const rendered = button.render();
document.body.appendChild(rendered);

rendered.click();

expect(mockCallback).toHaveBeenCalled();

document.body.removeChild(rendered);
});

it('should apply custom classes', () => {
const button = new RenderableButton('Custom Class', ()=>{}, 'btn-custom');

const rendered = button.render();
document.body.appendChild(rendered);

expect(rendered.classList.contains('btn-custom')).toBeTruthy();
expect(rendered.classList.contains('btn-default')).toBeFalsy();

document.body.removeChild(rendered);
});
});
21 changes: 21 additions & 0 deletions src/frontend/components/button/lib/RenderableButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Renderable } from "util/renderable";

export class RenderableButton implements Renderable<HTMLButtonElement> {
classList: string[] = [];

constructor(private readonly text: string, private readonly onClick: (ev: MouseEvent)=>void, ...classList: string[]) {
this.classList = classList;
}

render(): HTMLButtonElement {
const button = document.createElement('button');
button.textContent = this.text;
button.addEventListener('click', this.onClick);
button.classList.add(...this.classList, 'btn');
const btnType = this.classList.find(b=>b.startsWith('btn-')) ? '' : 'btn-default'
if(btnType) {
button.classList.add(btnType);
}
return button;
}
}
31 changes: 31 additions & 0 deletions src/frontend/components/form-group/autosave/_autosave.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,35 @@ li.li-error {
font-size: 1.5em;
margin-right: 0.5em;
}
}

// I don't know why this button wasn't hidden before, but it is now
#restoreValuesModal .btn-js-delete-values {
visibility: hidden;
display: none;
}

.alert-restore {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;

p {
flex-grow: 1;
}

// TODO: This will be moved over to BS5 on merge - remember, syntax is _completely_ different
.btn-alert-restore {
@include button-variant($white, $brand-secundary, $white, $brand-primary, $brand-secundary, $brand-secundary);
color: $brand-secundary;

&:hover {
color: $brand-primary;
}
}

.btn-alert-restore-cancel {
@extend .btn-danger;
}
}
26 changes: 15 additions & 11 deletions src/frontend/components/form-group/autosave/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ import AutosaveModal from './lib/modal';
import gadsStorage from 'util/gadsStorage';

export default (scope) => {
if (gadsStorage.enabled) {
try {
initializeComponent(scope, '.linkspace-field', AutosaveComponent);
initializeComponent(scope, '#restoreValuesModal', AutosaveModal);
} catch(e) {
console.error(e);
// Ensure the autosave functionality is only initialized on record pages.
// This is to deactivate autosave on other pages that may use the form-edit class
if(location.pathname.match(/record/)) {
if (gadsStorage.enabled) {
try {
initializeComponent(scope, '.linkspace-field', AutosaveComponent);
initializeComponent(scope, '#restoreValuesModal', AutosaveModal);
} catch(e) {
console.error(e);
if($('body').data('encryption-disabled')) return;
$('.content-block__main-content').prepend('<div class="alert alert-danger">Auto-recover failed to initialize. ' + e.message ? e.message : e + '</div>');
$('body').data('encryption-disabled', 'true');
}
} else {
if($('body').data('encryption-disabled')) return;
$('.content-block__main-content').prepend('<div class="alert alert-danger">Auto-recover failed to initialize. ' + e.message ? e.message : e + '</div>');
$('.content-block__main-content').prepend('<div class="alert alert-warning">Auto-recover is disabled as your browser does not support encryption</div>');
$('body').data('encryption-disabled', 'true');
}
} else {
if($('body').data('encryption-disabled')) return;
$('.content-block__main-content').prepend('<div class="alert alert-warning">Auto-recover is disabled as your browser does not support encryption</div>');
$('body').data('encryption-disabled', 'true');
}
};
Loading
Loading