From b8bf19028d9d421e555919653a7d5d9e50c3a006 Mon Sep 17 00:00:00 2001 From: paschal533 Date: Fri, 9 Aug 2024 22:54:40 +0100 Subject: [PATCH 1/9] Feat:Helia Angular example added --- examples/helia-angular/.editorconfig | 16 + examples/helia-angular/.gitignore | 42 +++ examples/helia-angular/README.md | 27 ++ examples/helia-angular/angular.json | 99 +++++ examples/helia-angular/package.json | 39 ++ examples/helia-angular/public/favicon.ico | Bin 0 -> 194291 bytes .../helia-angular/src/app/app.component.css | 0 .../helia-angular/src/app/app.component.html | 349 ++++++++++++++++++ .../src/app/app.component.spec.ts | 29 ++ examples/helia-angular/src/app/app.config.ts | 8 + examples/helia-angular/src/app/app.module.ts | 22 ++ examples/helia-angular/src/app/app.routes.ts | 3 + .../src/app/global-error-handler.ts | 7 + .../helia-angular/src/app/ipfs.component.ts | 34 ++ examples/helia-angular/src/index.html | 13 + examples/helia-angular/src/main.ts | 6 + examples/helia-angular/src/styles.css | 1 + examples/helia-angular/tsconfig.app.json | 15 + examples/helia-angular/tsconfig.json | 32 ++ examples/helia-angular/tsconfig.spec.json | 15 + 20 files changed, 757 insertions(+) create mode 100644 examples/helia-angular/.editorconfig create mode 100644 examples/helia-angular/.gitignore create mode 100644 examples/helia-angular/README.md create mode 100644 examples/helia-angular/angular.json create mode 100644 examples/helia-angular/package.json create mode 100644 examples/helia-angular/public/favicon.ico create mode 100644 examples/helia-angular/src/app/app.component.css create mode 100644 examples/helia-angular/src/app/app.component.html create mode 100644 examples/helia-angular/src/app/app.component.spec.ts create mode 100644 examples/helia-angular/src/app/app.config.ts create mode 100644 examples/helia-angular/src/app/app.module.ts create mode 100644 examples/helia-angular/src/app/app.routes.ts create mode 100644 examples/helia-angular/src/app/global-error-handler.ts create mode 100644 examples/helia-angular/src/app/ipfs.component.ts create mode 100644 examples/helia-angular/src/index.html create mode 100644 examples/helia-angular/src/main.ts create mode 100644 examples/helia-angular/src/styles.css create mode 100644 examples/helia-angular/tsconfig.app.json create mode 100644 examples/helia-angular/tsconfig.json create mode 100644 examples/helia-angular/tsconfig.spec.json diff --git a/examples/helia-angular/.editorconfig b/examples/helia-angular/.editorconfig new file mode 100644 index 00000000..59d9a3a3 --- /dev/null +++ b/examples/helia-angular/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/examples/helia-angular/.gitignore b/examples/helia-angular/.gitignore new file mode 100644 index 00000000..cc7b1413 --- /dev/null +++ b/examples/helia-angular/.gitignore @@ -0,0 +1,42 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db diff --git a/examples/helia-angular/README.md b/examples/helia-angular/README.md new file mode 100644 index 00000000..1f2dda7f --- /dev/null +++ b/examples/helia-angular/README.md @@ -0,0 +1,27 @@ +# HeliaAngular + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.1.4. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/examples/helia-angular/angular.json b/examples/helia-angular/angular.json new file mode 100644 index 00000000..76d5e0c4 --- /dev/null +++ b/examples/helia-angular/angular.json @@ -0,0 +1,99 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "helia-angular": { + "projectType": "application", + "schematics": {}, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist/helia-angular", + "index": "src/index.html", + "browser": "src/main.ts", + "polyfills": [ + "zone.js" + ], + "tsConfig": "tsconfig.app.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": [ + "src/styles.css" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kB", + "maximumError": "4kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "helia-angular:build:production" + }, + "development": { + "buildTarget": "helia-angular:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n" + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": [ + "zone.js", + "zone.js/testing" + ], + "tsConfig": "tsconfig.spec.json", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": [ + "src/styles.css" + ], + "scripts": [] + } + } + } + } + }, + "cli": { + "analytics": false + } +} diff --git a/examples/helia-angular/package.json b/examples/helia-angular/package.json new file mode 100644 index 00000000..93f9a4cc --- /dev/null +++ b/examples/helia-angular/package.json @@ -0,0 +1,39 @@ +{ + "name": "helia-angular", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "^18.1.0", + "@angular/common": "^18.1.0", + "@angular/compiler": "^18.1.0", + "@angular/core": "^18.1.0", + "@angular/forms": "^18.1.0", + "@angular/platform-browser": "^18.1.0", + "@angular/platform-browser-dynamic": "^18.1.0", + "@angular/router": "^18.1.0", + "helia": "^4.2.5", + "rxjs": "~7.8.0", + "tslib": "^2.3.0", + "zone.js": "~0.14.3" + }, + "devDependencies": { + "@angular-devkit/build-angular": "^18.1.4", + "@angular/cli": "^18.1.4", + "@angular/compiler-cli": "^18.1.0", + "@types/jasmine": "~5.1.0", + "jasmine-core": "~5.1.0", + "karma": "~6.4.0", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.0", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "typescript": "~5.5.2" + } +} diff --git a/examples/helia-angular/public/favicon.ico b/examples/helia-angular/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3580aaf4edb72b7d94efc403eb2ed2df059bccd9 GIT binary patch literal 194291 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

mWSq8OY_kV&K%2tiNX2&r-$Rl-45+-l` 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 literal 0 HcmV?d00001 diff --git a/examples/helia-angular/src/app/app.component.css b/examples/helia-angular/src/app/app.component.css new file mode 100644 index 00000000..e69de29b diff --git a/examples/helia-angular/src/app/app.component.html b/examples/helia-angular/src/app/app.component.html new file mode 100644 index 00000000..2447f5ba --- /dev/null +++ b/examples/helia-angular/src/app/app.component.html @@ -0,0 +1,349 @@ + + + + + + + + + + + +

+
+
+ +

Hello, {{ title }}

+

Congratulations! Your app is running. 🎉

+ + @if(!helia || !id){ +
+

Starting Helia...

+
+ } + + @if(helia && id){ +
+

ID: {{ id }}

+

Status: {{ isOnline ? 'Online' : 'Offline' }}

+
+ } +
+ +
+
+ @for (item of [ + { title: 'Explore the Docs', link: 'https://angular.dev' }, + { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, + { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, + { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, + { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, + ]; track item.title) { + + {{ item.title }} + + + + + } +
+ +
+
+
+ + + + + + + + + + + diff --git a/examples/helia-angular/src/app/app.component.spec.ts b/examples/helia-angular/src/app/app.component.spec.ts new file mode 100644 index 00000000..14bbad5e --- /dev/null +++ b/examples/helia-angular/src/app/app.component.spec.ts @@ -0,0 +1,29 @@ +import { TestBed } from '@angular/core/testing'; +import { IPFSComponent } from './ipfs.component'; + +describe('IPFSComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [IPFSComponent], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(IPFSComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have the 'helia-angular' title`, () => { + const fixture = TestBed.createComponent(IPFSComponent); + const app = fixture.componentInstance; + expect(app.title).toEqual('helia-angular'); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(IPFSComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('h1')?.textContent).toContain('Hello, helia-angular'); + }); +}); diff --git a/examples/helia-angular/src/app/app.config.ts b/examples/helia-angular/src/app/app.config.ts new file mode 100644 index 00000000..a1e7d6f8 --- /dev/null +++ b/examples/helia-angular/src/app/app.config.ts @@ -0,0 +1,8 @@ +import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +import { provideRouter } from '@angular/router'; + +import { routes } from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)] +}; diff --git a/examples/helia-angular/src/app/app.module.ts b/examples/helia-angular/src/app/app.module.ts new file mode 100644 index 00000000..10339898 --- /dev/null +++ b/examples/helia-angular/src/app/app.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { GlobalErrorHandler } from './global-error-handler'; +import { ErrorHandler } from '@angular/core'; +import { IPFSComponent } from './ipfs.component'; + +@NgModule({ + declarations: [ + IPFSComponent + ], + imports: [ + BrowserModule, + FormsModule, + ReactiveFormsModule, + BrowserAnimationsModule, + ], + providers: [{ provide: ErrorHandler, useClass: GlobalErrorHandler }], + bootstrap: [IPFSComponent] +}) +export class AppModule { } \ No newline at end of file diff --git a/examples/helia-angular/src/app/app.routes.ts b/examples/helia-angular/src/app/app.routes.ts new file mode 100644 index 00000000..dc39edb5 --- /dev/null +++ b/examples/helia-angular/src/app/app.routes.ts @@ -0,0 +1,3 @@ +import { Routes } from '@angular/router'; + +export const routes: Routes = []; diff --git a/examples/helia-angular/src/app/global-error-handler.ts b/examples/helia-angular/src/app/global-error-handler.ts new file mode 100644 index 00000000..805070bd --- /dev/null +++ b/examples/helia-angular/src/app/global-error-handler.ts @@ -0,0 +1,7 @@ +import { ErrorHandler } from '@angular/core'; + +export class GlobalErrorHandler implements ErrorHandler { + handleError(error: any): void { + console.error('Global Error Handler:', error); + } +} \ No newline at end of file diff --git a/examples/helia-angular/src/app/ipfs.component.ts b/examples/helia-angular/src/app/ipfs.component.ts new file mode 100644 index 00000000..04f4578d --- /dev/null +++ b/examples/helia-angular/src/app/ipfs.component.ts @@ -0,0 +1,34 @@ +import { Component, OnInit } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; +import { createHelia } from 'helia'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [RouterOutlet], + templateUrl: './app.component.html', + styleUrl: './app.component.css' +}) + +export class IPFSComponent implements OnInit { + title = 'helia-angular'; + id: string | null = null; + isOnline = false; + helia: any = null; + + async ngOnInit() { + if (this.helia) return; + + try { + const heliaNode = await createHelia(); + const nodeId = heliaNode.libp2p.peerId.toString(); + const nodeIsOnline = heliaNode.libp2p.status === 'started'; + + this.helia = heliaNode; + this.id = nodeId; + this.isOnline = nodeIsOnline; + } catch (error) { + console.error('Error initializing Helia:', error); + } + } +} diff --git a/examples/helia-angular/src/index.html b/examples/helia-angular/src/index.html new file mode 100644 index 00000000..0bdac97b --- /dev/null +++ b/examples/helia-angular/src/index.html @@ -0,0 +1,13 @@ + + + + + HeliaAngular + + + + + + + + diff --git a/examples/helia-angular/src/main.ts b/examples/helia-angular/src/main.ts new file mode 100644 index 00000000..e4d4387b --- /dev/null +++ b/examples/helia-angular/src/main.ts @@ -0,0 +1,6 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { IPFSComponent } from './app/ipfs.component'; + +bootstrapApplication(IPFSComponent, appConfig) + .catch((err) => console.error(err)); diff --git a/examples/helia-angular/src/styles.css b/examples/helia-angular/src/styles.css new file mode 100644 index 00000000..90d4ee00 --- /dev/null +++ b/examples/helia-angular/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/examples/helia-angular/tsconfig.app.json b/examples/helia-angular/tsconfig.app.json new file mode 100644 index 00000000..3775b37e --- /dev/null +++ b/examples/helia-angular/tsconfig.app.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/examples/helia-angular/tsconfig.json b/examples/helia-angular/tsconfig.json new file mode 100644 index 00000000..a9ae8b06 --- /dev/null +++ b/examples/helia-angular/tsconfig.json @@ -0,0 +1,32 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist/out-tsc", + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "esModuleInterop": true, + "sourceMap": true, + "declaration": false, + "experimentalDecorators": true, + "moduleResolution": "bundler", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "lib": [ + "ES2022", + "dom" + ] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true + } +} diff --git a/examples/helia-angular/tsconfig.spec.json b/examples/helia-angular/tsconfig.spec.json new file mode 100644 index 00000000..5fb748d9 --- /dev/null +++ b/examples/helia-angular/tsconfig.spec.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} From d8e430b0c5d9a13d46ea7154c88deff16566d649 Mon Sep 17 00:00:00 2001 From: paschal533 Date: Mon, 7 Oct 2024 12:36:26 +0100 Subject: [PATCH 2/9] Changes made to address @SgtPooki's review comments --- examples/helia-angular/.editorconfig | 17 +- examples/helia-angular/README.md | 126 ++++++++++++- .../helia-angular/src/app/app.component.css | 167 +++++++++++++++++ .../helia-angular/src/app/app.component.html | 170 ------------------ examples/helia-angular/src/styles.css | 168 ++++++++++++++++- 5 files changed, 453 insertions(+), 195 deletions(-) diff --git a/examples/helia-angular/.editorconfig b/examples/helia-angular/.editorconfig index 59d9a3a3..0d7d963a 100644 --- a/examples/helia-angular/.editorconfig +++ b/examples/helia-angular/.editorconfig @@ -1,16 +1 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false +# Editor configuration, see https://editorconfig.org \ No newline at end of file diff --git a/examples/helia-angular/README.md b/examples/helia-angular/README.md index 1f2dda7f..eafbb854 100644 --- a/examples/helia-angular/README.md +++ b/examples/helia-angular/README.md @@ -1,27 +1,137 @@ -# HeliaAngular +

+ + Helia logo + +

-This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.1.4. +

Using Helia inside an Angular.js app

-## Development server +

+ +
+ Explore the docs + · + View Demo + · + Report Bug + · + Request Feature/Example +

+ +## Table of Contents + +See https://github.com/ipfs-examples/helia-examples#table-of-contents for more information about Helia examples. + +## About The Project + +- Read the [docs](https://ipfs.github.io/helia/modules/helia.html) +- Look into other [examples](https://github.com/ipfs-examples/helia-examples) to learn how to spawn a Helia node in Node.js and in the Browser +- Visit https://dweb-primer.ipfs.io to learn about IPFS and the concepts that underpin it +- Head over to https://proto.school to take interactive tutorials that cover core IPFS APIs +- Check out https://docs.ipfs.io for tips, how-tos and more +- See https://blog.ipfs.io for news and more +- Need help? Please ask 'How do I?' questions on https://discuss.ipfs.io + +## Getting Started + +### Prerequisites + +https://github.com/ipfs-examples/helia-examples#prerequisites + +### Installation and Running example + +```console +> npm install +> ng serve +``` + +Now open your browser at `http://localhost:4200/` + +## Available Scripts + +In the project directory, you can run: + +### Development server Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. -## Code scaffolding +### Code scaffolding Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. -## Build +### Build Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. -## Running unit tests +### Running unit tests Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). -## Running end-to-end tests +### Running end-to-end tests Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. -## Further help +### Further help To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. + + +## Usage + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.1.4. integrated with `helia`. + +You can start editing the page by modifying `src/app/app.component.html`. The page auto-updates as you edit the file. + +### Learn More + +To learn more about Angular.js, take a look at the following resources: + +- [Angular.js Documentation](https://angular.dev/overview) - learn about Angular.js features. +- [Learn Angular.js](https://angular.dev/tutorials/learn-angular) - an interactive Angular.js tutorial. + +You can check out [the Angular.js GitHub repository](https://github.com/angular) - your feedback and contributions are welcome! + +### Deploy on Vercel + +The easiest way to deploy your Angular.js app is to use the [Vercel Platform](https://vercel.com/solutions/angular#the-easiest-way-to-deploy). + +Check out our [Angular.js deployment documentation](https://angular.dev/tools/cli/deployment) for more details. + +_For more examples, please refer to the [Documentation](https://github.com/ipfs-examples/helia-examples#documentation)_ + +## Documentation + +- [IPFS Primer](https://dweb-primer.ipfs.io/) +- [IPFS Docs](https://docs.ipfs.io/) +- [Tutorials](https://proto.school) +- [More examples](https://github.com/ipfs-examples/helia-examples) +- [API - Helia](https://ipfs.github.io/helia/modules/helia.html) +- [API - @helia/unixfs](https://ipfs.github.io/helia-unixfs/modules/helia.html) + +## Contributing + +Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. + +1. Fork the IPFS Project +2. Create your Feature Branch (`git checkout -b feature/amazing-feature`) +3. Commit your Changes (`git commit -a -m 'feat: add some amazing feature'`) +4. Push to the Branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +## Want to hack on IPFS? + +[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) + +The IPFS implementation in JavaScript needs your help! There are a few things you can do right now to help out: + +Read the [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md) and [JavaScript Contributing Guidelines](https://github.com/ipfs/community/blob/master/CONTRIBUTING_JS.md). + +- **Check out existing issues** The [issue list](https://github.com/ipfs/helia/issues) has many that are marked as ['help wanted'](https://github.com/ipfs/helia/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22help+wanted%22) or ['difficulty:easy'](https://github.com/ipfs/helia/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Adifficulty%3Aeasy) which make great starting points for development, many of which can be tackled with no prior IPFS knowledge +- **Look at the [Helia Roadmap](https://github.com/ipfs/helia/blob/main/ROADMAP.md)** This are the high priority items being worked on right now +- **Perform code reviews** More eyes will help + a. speed the project along + b. ensure quality, and + c. reduce possible future bugs +- **Add tests**. There can never be enough tests + + diff --git a/examples/helia-angular/src/app/app.component.css b/examples/helia-angular/src/app/app.component.css index e69de29b..a2a9beef 100644 --- a/examples/helia-angular/src/app/app.component.css +++ b/examples/helia-angular/src/app/app.component.css @@ -0,0 +1,167 @@ +:host { + --bright-blue: oklch(51.01% 0.274 263.83); + --electric-violet: oklch(53.18% 0.28 296.97); + --french-violet: oklch(47.66% 0.246 305.88); + --vivid-pink: oklch(69.02% 0.277 332.77); + --hot-red: oklch(61.42% 0.238 15.34); + --orange-red: oklch(63.32% 0.24 31.68); + + --gray-900: oklch(19.37% 0.006 300.98); + --gray-700: oklch(36.98% 0.014 302.71); + --gray-400: oklch(70.9% 0.015 304.04); + + --red-to-pink-to-purple-vertical-gradient: linear-gradient( + 180deg, + var(--orange-red) 0%, + var(--vivid-pink) 50%, + var(--electric-violet) 100% + ); + + --red-to-pink-to-purple-horizontal-gradient: linear-gradient( + 90deg, + var(--orange-red) 0%, + var(--vivid-pink) 50%, + var(--electric-violet) 100% + ); + + --pill-accent: var(--bright-blue); + + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol"; + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + h1 { + font-size: 3.125rem; + color: var(--gray-900); + font-weight: 500; + line-height: 100%; + letter-spacing: -0.125rem; + margin: 0; + font-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol"; + } + + p { + margin: 0; + color: var(--gray-700); + } + + main { + width: 100%; + min-height: 100%; + display: flex; + justify-content: center; + align-items: center; + padding: 1rem; + box-sizing: inherit; + position: relative; + } + + .angular-logo { + max-width: 9.2rem; + } + + .content { + display: flex; + justify-content: space-around; + width: 100%; + max-width: 700px; + margin-bottom: 3rem; + } + + .content h1 { + margin-top: 1.75rem; + } + + .content p { + margin-top: 1.5rem; + } + + .divider { + width: 1px; + background: var(--red-to-pink-to-purple-vertical-gradient); + margin-inline: 0.5rem; + } + + .pill-group { + display: flex; + flex-direction: column; + align-items: start; + flex-wrap: wrap; + gap: 1.25rem; + } + + .pill { + display: flex; + align-items: center; + --pill-accent: var(--bright-blue); + background: color-mix(in srgb, var(--pill-accent) 5%, transparent); + color: var(--pill-accent); + padding-inline: 0.75rem; + padding-block: 0.375rem; + border-radius: 2.75rem; + border: 0; + transition: background 0.3s ease; + font-family: var(--inter-font); + font-size: 0.875rem; + font-style: normal; + font-weight: 500; + line-height: 1.4rem; + letter-spacing: -0.00875rem; + text-decoration: none; + } + + .pill:hover { + background: color-mix(in srgb, var(--pill-accent) 15%, transparent); + } + + .pill-group .pill:nth-child(6n + 1) { + --pill-accent: var(--bright-blue); + } + .pill-group .pill:nth-child(6n + 2) { + --pill-accent: var(--french-violet); + } + .pill-group .pill:nth-child(6n + 3), + .pill-group .pill:nth-child(6n + 4), + .pill-group .pill:nth-child(6n + 5) { + --pill-accent: var(--hot-red); + } + + .pill-group svg { + margin-inline-start: 0.25rem; + } + + .social-links { + display: flex; + align-items: center; + gap: 0.73rem; + margin-top: 1.5rem; + } + + .social-links path { + transition: fill 0.3s ease; + fill: var(--gray-400); + } + + .social-links a:hover svg path { + fill: var(--gray-900); + } + + @media screen and (max-width: 650px) { + .content { + flex-direction: column; + width: max-content; + } + + .divider { + height: 1px; + width: 100%; + background: var(--red-to-pink-to-purple-horizontal-gradient); + margin-block: 1.5rem; + } + } \ No newline at end of file diff --git a/examples/helia-angular/src/app/app.component.html b/examples/helia-angular/src/app/app.component.html index 2447f5ba..0cfcc00a 100644 --- a/examples/helia-angular/src/app/app.component.html +++ b/examples/helia-angular/src/app/app.component.html @@ -7,176 +7,6 @@ - -
diff --git a/examples/helia-angular/src/styles.css b/examples/helia-angular/src/styles.css index 90d4ee00..f7a82eb4 100644 --- a/examples/helia-angular/src/styles.css +++ b/examples/helia-angular/src/styles.css @@ -1 +1,167 @@ -/* You can add global styles to this file, and also import other style files */ +:host { + --bright-blue: oklch(51.01% 0.274 263.83); + --electric-violet: oklch(53.18% 0.28 296.97); + --french-violet: oklch(47.66% 0.246 305.88); + --vivid-pink: oklch(69.02% 0.277 332.77); + --hot-red: oklch(61.42% 0.238 15.34); + --orange-red: oklch(63.32% 0.24 31.68); + + --gray-900: oklch(19.37% 0.006 300.98); + --gray-700: oklch(36.98% 0.014 302.71); + --gray-400: oklch(70.9% 0.015 304.04); + + --red-to-pink-to-purple-vertical-gradient: linear-gradient( + 180deg, + var(--orange-red) 0%, + var(--vivid-pink) 50%, + var(--electric-violet) 100% + ); + + --red-to-pink-to-purple-horizontal-gradient: linear-gradient( + 90deg, + var(--orange-red) 0%, + var(--vivid-pink) 50%, + var(--electric-violet) 100% + ); + + --pill-accent: var(--bright-blue); + + font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol"; + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + h1 { + font-size: 3.125rem; + color: var(--gray-900); + font-weight: 500; + line-height: 100%; + letter-spacing: -0.125rem; + margin: 0; + font-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol"; + } + + p { + margin: 0; + color: var(--gray-700); + } + + main { + width: 100%; + min-height: 100%; + display: flex; + justify-content: center; + align-items: center; + padding: 1rem; + box-sizing: inherit; + position: relative; + } + + .angular-logo { + max-width: 9.2rem; + } + + .content { + display: flex; + justify-content: space-around; + width: 100%; + max-width: 700px; + margin-bottom: 3rem; + } + + .content h1 { + margin-top: 1.75rem; + } + + .content p { + margin-top: 1.5rem; + } + + .divider { + width: 1px; + background: var(--red-to-pink-to-purple-vertical-gradient); + margin-inline: 0.5rem; + } + + .pill-group { + display: flex; + flex-direction: column; + align-items: start; + flex-wrap: wrap; + gap: 1.25rem; + } + + .pill { + display: flex; + align-items: center; + --pill-accent: var(--bright-blue); + background: color-mix(in srgb, var(--pill-accent) 5%, transparent); + color: var(--pill-accent); + padding-inline: 0.75rem; + padding-block: 0.375rem; + border-radius: 2.75rem; + border: 0; + transition: background 0.3s ease; + font-family: var(--inter-font); + font-size: 0.875rem; + font-style: normal; + font-weight: 500; + line-height: 1.4rem; + letter-spacing: -0.00875rem; + text-decoration: none; + } + + .pill:hover { + background: color-mix(in srgb, var(--pill-accent) 15%, transparent); + } + + .pill-group .pill:nth-child(6n + 1) { + --pill-accent: var(--bright-blue); + } + .pill-group .pill:nth-child(6n + 2) { + --pill-accent: var(--french-violet); + } + .pill-group .pill:nth-child(6n + 3), + .pill-group .pill:nth-child(6n + 4), + .pill-group .pill:nth-child(6n + 5) { + --pill-accent: var(--hot-red); + } + + .pill-group svg { + margin-inline-start: 0.25rem; + } + + .social-links { + display: flex; + align-items: center; + gap: 0.73rem; + margin-top: 1.5rem; + } + + .social-links path { + transition: fill 0.3s ease; + fill: var(--gray-400); + } + + .social-links a:hover svg path { + fill: var(--gray-900); + } + + @media screen and (max-width: 650px) { + .content { + flex-direction: column; + width: max-content; + } + + .divider { + height: 1px; + width: 100%; + background: var(--red-to-pink-to-purple-horizontal-gradient); + margin-block: 1.5rem; + } +} \ No newline at end of file From 88566b50554c2a4da0cc76016016cc090fb7ef59 Mon Sep 17 00:00:00 2001 From: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:20:53 -0500 Subject: [PATCH 3/9] fix: styling and helia structure --- examples/helia-angular/angular.json | 4 +- .../helia-angular/src/app/app.component.html | 4 +- .../src/app/app.component.spec.ts | 42 ++++++++--------- examples/helia-angular/src/app/app.config.ts | 8 ---- examples/helia-angular/src/app/app.module.ts | 33 ++++++++----- examples/helia-angular/src/app/app.routes.ts | 4 +- .../src/app/global-error-handler.ts | 9 ++-- .../helia-angular/src/app/helia.component.ts | 44 +++++++++++++++++ .../helia-angular/src/app/helia.service.ts | 47 +++++++++++++++++++ .../helia-angular/src/app/ipfs.component.ts | 34 -------------- examples/helia-angular/src/main.ts | 20 ++++++-- 11 files changed, 160 insertions(+), 89 deletions(-) delete mode 100644 examples/helia-angular/src/app/app.config.ts create mode 100644 examples/helia-angular/src/app/helia.component.ts create mode 100644 examples/helia-angular/src/app/helia.service.ts delete mode 100644 examples/helia-angular/src/app/ipfs.component.ts diff --git a/examples/helia-angular/angular.json b/examples/helia-angular/angular.json index 76d5e0c4..37324f6e 100644 --- a/examples/helia-angular/angular.json +++ b/examples/helia-angular/angular.json @@ -36,8 +36,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "500kB", - "maximumError": "1MB" + "maximumWarning": "1MB", + "maximumError": "1.5MB" }, { "type": "anyComponentStyle", diff --git a/examples/helia-angular/src/app/app.component.html b/examples/helia-angular/src/app/app.component.html index 0cfcc00a..1fc82a84 100644 --- a/examples/helia-angular/src/app/app.component.html +++ b/examples/helia-angular/src/app/app.component.html @@ -58,13 +58,13 @@

Hello, {{ title }}

Congratulations! Your app is running. 🎉

- @if(!helia || !id){ + @if(!id){

Starting Helia...

} - @if(helia && id){ + @if(id){

ID: {{ id }}

Status: {{ isOnline ? 'Online' : 'Offline' }}

diff --git a/examples/helia-angular/src/app/app.component.spec.ts b/examples/helia-angular/src/app/app.component.spec.ts index 14bbad5e..a5562f41 100644 --- a/examples/helia-angular/src/app/app.component.spec.ts +++ b/examples/helia-angular/src/app/app.component.spec.ts @@ -1,29 +1,29 @@ -import { TestBed } from '@angular/core/testing'; -import { IPFSComponent } from './ipfs.component'; +import { TestBed } from '@angular/core/testing' +import { HeliaComponent } from './helia.component' -describe('IPFSComponent', () => { +describe('HeliaComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [IPFSComponent], - }).compileComponents(); - }); + imports: [HeliaComponent] + }).compileComponents() + }) it('should create the app', () => { - const fixture = TestBed.createComponent(IPFSComponent); - const app = fixture.componentInstance; - expect(app).toBeTruthy(); - }); + const fixture = TestBed.createComponent(HeliaComponent) + const app = fixture.componentInstance + expect(app).toBeTruthy() + }) - it(`should have the 'helia-angular' title`, () => { - const fixture = TestBed.createComponent(IPFSComponent); - const app = fixture.componentInstance; - expect(app.title).toEqual('helia-angular'); - }); + it('should have the \'helia-angular\' title', () => { + const fixture = TestBed.createComponent(HeliaComponent) + const app = fixture.componentInstance + expect(app.title).toEqual('helia-angular') + }) it('should render title', () => { - const fixture = TestBed.createComponent(IPFSComponent); - fixture.detectChanges(); - const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('h1')?.textContent).toContain('Hello, helia-angular'); - }); -}); + const fixture = TestBed.createComponent(HeliaComponent) + fixture.detectChanges() + const compiled = fixture.nativeElement as HTMLElement + expect(compiled.querySelector('h1')?.textContent).toContain('Hello, helia-angular') + }) +}) diff --git a/examples/helia-angular/src/app/app.config.ts b/examples/helia-angular/src/app/app.config.ts deleted file mode 100644 index a1e7d6f8..00000000 --- a/examples/helia-angular/src/app/app.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; -import { provideRouter } from '@angular/router'; - -import { routes } from './app.routes'; - -export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)] -}; diff --git a/examples/helia-angular/src/app/app.module.ts b/examples/helia-angular/src/app/app.module.ts index 10339898..573af158 100644 --- a/examples/helia-angular/src/app/app.module.ts +++ b/examples/helia-angular/src/app/app.module.ts @@ -1,22 +1,33 @@ -import { NgModule } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { GlobalErrorHandler } from './global-error-handler'; -import { ErrorHandler } from '@angular/core'; -import { IPFSComponent } from './ipfs.component'; +import { NgModule, ErrorHandler } from '@angular/core' +import { FormsModule, ReactiveFormsModule } from '@angular/forms' +import { BrowserModule } from '@angular/platform-browser' +import { BrowserAnimationsModule } from '@angular/platform-browser/animations' +import { provideRouter, RouterOutlet } from '@angular/router' +import { routes } from './app.routes' +import { GlobalErrorHandler } from './global-error-handler' +import { HeliaComponent } from './helia.component' +import { HeliaService } from './helia.service' @NgModule({ declarations: [ - IPFSComponent + HeliaComponent ], imports: [ BrowserModule, FormsModule, ReactiveFormsModule, BrowserAnimationsModule, + RouterOutlet ], - providers: [{ provide: ErrorHandler, useClass: GlobalErrorHandler }], - bootstrap: [IPFSComponent] + providers: [ + { + provide: ErrorHandler, + useClass: GlobalErrorHandler + }, + HeliaService, + provideRouter(routes) + ], + bootstrap: [HeliaComponent] + // bootstrap: [] }) -export class AppModule { } \ No newline at end of file +export class AppModule { } diff --git a/examples/helia-angular/src/app/app.routes.ts b/examples/helia-angular/src/app/app.routes.ts index dc39edb5..51abde6c 100644 --- a/examples/helia-angular/src/app/app.routes.ts +++ b/examples/helia-angular/src/app/app.routes.ts @@ -1,3 +1,3 @@ -import { Routes } from '@angular/router'; +import { type Routes } from '@angular/router' -export const routes: Routes = []; +export const routes: Routes = [] diff --git a/examples/helia-angular/src/app/global-error-handler.ts b/examples/helia-angular/src/app/global-error-handler.ts index 805070bd..7124d54c 100644 --- a/examples/helia-angular/src/app/global-error-handler.ts +++ b/examples/helia-angular/src/app/global-error-handler.ts @@ -1,7 +1,8 @@ -import { ErrorHandler } from '@angular/core'; +import { type ErrorHandler } from '@angular/core' export class GlobalErrorHandler implements ErrorHandler { - handleError(error: any): void { - console.error('Global Error Handler:', error); + handleError (error: any): void { + // eslint-disable-next-line no-console + console.error('Global Error Handler:', error) } -} \ No newline at end of file +} diff --git a/examples/helia-angular/src/app/helia.component.ts b/examples/helia-angular/src/app/helia.component.ts new file mode 100644 index 00000000..618adc86 --- /dev/null +++ b/examples/helia-angular/src/app/helia.component.ts @@ -0,0 +1,44 @@ +import { ChangeDetectorRef, Component, Inject, type OnDestroy, type OnInit } from '@angular/core' +import { Subscription } from 'rxjs' +// The HeliaService is injected, and eslint can't tell that it needs to be referenced as a Class. +// eslint-disable-next-line @typescript-eslint/consistent-type-imports +import { HeliaService } from './helia.service' + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrl: './app.component.css' +}) + +export class HeliaComponent implements OnInit, OnDestroy { + title = 'helia-angular' + id: string | null = null + isOnline: boolean = false + private readonly subscriptions: Subscription = new Subscription() + + constructor (private readonly HeliaService: HeliaService, @Inject(ChangeDetectorRef) private readonly cdr: ChangeDetectorRef) {} + + ngOnInit (): void { + this.subscriptions.add( + this.HeliaService.getId()?.subscribe((newId) => { + if (this.id !== newId) { + this.id = newId + this.cdr.detectChanges() + } + }) + ) + + this.subscriptions.add( + this.HeliaService.isOnline().subscribe(newIsOnline => { + if (this.isOnline !== newIsOnline) { + this.isOnline = newIsOnline + this.cdr.detectChanges() + } + }) + ) + } + + ngOnDestroy (): void { + this.subscriptions.unsubscribe() + } +} diff --git a/examples/helia-angular/src/app/helia.service.ts b/examples/helia-angular/src/app/helia.service.ts new file mode 100644 index 00000000..4f641e2b --- /dev/null +++ b/examples/helia-angular/src/app/helia.service.ts @@ -0,0 +1,47 @@ +// services/globalsetting.service.ts +import { Injectable, NgZone } from '@angular/core' +import { createHelia, type HeliaLibp2p } from 'helia' +import { BehaviorSubject, type Observable } from 'rxjs' + +@Injectable({ + providedIn: 'root' +}) +export class HeliaService extends NgZone { + private readonly idSubject = new BehaviorSubject(null) + private readonly isOnlineSubject = new BehaviorSubject(false) + + title = 'helia-angular' + helia: HeliaLibp2p | null = null + + constructor () { + super({ enableLongStackTrace: true }) + + this.runOutsideAngular(async () => { + await this.initializeHelia() + }).catch((err) => { + // eslint-disable-next-line no-console + console.error('Error initializing Helia:', err) + }) + } + + async initializeHelia (): Promise { + if (this.helia != null) return + + try { + this.helia = await createHelia() + this.idSubject.next(this.helia?.libp2p.peerId.toString() ?? null) + this.isOnlineSubject.next(this.helia?.libp2p.status === 'started') + } catch (err) { + // eslint-disable-next-line no-console + console.error('Error initializing Helia:', err) + } + } + + getId (): Observable { + return this.idSubject.asObservable() + } + + isOnline (): Observable { + return this.isOnlineSubject.asObservable() + } +} diff --git a/examples/helia-angular/src/app/ipfs.component.ts b/examples/helia-angular/src/app/ipfs.component.ts deleted file mode 100644 index 04f4578d..00000000 --- a/examples/helia-angular/src/app/ipfs.component.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; -import { createHelia } from 'helia'; - -@Component({ - selector: 'app-root', - standalone: true, - imports: [RouterOutlet], - templateUrl: './app.component.html', - styleUrl: './app.component.css' -}) - -export class IPFSComponent implements OnInit { - title = 'helia-angular'; - id: string | null = null; - isOnline = false; - helia: any = null; - - async ngOnInit() { - if (this.helia) return; - - try { - const heliaNode = await createHelia(); - const nodeId = heliaNode.libp2p.peerId.toString(); - const nodeIsOnline = heliaNode.libp2p.status === 'started'; - - this.helia = heliaNode; - this.id = nodeId; - this.isOnline = nodeIsOnline; - } catch (error) { - console.error('Error initializing Helia:', error); - } - } -} diff --git a/examples/helia-angular/src/main.ts b/examples/helia-angular/src/main.ts index e4d4387b..0968ebf3 100644 --- a/examples/helia-angular/src/main.ts +++ b/examples/helia-angular/src/main.ts @@ -1,6 +1,16 @@ -import { bootstrapApplication } from '@angular/platform-browser'; -import { appConfig } from './app/app.config'; -import { IPFSComponent } from './app/ipfs.component'; +import { enableProdMode } from '@angular/core' +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' +import { AppModule } from './app/app.module' -bootstrapApplication(IPFSComponent, appConfig) - .catch((err) => console.error(err)); +if (process.env['NODE_ENV'] === 'production') { + enableProdMode() +} + +localStorage.setItem('debug', '*,*:trace') +platformBrowserDynamic().bootstrapModule(AppModule) + // eslint-disable-next-line no-console + .catch((err: any) => { console.log(err) }) + +// bootstrapApplication(IPFSComponent, appConfig) +// // eslint-disable-next-line no-console +// .catch((err) => { console.error(err) }) From 6d9d347e7ee321ca4e3543a0d0d7de21e7d0be0b Mon Sep 17 00:00:00 2001 From: Paschal okwuosa Date: Wed, 27 Nov 2024 23:19:37 +0100 Subject: [PATCH 4/9] feat:Helia Angular Example updated --- examples/helia-angular/.editorconfig | 17 +- examples/helia-angular/README.md | 4 +- examples/helia-angular/angular.json | 14 +- examples/helia-angular/package.json | 4 +- .../helia-angular/src/app/app.component.css | 43 +++++ .../helia-angular/src/app/app.component.html | 40 +++-- .../src/app/app.component.spec.ts | 42 ++--- .../helia-angular/src/app/app.component.ts | 32 ++++ examples/helia-angular/src/app/app.config.ts | 5 + examples/helia-angular/src/app/app.module.ts | 33 ---- examples/helia-angular/src/app/app.routes.ts | 3 - .../src/app/global-error-handler.ts | 8 - .../helia-angular/src/app/helia.component.ts | 44 ----- .../helia-angular/src/app/helia.service.ts | 47 ----- examples/helia-angular/src/index.html | 2 +- examples/helia-angular/src/main.ts | 20 +-- examples/helia-angular/src/styles.css | 168 +----------------- 17 files changed, 164 insertions(+), 362 deletions(-) create mode 100644 examples/helia-angular/src/app/app.component.ts create mode 100644 examples/helia-angular/src/app/app.config.ts delete mode 100644 examples/helia-angular/src/app/app.module.ts delete mode 100644 examples/helia-angular/src/app/app.routes.ts delete mode 100644 examples/helia-angular/src/app/global-error-handler.ts delete mode 100644 examples/helia-angular/src/app/helia.component.ts delete mode 100644 examples/helia-angular/src/app/helia.service.ts diff --git a/examples/helia-angular/.editorconfig b/examples/helia-angular/.editorconfig index 0d7d963a..59d9a3a3 100644 --- a/examples/helia-angular/.editorconfig +++ b/examples/helia-angular/.editorconfig @@ -1 +1,16 @@ -# Editor configuration, see https://editorconfig.org \ No newline at end of file +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/examples/helia-angular/README.md b/examples/helia-angular/README.md index eafbb854..f280c6d9 100644 --- a/examples/helia-angular/README.md +++ b/examples/helia-angular/README.md @@ -132,6 +132,4 @@ Read the [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of a. speed the project along b. ensure quality, and c. reduce possible future bugs -- **Add tests**. There can never be enough tests - - +- **Add tests**. There can never be enough tests \ No newline at end of file diff --git a/examples/helia-angular/angular.json b/examples/helia-angular/angular.json index 37324f6e..b2ef3377 100644 --- a/examples/helia-angular/angular.json +++ b/examples/helia-angular/angular.json @@ -3,7 +3,7 @@ "version": 1, "newProjectRoot": "projects", "projects": { - "helia-angular": { + "helia-angular-test": { "projectType": "application", "schematics": {}, "root": "", @@ -13,7 +13,7 @@ "build": { "builder": "@angular-devkit/build-angular:application", "options": { - "outputPath": "dist/helia-angular", + "outputPath": "dist/helia-angular-test", "index": "src/index.html", "browser": "src/main.ts", "polyfills": [ @@ -36,8 +36,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "1MB", - "maximumError": "1.5MB" + "maximumWarning": "500kB", + "maximumError": "1MB" }, { "type": "anyComponentStyle", @@ -59,10 +59,10 @@ "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { - "buildTarget": "helia-angular:build:production" + "buildTarget": "helia-angular-test:build:production" }, "development": { - "buildTarget": "helia-angular:build:development" + "buildTarget": "helia-angular-test:build:development" } }, "defaultConfiguration": "development" @@ -94,6 +94,6 @@ } }, "cli": { - "analytics": false + "analytics": "e3eb56d9-43f2-4cf0-b79b-84db083a6781" } } diff --git a/examples/helia-angular/package.json b/examples/helia-angular/package.json index 93f9a4cc..34184b33 100644 --- a/examples/helia-angular/package.json +++ b/examples/helia-angular/package.json @@ -1,5 +1,5 @@ { - "name": "helia-angular", + "name": "helia-angular-test", "version": "0.0.0", "scripts": { "ng": "ng", @@ -18,7 +18,7 @@ "@angular/platform-browser": "^18.1.0", "@angular/platform-browser-dynamic": "^18.1.0", "@angular/router": "^18.1.0", - "helia": "^4.2.5", + "helia": "^5.1.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.3" diff --git a/examples/helia-angular/src/app/app.component.css b/examples/helia-angular/src/app/app.component.css index a2a9beef..b4624c16 100644 --- a/examples/helia-angular/src/app/app.component.css +++ b/examples/helia-angular/src/app/app.component.css @@ -152,6 +152,49 @@ fill: var(--gray-900); } + .card { + margin: 1rem; + padding: 1.5rem; + text-align: left; + color: inherit; + text-decoration: none; + border: 1px solid #eaeaea; + border-radius: 10px; + transition: color 0.15s ease, border-color 0.15s ease; + width: 45%; + } + + .card:hover, + .card:focus, + .card:active { + color: var(--bright-blue); + border-color: var(--bright-blue); + } + + .card h2 { + margin: 0 0 1rem 0; + font-size: 1.5rem; + } + + .card p { + margin: 0; + font-size: 1.25rem; + line-height: 1.5; + } + + .logo { + height: 1em; + margin-left: 0.5rem; + } + + @media (max-width: 600px) { + .grid { + width: 100%; + flex-direction: column; + } + } + + @media screen and (max-width: 650px) { .content { flex-direction: column; diff --git a/examples/helia-angular/src/app/app.component.html b/examples/helia-angular/src/app/app.component.html index 1fc82a84..626d1702 100644 --- a/examples/helia-angular/src/app/app.component.html +++ b/examples/helia-angular/src/app/app.component.html @@ -57,19 +57,40 @@

Hello, {{ title }}

Congratulations! Your app is running. 🎉

- - @if(!id){ -
+ +
+

Starting Helia...

- } - - @if(id){ -
+ +

ID: {{ id }}

-

Status: {{ isOnline ? 'Online' : 'Offline' }}

+

Status: {{ id ? 'Online' : 'Offline' }}

+
+
+ + - }
@@ -176,4 +197,3 @@

Status: {{ isOnline ? 'Online' : 'Offline' }}

- diff --git a/examples/helia-angular/src/app/app.component.spec.ts b/examples/helia-angular/src/app/app.component.spec.ts index a5562f41..a9ea7a26 100644 --- a/examples/helia-angular/src/app/app.component.spec.ts +++ b/examples/helia-angular/src/app/app.component.spec.ts @@ -1,29 +1,29 @@ -import { TestBed } from '@angular/core/testing' -import { HeliaComponent } from './helia.component' +import { TestBed } from '@angular/core/testing'; +import { AppComponent } from './app.component'; -describe('HeliaComponent', () => { +describe('AppComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [HeliaComponent] - }).compileComponents() - }) + imports: [AppComponent], + }).compileComponents(); + }); it('should create the app', () => { - const fixture = TestBed.createComponent(HeliaComponent) - const app = fixture.componentInstance - expect(app).toBeTruthy() - }) + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); - it('should have the \'helia-angular\' title', () => { - const fixture = TestBed.createComponent(HeliaComponent) - const app = fixture.componentInstance - expect(app.title).toEqual('helia-angular') - }) + it(`should have the 'helia-angular-test' title`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app.title).toEqual('helia-angular-test'); + }); it('should render title', () => { - const fixture = TestBed.createComponent(HeliaComponent) - fixture.detectChanges() - const compiled = fixture.nativeElement as HTMLElement - expect(compiled.querySelector('h1')?.textContent).toContain('Hello, helia-angular') - }) -}) + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('h1')?.textContent).toContain('Hello, helia-angular-test'); + }); +}); diff --git a/examples/helia-angular/src/app/app.component.ts b/examples/helia-angular/src/app/app.component.ts new file mode 100644 index 00000000..072a57c7 --- /dev/null +++ b/examples/helia-angular/src/app/app.component.ts @@ -0,0 +1,32 @@ +import { Component, OnInit } from '@angular/core'; +import { createHelia } from 'helia'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [CommonModule], + templateUrl: './app.component.html', + styleUrl: './app.component.css' +}) + +export class AppComponent implements OnInit { + title = 'helia-angular-example'; + helia: any | null = null; + id: string | null = null; + isOnline: boolean = false; + + async ngOnInit() { + try { + this.helia = await createHelia(); + console.log('Helia initialized:', this.helia); + + // Fetch the node's ID + const peerId = await this.helia.libp2p.peerId; + this.id = peerId.toString(); + + } catch (error) { + console.error('Error initializing Helia:', error); + } + } +} diff --git a/examples/helia-angular/src/app/app.config.ts b/examples/helia-angular/src/app/app.config.ts new file mode 100644 index 00000000..d03bbbcf --- /dev/null +++ b/examples/helia-angular/src/app/app.config.ts @@ -0,0 +1,5 @@ +import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; + +export const appConfig: ApplicationConfig = { + providers: [provideZoneChangeDetection({ eventCoalescing: true })] +}; diff --git a/examples/helia-angular/src/app/app.module.ts b/examples/helia-angular/src/app/app.module.ts deleted file mode 100644 index 573af158..00000000 --- a/examples/helia-angular/src/app/app.module.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { NgModule, ErrorHandler } from '@angular/core' -import { FormsModule, ReactiveFormsModule } from '@angular/forms' -import { BrowserModule } from '@angular/platform-browser' -import { BrowserAnimationsModule } from '@angular/platform-browser/animations' -import { provideRouter, RouterOutlet } from '@angular/router' -import { routes } from './app.routes' -import { GlobalErrorHandler } from './global-error-handler' -import { HeliaComponent } from './helia.component' -import { HeliaService } from './helia.service' - -@NgModule({ - declarations: [ - HeliaComponent - ], - imports: [ - BrowserModule, - FormsModule, - ReactiveFormsModule, - BrowserAnimationsModule, - RouterOutlet - ], - providers: [ - { - provide: ErrorHandler, - useClass: GlobalErrorHandler - }, - HeliaService, - provideRouter(routes) - ], - bootstrap: [HeliaComponent] - // bootstrap: [] -}) -export class AppModule { } diff --git a/examples/helia-angular/src/app/app.routes.ts b/examples/helia-angular/src/app/app.routes.ts deleted file mode 100644 index 51abde6c..00000000 --- a/examples/helia-angular/src/app/app.routes.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { type Routes } from '@angular/router' - -export const routes: Routes = [] diff --git a/examples/helia-angular/src/app/global-error-handler.ts b/examples/helia-angular/src/app/global-error-handler.ts deleted file mode 100644 index 7124d54c..00000000 --- a/examples/helia-angular/src/app/global-error-handler.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { type ErrorHandler } from '@angular/core' - -export class GlobalErrorHandler implements ErrorHandler { - handleError (error: any): void { - // eslint-disable-next-line no-console - console.error('Global Error Handler:', error) - } -} diff --git a/examples/helia-angular/src/app/helia.component.ts b/examples/helia-angular/src/app/helia.component.ts deleted file mode 100644 index 618adc86..00000000 --- a/examples/helia-angular/src/app/helia.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ChangeDetectorRef, Component, Inject, type OnDestroy, type OnInit } from '@angular/core' -import { Subscription } from 'rxjs' -// The HeliaService is injected, and eslint can't tell that it needs to be referenced as a Class. -// eslint-disable-next-line @typescript-eslint/consistent-type-imports -import { HeliaService } from './helia.service' - -@Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrl: './app.component.css' -}) - -export class HeliaComponent implements OnInit, OnDestroy { - title = 'helia-angular' - id: string | null = null - isOnline: boolean = false - private readonly subscriptions: Subscription = new Subscription() - - constructor (private readonly HeliaService: HeliaService, @Inject(ChangeDetectorRef) private readonly cdr: ChangeDetectorRef) {} - - ngOnInit (): void { - this.subscriptions.add( - this.HeliaService.getId()?.subscribe((newId) => { - if (this.id !== newId) { - this.id = newId - this.cdr.detectChanges() - } - }) - ) - - this.subscriptions.add( - this.HeliaService.isOnline().subscribe(newIsOnline => { - if (this.isOnline !== newIsOnline) { - this.isOnline = newIsOnline - this.cdr.detectChanges() - } - }) - ) - } - - ngOnDestroy (): void { - this.subscriptions.unsubscribe() - } -} diff --git a/examples/helia-angular/src/app/helia.service.ts b/examples/helia-angular/src/app/helia.service.ts deleted file mode 100644 index 4f641e2b..00000000 --- a/examples/helia-angular/src/app/helia.service.ts +++ /dev/null @@ -1,47 +0,0 @@ -// services/globalsetting.service.ts -import { Injectable, NgZone } from '@angular/core' -import { createHelia, type HeliaLibp2p } from 'helia' -import { BehaviorSubject, type Observable } from 'rxjs' - -@Injectable({ - providedIn: 'root' -}) -export class HeliaService extends NgZone { - private readonly idSubject = new BehaviorSubject(null) - private readonly isOnlineSubject = new BehaviorSubject(false) - - title = 'helia-angular' - helia: HeliaLibp2p | null = null - - constructor () { - super({ enableLongStackTrace: true }) - - this.runOutsideAngular(async () => { - await this.initializeHelia() - }).catch((err) => { - // eslint-disable-next-line no-console - console.error('Error initializing Helia:', err) - }) - } - - async initializeHelia (): Promise { - if (this.helia != null) return - - try { - this.helia = await createHelia() - this.idSubject.next(this.helia?.libp2p.peerId.toString() ?? null) - this.isOnlineSubject.next(this.helia?.libp2p.status === 'started') - } catch (err) { - // eslint-disable-next-line no-console - console.error('Error initializing Helia:', err) - } - } - - getId (): Observable { - return this.idSubject.asObservable() - } - - isOnline (): Observable { - return this.isOnlineSubject.asObservable() - } -} diff --git a/examples/helia-angular/src/index.html b/examples/helia-angular/src/index.html index 0bdac97b..797ca765 100644 --- a/examples/helia-angular/src/index.html +++ b/examples/helia-angular/src/index.html @@ -2,7 +2,7 @@ - HeliaAngular + HeliaAngularExample diff --git a/examples/helia-angular/src/main.ts b/examples/helia-angular/src/main.ts index 0968ebf3..35b00f34 100644 --- a/examples/helia-angular/src/main.ts +++ b/examples/helia-angular/src/main.ts @@ -1,16 +1,6 @@ -import { enableProdMode } from '@angular/core' -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic' -import { AppModule } from './app/app.module' +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { AppComponent } from './app/app.component'; -if (process.env['NODE_ENV'] === 'production') { - enableProdMode() -} - -localStorage.setItem('debug', '*,*:trace') -platformBrowserDynamic().bootstrapModule(AppModule) - // eslint-disable-next-line no-console - .catch((err: any) => { console.log(err) }) - -// bootstrapApplication(IPFSComponent, appConfig) -// // eslint-disable-next-line no-console -// .catch((err) => { console.error(err) }) +bootstrapApplication(AppComponent, appConfig) + .catch((err) => console.error(err)); diff --git a/examples/helia-angular/src/styles.css b/examples/helia-angular/src/styles.css index f7a82eb4..90d4ee00 100644 --- a/examples/helia-angular/src/styles.css +++ b/examples/helia-angular/src/styles.css @@ -1,167 +1 @@ -:host { - --bright-blue: oklch(51.01% 0.274 263.83); - --electric-violet: oklch(53.18% 0.28 296.97); - --french-violet: oklch(47.66% 0.246 305.88); - --vivid-pink: oklch(69.02% 0.277 332.77); - --hot-red: oklch(61.42% 0.238 15.34); - --orange-red: oklch(63.32% 0.24 31.68); - - --gray-900: oklch(19.37% 0.006 300.98); - --gray-700: oklch(36.98% 0.014 302.71); - --gray-400: oklch(70.9% 0.015 304.04); - - --red-to-pink-to-purple-vertical-gradient: linear-gradient( - 180deg, - var(--orange-red) 0%, - var(--vivid-pink) 50%, - var(--electric-violet) 100% - ); - - --red-to-pink-to-purple-horizontal-gradient: linear-gradient( - 90deg, - var(--orange-red) 0%, - var(--vivid-pink) 50%, - var(--electric-violet) 100% - ); - - --pill-accent: var(--bright-blue); - - font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, - Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", - "Segoe UI Symbol"; - box-sizing: border-box; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - } - - h1 { - font-size: 3.125rem; - color: var(--gray-900); - font-weight: 500; - line-height: 100%; - letter-spacing: -0.125rem; - margin: 0; - font-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, - Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", - "Segoe UI Symbol"; - } - - p { - margin: 0; - color: var(--gray-700); - } - - main { - width: 100%; - min-height: 100%; - display: flex; - justify-content: center; - align-items: center; - padding: 1rem; - box-sizing: inherit; - position: relative; - } - - .angular-logo { - max-width: 9.2rem; - } - - .content { - display: flex; - justify-content: space-around; - width: 100%; - max-width: 700px; - margin-bottom: 3rem; - } - - .content h1 { - margin-top: 1.75rem; - } - - .content p { - margin-top: 1.5rem; - } - - .divider { - width: 1px; - background: var(--red-to-pink-to-purple-vertical-gradient); - margin-inline: 0.5rem; - } - - .pill-group { - display: flex; - flex-direction: column; - align-items: start; - flex-wrap: wrap; - gap: 1.25rem; - } - - .pill { - display: flex; - align-items: center; - --pill-accent: var(--bright-blue); - background: color-mix(in srgb, var(--pill-accent) 5%, transparent); - color: var(--pill-accent); - padding-inline: 0.75rem; - padding-block: 0.375rem; - border-radius: 2.75rem; - border: 0; - transition: background 0.3s ease; - font-family: var(--inter-font); - font-size: 0.875rem; - font-style: normal; - font-weight: 500; - line-height: 1.4rem; - letter-spacing: -0.00875rem; - text-decoration: none; - } - - .pill:hover { - background: color-mix(in srgb, var(--pill-accent) 15%, transparent); - } - - .pill-group .pill:nth-child(6n + 1) { - --pill-accent: var(--bright-blue); - } - .pill-group .pill:nth-child(6n + 2) { - --pill-accent: var(--french-violet); - } - .pill-group .pill:nth-child(6n + 3), - .pill-group .pill:nth-child(6n + 4), - .pill-group .pill:nth-child(6n + 5) { - --pill-accent: var(--hot-red); - } - - .pill-group svg { - margin-inline-start: 0.25rem; - } - - .social-links { - display: flex; - align-items: center; - gap: 0.73rem; - margin-top: 1.5rem; - } - - .social-links path { - transition: fill 0.3s ease; - fill: var(--gray-400); - } - - .social-links a:hover svg path { - fill: var(--gray-900); - } - - @media screen and (max-width: 650px) { - .content { - flex-direction: column; - width: max-content; - } - - .divider { - height: 1px; - width: 100%; - background: var(--red-to-pink-to-purple-horizontal-gradient); - margin-block: 1.5rem; - } -} \ No newline at end of file +/* You can add global styles to this file, and also import other style files */ From edb9c29d85f8037858e67211fd38bc7413c398aa Mon Sep 17 00:00:00 2001 From: Paschal okwuosa Date: Wed, 27 Nov 2024 23:23:20 +0100 Subject: [PATCH 5/9] feat:Helia Angular Example updated --- examples/helia-angular/.editorconfig | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 examples/helia-angular/.editorconfig diff --git a/examples/helia-angular/.editorconfig b/examples/helia-angular/.editorconfig deleted file mode 100644 index 59d9a3a3..00000000 --- a/examples/helia-angular/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false From 7ffba95cda026a7d4955749b44381518acac61eb Mon Sep 17 00:00:00 2001 From: Paschal okwuosa Date: Wed, 27 Nov 2024 23:27:20 +0100 Subject: [PATCH 6/9] feat:Helia Angular Example updated --- examples/helia-angular/src/styles.css | 63 ++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/examples/helia-angular/src/styles.css b/examples/helia-angular/src/styles.css index 90d4ee00..600a99e8 100644 --- a/examples/helia-angular/src/styles.css +++ b/examples/helia-angular/src/styles.css @@ -1 +1,62 @@ -/* You can add global styles to this file, and also import other style files */ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; + } + + a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; + } + a:hover { + color: #535bf2; + } + + + h1 { + font-size: 3.2em; + line-height: 1.1; + } + + button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; + } + button:hover { + border-color: #646cff; + } + button:focus, + button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; + } + + @media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } + } From 1ea924772abf7fad2bcc0e0c7a855958721637ad Mon Sep 17 00:00:00 2001 From: Paschal okwuosa Date: Mon, 2 Dec 2024 22:33:41 +0100 Subject: [PATCH 7/9] feat:Helia Angular Example updated --- examples/helia-angular/.editorconfig | 16 ++++++ examples/helia-angular/angular.json | 17 ++++-- examples/helia-angular/package.json | 10 +++- examples/helia-angular/server.ts | 57 +++++++++++++++++++ .../helia-angular/src/app/app.component.css | 46 +-------------- .../helia-angular/src/app/app.component.html | 2 +- .../src/app/app.component.spec.ts | 18 +++--- .../helia-angular/src/app/app.component.ts | 32 ----------- .../src/app/app.config.server.ts | 11 ++++ examples/helia-angular/src/app/app.config.ts | 6 +- examples/helia-angular/src/app/app.routes.ts | 3 + .../helia-angular/src/app/helia.component.ts | 35 ++++++++++++ examples/helia-angular/src/main.server.ts | 7 +++ examples/helia-angular/src/main.ts | 4 +- examples/helia-angular/src/styles.css | 1 + examples/helia-angular/tsconfig.app.json | 8 ++- 16 files changed, 174 insertions(+), 99 deletions(-) create mode 100644 examples/helia-angular/.editorconfig create mode 100644 examples/helia-angular/server.ts delete mode 100644 examples/helia-angular/src/app/app.component.ts create mode 100644 examples/helia-angular/src/app/app.config.server.ts create mode 100644 examples/helia-angular/src/app/app.routes.ts create mode 100644 examples/helia-angular/src/app/helia.component.ts create mode 100644 examples/helia-angular/src/main.server.ts diff --git a/examples/helia-angular/.editorconfig b/examples/helia-angular/.editorconfig new file mode 100644 index 00000000..59d9a3a3 --- /dev/null +++ b/examples/helia-angular/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/examples/helia-angular/angular.json b/examples/helia-angular/angular.json index b2ef3377..c3e1e60d 100644 --- a/examples/helia-angular/angular.json +++ b/examples/helia-angular/angular.json @@ -3,7 +3,7 @@ "version": 1, "newProjectRoot": "projects", "projects": { - "helia-angular-test": { + "helia-angular": { "projectType": "application", "schematics": {}, "root": "", @@ -13,7 +13,7 @@ "build": { "builder": "@angular-devkit/build-angular:application", "options": { - "outputPath": "dist/helia-angular-test", + "outputPath": "dist/helia-angular", "index": "src/index.html", "browser": "src/main.ts", "polyfills": [ @@ -29,7 +29,12 @@ "styles": [ "src/styles.css" ], - "scripts": [] + "scripts": [], + "server": "src/main.server.ts", + "prerender": true, + "ssr": { + "entry": "server.ts" + } }, "configurations": { "production": { @@ -59,10 +64,10 @@ "builder": "@angular-devkit/build-angular:dev-server", "configurations": { "production": { - "buildTarget": "helia-angular-test:build:production" + "buildTarget": "helia-angular:build:production" }, "development": { - "buildTarget": "helia-angular-test:build:development" + "buildTarget": "helia-angular:build:development" } }, "defaultConfiguration": "development" @@ -94,6 +99,6 @@ } }, "cli": { - "analytics": "e3eb56d9-43f2-4cf0-b79b-84db083a6781" + "analytics": "d305ee0c-891f-4040-af7e-020df473e208" } } diff --git a/examples/helia-angular/package.json b/examples/helia-angular/package.json index 34184b33..29b45155 100644 --- a/examples/helia-angular/package.json +++ b/examples/helia-angular/package.json @@ -1,12 +1,13 @@ { - "name": "helia-angular-test", + "name": "helia-angular", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "watch": "ng build --watch --configuration development", - "test": "ng test" + "test": "ng test", + "serve:ssr:helia-angular": "node dist/helia-angular/server/server.mjs" }, "private": true, "dependencies": { @@ -17,7 +18,10 @@ "@angular/forms": "^18.1.0", "@angular/platform-browser": "^18.1.0", "@angular/platform-browser-dynamic": "^18.1.0", + "@angular/platform-server": "^18.1.0", "@angular/router": "^18.1.0", + "@angular/ssr": "^18.1.4", + "express": "^4.18.2", "helia": "^5.1.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", @@ -27,7 +31,9 @@ "@angular-devkit/build-angular": "^18.1.4", "@angular/cli": "^18.1.4", "@angular/compiler-cli": "^18.1.0", + "@types/express": "^4.17.17", "@types/jasmine": "~5.1.0", + "@types/node": "^18.18.0", "jasmine-core": "~5.1.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", diff --git a/examples/helia-angular/server.ts b/examples/helia-angular/server.ts new file mode 100644 index 00000000..1a0df5e0 --- /dev/null +++ b/examples/helia-angular/server.ts @@ -0,0 +1,57 @@ +import { APP_BASE_HREF } from '@angular/common'; +import { CommonEngine } from '@angular/ssr'; +import express from 'express'; +import { fileURLToPath } from 'node:url'; +import { dirname, join, resolve } from 'node:path'; +import bootstrap from './src/main.server'; + +// The Express app is exported so that it can be used by serverless Functions. +export function app(): express.Express { + const server = express(); + const serverDistFolder = dirname(fileURLToPath(import.meta.url)); + const browserDistFolder = resolve(serverDistFolder, '../browser'); + const indexHtml = join(serverDistFolder, 'index.server.html'); + + const commonEngine = new CommonEngine(); + + server.set('view engine', 'html'); + server.set('views', browserDistFolder); + + // Example Express Rest API endpoints + // server.get('/api/**', (req, res) => { }); + // Serve static files from /browser + server.get('**', express.static(browserDistFolder, { + maxAge: '1y', + index: 'index.html', + })); + + // All regular routes use the Angular engine + server.get('**', (req, res, next) => { + const { protocol, originalUrl, baseUrl, headers } = req; + + commonEngine + .render({ + bootstrap, + documentFilePath: indexHtml, + url: `${protocol}://${headers.host}${originalUrl}`, + publicPath: browserDistFolder, + providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }], + }) + .then((html) => res.send(html)) + .catch((err) => next(err)); + }); + + return server; +} + +function run(): void { + const port = process.env['PORT'] || 4000; + + // Start up the Node server + const server = app(); + server.listen(port, () => { + console.log(`Node Express server listening on http://localhost:${port}`); + }); +} + +run(); diff --git a/examples/helia-angular/src/app/app.component.css b/examples/helia-angular/src/app/app.component.css index b4624c16..94782ef6 100644 --- a/examples/helia-angular/src/app/app.component.css +++ b/examples/helia-angular/src/app/app.component.css @@ -1,4 +1,5 @@ -:host { + + :host { --bright-blue: oklch(51.01% 0.274 263.83); --electric-violet: oklch(53.18% 0.28 296.97); --french-violet: oklch(47.66% 0.246 305.88); @@ -152,49 +153,6 @@ fill: var(--gray-900); } - .card { - margin: 1rem; - padding: 1.5rem; - text-align: left; - color: inherit; - text-decoration: none; - border: 1px solid #eaeaea; - border-radius: 10px; - transition: color 0.15s ease, border-color 0.15s ease; - width: 45%; - } - - .card:hover, - .card:focus, - .card:active { - color: var(--bright-blue); - border-color: var(--bright-blue); - } - - .card h2 { - margin: 0 0 1rem 0; - font-size: 1.5rem; - } - - .card p { - margin: 0; - font-size: 1.25rem; - line-height: 1.5; - } - - .logo { - height: 1em; - margin-left: 0.5rem; - } - - @media (max-width: 600px) { - .grid { - width: 100%; - flex-direction: column; - } - } - - @media screen and (max-width: 650px) { .content { flex-direction: column; diff --git a/examples/helia-angular/src/app/app.component.html b/examples/helia-angular/src/app/app.component.html index 626d1702..dc9040ef 100644 --- a/examples/helia-angular/src/app/app.component.html +++ b/examples/helia-angular/src/app/app.component.html @@ -90,7 +90,7 @@

IPFS Tutorials →

Interactive tutorials on decentralized web protocols

-
+
diff --git a/examples/helia-angular/src/app/app.component.spec.ts b/examples/helia-angular/src/app/app.component.spec.ts index a9ea7a26..02df5d02 100644 --- a/examples/helia-angular/src/app/app.component.spec.ts +++ b/examples/helia-angular/src/app/app.component.spec.ts @@ -1,29 +1,29 @@ import { TestBed } from '@angular/core/testing'; -import { AppComponent } from './app.component'; +import { HeliaComponent } from './helia.component'; -describe('AppComponent', () => { +describe('HeliaComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [AppComponent], + imports: [HeliaComponent], }).compileComponents(); }); it('should create the app', () => { - const fixture = TestBed.createComponent(AppComponent); + const fixture = TestBed.createComponent(HeliaComponent); const app = fixture.componentInstance; expect(app).toBeTruthy(); }); - it(`should have the 'helia-angular-test' title`, () => { - const fixture = TestBed.createComponent(AppComponent); + it(`should have the 'helia-angular' title`, () => { + const fixture = TestBed.createComponent(HeliaComponent); const app = fixture.componentInstance; - expect(app.title).toEqual('helia-angular-test'); + expect(app.title).toEqual('helia-angular'); }); it('should render title', () => { - const fixture = TestBed.createComponent(AppComponent); + const fixture = TestBed.createComponent(HeliaComponent); fixture.detectChanges(); const compiled = fixture.nativeElement as HTMLElement; - expect(compiled.querySelector('h1')?.textContent).toContain('Hello, helia-angular-test'); + expect(compiled.querySelector('h1')?.textContent).toContain('Hello, helia-angular'); }); }); diff --git a/examples/helia-angular/src/app/app.component.ts b/examples/helia-angular/src/app/app.component.ts deleted file mode 100644 index 072a57c7..00000000 --- a/examples/helia-angular/src/app/app.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { createHelia } from 'helia'; -import { CommonModule } from '@angular/common'; - -@Component({ - selector: 'app-root', - standalone: true, - imports: [CommonModule], - templateUrl: './app.component.html', - styleUrl: './app.component.css' -}) - -export class AppComponent implements OnInit { - title = 'helia-angular-example'; - helia: any | null = null; - id: string | null = null; - isOnline: boolean = false; - - async ngOnInit() { - try { - this.helia = await createHelia(); - console.log('Helia initialized:', this.helia); - - // Fetch the node's ID - const peerId = await this.helia.libp2p.peerId; - this.id = peerId.toString(); - - } catch (error) { - console.error('Error initializing Helia:', error); - } - } -} diff --git a/examples/helia-angular/src/app/app.config.server.ts b/examples/helia-angular/src/app/app.config.server.ts new file mode 100644 index 00000000..b4d57c94 --- /dev/null +++ b/examples/helia-angular/src/app/app.config.server.ts @@ -0,0 +1,11 @@ +import { mergeApplicationConfig, ApplicationConfig } from '@angular/core'; +import { provideServerRendering } from '@angular/platform-server'; +import { appConfig } from './app.config'; + +const serverConfig: ApplicationConfig = { + providers: [ + provideServerRendering() + ] +}; + +export const config = mergeApplicationConfig(appConfig, serverConfig); diff --git a/examples/helia-angular/src/app/app.config.ts b/examples/helia-angular/src/app/app.config.ts index d03bbbcf..52cd710c 100644 --- a/examples/helia-angular/src/app/app.config.ts +++ b/examples/helia-angular/src/app/app.config.ts @@ -1,5 +1,9 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +import { provideRouter } from '@angular/router'; + +import { routes } from './app.routes'; +import { provideClientHydration } from '@angular/platform-browser'; export const appConfig: ApplicationConfig = { - providers: [provideZoneChangeDetection({ eventCoalescing: true })] + providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideClientHydration()] }; diff --git a/examples/helia-angular/src/app/app.routes.ts b/examples/helia-angular/src/app/app.routes.ts new file mode 100644 index 00000000..dc39edb5 --- /dev/null +++ b/examples/helia-angular/src/app/app.routes.ts @@ -0,0 +1,3 @@ +import { Routes } from '@angular/router'; + +export const routes: Routes = []; diff --git a/examples/helia-angular/src/app/helia.component.ts b/examples/helia-angular/src/app/helia.component.ts new file mode 100644 index 00000000..30f9677e --- /dev/null +++ b/examples/helia-angular/src/app/helia.component.ts @@ -0,0 +1,35 @@ +import { Component, OnInit, Inject, PLATFORM_ID } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; +import { isPlatformBrowser, CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-root', + standalone: true, + imports: [RouterOutlet, CommonModule], + templateUrl: './app.component.html', + styleUrl: './app.component.css' +}) +export class HeliaComponent implements OnInit { + title = 'helia-angular'; + helia: any | null = null; + id: string | null = null; + isOnline: boolean = false; + + constructor(@Inject(PLATFORM_ID) private platformId: any) {} + + async ngOnInit() { + if (isPlatformBrowser(this.platformId)) { + try { + const { createHelia } = await import('helia'); + this.helia = await createHelia(); + console.log('Helia initialized:', this.helia); + + // Fetch the node's ID + const peerId = await this.helia.libp2p.peerId; + this.id = peerId.toString(); + } catch (error) { + console.error('Error initializing Helia:', error); + } + } + } +} diff --git a/examples/helia-angular/src/main.server.ts b/examples/helia-angular/src/main.server.ts new file mode 100644 index 00000000..05bb30c6 --- /dev/null +++ b/examples/helia-angular/src/main.server.ts @@ -0,0 +1,7 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { HeliaComponent } from './app/helia.component'; +import { config } from './app/app.config.server'; + +const bootstrap = () => bootstrapApplication(HeliaComponent, config); + +export default bootstrap; diff --git a/examples/helia-angular/src/main.ts b/examples/helia-angular/src/main.ts index 35b00f34..a2e73e1c 100644 --- a/examples/helia-angular/src/main.ts +++ b/examples/helia-angular/src/main.ts @@ -1,6 +1,6 @@ import { bootstrapApplication } from '@angular/platform-browser'; import { appConfig } from './app/app.config'; -import { AppComponent } from './app/app.component'; +import { HeliaComponent } from './app/helia.component'; -bootstrapApplication(AppComponent, appConfig) +bootstrapApplication(HeliaComponent, appConfig) .catch((err) => console.error(err)); diff --git a/examples/helia-angular/src/styles.css b/examples/helia-angular/src/styles.css index 600a99e8..3a112154 100644 --- a/examples/helia-angular/src/styles.css +++ b/examples/helia-angular/src/styles.css @@ -1,3 +1,4 @@ +/* You can add global styles to this file, and also import other style files */ :root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; diff --git a/examples/helia-angular/tsconfig.app.json b/examples/helia-angular/tsconfig.app.json index 3775b37e..cdc0b287 100644 --- a/examples/helia-angular/tsconfig.app.json +++ b/examples/helia-angular/tsconfig.app.json @@ -4,10 +4,14 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "./out-tsc/app", - "types": [] + "types": [ + "node" + ] }, "files": [ - "src/main.ts" + "src/main.ts", + "src/main.server.ts", + "server.ts" ], "include": [ "src/**/*.d.ts" From b51369274165150c6e40582d524579139ab8a951 Mon Sep 17 00:00:00 2001 From: Paschal okwuosa Date: Thu, 5 Dec 2024 10:06:09 +0100 Subject: [PATCH 8/9] feat:Helia Angular Example updated --- examples/helia-angular/.editorconfig | 16 ---- examples/helia-angular/src/styles.css | 122 ++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 16 deletions(-) delete mode 100644 examples/helia-angular/.editorconfig diff --git a/examples/helia-angular/.editorconfig b/examples/helia-angular/.editorconfig deleted file mode 100644 index 59d9a3a3..00000000 --- a/examples/helia-angular/.editorconfig +++ /dev/null @@ -1,16 +0,0 @@ -# Editor configuration, see https://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 2 -insert_final_newline = true -trim_trailing_whitespace = true - -[*.ts] -quote_type = single - -[*.md] -max_line_length = off -trim_trailing_whitespace = false diff --git a/examples/helia-angular/src/styles.css b/examples/helia-angular/src/styles.css index 3a112154..eeae72af 100644 --- a/examples/helia-angular/src/styles.css +++ b/examples/helia-angular/src/styles.css @@ -48,6 +48,128 @@ button:focus-visible { outline: 4px auto -webkit-focus-ring-color; } + + .container { + min-height: 100vh; + padding: 0 0.5rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100vh; + } + + .main { + padding: 5rem 0; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .footer { + width: 100%; + height: 100px; + border-top: 1px solid #eaeaea; + display: flex; + justify-content: center; + align-items: center; + } + + .footer a { + display: flex; + justify-content: center; + align-items: center; + flex-grow: 1; + } + + .title a { + color: #0070f3; + text-decoration: none; + } + + .title a:hover, + .title a:focus, + .title a:active { + text-decoration: underline; + } + + .title { + margin: 0; + line-height: 1.15; + font-size: 4rem; + } + + .title, + .description { + text-align: center; + } + + .description { + line-height: 1.5; + font-size: 1.5rem; + } + + .code { + background: #fafafa; + border-radius: 5px; + padding: 0.75rem; + font-size: 1.1rem; + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Courier New, monospace; + } + + .grid { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + max-width: 800px; + margin-top: 3rem; + } + + .card { + margin: 1rem; + padding: 1.5rem; + text-align: left; + color: inherit; + text-decoration: none; + border: 1px solid #eaeaea; + border-radius: 10px; + transition: color 0.15s ease, border-color 0.15s ease; + width: 45%; + } + + .card:hover, + .card:focus, + .card:active { + color: #0070f3; + border-color: #0070f3; + } + + .card h2 { + margin: 0 0 1rem 0; + font-size: 1.5rem; + } + + .card p { + margin: 0; + font-size: 1.25rem; + line-height: 1.5; + } + + .logo { + height: 1em; + margin-left: 0.5rem; + } + + @media (max-width: 600px) { + .grid { + width: 100%; + flex-direction: column; + } + } @media (prefers-color-scheme: light) { :root { From afad390acf3d14f609a5cc0854227a2ea212dea3 Mon Sep 17 00:00:00 2001 From: Paschal okwuosa Date: Thu, 5 Dec 2024 17:21:58 +0100 Subject: [PATCH 9/9] feat:Helia Angular Example added in ci.yml file --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3000c36..01f6f650 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,7 @@ jobs: matrix: project: - helia-101 + - helia-angular - helia-browser-verified-fetch - helia-cjs - helia-electron @@ -86,6 +87,7 @@ jobs: matrix: project: - helia-101 + - helia-angular - helia-cjs - helia-browser-verified-fetch - helia-electron