Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add arasaac integration #232

Merged
merged 19 commits into from
Jan 22, 2024
Merged
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ USER root
RUN apk add --no-cache chromium
USER node

RUN export CHROME_BIN=/usr/bin/chromium-browser
ENV CHROME_BIN=/usr/bin/chromium-browser

COPY --chown=node:node teammapper-frontend/package.json teammapper-frontend/package-lock.json $APP_FRONTEND_PATH/
RUN npm --prefix teammapper-frontend install
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@ TeamMapper is based on mindmapp (https://github.com/cedoor/mindmapp , discontinu
## Features:

- **Creation**: Host and create your own mindmaps
- **Customization**: Add images, colors, font properties and links to nodes
- **Customization**: Add images, pictograms*, colors, font properties and links to nodes
- **Collaboration**: Share your mindmap with friends and collegues, using either a view-only or modification invite!
- **Interoperability**: Import and export functionality (JSON, SVG, PDF, PNG...)
- **Shareability**: Use a QR Code or URL to share your maps
- **GDPR Compliancy**: By default, mindmaps are deleted after 30 days
- **Usability**: Redo / Undo, many Shortcuts


<sup>
*Pictogram note:

Pictograms author: Sergio Palao. Origin: ARASAAC (http://www.arasaac.org). License: CC (BY-NC-SA). Owner: Government of Aragon (Spain)
</sup>

## Getting started

### Quick Start (without building the image)
Expand Down Expand Up @@ -198,6 +205,10 @@ Example of running sql via typeorm:
docker-compose --file docker-compose-prod.yml --env-file .env.prod exec app_prod npx --prefix teammapper-backend typeorm query "select * from mmp_node" --dataSource ./teammapper-backend/dist/data-source.js
```

### Frontend feature flags
See file /teammapper-frontend/src/envrionments/environment.prod.ts to configure feature flags:
- featureFlagPictograms: Disables/Enables the pictogram feature (default: enabled). Note: You have to set this flag before build time!

### Further details

- Once this docker volume is initialized after the first `docker-compose up`, the database-related variables in `.env.prod` will not have any effect; please have this in mind => you will then need to setup your database manually
Expand Down Expand Up @@ -225,6 +236,7 @@ Logos and text provided with courtesy of kits.

## Acknowledgements

- Pictograms: https://arasaac.org/
- Mindmapp: https://github.com/cedoor/mindmapp (discontinued)
- mmp: https://github.com/cedoor/mmp (discontinued)
- D3: https://github.com/d3/d3
Expand Down
32 changes: 23 additions & 9 deletions teammapper-frontend/src/app/core/services/dialog/dialog.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Injectable } from '@angular/core';
import {
MatLegacyDialog as MatDialog,
MatLegacyDialogRef as MatDialogRef,
} from '@angular/material/legacy-dialog';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { DialogAboutComponent } from 'src/app/modules/application/components/dialog-about/dialog-about.component';
import { DialogConnectionInfoComponent } from 'src/app/modules/application/components/dialog-connection-info/dialog-connection-info.component';
import { DialogPictogramsComponent } from 'src/app/modules/application/components/dialog-pictograms/dialog-pictograms.component';
import { DialogShareComponent } from 'src/app/modules/application/components/dialog-share/dialog-share.component';

@Injectable({
Expand All @@ -14,15 +12,27 @@ export class DialogService {
private disconnectModalRef: MatDialogRef<DialogConnectionInfoComponent>;
private shareModalRef: MatDialogRef<DialogShareComponent>;
private aboutModalRef: MatDialogRef<DialogAboutComponent>;
private pictogramsModalRef: MatDialogRef<DialogPictogramsComponent>;

constructor(public dialog: MatDialog) {}
constructor(private dialog: MatDialog) {}

openDisconnectDialog() {
this.disconnectModalRef = this.dialog.open(DialogConnectionInfoComponent);
openPictogramDialog() {
this.pictogramsModalRef = this.dialog.open(DialogPictogramsComponent, {
width: '100%',
});
this.pictogramsModalRef.componentInstance.onPictogramAdd.subscribe(() => {
this.closePictogramDialog();
});
}

openShareDialog() {
this.shareModalRef = this.dialog.open(DialogShareComponent);
closePictogramDialog() {
if (!this.pictogramsModalRef) return;

this.pictogramsModalRef.close();
}

openDisconnectDialog() {
this.disconnectModalRef = this.dialog.open(DialogConnectionInfoComponent);
}

closeDisconnectDialog() {
Expand All @@ -43,6 +53,10 @@ export class DialogService {
this.aboutModalRef.close();
}

openShareDialog() {
this.shareModalRef = this.dialog.open(DialogShareComponent);
}

closeShareDialog() {
if (!this.shareModalRef) return;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ export class MmpService implements OnDestroy {
/**
* Insert an image in the selected node.
*/
public addNodeImage(image: string) {
public addNodeImage(image: string | ArrayBuffer) {
this.updateNode('imageSrc', image);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type IPictogramResponse = {
schematic: boolean;
sex: boolean;
violence: boolean;
aac: boolean;
aacColor: boolean;
skin: boolean;
hair: boolean;
downloads: number;
categories: string[];
synsets: string[];
tags: string[];
_id: number;
keywords: {
keyword: string;
type: number;
plural: string;
hasLocation: boolean;
}[];
created: Date;
lastUpdated: Date;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { PictogramService } from './pictogram.service';
import { TestBed } from '@angular/core/testing';
import { HttpClient } from '@angular/common/http';
import {
HttpClientTestingModule,
HttpTestingController,
} from '@angular/common/http/testing';
import { IPictogramResponse } from './picto-types';
import { SettingsService } from '../settings/settings.service';

const testData: IPictogramResponse = {
schematic: false,
sex: false,
violence: false,
aac: false,
aacColor: false,
skin: false,
hair: false,
downloads: 0,
categories: [],
synsets: [],
tags: [],
_id: 1,
created: new Date(),
lastUpdated: new Date(),
keywords: [],
};

describe('PictogramService', () => {
let httpClient: HttpClient;
let httpTestingController: HttpTestingController;
let settingsService: SettingsService;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
});

// Inject the http service and test controller for each test
httpClient = TestBed.inject(HttpClient);
httpTestingController = TestBed.inject(HttpTestingController);
settingsService = jasmine.createSpyObj('storageService', [
'getCachedSettings',
]);
});

it('fetches pictos', () => {
new PictogramService(httpClient, settingsService)
.getPictos('House')
.subscribe(data => {
expect(data).toEqual([testData]);
});
const httpRequest = httpTestingController.expectOne(
'https://api.arasaac.org/v1/pictograms/en/bestsearch/House'
);
expect(httpRequest.request.method).toBe('GET');
httpRequest.flush([testData]);
httpTestingController.verify();
});

it('constructs the asset url', () => {
const imageUrl = new PictogramService(
httpClient,
settingsService
).getPictoImageUrl(3);
expect(imageUrl).toEqual(
'https://static.arasaac.org/pictograms/3/3_300.png'
);
});

it('gets the image', () => {
const blob: Blob = new Blob();
new PictogramService(httpClient, settingsService)
.getPictoImage(3)
.subscribe(data => {
expect(data).toEqual(blob);
});
const httpRequest = httpTestingController.expectOne(
'https://static.arasaac.org/pictograms/3/3_300.png'
);
expect(httpRequest.request.method).toBe('GET');
httpRequest.flush(blob);
httpTestingController.verify();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/internal/Observable';
import { IPictogramResponse } from './picto-types';
import { environment } from 'src/environments/environment';
import { SettingsService } from '../settings/settings.service';

@Injectable({
providedIn: 'root',
})
export class PictogramService {
private apirUrl =
environment.pictogramApiUrl || 'https://api.arasaac.org/v1/pictograms';
private staticAssetUrl =
environment.pictogramStaticUrl || 'https://static.arasaac.org/pictograms';
private apiResource = 'bestsearch';

constructor(
private http: HttpClient,
private settingsSerivce: SettingsService
) {}

getPictos(seachTerm: string): Observable<IPictogramResponse[]> {
const language =
this.settingsSerivce.getCachedSettings()?.general?.language || 'en';
const url = `${this.apirUrl}/${language}/${this.apiResource}/${seachTerm}`;
return this.http.get<IPictogramResponse[]>(url);
}

getPictoImageUrl(id: number, size = 300, fileType = 'png') {
return `${this.staticAssetUrl}/${id}/${id}_${size}.${fileType}`;
}

getPictoImage(id: number): Observable<Blob> {
const url = this.getPictoImageUrl(id);
return this.http.get(url, { responseType: 'blob' });
}
}
13 changes: 13 additions & 0 deletions teammapper-frontend/src/app/core/services/utils/utils.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,17 @@ export class UtilsService {

return confirm(message);
}

/**
* Converts a blob to a data url
*/
public blobToBase64(blob: Blob): Promise<string | ArrayBuffer> {
const reader = new FileReader();
reader.readAsDataURL(blob);
return new Promise(resolve => {
reader.onloadend = () => {
resolve(reader.result);
};
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,42 @@ <h1>
<mat-list>
<mat-list-item>
<mat-icon mat-list-icon>check</mat-icon>
<h4 mat-line>
<span class="white-list">
{{
'PAGES.ABOUT.INTRODUCTION.APPLICATION_PROPERTIES.0' | translate
}}
</h4>
</span>
</mat-list-item>
<mat-list-item>
<mat-icon mat-list-icon>check</mat-icon>
<h4 mat-line>
<span class="white-list">
{{
'PAGES.ABOUT.INTRODUCTION.APPLICATION_PROPERTIES.1' | translate
}}
</h4>
</span>
</mat-list-item>
<mat-list-item>
<mat-icon mat-list-icon>check</mat-icon>
<h4 mat-line>
<span class="white-list">
{{
'PAGES.ABOUT.INTRODUCTION.APPLICATION_PROPERTIES.2' | translate
}}
</h4>
</span>
</mat-list-item>
<mat-list-item>
<mat-icon mat-list-icon>check</mat-icon>
<h4 mat-line>
<span class="white-list">
{{
'PAGES.ABOUT.INTRODUCTION.APPLICATION_PROPERTIES.3' | translate
}}
</h4>
</span>
</mat-list-item>
</mat-list>
</div>

<a color="accent" mat-flat-button routerLink="/map">
<button mat-flat-button color="accent" routerLink="/map">
{{ 'GENERAL.CREATE' | translate }}
</a>
</button>
</div>
<div>
<img src="./assets/images/screens.png" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ section.jumbotron {
}
}

> a {
width: 150px;
> button {
font-weight: bold;
color: mat.get-color-from-palette($app-primary);
margin-bottom: 10px;
Expand All @@ -76,9 +75,8 @@ section.jumbotron {
font-size: 0.7em;
}

.mat-list-item {
.white-list {
font-family: Fira Sans;
height: initial !important;
margin: 5px 0;
color: white;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import { ApplicationComponent } from './pages/application/application.component'
import { SettingsComponent } from './pages/settings/settings.component';
import { ShortcutsComponent } from './pages/shortcuts/shortcuts.component';
import { ToolbarComponent } from './components/toolbar/toolbar.component';
import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu';
import { MatLegacyTabsModule as MatTabsModule } from '@angular/material/legacy-tabs';
import { ClientColorPanelsComponent } from './components/client-color-panels/client-color-panels.component';
import { ColorPickerModule } from 'ngx-color-picker';
import { DialogAboutComponent } from './components/dialog-about/dialog-about.component';
import { DialogShareComponent } from './components/dialog-share/dialog-share.component';
import { DialogConnectionInfoComponent } from './components/dialog-connection-info/dialog-connection-info.component';
import { DialogPictogramsComponent } from './components/dialog-pictograms/dialog-pictograms.component';
import { MatMenuModule } from '@angular/material/menu';
import { MatTabsModule } from '@angular/material/tabs';

@NgModule({
imports: [
Expand All @@ -37,6 +38,7 @@ import { DialogConnectionInfoComponent } from './components/dialog-connection-in
ToolbarComponent,
DialogConnectionInfoComponent,
DialogShareComponent,
DialogPictogramsComponent,
DialogAboutComponent,
],
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { MatLegacyDialogRef as MatDialogRef } from '@angular/material/legacy-dialog';
import { faGithub } from '@fortawesome/free-brands-svg-icons';
import { MapProperties } from '@mmp/map/types';
import { SettingsService } from 'src/app/core/services/settings/settings.service';
Expand All @@ -21,7 +20,6 @@ export class DialogAboutComponent {
public mapAdminId: Promise<string>;

constructor(
public dialogRef: MatDialogRef<DialogAboutComponent>,
private translateService: TranslateService,
private settingsService: SettingsService,
private storageService: StorageService,
Expand Down
Loading
Loading