diff --git a/.github/workflows/extract-templates.yml b/.github/workflows/extract-templates.yml deleted file mode 100644 index cf88fc1..0000000 --- a/.github/workflows/extract-templates.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Extract Templates - -on: - push: - branches: [ main ] - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - starter: ['angular', 'javascript', 'react', 'svelte', 'typescript', 'vue'] - - steps: - - uses: actions/checkout@v3 - - run: | - # create output directory - mkdir dist/ - - # copy template to output directory - cp -r apps/battery/${{ matrix.starter }} dist/ - - # enter template directory - cd dist/battery/${{ matrix.starter }} - - # copy gitignore from template - cp ../../tools/assets/dot.gitignore .gitignore - - # remove nx project config - rm project.json - - # replace App_Resources path in the config - sed -i 's/\.\.\/\.\.\/tools\/assets\/App_Resources/App_Resources/g' nativescript.config.ts - - # print all files - ls -al . - - - name: Deploy - uses: s0/git-publish-subdir-action@develop - env: - REPO: self - BRANCH: ${{ matrix.starter }} - MESSAGE: "chore(update): {target-branch} to {sha}" - FOLDER: dist/battery/${{ matrix.starter }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-examples.yml b/.github/workflows/publish-examples.yml new file mode 100644 index 0000000..9e8f864 --- /dev/null +++ b/.github/workflows/publish-examples.yml @@ -0,0 +1,59 @@ +name: Publish Examples + +# Each apps// is extracted into a standalone project and pushed +# to the - branch, which StackBlitz opens directly from GitHub: +# https://stackblitz.com/github/NativeScript/stackblitz-examples/tree/- + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: write + strategy: + fail-fast: false + matrix: + example: [battery] + flavor: [angular, javascript, react, solid, svelte, typescript, vue] + env: + OUT_DIR: dist/${{ matrix.example }}-${{ matrix.flavor }} + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 22 + + - name: Extract + run: node tools/scripts/extract-example.mjs ${{ matrix.example }} ${{ matrix.flavor }} "$OUT_DIR" + + - name: Install and bundle for iOS and Android + run: tools/scripts/verify-example.sh "$OUT_DIR" + + - name: Publish to ${{ matrix.example }}-${{ matrix.flavor }} + if: github.ref == 'refs/heads/main' + env: + BRANCH: ${{ matrix.example }}-${{ matrix.flavor }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + remote="https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + if ! git clone --quiet --depth 1 --branch "$BRANCH" "$remote" publish; then + git init --quiet --initial-branch "$BRANCH" publish + fi + find publish -mindepth 1 -maxdepth 1 ! -name .git -exec rm -rf {} + + cp -R "$OUT_DIR"/. publish/ + cd publish + git add --all + if git diff --cached --quiet; then + echo "$BRANCH is already up to date" + exit 0 + fi + git -c user.name='github-actions[bot]' \ + -c user.email='41898282+github-actions[bot]@users.noreply.github.com' \ + commit --quiet -m "chore(update): $BRANCH to $GITHUB_SHA" + git push --quiet "$remote" "HEAD:refs/heads/$BRANCH" diff --git a/.gitignore b/.gitignore index 3f31468..3a538db 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ package-lock.json # System Files .DS_Store Thumbs.db + +.nx/cache +.nx/workspace-data \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index d0b804d..daffe92 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,6 @@ /dist /coverage + +/.nx/cache +/.nx/workspace-data \ No newline at end of file diff --git a/README.md b/README.md index b8c6a88..af7501d 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,59 @@ # StackBlitz Examples -The examples are managed on the `main` branch only - the individual flavor branches are automatically updated when pushing to `main` - -Each example is split into it's own folder within apps, for example: +Examples are edited on `main` only. Each one lives in its own folder: ``` apps/{example}/{flavor} ``` -Each example can utilize shared `libs` for their examples to reuse across flavors, for example: +Code shared across the flavors of an example lives in `libs`, e.g. `libs/nativescript-battery` is used by every `apps/battery/*` flavor. Apps import it by package name (`@stackblitz/nativescript-battery`), which each app's `webpack.config.js` aliases to the lib's `src` folder. -``` -libs/nativescript-battery -``` +## How examples reach StackBlitz + +Every push to `main` runs [`publish-examples.yml`](.github/workflows/publish-examples.yml), which for each `{example}/{flavor}`: + +1. extracts it into a standalone project ([`tools/scripts/extract-example.mjs`](tools/scripts/extract-example.mjs)), copying in the libs it uses and rewriting `../../../` paths; +2. installs it and bundles it for iOS and Android ([`tools/scripts/verify-example.sh`](tools/scripts/verify-example.sh)), the same build `ns preview` runs; +3. pushes it to the `{example}-{flavor}` branch. + +Pull requests run steps 1 and 2 without publishing. -^ is used across all `apps/battery` examples. +> **Warning:** Do not edit the `{example}-{flavor}` branches; they are overwritten on every publish. -## Test them out locally +### Saved projects linked from nativescript.org -Since each flavor utilizes it's own node_modules dependencies you want to clean each example before running it, for example: +nativescript.org links to saved StackBlitz projects owned by `nativescript-oss` (`stackblitz.com/edit/nativescript-battery-example-*`), because their view and fork counts are shown to visitors. The published branches are the source for those projects: after a change lands on `main`, sign in as `nativescript-oss`, open the saved project, replace its files with the contents of the matching `{example}-{flavor}` branch, and save. + +### Branch links (always current, no view counts) + +StackBlitz can also open each branch straight from GitHub. Use these to check what is on `main`, or as a fallback link if a saved project breaks: ``` -npx nx run battery-angular:clean +https://stackblitz.com/github/NativeScript/stackblitz-examples/tree/{example}-{flavor}?file={path} ``` -You can now try out the example: +| Flavor | Link | +| ---------- | ---- | +| TypeScript | https://stackblitz.com/github/NativeScript/stackblitz-examples/tree/battery-typescript?file=app%2Fmain-page.xml | +| JavaScript | https://stackblitz.com/github/NativeScript/stackblitz-examples/tree/battery-javascript?file=app%2Fmain-page.xml | +| Angular | https://stackblitz.com/github/NativeScript/stackblitz-examples/tree/battery-angular?file=src%2Fapp%2Fhome.component.html | +| React | https://stackblitz.com/github/NativeScript/stackblitz-examples/tree/battery-react?file=src%2Fcomponents%2FHomeScreen.tsx | +| Solid | https://stackblitz.com/github/NativeScript/stackblitz-examples/tree/battery-solid?file=src%2Fcomponents%2Fhome.jsx | +| Svelte | https://stackblitz.com/github/NativeScript/stackblitz-examples/tree/battery-svelte?file=app%2Fcomponents%2FHome.svelte | +| Vue | https://stackblitz.com/github/NativeScript/stackblitz-examples/tree/battery-vue?file=src%2Fcomponents%2FHome.vue | + +## Check an example locally + +Run what CI runs: ``` -npx nx run battery-angular:ios +node tools/scripts/extract-example.mjs battery vue dist/battery-vue +tools/scripts/verify-example.sh dist/battery-vue ``` -> **Warning:** Do not edit the individual flavor branches manually!. +`dist/battery-vue` is then the exact project StackBlitz will load. + +`@nativescript/core` stays on 8.9.x until NativeScript Preview supports v9. # Templates diff --git a/apps/battery/angular/package.json b/apps/battery/angular/package.json index acae392..f4e2a64 100644 --- a/apps/battery/angular/package.json +++ b/apps/battery/angular/package.json @@ -4,29 +4,29 @@ "version": "1.0.0", "private": true, "dependencies": { - "@angular/animations": "~14.1.0", - "@angular/common": "~14.1.0", - "@angular/compiler": "~14.1.0", - "@angular/core": "~14.1.0", - "@angular/forms": "~14.1.0", - "@angular/platform-browser": "~14.1.0", - "@angular/platform-browser-dynamic": "~14.1.0", - "@angular/router": "~14.1.0", - "@nativescript/angular": "^14.0.0", - "@nativescript/core": "~8.3.0", - "@nativescript/theme": "~3.0.2", - "rxjs": "~7.5.0", - "zone.js": "~0.11.5" + "@angular/animations": "~20.0.0", + "@angular/common": "~20.0.0", + "@angular/compiler": "~20.0.0", + "@angular/core": "~20.0.0", + "@angular/forms": "~20.0.0", + "@angular/platform-browser": "~20.0.0", + "@angular/platform-browser-dynamic": "~20.0.0", + "@angular/router": "~20.0.0", + "@nativescript/angular": "~20.0.0", + "@nativescript/core": "~8.9.0", + "rxjs": "~7.8.0", + "zone.js": "~0.15.0" }, "devDependencies": { - "@angular-devkit/build-angular": "~14.1.0", - "@angular/compiler-cli": "~14.1.0", - "@nativescript/ios": "8.3.3", - "@nativescript/preview-cli": "1.0.0-beta.18", - "@nativescript/stackblitz": "0.0.5", - "@nativescript/types-minimal": "~8.3.0", - "@nativescript/webpack": "~5.0.0", - "@ngtools/webpack": "~14.1.0", - "typescript": "~4.7.0" + "@angular-devkit/build-angular": "~20.0.0", + "@angular/compiler-cli": "~20.0.0", + "@nativescript/preview-cli": "^1.0.14", + "@nativescript/stackblitz": "^0.0.8", + "@nativescript/tailwind": "^2.1.0", + "@nativescript/types": "~8.9.0", + "@nativescript/webpack": "~5.0.27", + "@ngtools/webpack": "~20.0.0", + "tailwindcss": "~3.4.0", + "typescript": "~5.8.0" } } diff --git a/apps/battery/angular/project.json b/apps/battery/angular/project.json index 2d66930..e2474af 100644 --- a/apps/battery/angular/project.json +++ b/apps/battery/angular/project.json @@ -3,42 +3,34 @@ "sourceRoot": "apps/battery/angular/src", "projectType": "application", "targets": { - "ios": { + "build": { "executor": "@nativescript/nx:build", "options": { - "platform": "ios" + "noHmr": true, + "production": true, + "uglify": true, + "release": true, + "forDevice": true }, - "configurations": { - "build": { - "copyTo": "./dist/build.ipa" - }, - "prod": { - "combineWithConfig": "build:prod" - } - } + "configurations": {} }, - "android": { - "executor": "@nativescript/nx:build", + "debug": { + "executor": "@nativescript/nx:debug", "options": { - "platform": "android" + "noHmr": true, + "uglify": false, + "release": false, + "forDevice": false, + "prepare": false }, - "configurations": { - "build": { - "copyTo": "./dist/build.apk" - }, - "prod": { - "combineWithConfig": "build:prod" - } - } + "configurations": {} }, "clean": { - "executor": "@nativescript/nx:build", - "options": { - "clean": true - } + "executor": "@nativescript/nx:clean", + "options": {} }, "lint": { - "executor": "@nrwl/linter:eslint", + "executor": "@nx/eslint:lint", "options": { "lintFilePatterns": [ "apps/battery/angular/**/*.ts", @@ -46,5 +38,6 @@ ] } } - } + }, + "name": "angular" } diff --git a/apps/battery/angular/references.d.ts b/apps/battery/angular/references.d.ts index 5e8adff..d743326 100644 --- a/apps/battery/angular/references.d.ts +++ b/apps/battery/angular/references.d.ts @@ -1 +1 @@ -/// +/// diff --git a/apps/battery/angular/src/app.css b/apps/battery/angular/src/app.css index db7374a..9a27e59 100644 --- a/apps/battery/angular/src/app.css +++ b/apps/battery/angular/src/app.css @@ -1,5 +1,11 @@ -@import '@nativescript/theme/css/core.css'; -@import '@nativescript/theme/css/default.css'; +@tailwind base; +@tailwind components; +@tailwind utilities; + +ActionBar { + background-color: #65adf1; + color: white; +} Button.action-btn { border-radius: 25; diff --git a/apps/battery/angular/src/app/app-routing.module.ts b/apps/battery/angular/src/app/app-routing.module.ts deleted file mode 100644 index 28977ee..0000000 --- a/apps/battery/angular/src/app/app-routing.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NgModule } from '@angular/core' -import { Routes } from '@angular/router' -import { NativeScriptRouterModule } from '@nativescript/angular' - -import { HomeComponent } from './home.component' - -const routes: Routes = [ - { path: '', redirectTo: '/home', pathMatch: 'full' }, - { path: 'home', component: HomeComponent }, -] - -@NgModule({ - imports: [NativeScriptRouterModule.forRoot(routes)], - exports: [NativeScriptRouterModule], -}) -export class AppRoutingModule {} diff --git a/apps/battery/angular/src/app/app.component.ts b/apps/battery/angular/src/app/app.component.ts index 5920b7a..b391b90 100644 --- a/apps/battery/angular/src/app/app.component.ts +++ b/apps/battery/angular/src/app/app.component.ts @@ -1,7 +1,10 @@ -import { Component } from '@angular/core' +import { Component, NO_ERRORS_SCHEMA } from '@angular/core'; +import { PageRouterOutlet } from '@nativescript/angular'; @Component({ selector: 'ns-app', templateUrl: './app.component.html', + imports: [PageRouterOutlet], + schemas: [NO_ERRORS_SCHEMA], }) export class AppComponent {} diff --git a/apps/battery/angular/src/app/app.module.ts b/apps/battery/angular/src/app/app.module.ts deleted file mode 100644 index 43d0ceb..0000000 --- a/apps/battery/angular/src/app/app.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core' -import { NativeScriptModule } from '@nativescript/angular' - -import { AppRoutingModule } from './app-routing.module' -import { AppComponent } from './app.component' -import { HomeComponent } from './home.component' - -@NgModule({ - bootstrap: [AppComponent], - imports: [NativeScriptModule, AppRoutingModule], - declarations: [AppComponent, HomeComponent], - providers: [], - schemas: [NO_ERRORS_SCHEMA], -}) -export class AppModule {} diff --git a/apps/battery/angular/src/app/app.routes.ts b/apps/battery/angular/src/app/app.routes.ts new file mode 100644 index 0000000..05c129d --- /dev/null +++ b/apps/battery/angular/src/app/app.routes.ts @@ -0,0 +1,7 @@ +import { Routes } from '@angular/router'; +import { HomeComponent } from './home.component'; + +export const routes: Routes = [ + { path: '', redirectTo: '/home', pathMatch: 'full' }, + { path: 'home', component: HomeComponent }, +]; diff --git a/apps/battery/angular/src/app/home.component.html b/apps/battery/angular/src/app/home.component.html index 1f2d262..6292a93 100644 --- a/apps/battery/angular/src/app/home.component.html +++ b/apps/battery/angular/src/app/home.component.html @@ -1,8 +1,8 @@ - + - - - - - + + + + + diff --git a/apps/battery/angular/src/app/home.component.ts b/apps/battery/angular/src/app/home.component.ts index 8d0d8a2..9b141cd 100644 --- a/apps/battery/angular/src/app/home.component.ts +++ b/apps/battery/angular/src/app/home.component.ts @@ -1,41 +1,28 @@ -import { Component, OnInit, inject, NgZone } from '@angular/core'; +import { Component, NO_ERRORS_SCHEMA, OnInit, signal } from '@angular/core'; +import { NativeScriptCommonModule } from '@nativescript/angular'; import { checkBatteryLevel, listenForBatteryChanges } from '@stackblitz/nativescript-battery'; -const formatMessage = (level) => `The Battery Level is: ${level}%`; - @Component({ selector: 'ns-home', templateUrl: './home.component.html', + imports: [NativeScriptCommonModule], + schemas: [NO_ERRORS_SCHEMA], }) export class HomeComponent implements OnInit { - level: number; - isListening = false; - ngZone = inject(NgZone); - - ngOnInit() { - this.updateBatteryLevel(); - } + level = signal(undefined); + isListening = signal(false); - async updateBatteryLevel() { + async ngOnInit() { const value = await checkBatteryLevel(); - this.updateLevel(value); - alert(formatMessage(value)); + this.level.set(value); + alert(`The Battery Level is: ${value}%`); } async toggleListenForChanges() { - this.isListening = listenForBatteryChanges((value) => { - this.updateLevel(value); - }); - if (this.isListening) { - // update view binding right away - const value = await checkBatteryLevel(); - this.updateLevel(value); + const listening = listenForBatteryChanges((value) => this.level.set(value)); + this.isListening.set(listening); + if (listening) { + this.level.set(await checkBatteryLevel()); } } - - private updateLevel(value: number) { - this.ngZone.run(() => { - this.level = value; - }); - } } diff --git a/apps/battery/angular/src/main.ts b/apps/battery/angular/src/main.ts index 4883e8f..5403415 100644 --- a/apps/battery/angular/src/main.ts +++ b/apps/battery/angular/src/main.ts @@ -1,8 +1,11 @@ -import { platformNativeScript, runNativeScriptAngularApp } from '@nativescript/angular'; - -import { AppModule } from './app/app.module'; +import { provideZonelessChangeDetection } from '@angular/core'; +import { bootstrapApplication, provideNativeScriptRouter, runNativeScriptAngularApp } from '@nativescript/angular'; +import { AppComponent } from './app/app.component'; +import { routes } from './app/app.routes'; runNativeScriptAngularApp({ - appModuleBootstrap: () => platformNativeScript().bootstrapModule(AppModule), + appModuleBootstrap: () => + bootstrapApplication(AppComponent, { + providers: [provideNativeScriptRouter(routes), provideZonelessChangeDetection()], + }), }); - diff --git a/apps/battery/angular/src/polyfills.ts b/apps/battery/angular/src/polyfills.ts index 05df962..0929f3d 100644 --- a/apps/battery/angular/src/polyfills.ts +++ b/apps/battery/angular/src/polyfills.ts @@ -6,15 +6,3 @@ import '@nativescript/core/globals'; // Install @nativescript/angular specific polyfills import '@nativescript/angular/polyfills'; - -/** - * Zone.js and patches - */ -// Add pre-zone.js patches needed for the NativeScript platform -import '@nativescript/zone-js/dist/pre-zone-polyfills'; - -// Zone JS is required by default for Angular itself -import 'zone.js'; - -// Add NativeScript specific Zone JS patches -import '@nativescript/zone-js'; diff --git a/apps/battery/angular/tailwind.config.js b/apps/battery/angular/tailwind.config.js new file mode 100644 index 0000000..f8cfdb1 --- /dev/null +++ b/apps/battery/angular/tailwind.config.js @@ -0,0 +1,13 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./src/**/*.{css,xml,html,vue,svelte,ts,tsx}'], + // use the .ns-dark class to control dark mode (applied by NativeScript) - since 'media' (default) is not supported. + darkMode: ['class', '.ns-dark'], + theme: { + extend: {}, + }, + plugins: [], + corePlugins: { + preflight: false, // disables browser-specific resets + }, +}; diff --git a/apps/battery/angular/tsconfig.json b/apps/battery/angular/tsconfig.json index 4f668d1..fe68e93 100644 --- a/apps/battery/angular/tsconfig.json +++ b/apps/battery/angular/tsconfig.json @@ -1,14 +1,14 @@ { "compilerOptions": { "module": "esnext", - "target": "es2017", - "moduleResolution": "node", + "target": "ES2020", + "moduleResolution": "bundler", "experimentalDecorators": true, "emitDecoratorMetadata": true, "noEmitHelpers": true, "noEmitOnError": true, "skipLibCheck": true, - "lib": ["es2017", "dom"], + "lib": ["ESNext", "dom"], "baseUrl": ".", "paths": { "~/*": ["src/*"], diff --git a/apps/battery/angular/webpack.config.js b/apps/battery/angular/webpack.config.js index b80dc7f..bcf25f7 100644 --- a/apps/battery/angular/webpack.config.js +++ b/apps/battery/angular/webpack.config.js @@ -1,8 +1,16 @@ +const path = require('path'); const webpack = require('@nativescript/webpack'); module.exports = (env) => { webpack.init(env); + webpack.chainWebpack((config) => { + config.resolve.alias.set( + '@stackblitz/nativescript-battery', + path.resolve(__dirname, '../../../libs/nativescript-battery/src'), + ); + }); + // Learn how to customize: // https://docs.nativescript.org/webpack diff --git a/apps/battery/javascript/app/app.css b/apps/battery/javascript/app/app.css index e3d2c4a..9a27e59 100644 --- a/apps/battery/javascript/app/app.css +++ b/apps/battery/javascript/app/app.css @@ -1,6 +1,16 @@ -@import '@nativescript/theme/css/core.css'; -@import '@nativescript/theme/css/default.css'; +@tailwind base; +@tailwind components; +@tailwind utilities; -Button.-primary { +ActionBar { + background-color: #65adf1; + color: white; +} + +Button.action-btn { + border-radius: 25; + background-color: rgb(0, 162, 255); + color: white; font-size: 18; + padding: 5; } diff --git a/apps/battery/javascript/app/main-page.xml b/apps/battery/javascript/app/main-page.xml index 90bbc90..2917140 100644 --- a/apps/battery/javascript/app/main-page.xml +++ b/apps/battery/javascript/app/main-page.xml @@ -1,8 +1,10 @@ - - - - ); + return ( + + + +