Implement user works (#15)
continuous-integration/drone/push Build is passing Details

Co-authored-by: cofrizot <colin.frizot@etu.uca.fr>
Co-authored-by: matis.mazingue <matis.mazingue@etu.uca.fr>
Co-authored-by: Matis MAZINGUE <Matis.MAZINGUE@etu.uca.fr>
Co-authored-by: clfreville2 <clement.freville2@etu.uca.fr>
Reviewed-on: #15
Co-authored-by: Matis MAZINGUE <matis.mazingue@etu.uca.fr>
Co-committed-by: Matis MAZINGUE <matis.mazingue@etu.uca.fr>
master
Matis MAZINGUE 5 months ago committed by Clément FRÉVILLE
parent 80c0d99bee
commit 478d0e02e8

18
package-lock.json generated

@ -13920,6 +13920,15 @@
"websocket-driver": "^0.7.4"
}
},
"node_modules/sockjs/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/socks": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz",
@ -14876,15 +14885,6 @@
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"dev": true,
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/validate-npm-package-license": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",

@ -4,19 +4,20 @@ import { LandingPageComponent } from './components/landing-page/landing-page.com
import { DocumentationComponent } from './components/documentation/documentation.component';
import { FormComponent } from './components/form/form.component';
import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component';
import { OurStoryComponent } from './components/our-story/our-story.component';
import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component';
import { WorksListComponent } from './components/works-list/works-list.component';
import { RegisterComponent } from './components/register/register.component';
import { LoginComponent } from './components/login/login.component';
// Toutes les routes de l'application sont définies ici
export const routes: Routes = [
{ path: '', component: LandingPageComponent },
{ path: 'work/:work', component: EditorComponent },
{ path: 'works', component: WorksListComponent },
{ path: 'editor', component: EditorComponent },
{ path: 'editor-live/:idRoom', component: EditorComponent },
{ path: 'documentation', component: DocumentationComponent },
{ path: 'contact', component: FormComponent },
{ path: 'our-story', component: OurStoryComponent },
{ path: 'terms-of-service', component: TermsOfServiceComponent },
{ path: 'privacy-policy', component: PrivacyPolicyComponent },
{ path: 'register', component: RegisterComponent },

@ -51,6 +51,27 @@
</svg>
</button>
</div>
<div class="param-editor">
<button
class="btn-share"
type="button"
(click)="shareButtonClicked()"
[disabled]="isLoaded">
{{ 'Editor.Share' | translate }}
</button>
</div>
<!-- TODO mettre le bouton save dans la page work-->
<!--TODO if userID de la session =! userID du work then btn-save.style(display : none) else btn-save.style(display : block)-->
<div class="param-editor">
<button
class="btn-save"
type="button"
(click)="saveButtonClicked()"
[disabled]="isLoaded">
{{ 'Editor.Save' | translate }}
</button>
</div>
</div>
<div class="editor-section-bar-header">
@if (errorMessage) {
@ -72,9 +93,10 @@
<div class="param-editor">
<button
class="button-icon button-run"
type="button"
(click)="onRunButtonClicked()"
[disabled]="isLoaded">
<div>RUN</div>
<div style="margin-right: 10px">{{ 'Editor.Run' | translate }}</div>
<svg
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"

@ -1,3 +1,14 @@
@mixin btn-styles($bg-color, $font-color) {
background-color: $bg-color;
color: $font-color;
border: none;
color: white;
padding: 12px 16px;
font-size: 16px;
cursor: pointer;
border-radius: 10px;
}
#editor-bar-header {
display: flex;
align-items: center;
@ -82,3 +93,18 @@ select {
::ng-deep .codemirror6-editor {
height: 100%;
}
.btn-run {
display: flex;
align-items: center;
justify-content: space-between;
@include btn-styles(#04aa6d, white);
}
.btn-share {
@include btn-styles(rgb(41, 120, 184), white);
}
.btn-save {
@include btn-styles(rgb(49, 159, 49), white);
}

@ -1,6 +1,8 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EditorComponent } from './editor.component';
import { HttpClientModule } from '@angular/common/http';
import { TranslateModule } from '@ngx-translate/core';
describe('EditorComponent', () => {
let component: EditorComponent;
@ -8,7 +10,7 @@ describe('EditorComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [EditorComponent],
imports: [EditorComponent, HttpClientModule, TranslateModule.forRoot()],
});
fixture = TestBed.createComponent(EditorComponent);
component = fixture.componentInstance;

@ -5,6 +5,7 @@ import { CodeMirrorComponent } from '@sandkasten/codemirror6-editor';
import { LanguageDescription } from '@codemirror/language';
import { CODE_DEFAULTS, LANGUAGES } from '../languages';
import { SafeHTMLPipe } from '../../safe-html.pipe';
import { TranslateModule } from '@ngx-translate/core';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import {
@ -37,12 +38,11 @@ import {
closeBracketsKeymap,
} from '@codemirror/autocomplete';
import { lintKeymap } from '@codemirror/lint';
import {
Connection,
getDocument,
peerExtension,
} from '../../services/connection.service';
import { Work } from '../../models/work.model';
import { WorkService } from '../../services/work.service';
import { Connection, getDocument, peerExtension } from '../../services/connection.service';
import { environment } from '../../../environments/environment';
import { LocationStrategy } from '@angular/common';
const basicSetup: Extension = (() => [
highlightActiveLineGutter(),
@ -82,6 +82,7 @@ const basicSetup: Extension = (() => [
ReactiveFormsModule,
FormsModule,
SafeHTMLPipe,
TranslateModule,
],
})
export class EditorComponent {
@ -167,9 +168,37 @@ export class EditorComponent {
});
}
#work: Work | undefined;
@Input()
set work(work: string) {
if (work === undefined) {
return;
}
this.workService.getWorkByLink(work).subscribe((work) => {
if (!work) {
return;
}
this.#work = work;
this.selectedLanguage = this.languages.find(
(lang) => lang.name === work.language
)!;
this.editorContent = work.content;
this.codemirror.editor?.dispatch({
changes: {
from: 0,
to: this.codemirror.editor.state.doc.length,
insert: work.content,
},
});
});
}
constructor(
private router: Router,
private backendService: BackendService
private backendService: BackendService,
private workService: WorkService,
private locationStrategy: LocationStrategy,
) {
backendService.getResult().subscribe((msg) => {
if (msg.type === 'stdout' || msg.type === 'stderr') {
@ -223,4 +252,54 @@ export class EditorComponent {
a.href = URL.createObjectURL(blob);
a.click();
}
addToDatabase(): void {
this.workService.postWork(
this.editorContent,
this.selectedLanguage.name,
).subscribe((link) => {
this.prepareClipboard(link);
});
}
shareButtonClicked() {
if (this.#work) {
this.prepareClipboard(this.#work.link);
} else {
this.addToDatabase();
}
}
prepareClipboard(link: string) {
const path = this.locationStrategy.prepareExternalUrl(`/work/${link}`);
const url = new URL(path, window.location.href);
if (navigator.clipboard) {
navigator.clipboard
.writeText(url.toString())
.then(() => {
alert('URL copied to clipboard!');
});
} else {
alert('Clipboard API not available');
}
}
saveButtonClicked() {
if (this.#work) {
this.workService.updateWork(
this.#work.id_work.toString(),
this.editorContent,
this.selectedLanguage.name
);
} else {
this.workService.postWork(
this.editorContent,
this.selectedLanguage.name,
).subscribe((link) => {
const url = `/work/${link}`;
this.router.navigateByUrl(url);
});
}
}
}

@ -5,11 +5,27 @@
</head>
<div class="footer">
<span class="footer--rights"
>Copyright © 2024 Sandcasten. All rights reserved.</span
>
<span class="footer--rights">{{ 'FooterPage.Rights' | translate }}</span>
<span class="footer__links">
<a>Mentions légales</a> - <a>Politique de confidentialité</a> -
<a>Conditions générales d'utilisation</a>
<a
routerLink="terms-of-service"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>{{ 'FooterPage.Legal' | translate }}</a
>
-
<a
routerLink="privacy-policy"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>{{ 'FooterPage.Privacy' | translate }}</a
>
-
<a
routerLink="terms-of-service"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>{{ 'FooterPage.Terms' | translate }}</a
>
</span>
</div>

@ -19,6 +19,8 @@
a {
cursor: pointer;
text-decoration: none;
color: $color-gray;
}
}
}

@ -1,6 +1,8 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FooterComponent } from './footer.component';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
describe('FooterComponent', () => {
let component: FooterComponent;
@ -8,7 +10,7 @@ describe('FooterComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [FooterComponent],
imports: [FooterComponent, RouterModule.forRoot([]), TranslateModule.forRoot()],
});
fixture = TestBed.createComponent(FooterComponent);
component = fixture.componentInstance;

@ -27,6 +27,13 @@
</div>
<nav class="right_part--menu">
<a
routerLink="/works"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>{{ 'HeaderPage.Work' | translate }}</a
>
<a
routerLink="editor"
routerLinkActive="active"

@ -1,15 +1,9 @@
import {
Component,
ElementRef,
HostListener,
Input,
ViewChild,
} from '@angular/core';
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { TranslationService } from '../../services/translation.service';
import { ThemeService } from '../../services/theme.service';
import { TranslateModule } from '@ngx-translate/core';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterLink, RouterLinkActive } from '@angular/router';
import { Router, RouterLink, RouterLinkActive } from '@angular/router';
import { NgClass, NgOptimizedImage } from '@angular/common';
import { UserService } from 'src/app/services/user.service';
@ -32,12 +26,15 @@ export class HeaderComponent {
version: string = '1.0';
isMenuOpen: boolean = false;
isCheck: boolean = false;
linkLastWork = ''; //peut-être à cast en string
@ViewChild('menuRef') menuRef!: ElementRef;
@Input() themeClass!: string;
@Input() themeService!: ThemeService;
// Instanciation du service pour les actions de traduction
constructor(
private router: Router,
private translationService: TranslationService,
private userService: UserService
) {}
@ -68,6 +65,11 @@ export class HeaderComponent {
}
}
onLastWorkLink(): void {
const url = `/work/${this.linkLastWork}`;
this.router.navigateByUrl(url);
}
// Logout
logout() {
this.userService.logoutUser().subscribe((response) => {

@ -17,8 +17,7 @@
class="landing-page__hero_left__buttons--editor--image"
src="assets/img/logo.png"
alt="editor icon" />
<!--TODO - Add trad-->
<span>Editeur de code</span>
<span>{{ 'LandingPage.Bouton1' | translate }}</span>
</button>
<a href="https://codefirst.iut.uca.fr/git/sandkasten">
<button class="landing-page__hero_left__buttons--git">
@ -26,8 +25,7 @@
class="landing-page__hero_left__buttons--git--image"
src="assets/img/landing-page/gitea.png"
alt="editor icon" />
<!--TODO - Add trad-->
<span>Dépôt Git</span>
<span>{{ 'LandingPage.Bouton2' | translate }}</span>
</button>
</a>
</div>
@ -35,8 +33,8 @@
<div class="landing-page__hero_right">
<img
class="landing-page__hero_right--image"
src="assets/img/landing-page/cross.png"
alt="landing page" />
src="assets/img/logo.png"
alt="Sandkasten Logo" />
</div>
</div>
<!--endregion-->
@ -45,12 +43,11 @@
<div class="landing-page__informations">
<div class="landing-page__informations_top">
<h2 class="landing-page__informations_top--title">
Ce dont vous avez besoin de savoir sur notre éditeur
{{ 'LandingPage.Top.Tittle' | translate }}
</h2>
<span class="landing-page__informations_top--description"
>Astro comes batteries included. It takes the best parts of
state-of-the-art tools and adds its own innovations.</span
>
<span class="landing-page__informations_top--description">{{
'LandingPage.Top.Description' | translate
}}</span>
</div>
<div class="landing-page__informations--lists">
<!--SECURITE-->
@ -73,12 +70,11 @@
</g>
</svg>
</div>
<div class="landing-page__informations__list--title">Sécurisé</div>
<div class="landing-page__informations__list--title">
{{ 'LandingPage.List.Tittle1' | translate }}
</div>
<div class="landing-page__informations__list--description">
Pour garantir la solidité de notre outil nous exécutons votre code
sur un environnement fermé et limitant les requêtes trop coûteuse.
Ce procédé nous protège ainsi des tentatives de mauvaise utilisation
du système.
{{ 'LandingPage.List.Text1' | translate }}
</div>
</div>
<!--Import-->
@ -98,12 +94,10 @@
</svg>
</div>
<div class="landing-page__informations__list--title">
Import / Export
{{ 'LandingPage.List.Tittle2' | translate }}
</div>
<div class="landing-page__informations__list--description">
Vous pouvez importez des fichiers et l'éditeur va reconnaître le
langage à partir de son extension. Vous pouvez aussi exporter le
code que vous avez fait en seulement un clic.
{{ 'LandingPage.List.Text2' | translate }}
</div>
</div>
<!--Options-->
@ -139,11 +133,11 @@
</g>
</svg>
</div>
<div class="landing-page__informations__list--title">Options</div>
<div class="landing-page__informations__list--title">
{{ 'LandingPage.List.Tittle3' | translate }}
</div>
<div class="landing-page__informations__list--description">
Vous disposez de nombreuses options vous permettant de personnalisé
et d'optimiser votre utilisation de Sandkasten. Ces options sont
directement disponible depuis l'éditeur.
{{ 'LandingPage.List.Text3' | translate }}
</div>
</div>
<!--Multi-language-->
@ -172,13 +166,10 @@
</svg>
</div>
<div class="landing-page__informations__list--title">
Multi-language
{{ 'LandingPage.List.Tittle4' | translate }}
</div>
<div class="landing-page__informations__list--description">
De nombreux langages de codes sont reconnus dans Sandkasten. Lorsque
vous taper du code ou en importer un fichier l'éditeur reconnaît
automatiquement le langage. Vous pouvez aussi directement le
sélectionner dans l'éditeur.
{{ 'LandingPage.List.Text4' | translate }}
</div>
</div>
<!--TODO-->
@ -197,11 +188,11 @@
</g>
</svg>
</div>
<div class="landing-page__informations__list--title">Lorem ipsum</div>
<div class="landing-page__informations__list--title">
{{ 'LandingPage.List.Tittle5' | translate }}
</div>
<div class="landing-page__informations__list--description">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam,
{{ 'LandingPage.List.Text5' | translate }}
</div>
</div>
<!--TODO-->
@ -220,11 +211,11 @@
</g>
</svg>
</div>
<div class="landing-page__informations__list--title">Lorem ipsum</div>
<div class="landing-page__informations__list--title">
{{ 'LandingPage.List.Tittle6' | translate }}
</div>
<div class="landing-page__informations__list--description">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam,
{{ 'LandingPage.List.Text6' | translate }}
</div>
</div>
</div>
@ -233,9 +224,9 @@
<!--region Technologies-->
<div class="landing-page__technologies">
<span class="landing-page__technologies--title"
>Languages disponibles dès maintenant</span
>
<span class="landing-page__technologies--title">{{
'LandingPage.Technologie.Tittle' | translate
}}</span>
<div class="landing-page__technologies__list">
<!--C-->
<a href="https://www.open-std.org/jtc1/sc22/wg14/">
@ -313,12 +304,10 @@
<div class="landing-page__about">
<div class="landing-page__about__titles">
<h2 class="landing-page__about--title">
Empowering the world with Astro.
{{ 'LandingPage.About.Tittle' | translate }}
</h2>
<div class="landing-page__about--description">
We're a multi-cultural team from around the world! We come from
diverse backgrounds, bringing different personalities, experiences and
skills to the job. This is what makes our team so special.
{{ 'LandingPage.About.Text' | translate }}
</div>
</div>
<div class="landing-page__about__members">
@ -330,7 +319,7 @@
<div class="landing-page__about__member--texts">
<div class="landing-page__about__member--texts--name">Bastien</div>
<div class="landing-page__about__member--texts--role">
Développeur
{{ 'LandingPage.MemberRole' | translate }}
</div>
</div>
</div>
@ -342,7 +331,7 @@
<div class="landing-page__about__member--texts">
<div class="landing-page__about__member--texts--name">Clément</div>
<div class="landing-page__about__member--texts--role">
Développeur
{{ 'LandingPage.MemberRole' | translate }}
</div>
</div>
</div>
@ -354,7 +343,7 @@
<div class="landing-page__about__member--texts">
<div class="landing-page__about__member--texts--name">Colin</div>
<div class="landing-page__about__member--texts--role">
Développeur
{{ 'LandingPage.MemberRole' | translate }}
</div>
</div>
</div>
@ -366,7 +355,7 @@
<div class="landing-page__about__member--texts">
<div class="landing-page__about__member--texts--name">Hugo</div>
<div class="landing-page__about__member--texts--role">
Développeur
{{ 'LandingPage.MemberRole' | translate }}
</div>
</div>
</div>
@ -378,7 +367,7 @@
<div class="landing-page__about__member--texts">
<div class="landing-page__about__member--texts--name">Matis</div>
<div class="landing-page__about__member--texts--role">
Développeur
{{ 'LandingPage.MemberRole' | translate }}
</div>
</div>
</div>
@ -388,9 +377,9 @@
<!--region Socials-->
<div class="landing-page__socials">
<span class="landing-page__socials--title"
>Rejoinez-nous sur nos réseaux</span
>
<span class="landing-page__socials--title">{{
'LandingPage.SocialTittle' | translate
}}</span>
<div class="landing-page__socials__list">
<!--Twitter-->
<a href="https://twitter.com/Sandkasten3a">
@ -454,13 +443,14 @@
<!--region Get Started-->
<div class="landing-page__get_started">
<h3 class="landing-page__get_started--title">Allez essayer Sandkasten</h3>
<h3 class="landing-page__get_started--title">
{{ 'LandingPage.GetStarted.Tittle' | translate }}
</h3>
<div class="landing-page__get_started--description">
Rejoignez dès maintenant la communauté Sandkasten en testant notre
éditeur !
{{ 'LandingPage.GetStarted.Description' | translate }}
</div>
<button class="landing-page__get_started--button" routerLink="/editor">
Get Started
{{ 'LandingPage.GetStarted.Button' | translate }}
</button>
</div>
<!--endregion-->

@ -57,7 +57,7 @@ export class LoginComponent {
this.userService
.loginUser(formValue.login!, formValue.password!)
.subscribe((response) => {
.then((response) => {
console.log('response :', response);
if (response.success) {
this.successLogin = 'Vous êtes connecté.';

@ -1,36 +0,0 @@
<body>
<main>
<div>
<img class="colin" [src]="colin" alt="Photo-Colin" />
<h1>COLIN FRIZOT</h1>
<p>Bonjour, je suis Colin, un poisson développeur. (fraude)</p>
<img class="hugo" [src]="hugo" alt="Photo-Hugo" />
<h1>HUGO PRADIER</h1>
<p>
Bonjour, je suis Hugo, un développeur bientôt chauve. (suit tous les
tutos en ligne, mais ça marche jamais)
</p>
<img class="bastien" [src]="bastien" alt="Photo-Bastien" />
<h1>BASTIEN OLLIER</h1>
<p>
Bonjour, je suis Bastien, un développeur visuellement parlant, très
puant. (connaît l'adresse de la grand-mère de Colin)
</p>
<img class="clement" [src]="clement" alt="Photo-Clement" />
<h1>CLÉMENT FRÉVILLE</h1>
<p>
Bonjour, je suis Clément, un développeur meilleur que tes profs. (dieu)
</p>
<img class="matis" [src]="matis" alt="Photo-Matis" />
<h1>MATIS MAZINGUE</h1>
<p>
Bonjour, je suis Matis, un "développeur". (est là que pour l'argent,
mais il est nul)
</p>
</div>
</main>
</body>

@ -1,22 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OurStoryComponent } from './our-story.component';
describe('OurStoryComponent', () => {
let component: OurStoryComponent;
let fixture: ComponentFixture<OurStoryComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [OurStoryComponent],
}).compileComponents();
fixture = TestBed.createComponent(OurStoryComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -1,22 +0,0 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-our-story',
templateUrl: './our-story.component.html',
styleUrl: './our-story.component.scss',
standalone: true,
})
export class OurStoryComponent {
constructor(private router: Router) {}
colin: string = 'assets/img/colin.png';
hugo: string = 'assets/img/hugo.png';
bastien: string = 'assets/img/bastien.png';
clement: string = 'assets/img/clement.png';
matis: string = 'assets/img/matis.png';
// Si click sur "Run", on redirige vers la page de notre histoire
onContinue(): void {
this.router.navigateByUrl('/our-story');
}
}

@ -0,0 +1,9 @@
<div class="work-list-detail">
<h4 class="work-list-detail--title">{{ work?.title }}</h4>
<span class="work-list-detail--content">{{
work?.content | slice: 0 : 50
}}</span>
<button class="work-list-detail--btn" [routerLink]="['/work/', work?.link]">
Edit Code
</button>
</div>

@ -0,0 +1,26 @@
.work-list-detail {
background: lightgray;
width: fit-content;
padding: 2rem;
border-radius: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
&--title {
margin: 0;
font-size: 1.5rem;
font-weight: bold;
}
&--content {
}
&--btn {
width: fit-content;
padding: 0.5rem 1rem;
border: 1px solid black;
border-radius: 0.5rem;
}
}

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { WorkListDetailComponent } from './work-list-detail.component';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';
describe('WorkListDetailComponent', () => {
let component: WorkListDetailComponent;
let fixture: ComponentFixture<WorkListDetailComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [WorkListDetailComponent, HttpClientModule, RouterModule.forRoot([])],
}).compileComponents();
fixture = TestBed.createComponent(WorkListDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,15 @@
import { Component, Input } from '@angular/core';
import { Work } from '../../models/work.model';
import { RouterLink } from '@angular/router';
import { SlicePipe } from '@angular/common';
@Component({
selector: 'app-work-list-detail',
standalone: true,
imports: [RouterLink, SlicePipe],
templateUrl: './work-list-detail.component.html',
styleUrl: './work-list-detail.component.scss',
})
export class WorkListDetailComponent {
@Input() work?: Work;
}

@ -0,0 +1,15 @@
<div style="margin-left: 30px">
<h2>Works</h2>
<h3>Last Work</h3>
<div *ngFor="let work of works | slice: -1">
<app-work-list-detail [work]="work"></app-work-list-detail>
</div>
<h3>All Works</h3>
<div class="all-works">
<div *ngFor="let work of works">
<app-work-list-detail [work]="work"></app-work-list-detail>
</div>
</div>
</div>

@ -0,0 +1,5 @@
.all-works {
display: flex;
flex-wrap: wrap;
gap: 2rem;
}

@ -0,0 +1,24 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { WorksListComponent } from './works-list.component';
import { RouterModule } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';
describe('WorksListComponent', () => {
let component: WorksListComponent;
let fixture: ComponentFixture<WorksListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [WorksListComponent, HttpClientModule, RouterModule.forRoot([])],
}).compileComponents();
fixture = TestBed.createComponent(WorksListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,36 @@
import { Component, OnInit } from '@angular/core';
import { Work } from '../../models/work.model';
import { WorkService } from '../../services/work.service';
import { NgForOf, SlicePipe } from '@angular/common';
import { FormsModule, NgForm } from '@angular/forms';
import { WorkListDetailComponent } from '../work-list-detail/work-list-detail.component';
@Component({
selector: 'app-works-list',
standalone: true,
imports: [NgForOf, FormsModule, SlicePipe, WorkListDetailComponent],
templateUrl: './works-list.component.html',
styleUrl: './works-list.component.scss',
})
export class WorksListComponent implements OnInit {
works: Work[] = [];
// TODO - REMOVE WHEN USER MANAGEMENT DONE
FAKE_USER_ID = 1;
constructor(protected workService: WorkService) {}
ngOnInit() {
this.workService.getWorks().then((works: Work[]) => {
works.map((work: Work) => {
if (work.user_id === this.FAKE_USER_ID) {
this.works.push(work);
}
});
});
}
onSubmit(form: NgForm) {
this.workService.saveWork(form);
}
}

@ -0,0 +1,8 @@
export interface Work {
id_work: number;
link: string;
user_id: number;
language: string;
title: string;
content: string;
}

@ -1,15 +1,12 @@
import { Injectable } from '@angular/core';
import { User } from '../models/user.model';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { NgForm } from '@angular/forms';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
})
export class UserService {
API_URL = 'http://127.0.0.1:3000';
constructor(private http: HttpClient) {}
postUser(
@ -24,22 +21,27 @@ export class UserService {
permissions: 0,
};
return this.http.post<Response>(`${this.API_URL}/users`, body);
return this.http.post<Response>(`${environment.apiUrl}/users`, body);
}
loginUser(login: string, password: string): Observable<Response> {
loginUser(login: string, password: string): Promise<Response> {
const body = {
login: login,
password: password,
};
return this.http.post<Response>(`${this.API_URL}/users/login`, body, {
withCredentials: true,
});
return fetch(`${environment.apiUrl}/users/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
credentials: 'include',
}).then((response) => response.json());
}
logoutUser(): Observable<Response> {
return this.http.post<Response>(`${this.API_URL}/users/logout`, {
return this.http.post<Response>(`${environment.apiUrl}/users/logout`, {
withCredentials: true,
});
}

@ -0,0 +1,19 @@
import { TestBed } from '@angular/core/testing';
import { WorkService } from './work.service';
import { HttpClientModule } from '@angular/common/http';
describe('WorkService', () => {
let service: WorkService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule]
});
service = TestBed.inject(WorkService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

@ -0,0 +1,48 @@
import { Injectable } from '@angular/core';
import { Work } from '../models/work.model';
import { HttpClient } from '@angular/common/http';
import { map, Observable } from 'rxjs';
import { NgForm } from '@angular/forms';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
})
export class WorkService {
constructor(private http: HttpClient) {}
getWorks(): Promise<Work[]> {
return fetch(`${environment.apiUrl}/works`, {
method: 'GET',
credentials: 'include',
}).then((response) => response.json());
}
getWorkByLink(link: string): Observable<Work | null> {
return this.http.get<Work>(`${environment.apiUrl}/works/${link}`);
}
saveWork(form: NgForm): void {
const code = form.value.content;
this.http.post(`${environment.apiUrl}/works/save`, code, { withCredentials: true });
}
postWork(code: string, language: string): Observable<string> {
const body = {
language,
title: `Basic ${language}`,
code,
};
return this.http.post<Work>(`${environment.apiUrl}/works`, body).pipe(map((work) => work.link));
}
updateWork(id: string, code: string, language: string): void {
const body = {
newContent: code,
language: language,
};
this.http.put(`${environment.apiUrl}/works/${id}/content`, body, { withCredentials: true });
}
}

@ -2,21 +2,53 @@
"LandingPage": {
"Welcome": "Welcome to Sandkasten",
"Description": "You've arrived at the best code testing sandbox site! We let you test all your programs in your favorite languages. Thanks to the Code Mirror editor and our personalized management of execution on containers, your code becomes completely harmless, so you can have as much fun as you like!",
"Try": "Try it now !"
"Try": "Try it now !",
"Bouton1": "Code editor",
"Bouton2": "Git repository",
"Top": {
"Tittle": "What you need to know about our editor",
"Description": "Sandkasten, the editor with all the options you need. With these advantages, you can test your code and see the results."
},
"List": {
"Tittle1": "Security",
"Text1": "To guarantee the robustness of our tool, we run your code in a closed environment, limiting costly requests. This protects us from attempts to misuse the system.",
"Tittle2": "Import / Export",
"Text2": "You can import files and the editor will recognize the language by its extension. You can also export the code you've made with just one click.",
"Tittle3": "Options",
"Text3": "You have numerous options for customizing and optimizing your use of Sandkasten. These options are available directly from the editor.",
"Tittle4": "Multi-langues",
"Text4": "Sandkasten supports a wide range of code languages. When you type code or import a file, the editor automatically recognizes the language. You can also select it directly in the editor.",
"Tittle5": "Lorem Ipsum",
"Text5": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam",
"Tittle6": "Lorem Ipsum",
"Text6": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam"
},
"Technologie": {
"Tittle": "Languages disponibles dès maintenant"
},
"About": {
"Tittle": "Empowering the world with Sandkasten.",
"Text": "We're a team professionally engaged in the world of technology! We bring different personalities, experiences and skills to the table. That's what makes our team so special."
},
"MemberRole": "Developper",
"SocialTittle": "Join us on our networks",
"GetStarted": {
"Tittle": "Try Sandkasten",
"Description": "Join the Sandkasten community now by testing our editor!",
"Button": "Get Started !"
}
},
"HeaderPage": {
"Work": "Work",
"Editor": "Editor",
"Documentation": "Documentation",
"Contact": "Contact us"
},
"FooterPage": {
"About": "About Sandkasten",
"Contact": "Contact us",
"Story": "Our story",
"Legal": "Legal mentions",
"Terms": "Terms of use",
"Privacy": "Privacy policy",
"Rights": "©2024 Sandkasten, Inc. All rights reserved."
"Rights": "Copyright © 2024 Sandkasten. All rights reserved."
},
"TermsOfServicePage": {
"Mentions": "LEGALS MENTIONS",
@ -115,5 +147,11 @@
"Text2": "We will attempt to resolve complaints and disputes and make every reasonable effort to honor your wish to exercise your rights as quickly as possible and in any event, within the timescales provided by applicable data protection laws."
},
"Date": "This document was last updated on January 10, 2024"
},
"Editor": {
"Share": "Share",
"Save": "Save",
"Run": "RUN",
"Add": "ADD BDD"
}
}

@ -2,21 +2,53 @@
"LandingPage": {
"Welcome": "Bienvenue sur Sandkasten",
"Description": "Vous voici arrivé sur le meilleur site bac à sable de test de code ! Nous vous permettons de tester tous vos programmes sur vos langages préférés. Grâce à léditeur Code Mirror et à notre gestion personalisée de lexécution sur des conteneurs, votre code devient complétement inoffensif et vous pouvez donc vous amusez autant que vous le souhaitez !",
"Try": "Essayez maintenant !"
"Try": "Essayez maintenant !",
"Bouton1": "Editeur de code",
"Bouton2": "Dépôt Git",
"Top": {
"Tittle": "Ce dont vous avez besoin de savoir sur notre éditeur",
"Description": "Sandkasten, l'éditeur qui possède toutes les options dont vous avez besoin. Avec ces avantages vous pourrez tester votre code et en voir le résultat."
},
"List": {
"Tittle1": "Sécurité",
"Text1": "Pour garantir la solidité de notre outil nous exécutons votre code sur un environnement fermé et limitant les requêtes trop coûteuse. Ce procédé nous protège ainsi des tentatives de mauvaise utilisation du système.",
"Tittle2": "Importer / Exporter",
"Text2": "Vous pouvez importez des fichiers et l'éditeur va reconnaître le langage à partir de son extension. Vous pouvez aussi exporter le code que vous avez fait en seulement un clic.",
"Tittle3": "Options",
"Text3": "Vous disposez de nombreuses options vous permettant de personnalisé et d'optimiser votre utilisation de Sandkasten. Ces options sont directement disponible depuis l'éditeur.",
"Tittle4": "Multi-languages",
"Text4": "De nombreux langages de codes sont reconnus dans Sandkasten. Lorsque vous taper du code ou en importer un fichier l'éditeur reconnaît automatiquement le langage. Vous pouvez aussi directement le sélectionner dans l'éditeur.",
"Tittle5": "Lorem Ipsum",
"Text5": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam",
"Tittle6": "Lorem Ipsum",
"Text6": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam"
},
"Technologie": {
"Tittle": "Languages disponibles dès maintenant"
},
"About": {
"Tittle": "Renforcer le monde avec Sandkasten.",
"Text": "Nous sommes une équipe se professionalisant dans le monde des technologies ! Nous apportons des personnalités, des expériences et des compétences différentes. C'est ce qui rend notre équipe si spéciale."
},
"MemberRole": "Développeur",
"SocialTittle": "Rejoinez-nous sur nos réseaux",
"GetStarted": {
"Tittle": "Allez essayer Sandkasten",
"Description": "Rejoignez dès maintenant la communauté Sandkasten en testant notre éditeur !",
"Button": "Commencez !"
}
},
"HeaderPage": {
"Work": "Travail",
"Editor": "Éditeur",
"Documentation": "Documentation",
"Contact": "Nous contacter"
},
"FooterPage": {
"About": "À propos de Sandkasten",
"Contact": "Nous contacter",
"Story": "Notre histoire",
"Legal": "Mentions légales",
"Terms": "Conditions d'utilisation",
"Privacy": "Politique de confidentialité",
"Rights": "©2024 Sandkasten, Inc. Tous droits réservés"
"Rights": "Copyright © 2024 Sandkasten. Tous droits réservés"
},
"TermsOfServicePage": {
"Mentions": "MENTIONS LÉGALES",
@ -115,5 +147,11 @@
"Text2": "Nous nous efforcerons de résoudre les plaintes et les litiges et ferons tous les efforts raisonnables pour répondre à votre souhait d'exercer vos droits le plus rapidement possible et, en tout état de cause, dans les délais prévus par les lois applicables en matière de protection des données."
},
"Date": "Ce document a été mis à jour pour la dernière fois le 10 janvier 2024."
},
"Editor": {
"Share": "Partager",
"Save": "Sauvegarder",
"Run": "EXECUTER",
"Add": "AJOUTER A LA BDD"
}
}

Loading…
Cancel
Save