From 581eda1ea8d35a46e40be8e7e124330150de8e7f Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Tue, 14 Apr 2026 03:03:35 +0300 Subject: [PATCH] 2026-04-14-1 --- .../administrative-linking.service.ts | 304 + .../geocoding/entities/base-place.entity.ts | 8 +- .../entities/neighborhood-point.entity.ts | 29 + .../entities/neighborhood-polygon.entity.ts | 35 + .../src/geocoding/geocoding-init.service.ts | 33 +- .../api/src/geocoding/geocoding.controller.ts | 33 + apps/api/src/geocoding/geocoding.module.ts | 17 +- apps/api/src/geocoding/geocoding.service.ts | 75 +- .../src/geocoding/jordan-research.service.ts | 139 + apps/web/public/map-demo.html | 27 +- apps/web/public/style-mobile.json | 82 +- apps/web/public/style.json | 250 +- apps/web/src/components/MapComponent.tsx | 30 +- export_project.py | 69 + project_source_code_light.txt | 101242 +++++++++++++++ 15 files changed, 102069 insertions(+), 304 deletions(-) create mode 100644 apps/api/src/geocoding/administrative-linking.service.ts create mode 100644 apps/api/src/geocoding/entities/neighborhood-point.entity.ts create mode 100644 apps/api/src/geocoding/entities/neighborhood-polygon.entity.ts create mode 100644 apps/api/src/geocoding/jordan-research.service.ts create mode 100644 export_project.py create mode 100644 project_source_code_light.txt diff --git a/apps/api/src/geocoding/administrative-linking.service.ts b/apps/api/src/geocoding/administrative-linking.service.ts new file mode 100644 index 0000000..63406a0 --- /dev/null +++ b/apps/api/src/geocoding/administrative-linking.service.ts @@ -0,0 +1,304 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, DataSource } from 'typeorm'; +import { NeighborhoodPoint } from './entities/neighborhood-point.entity'; +import { NeighborhoodPolygon } from './entities/neighborhood-polygon.entity'; +import { AdminBoundary } from './entities/admin-boundary.entity'; +import { PlaceJordan } from './entities/place-jordan.entity'; +import { PlaceSyria } from './entities/place-syria.entity'; +import axios from 'axios'; + +@Injectable() +export class AdministrativeLinkingService { + private readonly logger = new Logger(AdministrativeLinkingService.name); + + constructor( + private dataSource: DataSource, + @InjectRepository(NeighborhoodPoint) + private readonly neighborhoodPointRepo: Repository, + @InjectRepository(NeighborhoodPolygon) + private readonly neighborhoodPolygonRepo: Repository, + @InjectRepository(AdminBoundary) + private readonly adminBoundaryRepo: Repository, + ) {} + + private readonly overpassMirrors = [ + 'https://overpass-api.de/api/interpreter', + 'https://overpass.kumi.systems/api/interpreter', + 'https://z.overpass-api.de/api/interpreter' + ]; + + private async fetchWithRetry(data: string, retries = 3): Promise { + for (let i = 0; i <= retries; i++) { + const url = this.overpassMirrors[i % this.overpassMirrors.length]; + try { + const response = await axios.post(url, data, { + timeout: 45000, + headers: { + 'User-Agent': 'IntaleqMapBot/1.0', + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); + return response.data; + } catch (error) { + this.logger.warn(`Mirror ${url} failed: ${error.message}. Trying next mirror...`); + if (i === retries) throw error; + await new Promise(res => setTimeout(res, 2000)); + } + } + } + + /** + * Step 1: Sync Neighborhood Points from OSM for a given BBox + */ + async syncOsmNeighborhoodPoints(bbox: string = '31.86,35.94,32.22,36.25') { + this.logger.log(`Syncing neighborhood points for bbox: ${bbox}`); + const query = `[out:json][timeout:900];node["place"~"neighbourhood|suburb|town"](${bbox});out;`; + + try { + const data = await this.fetchWithRetry(`data=${encodeURIComponent(query)}`); + const elements = data.elements; + this.logger.log(`Found ${elements.length} points in OSM.`); + + for (const e of elements) { + const nameAr = e.tags['name:ar'] || e.tags['name']; + const nameEn = e.tags['name:en'] || e.tags['name']; + const geometry = { type: 'Point', coordinates: [e.lon, e.lat] }; + + await this.dataSource.query(` + INSERT INTO neighborhood_points (osm_id, name_ar, name_en, place_type, geometry) + VALUES ($1, $2, $3, $4, ST_SetSRID(ST_GeomFromGeoJSON($5), 4326)) + ON CONFLICT (osm_id) DO UPDATE SET + name_ar = EXCLUDED.name_ar, + name_en = EXCLUDED.name_en, + place_type = EXCLUDED.place_type, + geometry = EXCLUDED.geometry; + `, [e.id, nameAr, nameEn, e.tags['place'], JSON.stringify(geometry)]); + } + + // Link points to parent districts using containment or nearest neighbor + await this.dataSource.query(` + UPDATE neighborhood_points np + SET district_id = COALESCE( + ( + SELECT id FROM admin_boundaries ab + WHERE ab.admin_level IN (6, 8) + AND ST_Contains(ab.geom::geometry, np.geometry::geometry) + ORDER BY ab.admin_level DESC + LIMIT 1 + ), + ( + SELECT id FROM admin_boundaries ab + WHERE ab.admin_level IN (6, 8) + ORDER BY ab.geom::geometry <-> np.geometry::geometry + LIMIT 1 + ) + ) + WHERE district_id IS NULL; + `); + + // Diagnostics + const totalPoints = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_points`); + const linkedPoints = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_points WHERE district_id IS NOT NULL`); + const unlinkedPoints = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_points WHERE district_id IS NULL`); + const adminCount = await this.dataSource.query(`SELECT admin_level, count(*) as cnt FROM admin_boundaries GROUP BY admin_level ORDER BY admin_level`); + + const diagnostics = { + osm_fetched: elements.length, + total_points_in_db: parseInt(totalPoints[0].cnt), + linked_to_district: parseInt(linkedPoints[0].cnt), + unlinked: parseInt(unlinkedPoints[0].cnt), + admin_boundaries: adminCount.map(r => ({ level: r.admin_level, count: parseInt(r.cnt) })), + }; + this.logger.log(`Step 1 diagnostics: ${JSON.stringify(diagnostics)}`); + return diagnostics; + } catch (error) { + this.logger.error(`Failed to sync OSM points: ${error.message}`, error.stack); + throw error; + } + } + + /** + * Step 2: Generate Voronoi Polygons for Neighborhoods + */ + async generateVoronoiNeighborhoods() { + this.logger.log('Generating Voronoi polygons for neighborhoods...'); + + // Pre-flight diagnostics + const prePoints = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_points WHERE district_id IS NOT NULL`); + const preDistricts = await this.dataSource.query(`SELECT district_id, count(*) as cnt FROM neighborhood_points WHERE district_id IS NOT NULL GROUP BY district_id`); + this.logger.log(`Pre-flight: ${prePoints[0].cnt} points across ${preDistricts.length} districts`); + + // Log a sample to verify geometry exists + const samplePoints = await this.dataSource.query(`SELECT osm_id, name_ar, district_id, ST_AsText(geometry) as geom_text FROM neighborhood_points WHERE district_id IS NOT NULL LIMIT 3`); + this.logger.log(`Sample points: ${JSON.stringify(samplePoints)}`); + + const sampleDistricts = await this.dataSource.query(`SELECT id, name_ar, admin_level, ST_AsText(ST_Centroid(geom)) as centroid FROM admin_boundaries WHERE admin_level IN (6, 8) LIMIT 3`); + this.logger.log(`Sample districts: ${JSON.stringify(sampleDistricts)}`); + + // Delete old voronoi polygons + await this.dataSource.query(`DELETE FROM neighborhood_polygons WHERE method = 'voronoi'`); + this.logger.log('Deleted old voronoi polygons.'); + + // Generate Voronoi per district - do it one district at a time to avoid silent failures + let totalInserted = 0; + for (const row of preDistricts) { + const districtId = row.district_id; + const pointCount = parseInt(row.cnt); + + try { + if (pointCount >= 2) { + // Normal Voronoi for 2+ points + const result = await this.dataSource.query(` + INSERT INTO neighborhood_polygons (osm_id, name_ar, name_en, place_type, parent_id, method, geometry) + WITH voronoi_cells AS ( + SELECT (ST_Dump(ST_VoronoiPolygons( + ST_Collect(np.geometry::geometry), + 0.00001, + d.geom::geometry + ))).geom as cell + FROM neighborhood_points np + JOIN admin_boundaries d ON d.id = np.district_id + WHERE np.district_id = $1 + GROUP BY d.geom + ), + matched AS ( + SELECT + np.osm_id, np.name_ar, np.name_en, np.place_type, np.district_id, + ST_Multi(ST_Intersection(vc.cell::geometry, d.geom::geometry)) as geometry + FROM voronoi_cells vc + CROSS JOIN LATERAL ( + SELECT np2.* + FROM neighborhood_points np2 + WHERE np2.district_id = $1 + ORDER BY np2.geometry::geometry <-> vc.cell::geometry + LIMIT 1 + ) np + JOIN admin_boundaries d ON d.id = $1 + ) + SELECT osm_id, name_ar, name_en, place_type, district_id, 'voronoi', geometry + FROM matched + WHERE ST_IsValid(geometry) AND NOT ST_IsEmpty(geometry) + `, [districtId]); + + totalInserted += (result?.length || result?.[1] || 0); + this.logger.log(`District ${districtId}: ${pointCount} points -> ${result?.length || result?.[1] || '?'} polygons`); + } else if (pointCount === 1) { + // Single point: assign entire district boundary + const result = await this.dataSource.query(` + INSERT INTO neighborhood_polygons (osm_id, name_ar, name_en, place_type, parent_id, method, geometry) + SELECT np.osm_id, np.name_ar, np.name_en, np.place_type, np.district_id, 'voronoi', ST_Multi(d.geom::geometry) + FROM neighborhood_points np + JOIN admin_boundaries d ON d.id = np.district_id + WHERE np.district_id = $1 + `, [districtId]); + + totalInserted += (result?.length || result?.[1] || 0); + this.logger.log(`District ${districtId}: 1 point -> assigned district boundary`); + } + } catch (err) { + this.logger.error(`Voronoi FAILED for district ${districtId} (${pointCount} points): ${err.message}`); + } + } + + // Post-flight count + const postCount = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_polygons`); + const finalCount = parseInt(postCount[0].cnt); + this.logger.log(`Step 2 complete: ${finalCount} total polygons in neighborhood_polygons`); + + // Sample polygon names + const samplePolygons = await this.dataSource.query(`SELECT id, osm_id, name_ar, parent_id, method FROM neighborhood_polygons LIMIT 5`); + this.logger.log(`Sample polygons: ${JSON.stringify(samplePolygons)}`); + + return { + status: finalCount > 0 ? 'success' : 'WARNING_EMPTY', + total_polygons: finalCount, + districts_processed: preDistricts.length, + sample: samplePolygons + }; + } + + /** + * Step 3: Link all Places (Jordan & Syria) to the full administrative hierarchy + */ + async linkPlaces(country: 'jordan' | 'syria') { + const tableName = country === 'jordan' ? 'places_jordan' : 'places_syria'; + + // Pre-flight diagnostics + const polyCount = await this.dataSource.query(`SELECT count(*) as cnt FROM neighborhood_polygons`); + const placeCount = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName} WHERE location IS NOT NULL`); + this.logger.log(`linkPlaces pre-flight: ${polyCount[0].cnt} polygons, ${placeCount[0].cnt} places with location in ${tableName}`); + + if (parseInt(polyCount[0].cnt) === 0) { + this.logger.error('ABORT: neighborhood_polygons is EMPTY. Run generate-voronoi first!'); + return { + status: 'FAILED', + error: 'neighborhood_polygons table is empty. Run generate-voronoi first.', + polygons: 0, + places: parseInt(placeCount[0].cnt) + }; + } + + this.logger.log(`Linking administrative hierarchy for ${tableName} using KNN...`); + + // Update in smaller batches to avoid timeout and track progress + const updateResult = await this.dataSource.query(` + UPDATE ${tableName} p + SET + neighborhood_id = ( + SELECT np.id FROM neighborhood_polygons np + ORDER BY p.location::geometry <-> np.geometry::geometry + LIMIT 1 + ), + sub_district_id = ( + SELECT ab.id FROM admin_boundaries ab + WHERE ab.admin_level = 8 + AND ST_Within(p.location::geometry, ab.geom::geometry) + LIMIT 1 + ), + district_id = ( + SELECT ab.id FROM admin_boundaries ab + WHERE ab.admin_level = 6 + AND ST_Within(p.location::geometry, ab.geom::geometry) + LIMIT 1 + ), + governorate_id = ( + SELECT ab.id FROM admin_boundaries ab + WHERE ab.admin_level = 4 + AND ST_Within(p.location::geometry, ab.geom::geometry) + LIMIT 1 + ) + WHERE p.location IS NOT NULL; + `); + + // Post-flight diagnostics + const linkedNeighborhood = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName} WHERE neighborhood_id IS NOT NULL`); + const linkedDistrict = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName} WHERE district_id IS NOT NULL`); + const linkedGov = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName} WHERE governorate_id IS NOT NULL`); + const totalPlaces = await this.dataSource.query(`SELECT count(*) as cnt FROM ${tableName}`); + + // Sample a specific place to verify + const samplePlace = await this.dataSource.query(` + SELECT p.id, p.name_ar, p.neighborhood_id, p.district_id, p.governorate_id, + n.name_ar as neighborhood_name + FROM ${tableName} p + LEFT JOIN neighborhood_polygons n ON p.neighborhood_id = n.id + WHERE p.name_ar LIKE '%معصوم%' + LIMIT 3 + `); + + const diagnostics = { + status: 'completed', + country, + total_places: parseInt(totalPlaces[0].cnt), + linked_neighborhood: parseInt(linkedNeighborhood[0].cnt), + linked_district: parseInt(linkedDistrict[0].cnt), + linked_governorate: parseInt(linkedGov[0].cnt), + sample_masoum: samplePlace, + rows_affected: updateResult?.[1] || 'unknown' + }; + + this.logger.log(`Step 3 diagnostics: ${JSON.stringify(diagnostics)}`); + return diagnostics; + } +} diff --git a/apps/api/src/geocoding/entities/base-place.entity.ts b/apps/api/src/geocoding/entities/base-place.entity.ts index 15add15..650d988 100644 --- a/apps/api/src/geocoding/entities/base-place.entity.ts +++ b/apps/api/src/geocoding/entities/base-place.entity.ts @@ -48,17 +48,17 @@ export abstract class BasePlace { @Column({ type: 'int', nullable: true }) @Index() - admin_level4_id: number; + governorate_id: number; @Column({ type: 'int', nullable: true }) @Index() - admin_level6_id: number; + district_id: number; @Column({ type: 'int', nullable: true }) @Index() - admin_level8_id: number; + sub_district_id: number; @Column({ type: 'int', nullable: true }) @Index() - admin_level10_id: number; + neighborhood_id: number; } diff --git a/apps/api/src/geocoding/entities/neighborhood-point.entity.ts b/apps/api/src/geocoding/entities/neighborhood-point.entity.ts new file mode 100644 index 0000000..48c9e27 --- /dev/null +++ b/apps/api/src/geocoding/entities/neighborhood-point.entity.ts @@ -0,0 +1,29 @@ +import { Entity, Column, PrimaryGeneratedColumn, Index } from 'typeorm'; + +@Entity('neighborhood_points') +export class NeighborhoodPoint { + @PrimaryGeneratedColumn() + id: number; + + @Column({ type: 'bigint', unique: true, nullable: true }) + @Index() + osm_id: number; + + @Column({ nullable: true }) + @Index() + name_ar: string; + + @Column({ nullable: true }) + name_en: string; + + @Column({ nullable: true }) + place_type: string; + + @Column({ type: 'int', nullable: true }) + @Index() + district_id: number; + + @Column({ type: 'geometry', spatialFeatureType: 'Point', srid: 4326 }) + @Index({ spatial: true }) + geometry: any; +} diff --git a/apps/api/src/geocoding/entities/neighborhood-polygon.entity.ts b/apps/api/src/geocoding/entities/neighborhood-polygon.entity.ts new file mode 100644 index 0000000..e2bc901 --- /dev/null +++ b/apps/api/src/geocoding/entities/neighborhood-polygon.entity.ts @@ -0,0 +1,35 @@ +import { Entity, Column, PrimaryGeneratedColumn, Index } from 'typeorm'; + +@Entity('neighborhood_polygons') +export class NeighborhoodPolygon { + @PrimaryGeneratedColumn() + id: number; + + @Column({ type: 'bigint', nullable: true }) + @Index() + osm_id: number; + + @Column({ nullable: true }) + @Index() + name_ar: string; + + @Column({ nullable: true }) + name_en: string; + + @Column({ nullable: true }) + place_type: string; + + @Column({ type: 'int', nullable: true }) + @Index() + parent_id: number; // Links to admin_boundaries (District/Sub-district) + + @Column({ default: 'voronoi' }) + method: string; + + @Column({ type: 'float', default: 0.7 }) + confidence: number; + + @Column({ type: 'geometry', spatialFeatureType: 'MultiPolygon', srid: 4326 }) + @Index({ spatial: true }) + geometry: any; +} diff --git a/apps/api/src/geocoding/geocoding-init.service.ts b/apps/api/src/geocoding/geocoding-init.service.ts index 9f99e48..1c89eb9 100644 --- a/apps/api/src/geocoding/geocoding-init.service.ts +++ b/apps/api/src/geocoding/geocoding-init.service.ts @@ -19,6 +19,28 @@ export class GeocodingInitService implements OnModuleInit { await this.repo.query('CREATE EXTENSION IF NOT EXISTS postgis;'); await this.repo.query('CREATE EXTENSION IF NOT EXISTS pg_trgm;'); + // Migration: Rename old admin_level columns to descriptive names if they exist + const tables = ['places_jordan', 'places_syria', 'places_egypt']; + const columnMapping = [ + { old: 'admin_level4_id', new: 'governorate_id' }, + { old: 'admin_level6_id', new: 'district_id' }, + { old: 'admin_level8_id', new: 'sub_district_id' }, + { old: 'admin_level10_id', new: 'neighborhood_id' } + ]; + + for (const table of tables) { + for (const mapping of columnMapping) { + await this.repo.query(` + DO $$ + BEGIN + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = '${table}' AND column_name = '${mapping.old}') THEN + ALTER TABLE ${table} RENAME COLUMN ${mapping.old} TO ${mapping.new}; + END IF; + END $$; + `); + } + } + // Cleanup: Create a trigger to automatically update 'location' when lat/lng changes await this.repo.query(` CREATE OR REPLACE FUNCTION sync_place_location() RETURNS trigger AS $$ @@ -26,10 +48,13 @@ export class GeocodingInitService implements OnModuleInit { IF NEW.latitude IS NOT NULL AND NEW.longitude IS NOT NULL THEN NEW.location := ST_SetSRID(ST_MakePoint(CAST(NEW.longitude AS FLOAT), CAST(NEW.latitude AS FLOAT)), 4326); - NEW.admin_level4_id := (SELECT id FROM admin_boundaries WHERE admin_level = 4 AND ST_Contains(geom, NEW.location) LIMIT 1); - NEW.admin_level6_id := (SELECT id FROM admin_boundaries WHERE admin_level = 6 AND ST_Contains(geom, NEW.location) LIMIT 1); - NEW.admin_level8_id := (SELECT id FROM admin_boundaries WHERE admin_level = 8 AND ST_Contains(geom, NEW.location) LIMIT 1); - NEW.admin_level10_id := (SELECT id FROM admin_boundaries WHERE admin_level = 10 AND ST_Contains(geom, NEW.location) LIMIT 1); + -- Keep standard admin boundary logic for higher levels + NEW.governorate_id := (SELECT id FROM admin_boundaries WHERE admin_level = 4 AND ST_Contains(geom, NEW.location) LIMIT 1); + NEW.district_id := (SELECT id FROM admin_boundaries WHERE admin_level = 6 AND ST_Contains(geom, NEW.location) LIMIT 1); + NEW.sub_district_id := (SELECT id FROM admin_boundaries WHERE admin_level = 8 AND ST_Contains(geom, NEW.location) LIMIT 1); + + -- ROOT CAUSE FIX: Use the new Voronoi polygons for neighborhoods! + NEW.neighborhood_id := (SELECT id FROM neighborhood_polygons ORDER BY NEW.location::geometry <-> geometry::geometry LIMIT 1); END IF; RETURN NEW; END; diff --git a/apps/api/src/geocoding/geocoding.controller.ts b/apps/api/src/geocoding/geocoding.controller.ts index 155ca61..afde77f 100644 --- a/apps/api/src/geocoding/geocoding.controller.ts +++ b/apps/api/src/geocoding/geocoding.controller.ts @@ -2,6 +2,8 @@ import { Controller, Get, Post, Delete, Body, Query, UseGuards, HttpException, H import { ApiTags, ApiOperation, ApiQuery } from '@nestjs/swagger'; import { GeocodingService } from './geocoding.service'; import { AdminBoundariesService } from './admin-boundaries.service'; +import { JordanResearchService } from './jordan-research.service'; +import { AdministrativeLinkingService } from './administrative-linking.service'; import { ApiKeyGuard } from '../common/guards/api-key.guard'; @ApiTags('geocoding') @@ -10,6 +12,8 @@ export class GeocodingController { constructor( private readonly geocodingService: GeocodingService, private readonly adminBoundariesService: AdminBoundariesService, + private readonly jordanResearchService: JordanResearchService, + private readonly adminLinkingService: AdministrativeLinkingService, ) {} @Get('search') @@ -120,4 +124,33 @@ export class GeocodingController { ) { return this.adminBoundariesService.importFromFile(country, filePath); } + + @Get('research/zarqa') + @ApiOperation({ summary: 'Generate a research report for Zarqa, Jordan (Sample Data)' }) + async zarqaResearch() { + return this.jordanResearchService.generateZarqaReport(); + } + + @Post('admin/sync-neighborhoods') + @UseGuards(ApiKeyGuard) + @ApiOperation({ summary: 'Sync neighborhood points from OSM for a bbox' }) + @ApiQuery({ name: 'bbox', required: false }) + async syncNeighborhoods(@Query('bbox') bbox?: string) { + return this.adminLinkingService.syncOsmNeighborhoodPoints(bbox); + } + + @Post('admin/generate-voronoi') + @UseGuards(ApiKeyGuard) + @ApiOperation({ summary: 'Generate Voronoi polygons for neighborhoods' }) + async generateVoronoi() { + return this.adminLinkingService.generateVoronoiNeighborhoods(); + } + + @Post('admin/link-places') + @UseGuards(ApiKeyGuard) + @ApiOperation({ summary: 'Link places to administrative hierarchy' }) + @ApiQuery({ name: 'country', required: true, enum: ['jordan', 'syria'] }) + async linkPlaces(@Query('country') country: 'jordan' | 'syria') { + return this.adminLinkingService.linkPlaces(country); + } } diff --git a/apps/api/src/geocoding/geocoding.module.ts b/apps/api/src/geocoding/geocoding.module.ts index ae52476..cc481f1 100644 --- a/apps/api/src/geocoding/geocoding.module.ts +++ b/apps/api/src/geocoding/geocoding.module.ts @@ -10,6 +10,11 @@ import { OsmArea } from './entities/osm-area.entity'; import { OsmPointWithArea } from './entities/osm-point-with-area.entity'; import { AdminBoundary } from './entities/admin-boundary.entity'; import { AdminBoundariesService } from './admin-boundaries.service'; +import { JordanResearchService } from './jordan-research.service'; +import { AdministrativeLinkingService } from './administrative-linking.service'; +import { NeighborhoodPoint } from './entities/neighborhood-point.entity'; +import { NeighborhoodPolygon } from './entities/neighborhood-polygon.entity'; + @Module({ imports: [ TypeOrmModule.forFeature([ @@ -18,10 +23,18 @@ import { AdminBoundariesService } from './admin-boundaries.service'; PlaceEgypt, OsmArea, OsmPointWithArea, - AdminBoundary + AdminBoundary, + NeighborhoodPoint, + NeighborhoodPolygon ]), ], controllers: [GeocodingController], - providers: [GeocodingService, GeocodingInitService, AdminBoundariesService], + providers: [ + GeocodingService, + GeocodingInitService, + AdminBoundariesService, + JordanResearchService, + AdministrativeLinkingService + ], }) export class GeocodingModule {} diff --git a/apps/api/src/geocoding/geocoding.service.ts b/apps/api/src/geocoding/geocoding.service.ts index 776f7b4..9ccdc10 100644 --- a/apps/api/src/geocoding/geocoding.service.ts +++ b/apps/api/src/geocoding/geocoding.service.ts @@ -69,6 +69,7 @@ export class GeocodingService { const userQuery = ` SELECT p.id, p.name, p.name_ar, p.name_en, p.category, + p.neighborhood_id as db_neighborhood_id, p.neighbourhood as original_neighbourhood, n.name_ar as neighbourhood, d.name_ar as district, @@ -77,9 +78,9 @@ export class GeocodingService { CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(p.location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) ELSE 0 END as distance, similarity(COALESCE(p.name_ar, ''), $1) + similarity(COALESCE(p.name, ''), $1) + similarity(COALESCE(p.neighbourhood, ''), $1) as relevance FROM ${tableName} p - LEFT JOIN admin_boundaries n ON p.admin_level10_id = n.id - LEFT JOIN admin_boundaries d ON p.admin_level8_id = d.id - LEFT JOIN admin_boundaries g ON p.admin_level4_id = g.id + LEFT JOIN neighborhood_polygons n ON p.neighborhood_id = n.id + LEFT JOIN admin_boundaries d ON p.sub_district_id = d.id + LEFT JOIN admin_boundaries g ON p.governorate_id = g.id WHERE (p.name_ar % $1 OR p.name % $1 OR p.neighbourhood % $1 OR p.name_ar ILIKE $4 OR p.name ILIKE $4 OR p.neighbourhood ILIKE $4) ${hasLocation ? `AND (p.location && ST_Expand(ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326), $5::float) OR ST_DistanceSphere(p.location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) <= $6::float)` : ''} ORDER BY relevance DESC, distance ASC LIMIT 15 @@ -107,55 +108,27 @@ export class GeocodingService { const osmResults = await this.osmPointsRepository.query(osmQuery, [cleanQuery, lat || null, lon || null, ILikeQuery, radiusInDegrees, radius]); allResults.push(...osmResults); - // --- Consolidation: Overture Maps Integration (Now in primary DB) --- - const overtureQuery = ` - (SELECT id::text, - COALESCE(names->>'primary', names->>'common', 'Building') as name, - COALESCE(names->>'primary', names->>'common', '') as name_ar, - NULL as name_en, - 'building' as category, - ST_Y(ST_Centroid(location)) as latitude, - ST_X(ST_Centroid(location)) as longitude, - '' as address, - 'overture_buildings' as source, - CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) ELSE 0 END as distance, - 0.6 as relevance - FROM overture_building - WHERE (names->>'primary' ILIKE $4 OR names->>'common' ILIKE $4) - LIMIT 15) - UNION ALL - (SELECT id::text, - COALESCE(names->>'primary', names->>'common', 'Street') as name, - COALESCE(names->>'primary', names->>'common', '') as name_ar, - NULL as name_en, - 'transportation' as category, - ST_Y(ST_Centroid(location)) as latitude, - ST_X(ST_Centroid(location)) as longitude, - '' as address, - 'overture_streets' as source, - CASE WHEN $2::float IS NOT NULL THEN ST_DistanceSphere(location, ST_SetSRID(ST_MakePoint($3::float, $2::float), 4326)) ELSE 0 END as distance, - 0.6 as relevance - FROM overture_segment - WHERE (names->>'primary' ILIKE $4 OR names->>'common' ILIKE $4) - LIMIT 15) - `; - try { - const overtureResults = await this.osmPointsRepository.query(overtureQuery, [cleanQuery, lat || null, lon || null, ILikeQuery]); - allResults.push(...overtureResults); - } catch (err) { - this.logger.warn('Overture tables search failed, logging error.', err); - } + // Note: We removed the raw query to 'overture_building' because raw overture tables + // do not have administrative linking, causing empty full_addresses, and + // scanning them with ILIKE without trigram indices causes a 2-second latency spike. + // Overture data is already properly ingested via the Scraper into places_jordan. const sortedResults = allResults .sort((a, b) => hasLocation ? ((a.distance - b.distance) || (b.relevance - a.relevance)) : ((b.relevance - a.relevance) || (a.distance - b.distance))) .slice(0, 25) - .map(r => ({ - ...r, - latitude: parseFloat(r.latitude), - longitude: parseFloat(r.longitude), - distance_km: r.distance ? (Number(r.distance) / 1000).toFixed(2) : null, - location: { lat: parseFloat(r.latitude), lng: parseFloat(r.longitude) }, - })); + .map(r => { + // Build full administrative address from admin_boundaries + const addressParts = [r.neighbourhood, r.district, r.governorate].filter(Boolean); + const full_address = addressParts.length > 0 ? addressParts.join('، ') : (r.address || ''); + return { + ...r, + latitude: parseFloat(r.latitude), + longitude: parseFloat(r.longitude), + distance_km: r.distance ? (Number(r.distance) / 1000).toFixed(2) : null, + location: { lat: parseFloat(r.latitude), lng: parseFloat(r.longitude) }, + full_address, + }; + }); return { results: sortedResults }; } catch (e) { @@ -177,9 +150,9 @@ export class GeocodingService { p.latitude, p.longitude, p.address, 'user_place' as source, ST_DistanceSphere(p.location::geometry, ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326)) as distance FROM ${tableName} p - LEFT JOIN admin_boundaries n ON p.admin_level10_id = n.id - LEFT JOIN admin_boundaries d ON p.admin_level8_id = d.id - LEFT JOIN admin_boundaries g ON p.admin_level4_id = g.id + LEFT JOIN neighborhood_polygons n ON p.neighborhood_id = n.id + LEFT JOIN admin_boundaries d ON p.sub_district_id = d.id + LEFT JOIN admin_boundaries g ON p.governorate_id = g.id WHERE p.location IS NOT NULL ORDER BY p.location::geometry <-> ST_SetSRID(ST_MakePoint($1::float, $2::float), 4326) ASC LIMIT 3 `; diff --git a/apps/api/src/geocoding/jordan-research.service.ts b/apps/api/src/geocoding/jordan-research.service.ts new file mode 100644 index 0000000..e956bb7 --- /dev/null +++ b/apps/api/src/geocoding/jordan-research.service.ts @@ -0,0 +1,139 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { PlaceJordan } from './entities/place-jordan.entity'; +import axios from 'axios'; +import * as fs from 'fs'; +import * as path from 'path'; + +@Injectable() +export class JordanResearchService { + private readonly logger = new Logger(JordanResearchService.name); + + constructor( + @InjectRepository(PlaceJordan) + private readonly placeRepo: Repository, + ) {} + + private async fetchWithRetry(url: string, data?: string, method: 'get' | 'post' = 'get', retries = 2) { + for (let i = 0; i <= retries; i++) { + try { + const config = { + timeout: 20000, + headers: { + 'User-Agent': 'Mozilla/5.0 (compatible; IntaleqMapBot/1.0; +https://intaleq.xyz)', + 'Accept': 'application/json, application/sparql-results+json' + } + }; + const response = method === 'post' + ? await axios.post(url, data, config) + : await axios.get(url, { ...config, params: data ? { query: data, format: 'json' } : {} }); + return response.data; + } catch (error) { + if (i === retries) { + this.logger.error(`Failed to fetch from ${url} after ${retries} retries: ${error.message}`); + throw error; + } + await new Promise(res => setTimeout(res, 2000)); + } + } + } + + /** + * Source 1: OSM (Overpass API) with Geometries + */ + async fetchOsmZarqa() { + this.logger.log('Fetching Zarqa Geometries from OSM...'); + const query = `[out:json];(relation["boundary"="administrative"]["admin_level"~"6|8|10"](31.86,35.94,32.22,36.25);node["place"~"neighbourhood|suburb|town"](31.86,35.94,32.22,36.25););out geom;`; + try { + const data = await this.fetchWithRetry('https://overpass-api.de/api/interpreter', `data=${encodeURIComponent(query)}`, 'post'); + return data.elements.map(e => ({ + id: e.id, + name_ar: e.tags['name:ar'] || e.tags['name'], + type: e.tags['admin_level'] ? `admin_level_${e.tags['admin_level']}` : `place_${e.tags['place']}`, + geometry: e.geometry ? e.geometry : (e.lat && e.lon ? { type: 'Point', coordinates: [e.lon, e.lat] } : null) + })); + } catch (e) { return { error: `OSM Fetch failed: ${e.message}` }; } + } + + /** + * Source 2: Overture (Local DB) - Corrected columns + */ + async fetchOvertureZarqa() { + try { + const tableCheck = await this.placeRepo.query("SELECT count(*) FROM information_schema.tables WHERE table_name = 'overture_segment'"); + if (parseInt(tableCheck[0].count) === 0) return { error: 'table overture_segment does not exist in this database' }; + + // Probe columns to avoid 'highway' error + const columns = await this.placeRepo.query("SELECT column_name FROM information_schema.columns WHERE table_name = 'overture_segment'"); + const colList = columns.map(c => c.column_name); + const roadClassCol = colList.includes('road_class') ? 'road_class' : (colList.includes('class') ? 'class' : 'NULL'); + + return await this.placeRepo.query(` + SELECT DISTINCT COALESCE(names->>'primary', names->>'common') as name_ar, ${roadClassCol} as road_class, + ST_AsGeoJSON(ST_Centroid(location)) as centroid + FROM overture_segment + WHERE (names->>'primary' IS NOT NULL OR names->>'common' IS NOT NULL) + AND ST_Within(location, ST_MakeEnvelope(35.94, 31.86, 36.25, 32.22, 4326)) + LIMIT 10 + `); + } catch (e) { return { error: `Overture query failed: ${e.message}` }; } + } + + /** + * Source 3: Wikidata (SPARQL) with User-Agent + */ + async fetchWikidataZarqa() { + const sparql = `SELECT ?item ?itemLabel WHERE { ?item wdt:P131 wd:Q231710. SERVICE wikibase:label { bd:serviceParam wikibase:language "ar,en". } } LIMIT 20`; + try { + const data = await this.fetchWithRetry('https://query.wikidata.org/sparql', sparql, 'get'); + return data.results.bindings.map(b => ({ id: b.item.value.split('/').pop(), name_ar: b.itemLabel.value })); + } catch (e) { return { error: `Wikidata Fetch failed: ${e.message}` }; } + } + + /** + * Source 4 & 5: Static Files (HDX & GADM) + */ + async checkStaticFiles() { + // Check various common paths from overture_ingest script or data directory + const paths = ['/data/infrastructure/osm-data/', './data/', './infrastructure/osm-data/']; + const files = ['jor_adm2_geoboundaries.geojson', 'gadm41_JOR_2.json']; + const results = {}; + + for (const file of files) { + let found = false; + for (const p of paths) { + if (fs.existsSync(path.join(p, file))) { + results[file] = `Found at ${p}`; + found = true; + break; + } + } + if (!found) results[file] = 'Not Found locally - Check if overture_ingest.sh was run for division_area'; + } + return results; + } + + async generateZarqaReport() { + const [osm, overture, wikidata, staticFiles] = await Promise.all([ + this.fetchOsmZarqa(), + this.fetchOvertureZarqa(), + this.fetchWikidataZarqa(), + this.checkStaticFiles() + ]); + + return { + area: 'Zarqa Governorate, Jordan', + report_timestamp: new Date().toISOString(), + sources: { + osm, + overture, + wikidata, + static_files: staticFiles, + hdx: "Requires GeoBoundaries GeoJSON for Level 2 (Districts)", + gadm: "Requires GADM v4.1 for Level 1-2" + }, + comparison_summary: "Multi-source research enabled with geometries. OSM provides precise neighborhood centroids and boundaries where available. Use the provided centroids to visualize Zarqa subunits." + }; + } +} diff --git a/apps/web/public/map-demo.html b/apps/web/public/map-demo.html index d226505..18ac6f2 100644 --- a/apps/web/public/map-demo.html +++ b/apps/web/public/map-demo.html @@ -294,6 +294,11 @@ عرض المعالم / Show POIs + + @@ -672,9 +677,15 @@ function updateLayers() { const show3D = document.getElementById('toggle-3d').checked; const showPOIs = document.getElementById('toggle-pois').checked; + const showArrows = document.getElementById('toggle-arrows').checked; - if (map.getLayer('3d-buildings')) { - map.setLayoutProperty('3d-buildings', 'visibility', show3D ? 'visible' : 'none'); + // Toggle 3D Overture buildings + if (map.getLayer('building-3d')) { + map.setLayoutProperty('building-3d', 'visibility', show3D ? 'visible' : 'none'); + } + // Show flat footprints when 3D is off + if (map.getLayer('overture-building-footprint')) { + map.setLayoutProperty('overture-building-footprint', 'visibility', show3D ? 'none' : 'visible'); } if (map.getLayer('intaleq-db-label')) { @@ -685,6 +696,18 @@ if (map.getLayer('intaleq_pois')) { map.setLayoutProperty('intaleq_pois', 'visibility', showPOIs ? 'visible' : 'none'); } + + // Toggle places label layers + Overture name layers + ['places-syria-labels', 'places-jordan-labels', 'places-egypt-labels', 'overture-building-names', 'overture-street-names'].forEach(id => { + if (map.getLayer(id)) { + map.setLayoutProperty(id, 'visibility', showPOIs ? 'visible' : 'none'); + } + }); + + // Toggle street direction arrows + if (map.getLayer('road-direction-arrows')) { + map.setLayoutProperty('road-direction-arrows', 'visibility', showArrows ? 'visible' : 'none'); + } } diff --git a/apps/web/public/style-mobile.json b/apps/web/public/style-mobile.json index 13c17f3..196bc43 100644 --- a/apps/web/public/style-mobile.json +++ b/apps/web/public/style-mobile.json @@ -42,6 +42,16 @@ "https://tiles.intaleqapp.com/places_egypt/{z}/{x}/{y}" ], "maxzoom": 14 + }, + "overture_buildings": { + "type": "vector", + "tiles": ["https://tiles.intaleqapp.com/overture_building/{z}/{x}/{y}"], + "maxzoom": 14 + }, + "overture_segments": { + "type": "vector", + "tiles": ["https://tiles.intaleqapp.com/overture_segment/{z}/{x}/{y}"], + "maxzoom": 14 } }, "layers": [ @@ -909,78 +919,18 @@ { "id": "building-3d", "type": "fill-extrusion", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", + "source": "overture_buildings", + "source-layer": "overture_building", "minzoom": 15, - "filter": [ - "has", - "building" - ], "paint": { - "fill-extrusion-color": [ - "match", - [ - "get", - "building" - ], - "commercial", - "#E0D8CC", - "retail", - "#E8DDD0", - "industrial", - "#D8DDE4", - "church", - "#E4DCF0", - "mosque", - "#D4EAD8", - "hospital", - "#F0E0E0", - "school", - "#E8ECD4", - "university", - "#E4E8D4", - "hotel", - "#E0E4EE", - "apartments", - "#E8E4DC", - "#E4DFDA" - ], + "fill-extrusion-color": "#DDD8D0", "fill-extrusion-height": [ "coalesce", - [ - "to-number", - [ - "get", - "height" - ], - 0 - ], - [ - "*", - [ - "to-number", - [ - "get", - "building:levels" - ], - 3 - ], - 3.5 - ], + ["to-number", ["get", "height"], 0], + ["*", ["coalesce", ["to-number", ["get", "num_floors"], null], 3], 3.5], 12 ], - "fill-extrusion-base": [ - "coalesce", - [ - "to-number", - [ - "get", - "min_height" - ], - 0 - ], - 0 - ], + "fill-extrusion-base": 0, "fill-extrusion-opacity": 0.85, "fill-extrusion-vertical-gradient": true } diff --git a/apps/web/public/style.json b/apps/web/public/style.json index d8ed36c..9954b44 100644 --- a/apps/web/public/style.json +++ b/apps/web/public/style.json @@ -919,6 +919,35 @@ ] } }, + { + "id": "road-direction-arrows", + "type": "symbol", + "source": "local-osm-lines", + "source-layer": "planet_osm_line", + "minzoom": 15, + "filter": [ + "all", + ["in", "highway", "motorway", "trunk", "primary", "secondary", "tertiary", "residential", "unclassified", "living_street"], + ["==", "oneway", "yes"] + ], + "layout": { + "symbol-placement": "line", + "symbol-spacing": 80, + "text-field": "▸", + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], 15, 12, 18, 18], + "text-keep-upright": false, + "text-rotation-alignment": "map", + "text-allow-overlap": true, + "text-ignore-placement": true, + "text-padding": 0 + }, + "paint": { + "text-color": ["interpolate", ["linear"], ["zoom"], 15, "rgba(100,116,139,0.4)", 18, "rgba(100,116,139,0.7)"], + "text-halo-color": "rgba(255,255,255,0.3)", + "text-halo-width": 0.5 + } + }, { "id": "building-fill-flat", "type": "fill", @@ -938,120 +967,43 @@ { "id": "building-3d", "type": "fill-extrusion", - "source": "local-osm-polygons", - "source-layer": "planet_osm_polygon", + "source": "overture_buildings", + "source-layer": "overture_building", "minzoom": 14, "layout": { - "visibility": "none" + "visibility": "visible" }, - "filter": [ - "has", - "building" - ], "paint": { - "fill-extrusion-color": [ - "match", - [ - "get", - "building" - ], - "commercial", - "#DDD5C5", - "retail", - "#E5D8C8", - "industrial", - "#D2D8E0", - "church", - "#DDD4EE", - "mosque", - "#CCE4D0", - "hospital", - "#EDD8D8", - "school", - "#E0E6CC", - "university", - "#D8E0C8", - "hotel", - "#D8DCF0", - "apartments", - "#E0DCD4", - "#DDD8D2" - ], + "fill-extrusion-color": "#DDD8D0", "fill-extrusion-height": [ "interpolate", - [ - "linear" - ], - [ - "zoom" - ], + ["linear"], + ["zoom"], 14, [ "*", - [ - "coalesce", - [ - "to-number", - [ - "get", - "building:levels" - ], - null - ], - 3 - ], + ["coalesce", ["to-number", ["get", "num_floors"], null], 3], 2.5 ], 17, [ "coalesce", - [ - "to-number", - [ - "get", - "height" - ], - null - ], + ["to-number", ["get", "height"], null], [ "*", - [ - "to-number", - [ - "get", - "building:levels" - ], - 3 - ], + ["coalesce", ["to-number", ["get", "num_floors"], null], 3], 3.5 ], 12 ] ], - "fill-extrusion-base": [ - "coalesce", - [ - "to-number", - [ - "get", - "min_height" - ], - null - ], - 0 - ], + "fill-extrusion-base": 0, "fill-extrusion-opacity": [ "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 14, - 0.6, - 16, - 0.88 + ["linear"], + ["zoom"], + 14, 0.55, + 16, 0.85 ], "fill-extrusion-vertical-gradient": true } @@ -1222,33 +1174,28 @@ "unclassified", "living_street" ], - "minzoom": 16, + "minzoom": 15, "layout": { "text-field": [ "coalesce", - [ - "get", - "name:ar" - ], - [ - "get", - "name" - ], + ["get", "name:ar"], + ["get", "name"], "" ], - "text-font": [ - "Noto Sans Regular" - ], - "text-size": 11, + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], 15, 10, 18, 13], "symbol-placement": "line", - "text-letter-spacing": 0.04, - "text-padding": 4, - "text-allow-overlap": false + "text-letter-spacing": 0.05, + "text-padding": 15, + "symbol-spacing": 300, + "text-max-angle": 30, + "text-allow-overlap": false, + "text-ignore-placement": false }, "paint": { "text-color": "#4A5568", - "text-halo-color": "rgba(255,255,255,0.85)", - "text-halo-width": 1.5 + "text-halo-color": "rgba(255,255,255,0.92)", + "text-halo-width": 1.8 } }, { @@ -1265,45 +1212,28 @@ "motorway", "trunk" ], - "minzoom": 13, + "minzoom": 12, "layout": { "text-field": [ "coalesce", - [ - "get", - "name:ar" - ], - [ - "get", - "name" - ], + ["get", "name:ar"], + ["get", "name"], "" ], - "text-font": [ - "Noto Sans Regular" - ], - "text-size": [ - "interpolate", - [ - "linear" - ], - [ - "zoom" - ], - 13, - 11, - 16, - 14 - ], + "text-font": ["Noto Sans Bold"], + "text-size": ["interpolate", ["linear"], ["zoom"], 12, 10, 14, 13, 18, 16], "symbol-placement": "line", - "text-letter-spacing": 0.05, - "text-padding": 5, - "text-allow-overlap": false + "text-letter-spacing": 0.06, + "text-padding": 20, + "symbol-spacing": 350, + "text-max-angle": 25, + "text-allow-overlap": false, + "text-ignore-placement": false }, "paint": { - "text-color": "#2D3748", - "text-halo-color": "rgba(255,255,255,0.9)", - "text-halo-width": 2 + "text-color": "#1A2332", + "text-halo-color": "rgba(255,255,255,0.95)", + "text-halo-width": 2.5 } }, { @@ -1929,14 +1859,11 @@ "type": "fill", "source": "overture_buildings", "source-layer": "overture_building", - "minzoom": 14, - "layout": { - "visibility": "visible" - }, + "maxzoom": 14, "paint": { - "fill-color": "#dcd8d0", - "fill-opacity": 0.8, - "fill-outline-color": "#c4beb4" + "fill-color": "#DDD8D0", + "fill-opacity": 0.85, + "fill-outline-color": "#C4BEB4" } }, { @@ -1978,31 +1905,26 @@ "source": "overture_segments", "source-layer": "overture_segment", "minzoom": 14, + "filter": ["has", "names"], "layout": { - "text-field": [ - "coalesce", - [ - "get", - "names" - ], - "" - ], - "text-font": [ - "Noto Sans Regular" - ], - "text-size": 11, + "text-field": ["coalesce", ["get", "names"], ""], + "text-font": ["Noto Sans Regular"], + "text-size": ["interpolate", ["linear"], ["zoom"], 14, 9, 16, 12, 18, 14], "symbol-placement": "line", - "text-padding": 10, + "text-rotation-alignment": "map", + "text-pitch-alignment": "viewport", + "text-keep-upright": true, + "text-padding": 20, "text-letter-spacing": 0.04, "text-max-angle": 30, - "symbol-spacing": 250, + "symbol-spacing": 300, "text-allow-overlap": false, "text-ignore-placement": false }, "paint": { - "text-color": "#666", - "text-halo-color": "rgba(255, 255, 255, 0.9)", - "text-halo-width": 2 + "text-color": "#4A5568", + "text-halo-color": "rgba(255, 255, 255, 0.95)", + "text-halo-width": 2.2 } } ] diff --git a/apps/web/src/components/MapComponent.tsx b/apps/web/src/components/MapComponent.tsx index 933928a..5cb86be 100644 --- a/apps/web/src/components/MapComponent.tsx +++ b/apps/web/src/components/MapComponent.tsx @@ -21,13 +21,17 @@ const MapComponent: React.FC = ({ useEffect(() => { if (!map.current) return; - // Toggle 3D Buildings + // Toggle 3D Buildings (now on Overture source) if (map.current.getLayer('building-3d')) { map.current.setLayoutProperty('building-3d', 'visibility', show3D ? 'visible' : 'none'); } + // Toggle flat Overture footprint (show when 3D is off) + if (map.current.getLayer('overture-building-footprint')) { + map.current.setLayoutProperty('overture-building-footprint', 'visibility', show3D ? 'none' : 'visible'); + } // Toggle POI Layers - const poiLayers = ['poi-icons', 'place-labels']; + const poiLayers = ['poi-icons', 'place-labels', 'overture-building-names']; poiLayers.forEach(layerId => { if (map.current!.getLayer(layerId)) { map.current!.setLayoutProperty(layerId, 'visibility', showPOIs ? 'visible' : 'none'); @@ -389,7 +393,7 @@ const MapComponent: React.FC = ({ return (
- {/* Intaleq Branding Watermark */} + {/* Intaleq Premium Branding Watermark */}
= ({ zIndex: 10, display: 'flex', alignItems: 'center', - backgroundColor: 'rgba(255, 255, 255, 0.7)', - padding: '4px 8px', - borderRadius: '4px', + background: 'rgba(255, 255, 255, 0.85)', + backdropFilter: 'blur(12px) saturate(180%)', + WebkitBackdropFilter: 'blur(12px) saturate(180%)', + padding: '6px 12px', + borderRadius: '10px', pointerEvents: 'none', userSelect: 'none', - gap: '6px' + gap: '8px', + boxShadow: '0 4px 16px rgba(0,0,0,0.08)', + border: '1px solid rgba(255,255,255,0.35)' }} > Intaleq diff --git a/export_project.py b/export_project.py new file mode 100644 index 0000000..2600a8e --- /dev/null +++ b/export_project.py @@ -0,0 +1,69 @@ +import os + +def export_minimalist_project(): + output_filename = "project_source_code_light.txt" + + # 1. الملفات المسموح بها بالاسم الكامل (حساسة لحالة الأحرف) + strictly_allowed_names = {'style.json', 'style_mobile.json', '.env', 'env'} + + # 2. الامتدادات البرمجية المسموح بها (الكود المصدري فقط) + allowed_logic_extensions = {'.ts', '.sh'} + + # 3. مجلدات يتم تجاوزها فوراً لتسريع العملية + ignore_dirs = {'node_modules', 'dist', '.git', 'coverage', 'build', '.idea', '.vscode'} + + file_counter = 0 + + print("جاري استخراج الملفات المطلوبة فقط (Light Mode)...") + + with open(output_filename, 'w', encoding='utf-8') as outfile: + outfile.write("=" * 60 + "\n") + outfile.write("ملفات المشروع المختارة (Style JSON + Source Code)\n") + outfile.write("=" * 60 + "\n\n") + + for root, dirs, files in os.walk('.'): + # استثناء المجلدات الثقيلة + dirs[:] = [d for d in dirs if d not in ignore_dirs] + + for file in files: + name_lower = file.lower() + ext = os.path.splitext(name_lower)[1] + + # شرط القبول المنطقي: + # - إما أن يكون الاسم مطابقاً تماماً لملفات الستايل أو env + # - أو أن يكون امتداد ملف كود (ts, sh) + if file in strictly_allowed_names or ext in allowed_logic_extensions: + + # استثناء إضافي لملفات الاختبار أو التكوين الفرعية لتقليل الحجم + if '.spec.ts' in name_lower or 'test.ts' in name_lower: + continue + + filepath = os.path.join(root, file) + + try: + with open(filepath, 'r', encoding='utf-8') as f: + lines = f.readlines() + + file_counter += 1 + outfile.write("-" * 60 + "\n") + outfile.write(f"[{file_counter}] اسم الملف: {file}\n") + outfile.write(f"المسار: {filepath}\n") + outfile.write("-" * 60 + "\n") + + for line_number, line in enumerate(lines, 1): + # كتابة رقم السطر مع الكود + outfile.write(f"{line_number:04d} | {line.rstrip()}\n") + + outfile.write("\n\n") + print(f"تمت الإضافة: {filepath}") + + except Exception: + # تجاوز أي ملف يسبب خطأ في القراءة + continue + + print(f"\n✅ اكتملت العملية بنجاح.") + print(f"إجمالي الملفات المضافة: {file_counter}") + print(f"الملف الناتج: {output_filename}") + +if __name__ == "__main__": + export_minimalist_project() \ No newline at end of file diff --git a/project_source_code_light.txt b/project_source_code_light.txt new file mode 100644 index 0000000..0d73b90 --- /dev/null +++ b/project_source_code_light.txt @@ -0,0 +1,101242 @@ +============================================================ +ملفات المشروع المختارة (Style JSON + Source Code) +============================================================ + +------------------------------------------------------------ +[1] اسم الملف: setup_map2.sh +المسار: ./setup_map2.sh +------------------------------------------------------------ +0001 | #!/bin/bash +0002 | +0003 | # setup_map2.sh - Automated setup for the isolated Map-2 environment +0004 | # إعداد بيئة الخرائط الثانية المعزولة بالكامل +0005 | +0006 | echo "🚀 Starting Map-2 Environment Setup..." +0007 | +0008 | # 1. Create Data Directory on Server +0009 | echo "📂 Preparing data directory: infrastructure/osm-data-v2" +0010 | mkdir -p infrastructure/osm-data-v2 +0011 | +0012 | # 2. Check for PBF file +0013 | if [ ! -f "infrastructure/osm-data-v2/map2_enriched.osm.pbf" ]; then +0014 | echo "⚠️ No map2_enriched.osm.pbf found." +0015 | echo "💡 Copying existing master_map as a starting point..." +0016 | cp infrastructure/osm-data/master_map.osm.pbf infrastructure/osm-data-v2/map2_enriched.osm.pbf +0017 | fi +0018 | +0019 | # 3. Spin up the containers +0020 | echo "🏗️ Starting Map-2 Stack (DB, Redis, Tiles, Routing)..." +0021 | docker compose -f docker-compose.map2.yml up -d +0022 | +0023 | echo "✅ Map-2 is being provisioned." +0024 | echo "------------------------------------------------" +0025 | echo "Ports used:" +0026 | echo " - Martin (Tiles): 3203" +0027 | echo " - Database: 5433" +0028 | echo " - Routing: 8990" +0029 | echo " - Redis: 6382" +0030 | echo "------------------------------------------------" +0031 | echo "Intelligence Roadmap:" +0032 | echo "1. Run Overture downloader (Next step)" +0033 | echo "2. Inject Arabic names via Overpass API" +0034 | echo "3. Connect Martin to the new DB" + + +------------------------------------------------------------ +[2] اسم الملف: style.json +المسار: ./style.json +------------------------------------------------------------ +0001 | { +0002 | "version": 8, +0003 | "name": "Intaleq Premium Map Style", +0004 | "metadata": { +0005 | "brand": "Intaleq", +0006 | "version": "2.0.0", +0007 | "description": "Google + OSM hybrid style with 3D buildings, railways, subway, waterways, and Intaleq brand palette" +0008 | }, +0009 | "center": [ +0010 | 36.276008, +0011 | 33.513685 +0012 | ], +0013 | "zoom": 15, +0014 | "glyphs": "https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf", +0015 | "sprite": "https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite", +0016 | "sources": { +0017 | "local-osm-polygons": { +0018 | "type": "vector", +0019 | "tiles": [ +0020 | "https://tiles.intaleqapp.com/planet_osm_polygon/{z}/{x}/{y}" +0021 | ], +0022 | "maxzoom": 14, +0023 | "attribution": "© Intaleq | © OpenStreetMap contributors" +0024 | }, +0025 | "local-osm-lines": { +0026 | "type": "vector", +0027 | "tiles": [ +0028 | "https://tiles.intaleqapp.com/planet_osm_line/{z}/{x}/{y}" +0029 | ], +0030 | "maxzoom": 14 +0031 | }, +0032 | "local-osm-points": { +0033 | "type": "vector", +0034 | "tiles": [ +0035 | "https://tiles.intaleqapp.com/planet_osm_point/{z}/{x}/{y}" +0036 | ], +0037 | "maxzoom": 14 +0038 | }, +0039 | "places_egypt": { +0040 | "type": "vector", +0041 | "tiles": [ +0042 | "https://tiles.intaleqapp.com/places_egypt/{z}/{x}/{y}" +0043 | ], +0044 | "maxzoom": 14 +0045 | } +0046 | }, +0047 | "layers": [ +0048 | { +0049 | "id": "background", +0050 | "type": "background", +0051 | "paint": { +0052 | "background-color": "#EEF2F7" +0053 | } +0054 | }, +0055 | { +0056 | "id": "landuse-residential", +0057 | "type": "fill", +0058 | "source": "local-osm-polygons", +0059 | "source-layer": "planet_osm_polygon", +0060 | "filter": [ +0061 | "==", +0062 | "landuse", +0063 | "residential" +0064 | ], +0065 | "paint": { +0066 | "fill-color": "#F0F4F8", +0067 | "fill-opacity": 1 +0068 | } +0069 | }, +0070 | { +0071 | "id": "landuse-commercial", +0072 | "type": "fill", +0073 | "source": "local-osm-polygons", +0074 | "source-layer": "planet_osm_polygon", +0075 | "filter": [ +0076 | "==", +0077 | "landuse", +0078 | "commercial" +0079 | ], +0080 | "paint": { +0081 | "fill-color": "#FAF5EE", +0082 | "fill-opacity": 1 +0083 | } +0084 | }, +0085 | { +0086 | "id": "landuse-industrial", +0087 | "type": "fill", +0088 | "source": "local-osm-polygons", +0089 | "source-layer": "planet_osm_polygon", +0090 | "filter": [ +0091 | "in", +0092 | "landuse", +0093 | "industrial", +0094 | "railway" +0095 | ], +0096 | "paint": { +0097 | "fill-color": "#E4E8EE", +0098 | "fill-opacity": 1 +0099 | } +0100 | }, +0101 | { +0102 | "id": "landuse-cemetery", +0103 | "type": "fill", +0104 | "source": "local-osm-polygons", +0105 | "source-layer": "planet_osm_polygon", +0106 | "filter": [ +0107 | "==", +0108 | "landuse", +0109 | "cemetery" +0110 | ], +0111 | "paint": { +0112 | "fill-color": "#B8D4BA", +0113 | "fill-opacity": 0.9 +0114 | } +0115 | }, +0116 | { +0117 | "id": "landuse-military", +0118 | "type": "fill", +0119 | "source": "local-osm-polygons", +0120 | "source-layer": "planet_osm_polygon", +0121 | "filter": [ +0122 | "==", +0123 | "landuse", +0124 | "military" +0125 | ], +0126 | "paint": { +0127 | "fill-color": "#E2D9CC", +0128 | "fill-opacity": 0.8 +0129 | } +0130 | }, +0131 | { +0132 | "id": "park-layer", +0133 | "type": "fill", +0134 | "source": "local-osm-polygons", +0135 | "source-layer": "planet_osm_polygon", +0136 | "filter": [ +0137 | "any", +0138 | [ +0139 | "in", +0140 | "leisure", +0141 | "park", +0142 | "garden", +0143 | "nature_reserve", +0144 | "pitch", +0145 | "playground" +0146 | ], +0147 | [ +0148 | "in", +0149 | "landuse", +0150 | "grass", +0151 | "meadow", +0152 | "forest" +0153 | ], +0154 | [ +0155 | "in", +0156 | "natural", +0157 | "wood", +0158 | "scrub", +0159 | "heath" +0160 | ] +0161 | ], +0162 | "paint": { +0163 | "fill-color": [ +0164 | "match", +0165 | [ +0166 | "get", +0167 | "leisure" +0168 | ], +0169 | "pitch", +0170 | "#9ED4A0", +0171 | "playground", +0172 | "#B8E6B8", +0173 | "#C5E8C5" +0174 | ], +0175 | "fill-opacity": 0.85 +0176 | } +0177 | }, +0178 | { +0179 | "id": "park-outline", +0180 | "type": "line", +0181 | "source": "local-osm-polygons", +0182 | "source-layer": "planet_osm_polygon", +0183 | "filter": [ +0184 | "in", +0185 | "leisure", +0186 | "park", +0187 | "garden", +0188 | "nature_reserve" +0189 | ], +0190 | "paint": { +0191 | "line-color": "#94D4A0", +0192 | "line-width": 0.8, +0193 | "line-opacity": 0.7 +0194 | } +0195 | }, +0196 | { +0197 | "id": "water-polygon", +0198 | "type": "fill", +0199 | "source": "local-osm-polygons", +0200 | "source-layer": "planet_osm_polygon", +0201 | "filter": [ +0202 | "any", +0203 | [ +0204 | "in", +0205 | "natural", +0206 | "water", +0207 | "lake", +0208 | "bay" +0209 | ], +0210 | [ +0211 | "==", +0212 | "waterway", +0213 | "riverbank" +0214 | ], +0215 | [ +0216 | "in", +0217 | "landuse", +0218 | "basin", +0219 | "reservoir" +0220 | ], +0221 | [ +0222 | "==", +0223 | "amenity", +0224 | "fountain" +0225 | ] +0226 | ], +0227 | "paint": { +0228 | "fill-color": "#9ECFE8", +0229 | "fill-opacity": 0.95 +0230 | } +0231 | }, +0232 | { +0233 | "id": "water-polygon-outline", +0234 | "type": "line", +0235 | "source": "local-osm-polygons", +0236 | "source-layer": "planet_osm_polygon", +0237 | "filter": [ +0238 | "any", +0239 | [ +0240 | "in", +0241 | "natural", +0242 | "water", +0243 | "lake", +0244 | "bay" +0245 | ], +0246 | [ +0247 | "==", +0248 | "waterway", +0249 | "riverbank" +0250 | ], +0251 | [ +0252 | "in", +0253 | "landuse", +0254 | "basin", +0255 | "reservoir" +0256 | ] +0257 | ], +0258 | "paint": { +0259 | "line-color": "#6BB8D8", +0260 | "line-width": 0.8, +0261 | "line-opacity": 0.8 +0262 | } +0263 | }, +0264 | { +0265 | "id": "waterway-river", +0266 | "type": "line", +0267 | "source": "local-osm-lines", +0268 | "source-layer": "planet_osm_line", +0269 | "filter": [ +0270 | "all", +0271 | [ +0272 | "in", +0273 | "waterway", +0274 | "river", +0275 | "canal" +0276 | ], +0277 | [ +0278 | "!=", +0279 | "intermittent", +0280 | "yes" +0281 | ], +0282 | [ +0283 | "!=", +0284 | "seasonal", +0285 | "yes" +0286 | ], +0287 | [ +0288 | "!=", +0289 | "tunnel", +0290 | "yes" +0291 | ] +0292 | ], +0293 | "paint": { +0294 | "line-color": "#6BB8D8", +0295 | "line-width": [ +0296 | "interpolate", +0297 | [ +0298 | "linear" +0299 | ], +0300 | [ +0301 | "zoom" +0302 | ], +0303 | 10, +0304 | 1.5, +0305 | 14, +0306 | 4, +0307 | 16, +0308 | 7 +0309 | ], +0310 | "line-opacity": 0.95 +0311 | } +0312 | }, +0313 | { +0314 | "id": "waterway-stream-drain", +0315 | "type": "line", +0316 | "source": "local-osm-lines", +0317 | "source-layer": "planet_osm_line", +0318 | "filter": [ +0319 | "all", +0320 | [ +0321 | "in", +0322 | "waterway", +0323 | "stream", +0324 | "drain", +0325 | "ditch" +0326 | ], +0327 | [ +0328 | "!=", +0329 | "intermittent", +0330 | "yes" +0331 | ], +0332 | [ +0333 | "!=", +0334 | "seasonal", +0335 | "yes" +0336 | ], +0337 | [ +0338 | "!=", +0339 | "tunnel", +0340 | "yes" +0341 | ] +0342 | ], +0343 | "minzoom": 13, +0344 | "paint": { +0345 | "line-color": "#7FBFD8", +0346 | "line-width": [ +0347 | "interpolate", +0348 | [ +0349 | "linear" +0350 | ], +0351 | [ +0352 | "zoom" +0353 | ], +0354 | 13, +0355 | 0.8, +0356 | 16, +0357 | 2.5 +0358 | ], +0359 | "line-opacity": 0.85 +0360 | } +0361 | }, +0362 | { +0363 | "id": "railway-area", +0364 | "type": "fill", +0365 | "source": "local-osm-polygons", +0366 | "source-layer": "planet_osm_polygon", +0367 | "filter": [ +0368 | "==", +0369 | "landuse", +0370 | "railway" +0371 | ], +0372 | "paint": { +0373 | "fill-color": "#DDE2EA", +0374 | "fill-opacity": 0.9 +0375 | } +0376 | }, +0377 | { +0378 | "id": "railway-rail-casing", +0379 | "type": "line", +0380 | "source": "local-osm-lines", +0381 | "source-layer": "planet_osm_line", +0382 | "filter": [ +0383 | "all", +0384 | [ +0385 | "in", +0386 | "railway", +0387 | "rail", +0388 | "narrow_gauge", +0389 | "preserved" +0390 | ], +0391 | [ +0392 | "!=", +0393 | "service", +0394 | "yard" +0395 | ], +0396 | [ +0397 | "!=", +0398 | "service", +0399 | "siding" +0400 | ] +0401 | ], +0402 | "minzoom": 8, +0403 | "paint": { +0404 | "line-color": "#B0B8C5", +0405 | "line-width": [ +0406 | "interpolate", +0407 | [ +0408 | "linear" +0409 | ], +0410 | [ +0411 | "zoom" +0412 | ], +0413 | 8, +0414 | 2, +0415 | 12, +0416 | 4, +0417 | 16, +0418 | 8 +0419 | ] +0420 | } +0421 | }, +0422 | { +0423 | "id": "railway-rail-core", +0424 | "type": "line", +0425 | "source": "local-osm-lines", +0426 | "source-layer": "planet_osm_line", +0427 | "filter": [ +0428 | "all", +0429 | [ +0430 | "in", +0431 | "railway", +0432 | "rail", +0433 | "narrow_gauge", +0434 | "preserved" +0435 | ], +0436 | [ +0437 | "!=", +0438 | "service", +0439 | "yard" +0440 | ], +0441 | [ +0442 | "!=", +0443 | "service", +0444 | "siding" +0445 | ] +0446 | ], +0447 | "minzoom": 8, +0448 | "paint": { +0449 | "line-color": "#6B7A8E", +0450 | "line-width": [ +0451 | "interpolate", +0452 | [ +0453 | "linear" +0454 | ], +0455 | [ +0456 | "zoom" +0457 | ], +0458 | 8, +0459 | 1, +0460 | 12, +0461 | 2.5, +0462 | 16, +0463 | 5 +0464 | ], +0465 | "line-dasharray": [ +0466 | 6, +0467 | 4 +0468 | ] +0469 | } +0470 | }, +0471 | { +0472 | "id": "railway-subway-casing", +0473 | "type": "line", +0474 | "source": "local-osm-lines", +0475 | "source-layer": "planet_osm_line", +0476 | "filter": [ +0477 | "in", +0478 | "railway", +0479 | "subway", +0480 | "light_rail", +0481 | "tram", +0482 | "monorail" +0483 | ], +0484 | "minzoom": 10, +0485 | "paint": { +0486 | "line-color": [ +0487 | "match", +0488 | [ +0489 | "get", +0490 | "railway" +0491 | ], +0492 | "subway", +0493 | "#CC2233", +0494 | "light_rail", +0495 | "#0066CC", +0496 | "tram", +0497 | "#8833BB", +0498 | "monorail", +0499 | "#008855", +0500 | "#BB3344" +0501 | ], +0502 | "line-width": [ +0503 | "interpolate", +0504 | [ +0505 | "linear" +0506 | ], +0507 | [ +0508 | "zoom" +0509 | ], +0510 | 10, +0511 | 3, +0512 | 14, +0513 | 6, +0514 | 16, +0515 | 10 +0516 | ] +0517 | } +0518 | }, +0519 | { +0520 | "id": "railway-subway-core", +0521 | "type": "line", +0522 | "source": "local-osm-lines", +0523 | "source-layer": "planet_osm_line", +0524 | "filter": [ +0525 | "in", +0526 | "railway", +0527 | "subway", +0528 | "light_rail", +0529 | "tram", +0530 | "monorail" +0531 | ], +0532 | "minzoom": 10, +0533 | "paint": { +0534 | "line-color": [ +0535 | "match", +0536 | [ +0537 | "get", +0538 | "railway" +0539 | ], +0540 | "subway", +0541 | "#FF3347", +0542 | "light_rail", +0543 | "#2288FF", +0544 | "tram", +0545 | "#AA44EE", +0546 | "monorail", +0547 | "#00BB66", +0548 | "#FF4455" +0549 | ], +0550 | "line-width": [ +0551 | "interpolate", +0552 | [ +0553 | "linear" +0554 | ], +0555 | [ +0556 | "zoom" +0557 | ], +0558 | 10, +0559 | 1.5, +0560 | 14, +0561 | 3.5, +0562 | 16, +0563 | 6 +0564 | ] +0565 | } +0566 | }, +0567 | { +0568 | "id": "road-casing-track-path", +0569 | "type": "line", +0570 | "source": "local-osm-lines", +0571 | "source-layer": "planet_osm_line", +0572 | "filter": [ +0573 | "in", +0574 | "highway", +0575 | "track", +0576 | "path", +0577 | "footway", +0578 | "cycleway", +0579 | "steps" +0580 | ], +0581 | "minzoom": 14, +0582 | "paint": { +0583 | "line-color": "#C8CDD6", +0584 | "line-width": [ +0585 | "interpolate", +0586 | [ +0587 | "linear" +0588 | ], +0589 | [ +0590 | "zoom" +0591 | ], +0592 | 14, +0593 | 1, +0594 | 16, +0595 | 4 +0596 | ], +0597 | "line-dasharray": [ +0598 | 4, +0599 | 3 +0600 | ] +0601 | } +0602 | }, +0603 | { +0604 | "id": "road-casing-minor", +0605 | "type": "line", +0606 | "source": "local-osm-lines", +0607 | "source-layer": "planet_osm_line", +0608 | "filter": [ +0609 | "in", +0610 | "highway", +0611 | "residential", +0612 | "service", +0613 | "unclassified", +0614 | "living_street", +0615 | "pedestrian" +0616 | ], +0617 | "paint": { +0618 | "line-color": "#D4D8DF", +0619 | "line-width": [ +0620 | "interpolate", +0621 | [ +0622 | "linear" +0623 | ], +0624 | [ +0625 | "zoom" +0626 | ], +0627 | 12, +0628 | 1.5, +0629 | 16, +0630 | 10 +0631 | ], +0632 | "line-opacity": 0.7 +0633 | } +0634 | }, +0635 | { +0636 | "id": "road-core-minor", +0637 | "type": "line", +0638 | "source": "local-osm-lines", +0639 | "source-layer": "planet_osm_line", +0640 | "filter": [ +0641 | "in", +0642 | "highway", +0643 | "residential", +0644 | "service", +0645 | "unclassified", +0646 | "living_street", +0647 | "pedestrian" +0648 | ], +0649 | "paint": { +0650 | "line-color": "#FFFFFF", +0651 | "line-width": [ +0652 | "interpolate", +0653 | [ +0654 | "linear" +0655 | ], +0656 | [ +0657 | "zoom" +0658 | ], +0659 | 12, +0660 | 0.8, +0661 | 16, +0662 | 8 +0663 | ] +0664 | } +0665 | }, +0666 | { +0667 | "id": "road-casing-tertiary", +0668 | "type": "line", +0669 | "source": "local-osm-lines", +0670 | "source-layer": "planet_osm_line", +0671 | "filter": [ +0672 | "in", +0673 | "highway", +0674 | "tertiary", +0675 | "tertiary_link" +0676 | ], +0677 | "paint": { +0678 | "line-color": "#C9CED8", +0679 | "line-width": [ +0680 | "interpolate", +0681 | [ +0682 | "linear" +0683 | ], +0684 | [ +0685 | "zoom" +0686 | ], +0687 | 11, +0688 | 2, +0689 | 16, +0690 | 14 +0691 | ], +0692 | "line-opacity": 0.75 +0693 | } +0694 | }, +0695 | { +0696 | "id": "road-core-tertiary", +0697 | "type": "line", +0698 | "source": "local-osm-lines", +0699 | "source-layer": "planet_osm_line", +0700 | "filter": [ +0701 | "in", +0702 | "highway", +0703 | "tertiary", +0704 | "tertiary_link" +0705 | ], +0706 | "paint": { +0707 | "line-color": "#FFFFFF", +0708 | "line-width": [ +0709 | "interpolate", +0710 | [ +0711 | "linear" +0712 | ], +0713 | [ +0714 | "zoom" +0715 | ], +0716 | 11, +0717 | 1.2, +0718 | 16, +0719 | 11 +0720 | ] +0721 | } +0722 | }, +0723 | { +0724 | "id": "road-casing-secondary", +0725 | "type": "line", +0726 | "source": "local-osm-lines", +0727 | "source-layer": "planet_osm_line", +0728 | "filter": [ +0729 | "in", +0730 | "highway", +0731 | "secondary", +0732 | "secondary_link" +0733 | ], +0734 | "paint": { +0735 | "line-color": "#C4CFDE", +0736 | "line-width": [ +0737 | "interpolate", +0738 | [ +0739 | "linear" +0740 | ], +0741 | [ +0742 | "zoom" +0743 | ], +0744 | 11, +0745 | 2.5, +0746 | 16, +0747 | 16 +0748 | ], +0749 | "line-opacity": 0.8 +0750 | } +0751 | }, +0752 | { +0753 | "id": "road-core-secondary", +0754 | "type": "line", +0755 | "source": "local-osm-lines", +0756 | "source-layer": "planet_osm_line", +0757 | "filter": [ +0758 | "in", +0759 | "highway", +0760 | "secondary", +0761 | "secondary_link" +0762 | ], +0763 | "paint": { +0764 | "line-color": "#F8FBFF", +0765 | "line-width": [ +0766 | "interpolate", +0767 | [ +0768 | "linear" +0769 | ], +0770 | [ +0771 | "zoom" +0772 | ], +0773 | 11, +0774 | 1.8, +0775 | 16, +0776 | 13 +0777 | ] +0778 | } +0779 | }, +0780 | { +0781 | "id": "road-casing-primary", +0782 | "type": "line", +0783 | "source": "local-osm-lines", +0784 | "source-layer": "planet_osm_line", +0785 | "filter": [ +0786 | "in", +0787 | "highway", +0788 | "primary", +0789 | "primary_link" +0790 | ], +0791 | "paint": { +0792 | "line-color": "#C8B868", +0793 | "line-width": [ +0794 | "interpolate", +0795 | [ +0796 | "linear" +0797 | ], +0798 | [ +0799 | "zoom" +0800 | ], +0801 | 10, +0802 | 3, +0803 | 16, +0804 | 18 +0805 | ], +0806 | "line-opacity": 0.7 +0807 | } +0808 | }, +0809 | { +0810 | "id": "road-core-primary", +0811 | "type": "line", +0812 | "source": "local-osm-lines", +0813 | "source-layer": "planet_osm_line", +0814 | "filter": [ +0815 | "in", +0816 | "highway", +0817 | "primary", +0818 | "primary_link" +0819 | ], +0820 | "paint": { +0821 | "line-color": "#EDD870", +0822 | "line-width": [ +0823 | "interpolate", +0824 | [ +0825 | "linear" +0826 | ], +0827 | [ +0828 | "zoom" +0829 | ], +0830 | 10, +0831 | 2, +0832 | 16, +0833 | 14 +0834 | ] +0835 | } +0836 | }, +0837 | { +0838 | "id": "road-casing-motorway-trunk", +0839 | "type": "line", +0840 | "source": "local-osm-lines", +0841 | "source-layer": "planet_osm_line", +0842 | "filter": [ +0843 | "in", +0844 | "highway", +0845 | "motorway", +0846 | "motorway_link", +0847 | "trunk", +0848 | "trunk_link" +0849 | ], +0850 | "paint": { +0851 | "line-color": "#C8A84B", +0852 | "line-width": [ +0853 | "interpolate", +0854 | [ +0855 | "linear" +0856 | ], +0857 | [ +0858 | "zoom" +0859 | ], +0860 | 9, +0861 | 4, +0862 | 16, +0863 | 20 +0864 | ], +0865 | "line-opacity": 0.75 +0866 | } +0867 | }, +0868 | { +0869 | "id": "road-core-motorway-trunk", +0870 | "type": "line", +0871 | "source": "local-osm-lines", +0872 | "source-layer": "planet_osm_line", +0873 | "filter": [ +0874 | "in", +0875 | "highway", +0876 | "motorway", +0877 | "motorway_link", +0878 | "trunk", +0879 | "trunk_link" +0880 | ], +0881 | "paint": { +0882 | "line-color": "#F0C040", +0883 | "line-width": [ +0884 | "interpolate", +0885 | [ +0886 | "linear" +0887 | ], +0888 | [ +0889 | "zoom" +0890 | ], +0891 | 9, +0892 | 2.5, +0893 | 16, +0894 | 16 +0895 | ] +0896 | } +0897 | }, +0898 | { +0899 | "id": "building-fill-flat", +0900 | "type": "fill", +0901 | "source": "local-osm-polygons", +0902 | "source-layer": "planet_osm_polygon", +0903 | "filter": [ +0904 | "has", +0905 | "building" +0906 | ], +0907 | "maxzoom": 14, +0908 | "paint": { +0909 | "fill-color": "#DDD8D0", +0910 | "fill-opacity": 0.85, +0911 | "fill-outline-color": "#C4BEB4" +0912 | } +0913 | }, +0914 | { +0915 | "id": "building-3d", +0916 | "type": "fill-extrusion", +0917 | "source": "local-osm-polygons", +0918 | "source-layer": "planet_osm_polygon", +0919 | "minzoom": 14, +0920 | "filter": [ +0921 | "has", +0922 | "building" +0923 | ], +0924 | "paint": { +0925 | "fill-extrusion-color": [ +0926 | "match", +0927 | [ +0928 | "get", +0929 | "building" +0930 | ], +0931 | "commercial", +0932 | "#DDD5C5", +0933 | "retail", +0934 | "#E5D8C8", +0935 | "industrial", +0936 | "#D2D8E0", +0937 | "church", +0938 | "#DDD4EE", +0939 | "mosque", +0940 | "#CCE4D0", +0941 | "hospital", +0942 | "#EDD8D8", +0943 | "school", +0944 | "#E0E6CC", +0945 | "university", +0946 | "#D8E0C8", +0947 | "hotel", +0948 | "#D8DCF0", +0949 | "apartments", +0950 | "#E0DCD4", +0951 | "#DDD8D2" +0952 | ], +0953 | "fill-extrusion-height": [ +0954 | "interpolate", +0955 | [ +0956 | "linear" +0957 | ], +0958 | [ +0959 | "zoom" +0960 | ], +0961 | 14, +0962 | [ +0963 | "*", +0964 | [ +0965 | "coalesce", +0966 | [ +0967 | "to-number", +0968 | [ +0969 | "get", +0970 | "building:levels" +0971 | ], +0972 | null +0973 | ], +0974 | 3 +0975 | ], +0976 | 2.5 +0977 | ], +0978 | 17, +0979 | [ +0980 | "coalesce", +0981 | [ +0982 | "to-number", +0983 | [ +0984 | "get", +0985 | "height" +0986 | ], +0987 | null +0988 | ], +0989 | [ +0990 | "*", +0991 | [ +0992 | "to-number", +0993 | [ +0994 | "get", +0995 | "building:levels" +0996 | ], +0997 | 3 +0998 | ], +0999 | 3.5 +1000 | ], +1001 | 12 +1002 | ] +1003 | ], +1004 | "fill-extrusion-base": [ +1005 | "coalesce", +1006 | [ +1007 | "to-number", +1008 | [ +1009 | "get", +1010 | "min_height" +1011 | ], +1012 | null +1013 | ], +1014 | 0 +1015 | ], +1016 | "fill-extrusion-opacity": [ +1017 | "interpolate", +1018 | [ +1019 | "linear" +1020 | ], +1021 | [ +1022 | "zoom" +1023 | ], +1024 | 14, +1025 | 0.6, +1026 | 16, +1027 | 0.88 +1028 | ], +1029 | "fill-extrusion-vertical-gradient": true +1030 | } +1031 | }, +1032 | { +1033 | "id": "railway-label", +1034 | "type": "symbol", +1035 | "source": "local-osm-lines", +1036 | "source-layer": "planet_osm_line", +1037 | "filter": [ +1038 | "in", +1039 | "railway", +1040 | "rail", +1041 | "subway", +1042 | "light_rail", +1043 | "tram" +1044 | ], +1045 | "minzoom": 13, +1046 | "layout": { +1047 | "text-field": [ +1048 | "coalesce", +1049 | [ +1050 | "get", +1051 | "name:ar" +1052 | ], +1053 | [ +1054 | "get", +1055 | "name" +1056 | ], +1057 | "" +1058 | ], +1059 | "text-font": [ +1060 | "Noto Sans Regular" +1061 | ], +1062 | "text-size": 10, +1063 | "symbol-placement": "line", +1064 | "text-padding": 6, +1065 | "text-allow-overlap": false +1066 | }, +1067 | "paint": { +1068 | "text-color": [ +1069 | "match", +1070 | [ +1071 | "get", +1072 | "railway" +1073 | ], +1074 | "subway", +1075 | "#CC2233", +1076 | "light_rail", +1077 | "#0055BB", +1078 | "tram", +1079 | "#7722AA", +1080 | "#4A5568" +1081 | ], +1082 | "text-halo-color": "rgba(255,255,255,0.9)", +1083 | "text-halo-width": 2 +1084 | } +1085 | }, +1086 | { +1087 | "id": "waterway-label", +1088 | "type": "symbol", +1089 | "source": "local-osm-lines", +1090 | "source-layer": "planet_osm_line", +1091 | "filter": [ +1092 | "all", +1093 | [ +1094 | "in", +1095 | "waterway", +1096 | "river", +1097 | "canal" +1098 | ], +1099 | [ +1100 | "!=", +1101 | "intermittent", +1102 | "yes" +1103 | ], +1104 | [ +1105 | "has", +1106 | "name" +1107 | ] +1108 | ], +1109 | "minzoom": 12, +1110 | "layout": { +1111 | "text-field": [ +1112 | "coalesce", +1113 | [ +1114 | "get", +1115 | "name:ar" +1116 | ], +1117 | [ +1118 | "get", +1119 | "name" +1120 | ], +1121 | "" +1122 | ], +1123 | "text-font": [ +1124 | "Noto Sans Regular" +1125 | ], +1126 | "text-size": 11, +1127 | "symbol-placement": "line", +1128 | "text-letter-spacing": 0.1 +1129 | }, +1130 | "paint": { +1131 | "text-color": "#2E86AB", +1132 | "text-halo-color": "rgba(255,255,255,0.85)", +1133 | "text-halo-width": 2 +1134 | } +1135 | }, +1136 | { +1137 | "id": "building-number-polygon", +1138 | "type": "symbol", +1139 | "source": "local-osm-polygons", +1140 | "source-layer": "planet_osm_polygon", +1141 | "minzoom": 17, +1142 | "filter": [ +1143 | "has", +1144 | "addr:housenumber" +1145 | ], +1146 | "layout": { +1147 | "text-field": "{addr:housenumber}", +1148 | "text-font": [ +1149 | "Noto Sans Regular" +1150 | ], +1151 | "text-size": 10, +1152 | "text-allow-overlap": false, +1153 | "text-ignore-placement": false +1154 | }, +1155 | "paint": { +1156 | "text-color": "#5A5048", +1157 | "text-halo-color": "rgba(255,255,255,0.95)", +1158 | "text-halo-width": 1.5 +1159 | } +1160 | }, +1161 | { +1162 | "id": "building-number-point", +1163 | "type": "symbol", +1164 | "source": "local-osm-points", +1165 | "source-layer": "planet_osm_point", +1166 | "minzoom": 17, +1167 | "filter": [ +1168 | "has", +1169 | "addr:housenumber" +1170 | ], +1171 | "layout": { +1172 | "text-field": "{addr:housenumber}", +1173 | "text-font": [ +1174 | "Noto Sans Regular" +1175 | ], +1176 | "text-size": 10, +1177 | "text-allow-overlap": false +1178 | }, +1179 | "paint": { +1180 | "text-color": "#5A5048", +1181 | "text-halo-color": "rgba(255,255,255,0.95)", +1182 | "text-halo-width": 1.5 +1183 | } +1184 | }, +1185 | { +1186 | "id": "road-labels-minor", +1187 | "type": "symbol", +1188 | "source": "local-osm-lines", +1189 | "source-layer": "planet_osm_line", +1190 | "filter": [ +1191 | "in", +1192 | "highway", +1193 | "residential", +1194 | "service", +1195 | "unclassified", +1196 | "living_street" +1197 | ], +1198 | "minzoom": 16, +1199 | "layout": { +1200 | "text-field": [ +1201 | "coalesce", +1202 | [ +1203 | "get", +1204 | "name:ar" +1205 | ], +1206 | [ +1207 | "get", +1208 | "name" +1209 | ], +1210 | "" +1211 | ], +1212 | "text-font": [ +1213 | "Noto Sans Regular" +1214 | ], +1215 | "text-size": 11, +1216 | "symbol-placement": "line", +1217 | "text-letter-spacing": 0.04, +1218 | "text-padding": 4, +1219 | "text-allow-overlap": false +1220 | }, +1221 | "paint": { +1222 | "text-color": "#4A5568", +1223 | "text-halo-color": "rgba(255,255,255,0.85)", +1224 | "text-halo-width": 1.5 +1225 | } +1226 | }, +1227 | { +1228 | "id": "road-labels-major", +1229 | "type": "symbol", +1230 | "source": "local-osm-lines", +1231 | "source-layer": "planet_osm_line", +1232 | "filter": [ +1233 | "in", +1234 | "highway", +1235 | "primary", +1236 | "secondary", +1237 | "tertiary", +1238 | "motorway", +1239 | "trunk" +1240 | ], +1241 | "minzoom": 13, +1242 | "layout": { +1243 | "text-field": [ +1244 | "coalesce", +1245 | [ +1246 | "get", +1247 | "name:ar" +1248 | ], +1249 | [ +1250 | "get", +1251 | "name" +1252 | ], +1253 | "" +1254 | ], +1255 | "text-font": [ +1256 | "Noto Sans Regular" +1257 | ], +1258 | "text-size": [ +1259 | "interpolate", +1260 | [ +1261 | "linear" +1262 | ], +1263 | [ +1264 | "zoom" +1265 | ], +1266 | 13, +1267 | 11, +1268 | 16, +1269 | 14 +1270 | ], +1271 | "symbol-placement": "line", +1272 | "text-letter-spacing": 0.05, +1273 | "text-padding": 5, +1274 | "text-allow-overlap": false +1275 | }, +1276 | "paint": { +1277 | "text-color": "#2D3748", +1278 | "text-halo-color": "rgba(255,255,255,0.9)", +1279 | "text-halo-width": 2 +1280 | } +1281 | }, +1282 | { +1283 | "id": "poi-hospital", +1284 | "type": "symbol", +1285 | "source": "local-osm-points", +1286 | "source-layer": "planet_osm_point", +1287 | "minzoom": 13, +1288 | "filter": [ +1289 | "==", +1290 | "amenity", +1291 | "hospital" +1292 | ], +1293 | "layout": { +1294 | "icon-image": "hospital", +1295 | "icon-size": 1, +1296 | "text-field": [ +1297 | "coalesce", +1298 | [ +1299 | "get", +1300 | "name:ar" +1301 | ], +1302 | [ +1303 | "get", +1304 | "name" +1305 | ], +1306 | "" +1307 | ], +1308 | "text-font": [ +1309 | "Noto Sans Regular" +1310 | ], +1311 | "text-size": 11, +1312 | "text-offset": [ +1313 | 0, +1314 | 1.2 +1315 | ], +1316 | "text-anchor": "top", +1317 | "text-allow-overlap": false +1318 | }, +1319 | "paint": { +1320 | "text-color": "#C0392B", +1321 | "text-halo-color": "white", +1322 | "text-halo-width": 2 +1323 | } +1324 | }, +1325 | { +1326 | "id": "poi-pharmacy", +1327 | "type": "symbol", +1328 | "source": "local-osm-points", +1329 | "source-layer": "planet_osm_point", +1330 | "minzoom": 15, +1331 | "filter": [ +1332 | "==", +1333 | "amenity", +1334 | "pharmacy" +1335 | ], +1336 | "layout": { +1337 | "icon-image": "pharmacy", +1338 | "icon-size": 0.8, +1339 | "text-field": [ +1340 | "coalesce", +1341 | [ +1342 | "get", +1343 | "name:ar" +1344 | ], +1345 | [ +1346 | "get", +1347 | "name" +1348 | ], +1349 | "" +1350 | ], +1351 | "text-font": [ +1352 | "Noto Sans Regular" +1353 | ], +1354 | "text-size": 10, +1355 | "text-offset": [ +1356 | 0, +1357 | 1.2 +1358 | ], +1359 | "text-anchor": "top" +1360 | }, +1361 | "paint": { +1362 | "text-color": "#1A7A3C", +1363 | "text-halo-color": "white", +1364 | "text-halo-width": 1.5 +1365 | } +1366 | }, +1367 | { +1368 | "id": "poi-place-of-worship", +1369 | "type": "symbol", +1370 | "source": "local-osm-points", +1371 | "source-layer": "planet_osm_point", +1372 | "minzoom": 14, +1373 | "filter": [ +1374 | "==", +1375 | "amenity", +1376 | "place_of_worship" +1377 | ], +1378 | "layout": { +1379 | "icon-image": "tourist", +1380 | "icon-size": 0.8, +1381 | "text-field": [ +1382 | "coalesce", +1383 | [ +1384 | "get", +1385 | "name:ar" +1386 | ], +1387 | [ +1388 | "get", +1389 | "name" +1390 | ], +1391 | "" +1392 | ], +1393 | "text-font": [ +1394 | "Noto Sans Regular" +1395 | ], +1396 | "text-size": 11, +1397 | "text-offset": [ +1398 | 0, +1399 | 1.2 +1400 | ], +1401 | "text-anchor": "top" +1402 | }, +1403 | "paint": { +1404 | "text-color": "#1A6B3A", +1405 | "text-halo-color": "white", +1406 | "text-halo-width": 2 +1407 | } +1408 | }, +1409 | { +1410 | "id": "poi-restaurant-cafe", +1411 | "type": "symbol", +1412 | "source": "local-osm-points", +1413 | "source-layer": "planet_osm_point", +1414 | "minzoom": 16, +1415 | "filter": [ +1416 | "in", +1417 | "amenity", +1418 | "restaurant", +1419 | "cafe", +1420 | "fast_food" +1421 | ], +1422 | "layout": { +1423 | "icon-image": [ +1424 | "match", +1425 | [ +1426 | "get", +1427 | "amenity" +1428 | ], +1429 | "cafe", +1430 | "cafe", +1431 | "restaurant" +1432 | ], +1433 | "icon-size": 0.8, +1434 | "text-field": [ +1435 | "coalesce", +1436 | [ +1437 | "get", +1438 | "name:ar" +1439 | ], +1440 | [ +1441 | "get", +1442 | "name" +1443 | ], +1444 | "" +1445 | ], +1446 | "text-font": [ +1447 | "Noto Sans Regular" +1448 | ], +1449 | "text-size": 10, +1450 | "text-offset": [ +1451 | 0, +1452 | 1.2 +1453 | ], +1454 | "text-anchor": "top" +1455 | }, +1456 | "paint": { +1457 | "text-color": "#3D4A5C", +1458 | "text-halo-color": "white", +1459 | "text-halo-width": 1.5 +1460 | } +1461 | }, +1462 | { +1463 | "id": "poi-school", +1464 | "type": "symbol", +1465 | "source": "local-osm-points", +1466 | "source-layer": "planet_osm_point", +1467 | "minzoom": 14, +1468 | "filter": [ +1469 | "in", +1470 | "amenity", +1471 | "school", +1472 | "university", +1473 | "college" +1474 | ], +1475 | "layout": { +1476 | "icon-image": "college", +1477 | "icon-size": 0.8, +1478 | "text-field": [ +1479 | "coalesce", +1480 | [ +1481 | "get", +1482 | "name:ar" +1483 | ], +1484 | [ +1485 | "get", +1486 | "name" +1487 | ], +1488 | "" +1489 | ], +1490 | "text-font": [ +1491 | "Noto Sans Regular" +1492 | ], +1493 | "text-size": 11, +1494 | "text-offset": [ +1495 | 0, +1496 | 1.2 +1497 | ], +1498 | "text-anchor": "top" +1499 | }, +1500 | "paint": { +1501 | "text-color": "#5A4A8A", +1502 | "text-halo-color": "white", +1503 | "text-halo-width": 2 +1504 | } +1505 | }, +1506 | { +1507 | "id": "poi-transit-station", +1508 | "type": "symbol", +1509 | "source": "local-osm-points", +1510 | "source-layer": "planet_osm_point", +1511 | "minzoom": 12, +1512 | "filter": [ +1513 | "any", +1514 | [ +1515 | "==", +1516 | "railway", +1517 | "station" +1518 | ], +1519 | [ +1520 | "==", +1521 | "railway", +1522 | "halt" +1523 | ], +1524 | [ +1525 | "==", +1526 | "railway", +1527 | "tram_stop" +1528 | ], +1529 | [ +1530 | "==", +1531 | "station", +1532 | "subway" +1533 | ], +1534 | [ +1535 | "==", +1536 | "amenity", +1537 | "bus_station" +1538 | ] +1539 | ], +1540 | "layout": { +1541 | "icon-image": "rail", +1542 | "icon-size": 1, +1543 | "text-field": [ +1544 | "coalesce", +1545 | [ +1546 | "get", +1547 | "name:ar" +1548 | ], +1549 | [ +1550 | "get", +1551 | "name" +1552 | ], +1553 | "" +1554 | ], +1555 | "text-font": [ +1556 | "Noto Sans Regular" +1557 | ], +1558 | "text-size": 11, +1559 | "text-offset": [ +1560 | 0, +1561 | 1.4 +1562 | ], +1563 | "text-anchor": "top", +1564 | "text-allow-overlap": false +1565 | }, +1566 | "paint": { +1567 | "text-color": "#CC2233", +1568 | "text-halo-color": "rgba(255,255,255,0.95)", +1569 | "text-halo-width": 2 +1570 | } +1571 | }, +1572 | { +1573 | "id": "place-labels-area", +1574 | "type": "symbol", +1575 | "source": "local-osm-polygons", +1576 | "source-layer": "planet_osm_polygon", +1577 | "minzoom": 10, +1578 | "filter": [ +1579 | "has", +1580 | "name" +1581 | ], +1582 | "layout": { +1583 | "text-field": [ +1584 | "coalesce", +1585 | [ +1586 | "get", +1587 | "name:ar" +1588 | ], +1589 | [ +1590 | "get", +1591 | "name" +1592 | ], +1593 | "" +1594 | ], +1595 | "text-font": [ +1596 | "Noto Sans Regular" +1597 | ], +1598 | "text-size": [ +1599 | "interpolate", +1600 | [ +1601 | "linear" +1602 | ], +1603 | [ +1604 | "zoom" +1605 | ], +1606 | 10, +1607 | 10, +1608 | 14, +1609 | 13 +1610 | ], +1611 | "text-padding": 8, +1612 | "text-allow-overlap": false, +1613 | "text-ignore-placement": false +1614 | }, +1615 | "paint": { +1616 | "text-color": "#34495E", +1617 | "text-halo-color": "rgba(255,255,255,0.85)", +1618 | "text-halo-width": 2 +1619 | } +1620 | }, +1621 | { +1622 | "id": "place-labels-point", +1623 | "type": "symbol", +1624 | "source": "local-osm-points", +1625 | "source-layer": "planet_osm_point", +1626 | "minzoom": 10, +1627 | "filter": [ +1628 | "any", +1629 | [ +1630 | "in", +1631 | "place", +1632 | "city", +1633 | "town", +1634 | "village", +1635 | "suburb", +1636 | "neighbourhood", +1637 | "hamlet", +1638 | "locality", +1639 | "quarter" +1640 | ], +1641 | [ +1642 | "in", +1643 | "natural", +1644 | "peak", +1645 | "spring" +1646 | ] +1647 | ], +1648 | "layout": { +1649 | "text-field": [ +1650 | "coalesce", +1651 | [ +1652 | "get", +1653 | "name:ar" +1654 | ], +1655 | [ +1656 | "get", +1657 | "name" +1658 | ], +1659 | "" +1660 | ], +1661 | "text-font": [ +1662 | "Noto Sans Regular" +1663 | ], +1664 | "text-size": [ +1665 | "interpolate", +1666 | [ +1667 | "linear" +1668 | ], +1669 | [ +1670 | "zoom" +1671 | ], +1672 | 10, +1673 | [ +1674 | "match", +1675 | [ +1676 | "get", +1677 | "place" +1678 | ], +1679 | "city", +1680 | 16, +1681 | "town", +1682 | 14, +1683 | 11 +1684 | ], +1685 | 14, +1686 | [ +1687 | "match", +1688 | [ +1689 | "get", +1690 | "place" +1691 | ], +1692 | "city", +1693 | 20, +1694 | "town", +1695 | 16, +1696 | 13 +1697 | ], +1698 | 17, +1699 | 14 +1700 | ], +1701 | "text-letter-spacing": [ +1702 | "match", +1703 | [ +1704 | "get", +1705 | "place" +1706 | ], +1707 | "city", +1708 | 0.08, +1709 | "town", +1710 | 0.05, +1711 | 0.02 +1712 | ], +1713 | "text-anchor": "center", +1714 | "text-padding": 10, +1715 | "text-allow-overlap": false +1716 | }, +1717 | "paint": { +1718 | "text-color": [ +1719 | "match", +1720 | [ +1721 | "get", +1722 | "place" +1723 | ], +1724 | "city", +1725 | "#1A2740", +1726 | "town", +1727 | "#2C3E50", +1728 | "village", +1729 | "#3D4F62", +1730 | "suburb", +1731 | "#4A5568", +1732 | "neighbourhood", +1733 | "#556677", +1734 | "#607080" +1735 | ], +1736 | "text-halo-color": "rgba(255,255,255,0.92)", +1737 | "text-halo-width": [ +1738 | "match", +1739 | [ +1740 | "get", +1741 | "place" +1742 | ], +1743 | "city", +1744 | 3, +1745 | "town", +1746 | 2.5, +1747 | 2 +1748 | ] +1749 | } +1750 | }, +1751 | { +1752 | "id": "places-egypt-labels", +1753 | "type": "symbol", +1754 | "source": "places_egypt", +1755 | "source-layer": "places_egypt", +1756 | "minzoom": 12, +1757 | "layout": { +1758 | "text-field": [ +1759 | "coalesce", +1760 | [ +1761 | "get", +1762 | "name_ar" +1763 | ], +1764 | [ +1765 | "get", +1766 | "name" +1767 | ], +1768 | "" +1769 | ], +1770 | "text-font": [ +1771 | "Noto Sans Bold" +1772 | ], +1773 | "text-size": [ +1774 | "interpolate", +1775 | [ +1776 | "linear" +1777 | ], +1778 | [ +1779 | "zoom" +1780 | ], +1781 | 12, +1782 | 9, +1783 | 16, +1784 | 13 +1785 | ], +1786 | "text-offset": [ +1787 | 0, +1788 | 1.5 +1789 | ], +1790 | "text-anchor": "top", +1791 | "text-padding": 8, +1792 | "text-allow-overlap": false +1793 | }, +1794 | "paint": { +1795 | "text-color": "#2D3748", +1796 | "text-halo-color": "rgba(255, 255, 255, 0.9)", +1797 | "text-halo-width": 2 +1798 | } +1799 | } +1800 | ] +1801 | } + + +------------------------------------------------------------ +[3] اسم الملف: add-syria.sh +المسار: ./add-syria.sh +------------------------------------------------------------ +0001 | #!/bin/bash +0002 | set -e +0003 | +0004 | DATA_DIR="/home/hamzadoctor/app/infrastructure/osm-data" +0005 | +0006 | echo "Downloading Syria map data..." +0007 | curl -L -o "$DATA_DIR/syria-latest.osm.pbf" https://download.geofabrik.de/asia/syria-latest.osm.pbf +0008 | +0009 | echo "Merging Syria and Jordan..." +0010 | # Backup original Jordan if not already backed up +0011 | if [ ! -f "$DATA_DIR/jordan-latest.orig.osm.pbf" ]; then +0012 | cp "$DATA_DIR/jordan-latest.osm.pbf" "$DATA_DIR/jordan-latest.orig.osm.pbf" +0013 | fi +0014 | +0015 | # Use --overwrite flag and ensure .pbf extension is at the end +0016 | osmium merge "$DATA_DIR/jordan-latest.orig.osm.pbf" "$DATA_DIR/syria-latest.osm.pbf" -o "$DATA_DIR/levant-latest.osm.pbf" --overwrite +0017 | +0018 | echo "Replacing data file for import..." +0019 | mv "$DATA_DIR/levant-latest.osm.pbf" "$DATA_DIR/jordan-latest.osm.pbf" +0020 | +0021 | echo "Re-importing to database (this may take a few minutes)..." +0022 | cd /home/hamzadoctor/app +0023 | docker compose run --rm osm-import +0024 | +0025 | echo "Restarting services..." +0026 | docker compose restart martin api +0027 | +0028 | echo "✅ Success! Jordan and Syria are now merged and imported." + + +------------------------------------------------------------ +[4] اسم الملف: .env +المسار: ./.env +------------------------------------------------------------ +0001 | # Jordan Map Platform - Environment Variables +0002 | # المتغيرات البيئية لمنصة خرائط الأردن +0003 | +0004 | # Database +0005 | POSTGRES_USER=mapuser +0006 | POSTGRES_PASSWORD=mappass +0007 | POSTGRES_DB=mapdb +0008 | DB_HOST=db +0009 | DB_PORT=5432 +0010 | +0011 | # Redis +0012 | REDIS_HOST=redis +0013 | REDIS_PORT=6379 +0014 | +0015 | # GraphHopper (Routing) +0016 | GRAPH_HOPPER_URL=http://routing:8080 +0017 | +0018 | # Map Server +0019 | TILE_SERVER_URL=http://tileserver:8080 +0020 | +0021 | # API +0022 | API_PORT=3200 +0023 | WEB_PORT=3201 +0024 | NODE_ENV=development +0025 | +0026 | # External Location Server Integration (Every 10 days) +0027 | # إعدادات الاتصال بسيرفر المواقع الخارجي +0028 | LOCATION_SERVER_URL=https://location.intaleq.xyz +0029 | LOCATION_SERVER_API_KEY=intaleq_secure_key_2026_jy@kjhk +0030 | MAP_API_KEY=zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX +0031 | +0032 | # Telegram Notifications +0033 | TELEGRAM_BOT_TOKEN=7618792580:AAE6YAdrgUdcuUu9g8kXveCb-hiO3ECOd1g +0034 | TELEGRAM_CHAT_ID=1766663126 + + +------------------------------------------------------------ +[5] اسم الملف: sync_to_server.sh +المسار: ./sync_to_server.sh +------------------------------------------------------------ +0001 | #!/bin/bash +0002 | +0003 | # Configuration +0004 | SERVER_IP="188.68.36.205" +0005 | SERVER_USER="hamzadoctor" +0006 | REMOTE_PATH="/home/hamzadoctor/app" +0007 | KEY_PATH="/Users/hamzaaleghwairyeen/.ssh/doctory-key" +0008 | +0009 | echo "🚀 Starting sync to server: $SERVER_IP..." +0010 | +0011 | # Verify if the key exists +0012 | if [ ! -f "$KEY_PATH" ]; then +0013 | echo "⚠️ Warning: Identity file $KEY_PATH not found locally." +0014 | echo "Attempting sync using default SSH configuration..." +0015 | KEY_FLAG="" +0016 | else +0017 | KEY_FLAG="-i $KEY_PATH" +0018 | fi +0019 | +0020 | # Sync files using rsync (surgical sync) +0021 | rsync -avz --progress -e "ssh -o StrictHostKeyChecking=no" $KEY_FLAG \ +0022 | --exclude 'infrastructure/osm-data' \ +0023 | --exclude 'osm-data' \ +0024 | --exclude 'venv' \ +0025 | --exclude '.git' \ +0026 | --exclude 'node_modules' \ +0027 | --exclude '.DS_Store' \ +0028 | .env \ +0029 | docker-compose.yml \ +0030 | docker-compose.map2.yml \ +0031 | setup_map2.sh \ +0032 | apps \ +0033 | packages \ +0034 | infrastructure \ +0035 | $SERVER_USER@$SERVER_IP:$REMOTE_PATH/ +0036 | +0037 | if [ $? -eq 0 ]; then +0038 | echo "✅ Sync successful!" +0039 | echo "------------------------------------------------" +0040 | echo "Now run the following on your SERVER terminal:" +0041 | echo "cd $REMOTE_PATH" +0042 | echo "docker-compose up -d --build api" +0043 | echo "------------------------------------------------" +0044 | else +0045 | echo "❌ Sync failed. Please check your SSH connection or manually update the files." +0046 | fi + + +------------------------------------------------------------ +[6] اسم الملف: import_syria_csv.sh +المسار: ./import_syria_csv.sh +------------------------------------------------------------ +0001 | #!/bin/bash +0002 | set -e +0003 | +0004 | # Configuration +0005 | CONTAINER_NAME="map-db" +0006 | DB_USER="postgres" +0007 | DB_NAME="map_saas" +0008 | CSV_FILE="infrastructure/docker/postgis/data_clean_syria.csv" +0009 | SQL_SCHEMA="infrastructure/docker/postgis/create_syria_table.sql" +0010 | +0011 | # 1. Ensure the table exists +0012 | echo "Setting up Syria table..." +0013 | docker exec -i $CONTAINER_NAME psql -U $DB_USER -d $DB_NAME < $SQL_SCHEMA +0014 | +0015 | # 2. Upload CSV to container for fast bulk loading +0016 | echo "Copying CSV to database container..." +0017 | docker cp $CSV_FILE $CONTAINER_NAME:/tmp/data_clean_syria.csv +0018 | +0019 | # 3. Import data using a temporary table to map columns correctly +0020 | echo "Importing data into PostGIS..." +0021 | docker exec -i $CONTAINER_NAME psql -U $DB_USER -d $DB_NAME < neighbourhood +0039 | INSERT INTO places_syria (name, neighbourhood, latitude, longitude, created_at) +0040 | SELECT name, search_query, latitude, longitude, +0041 | CASE WHEN scraped_at IS NOT NULL AND scraped_at <> '' THEN scraped_at::timestamp ELSE CURRENT_TIMESTAMP END +0042 | FROM temp_syria_import; +0043 | +0044 | -- Update PostGIS geometry column +0045 | UPDATE places_syria +0046 | SET location = ST_SetSRID(ST_MakePoint(longitude, latitude), 4326) +0047 | WHERE location IS NULL AND latitude IS NOT NULL AND longitude IS NOT NULL; +0048 | +0049 | -- Clean up +0050 | DROP TABLE temp_syria_import; +0051 | EOF +0052 | +0053 | echo "✅ Import completed successfully!" +0054 | echo "Data sample from places_syria:" +0055 | docker exec -i $CONTAINER_NAME psql -U $DB_USER -d $DB_NAME -c "SELECT name, neighbourhood, latitude, longitude FROM places_syria LIMIT 5;" + + +------------------------------------------------------------ +[7] اسم الملف: index.d.ts +المسار: ./scraper/venv/lib/python3.13/site-packages/playwright/driver/package/index.d.ts +------------------------------------------------------------ +0001 | /** +0002 | * Copyright (c) Microsoft Corporation. +0003 | * +0004 | * Licensed under the Apache License, Version 2.0 (the "License"); +0005 | * you may not use this file except in compliance with the License. +0006 | * You may obtain a copy of the License at +0007 | * +0008 | * http://www.apache.org/licenses/LICENSE-2.0 +0009 | * +0010 | * Unless required by applicable law or agreed to in writing, software +0011 | * distributed under the License is distributed on an "AS IS" BASIS, +0012 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +0013 | * See the License for the specific language governing permissions and +0014 | * limitations under the License. +0015 | */ +0016 | +0017 | export * from './types/types'; + + +------------------------------------------------------------ +[8] اسم الملف: types.d.ts +المسار: ./scraper/venv/lib/python3.13/site-packages/playwright/driver/package/types/types.d.ts +------------------------------------------------------------ +0001 | // This file is generated by /utils/generate_types/index.js +0002 | /** +0003 | * Copyright (c) Microsoft Corporation. +0004 | * +0005 | * Licensed under the Apache License, Version 2.0 (the "License"); +0006 | * you may not use this file except in compliance with the License. +0007 | * You may obtain a copy of the License at +0008 | * +0009 | * http://www.apache.org/licenses/LICENSE-2.0 +0010 | * +0011 | * Unless required by applicable law or agreed to in writing, software +0012 | * distributed under the License is distributed on an "AS IS" BASIS, +0013 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +0014 | * See the License for the specific language governing permissions and +0015 | * limitations under the License. +0016 | */ +0017 | import { ChildProcess } from 'child_process'; +0018 | import { EventEmitter } from 'events'; +0019 | import { Readable } from 'stream'; +0020 | import { ReadStream } from 'fs'; +0021 | import { Protocol } from './protocol'; +0022 | import { Serializable, EvaluationArgument, PageFunction, PageFunctionOn, SmartHandle, ElementHandleForTag, BindingSource } from './structs'; +0023 | +0024 | type PageWaitForSelectorOptionsNotHidden = PageWaitForSelectorOptions & { +0025 | state?: 'visible'|'attached'; +0026 | }; +0027 | type ElementHandleWaitForSelectorOptionsNotHidden = ElementHandleWaitForSelectorOptions & { +0028 | state?: 'visible'|'attached'; +0029 | }; +0030 | +0031 | /** +0032 | * Page provides methods to interact with a single tab in a [Browser](https://playwright.dev/docs/api/class-browser), +0033 | * or an [extension background page](https://developer.chrome.com/extensions/background_pages) in Chromium. One +0034 | * [Browser](https://playwright.dev/docs/api/class-browser) instance might have multiple +0035 | * [Page](https://playwright.dev/docs/api/class-page) instances. +0036 | * +0037 | * This example creates a page, navigates it to a URL, and then saves a screenshot: +0038 | * +0039 | * ```js +0040 | * const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'. +0041 | * +0042 | * (async () => { +0043 | * const browser = await webkit.launch(); +0044 | * const context = await browser.newContext(); +0045 | * const page = await context.newPage(); +0046 | * await page.goto('https://example.com'); +0047 | * await page.screenshot({ path: 'screenshot.png' }); +0048 | * await browser.close(); +0049 | * })(); +0050 | * ``` +0051 | * +0052 | * The Page class emits various events (described below) which can be handled using any of Node's native +0053 | * [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter) methods, such as `on`, `once` or +0054 | * `removeListener`. +0055 | * +0056 | * This example logs a message for a single page `load` event: +0057 | * +0058 | * ```js +0059 | * page.once('load', () => console.log('Page loaded!')); +0060 | * ``` +0061 | * +0062 | * To unsubscribe from events use the `removeListener` method: +0063 | * +0064 | * ```js +0065 | * function logRequest(interceptedRequest) { +0066 | * console.log('A request was made:', interceptedRequest.url()); +0067 | * } +0068 | * page.on('request', logRequest); +0069 | * // Sometime later... +0070 | * page.removeListener('request', logRequest); +0071 | * ``` +0072 | * +0073 | */ +0074 | export interface Page { +0075 | /** +0076 | * Returns the value of the +0077 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression) invocation. +0078 | * +0079 | * If the function passed to the +0080 | * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a [Promise], +0081 | * then [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) would wait for +0082 | * the promise to resolve and return its value. +0083 | * +0084 | * If the function passed to the +0085 | * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a +0086 | * non-[Serializable] value, then +0087 | * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) resolves to +0088 | * `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: +0089 | * `-0`, `NaN`, `Infinity`, `-Infinity`. +0090 | * +0091 | * **Usage** +0092 | * +0093 | * Passing argument to [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression): +0094 | * +0095 | * ```js +0096 | * const result = await page.evaluate(([x, y]) => { +0097 | * return Promise.resolve(x * y); +0098 | * }, [7, 8]); +0099 | * console.log(result); // prints "56" +0100 | * ``` +0101 | * +0102 | * A string can also be passed in instead of a function: +0103 | * +0104 | * ```js +0105 | * console.log(await page.evaluate('1 + 2')); // prints "3" +0106 | * const x = 10; +0107 | * console.log(await page.evaluate(`1 + ${x}`)); // prints "11" +0108 | * ``` +0109 | * +0110 | * [ElementHandle](https://playwright.dev/docs/api/class-elementhandle) instances can be passed as an argument to the +0111 | * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate): +0112 | * +0113 | * ```js +0114 | * const bodyHandle = await page.evaluate('document.body'); +0115 | * const html = await page.evaluate(([body, suffix]) => +0116 | * body.innerHTML + suffix, [bodyHandle, 'hello'] +0117 | * ); +0118 | * await bodyHandle.dispose(); +0119 | * ``` +0120 | * +0121 | * @param pageFunction Function to be evaluated in the page context. +0122 | * @param arg Optional argument to pass to +0123 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression). +0124 | */ +0125 | evaluate(pageFunction: PageFunction, arg: Arg): Promise; +0126 | /** +0127 | * Returns the value of the +0128 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression) invocation. +0129 | * +0130 | * If the function passed to the +0131 | * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a [Promise], +0132 | * then [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) would wait for +0133 | * the promise to resolve and return its value. +0134 | * +0135 | * If the function passed to the +0136 | * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) returns a +0137 | * non-[Serializable] value, then +0138 | * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) resolves to +0139 | * `undefined`. Playwright also supports transferring some additional values that are not serializable by `JSON`: +0140 | * `-0`, `NaN`, `Infinity`, `-Infinity`. +0141 | * +0142 | * **Usage** +0143 | * +0144 | * Passing argument to [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression): +0145 | * +0146 | * ```js +0147 | * const result = await page.evaluate(([x, y]) => { +0148 | * return Promise.resolve(x * y); +0149 | * }, [7, 8]); +0150 | * console.log(result); // prints "56" +0151 | * ``` +0152 | * +0153 | * A string can also be passed in instead of a function: +0154 | * +0155 | * ```js +0156 | * console.log(await page.evaluate('1 + 2')); // prints "3" +0157 | * const x = 10; +0158 | * console.log(await page.evaluate(`1 + ${x}`)); // prints "11" +0159 | * ``` +0160 | * +0161 | * [ElementHandle](https://playwright.dev/docs/api/class-elementhandle) instances can be passed as an argument to the +0162 | * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate): +0163 | * +0164 | * ```js +0165 | * const bodyHandle = await page.evaluate('document.body'); +0166 | * const html = await page.evaluate(([body, suffix]) => +0167 | * body.innerHTML + suffix, [bodyHandle, 'hello'] +0168 | * ); +0169 | * await bodyHandle.dispose(); +0170 | * ``` +0171 | * +0172 | * @param pageFunction Function to be evaluated in the page context. +0173 | * @param arg Optional argument to pass to +0174 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-option-expression). +0175 | */ +0176 | evaluate(pageFunction: PageFunction, arg?: any): Promise; +0177 | +0178 | /** +0179 | * Returns the value of the +0180 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-handle-option-expression) invocation as a +0181 | * [JSHandle](https://playwright.dev/docs/api/class-jshandle). +0182 | * +0183 | * The only difference between +0184 | * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) and +0185 | * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) is that +0186 | * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) returns +0187 | * [JSHandle](https://playwright.dev/docs/api/class-jshandle). +0188 | * +0189 | * If the function passed to the +0190 | * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) returns +0191 | * a [Promise], then +0192 | * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) would +0193 | * wait for the promise to resolve and return its value. +0194 | * +0195 | * **Usage** +0196 | * +0197 | * ```js +0198 | * // Handle for the window object. +0199 | * const aWindowHandle = await page.evaluateHandle(() => Promise.resolve(window)); +0200 | * ``` +0201 | * +0202 | * A string can also be passed in instead of a function: +0203 | * +0204 | * ```js +0205 | * const aHandle = await page.evaluateHandle('document'); // Handle for the 'document' +0206 | * ``` +0207 | * +0208 | * [JSHandle](https://playwright.dev/docs/api/class-jshandle) instances can be passed as an argument to the +0209 | * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle): +0210 | * +0211 | * ```js +0212 | * const aHandle = await page.evaluateHandle(() => document.body); +0213 | * const resultHandle = await page.evaluateHandle(body => body.innerHTML, aHandle); +0214 | * console.log(await resultHandle.jsonValue()); +0215 | * await resultHandle.dispose(); +0216 | * ``` +0217 | * +0218 | * @param pageFunction Function to be evaluated in the page context. +0219 | * @param arg Optional argument to pass to +0220 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-handle-option-expression). +0221 | */ +0222 | evaluateHandle(pageFunction: PageFunction, arg: Arg): Promise>; +0223 | /** +0224 | * Returns the value of the +0225 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-handle-option-expression) invocation as a +0226 | * [JSHandle](https://playwright.dev/docs/api/class-jshandle). +0227 | * +0228 | * The only difference between +0229 | * [page.evaluate(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate) and +0230 | * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) is that +0231 | * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) returns +0232 | * [JSHandle](https://playwright.dev/docs/api/class-jshandle). +0233 | * +0234 | * If the function passed to the +0235 | * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) returns +0236 | * a [Promise], then +0237 | * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle) would +0238 | * wait for the promise to resolve and return its value. +0239 | * +0240 | * **Usage** +0241 | * +0242 | * ```js +0243 | * // Handle for the window object. +0244 | * const aWindowHandle = await page.evaluateHandle(() => Promise.resolve(window)); +0245 | * ``` +0246 | * +0247 | * A string can also be passed in instead of a function: +0248 | * +0249 | * ```js +0250 | * const aHandle = await page.evaluateHandle('document'); // Handle for the 'document' +0251 | * ``` +0252 | * +0253 | * [JSHandle](https://playwright.dev/docs/api/class-jshandle) instances can be passed as an argument to the +0254 | * [page.evaluateHandle(pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-evaluate-handle): +0255 | * +0256 | * ```js +0257 | * const aHandle = await page.evaluateHandle(() => document.body); +0258 | * const resultHandle = await page.evaluateHandle(body => body.innerHTML, aHandle); +0259 | * console.log(await resultHandle.jsonValue()); +0260 | * await resultHandle.dispose(); +0261 | * ``` +0262 | * +0263 | * @param pageFunction Function to be evaluated in the page context. +0264 | * @param arg Optional argument to pass to +0265 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-evaluate-handle-option-expression). +0266 | */ +0267 | evaluateHandle(pageFunction: PageFunction, arg?: any): Promise>; +0268 | +0269 | /** +0270 | * Adds a script which would be evaluated in one of the following scenarios: +0271 | * - Whenever the page is navigated. +0272 | * - Whenever the child frame is attached or navigated. In this case, the script is evaluated in the context of the +0273 | * newly attached frame. +0274 | * +0275 | * The script is evaluated after the document was created but before any of its scripts were run. This is useful to +0276 | * amend the JavaScript environment, e.g. to seed `Math.random`. +0277 | * +0278 | * **Usage** +0279 | * +0280 | * An example of overriding `Math.random` before the page loads: +0281 | * +0282 | * ```js +0283 | * // preload.js +0284 | * Math.random = () => 42; +0285 | * ``` +0286 | * +0287 | * ```js +0288 | * // In your playwright script, assuming the preload.js file is in same directory +0289 | * await page.addInitScript({ path: './preload.js' }); +0290 | * ``` +0291 | * +0292 | * ```js +0293 | * await page.addInitScript(mock => { +0294 | * window.mock = mock; +0295 | * }, mock); +0296 | * ``` +0297 | * +0298 | * **NOTE** The order of evaluation of multiple scripts installed via +0299 | * [browserContext.addInitScript(script[, arg])](https://playwright.dev/docs/api/class-browsercontext#browser-context-add-init-script) +0300 | * and [page.addInitScript(script[, arg])](https://playwright.dev/docs/api/class-page#page-add-init-script) is not +0301 | * defined. +0302 | * +0303 | * @param script Script to be evaluated in the page. +0304 | * @param arg Optional argument to pass to +0305 | * [`script`](https://playwright.dev/docs/api/class-page#page-add-init-script-option-script) (only supported when +0306 | * passing a function). +0307 | */ +0308 | addInitScript(script: PageFunction | { path?: string, content?: string }, arg?: Arg): Promise; +0309 | +0310 | /** +0311 | * **NOTE** Use locator-based [page.locator(selector[, options])](https://playwright.dev/docs/api/class-page#page-locator) +0312 | * instead. Read more about [locators](https://playwright.dev/docs/locators). +0313 | * +0314 | * The method finds an element matching the specified selector within the page. If no elements match the selector, the +0315 | * return value resolves to `null`. To wait for an element on the page, use +0316 | * [locator.waitFor([options])](https://playwright.dev/docs/api/class-locator#locator-wait-for). +0317 | * @param selector A selector to query for. +0318 | * @param options +0319 | */ +0320 | $(selector: K, options?: { strict: boolean }): Promise | null>; +0321 | /** +0322 | * **NOTE** Use locator-based [page.locator(selector[, options])](https://playwright.dev/docs/api/class-page#page-locator) +0323 | * instead. Read more about [locators](https://playwright.dev/docs/locators). +0324 | * +0325 | * The method finds an element matching the specified selector within the page. If no elements match the selector, the +0326 | * return value resolves to `null`. To wait for an element on the page, use +0327 | * [locator.waitFor([options])](https://playwright.dev/docs/api/class-locator#locator-wait-for). +0328 | * @param selector A selector to query for. +0329 | * @param options +0330 | */ +0331 | $(selector: string, options?: { strict: boolean }): Promise | null>; +0332 | +0333 | /** +0334 | * **NOTE** Use locator-based [page.locator(selector[, options])](https://playwright.dev/docs/api/class-page#page-locator) +0335 | * instead. Read more about [locators](https://playwright.dev/docs/locators). +0336 | * +0337 | * The method finds all elements matching the specified selector within the page. If no elements match the selector, +0338 | * the return value resolves to `[]`. +0339 | * @param selector A selector to query for. +0340 | */ +0341 | $$(selector: K): Promise[]>; +0342 | /** +0343 | * **NOTE** Use locator-based [page.locator(selector[, options])](https://playwright.dev/docs/api/class-page#page-locator) +0344 | * instead. Read more about [locators](https://playwright.dev/docs/locators). +0345 | * +0346 | * The method finds all elements matching the specified selector within the page. If no elements match the selector, +0347 | * the return value resolves to `[]`. +0348 | * @param selector A selector to query for. +0349 | */ +0350 | $$(selector: string): Promise[]>; +0351 | +0352 | /** +0353 | * **NOTE** This method does not wait for the element to pass actionability checks and therefore can lead to the flaky tests. +0354 | * Use +0355 | * [locator.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-locator#locator-evaluate), +0356 | * other [Locator](https://playwright.dev/docs/api/class-locator) helper methods or web-first assertions instead. +0357 | * +0358 | * The method finds an element matching the specified selector within the page and passes it as a first argument to +0359 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression). If no +0360 | * elements match the selector, the method throws an error. Returns the value of +0361 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression). +0362 | * +0363 | * If [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression) returns a +0364 | * [Promise], then +0365 | * [page.$eval(selector, pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-eval-on-selector) +0366 | * would wait for the promise to resolve and return its value. +0367 | * +0368 | * **Usage** +0369 | * +0370 | * ```js +0371 | * const searchValue = await page.$eval('#search', el => el.value); +0372 | * const preloadHref = await page.$eval('link[rel=preload]', el => el.href); +0373 | * const html = await page.$eval('.main-container', (e, suffix) => e.outerHTML + suffix, 'hello'); +0374 | * // In TypeScript, this example requires an explicit type annotation (HTMLLinkElement) on el: +0375 | * const preloadHrefTS = await page.$eval('link[rel=preload]', (el: HTMLLinkElement) => el.href); +0376 | * ``` +0377 | * +0378 | * @param selector A selector to query for. +0379 | * @param pageFunction Function to be evaluated in the page context. +0380 | * @param arg Optional argument to pass to +0381 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression). +0382 | * @param options +0383 | */ +0384 | $eval(selector: K, pageFunction: PageFunctionOn, arg: Arg): Promise; +0385 | /** +0386 | * **NOTE** This method does not wait for the element to pass actionability checks and therefore can lead to the flaky tests. +0387 | * Use +0388 | * [locator.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-locator#locator-evaluate), +0389 | * other [Locator](https://playwright.dev/docs/api/class-locator) helper methods or web-first assertions instead. +0390 | * +0391 | * The method finds an element matching the specified selector within the page and passes it as a first argument to +0392 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression). If no +0393 | * elements match the selector, the method throws an error. Returns the value of +0394 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression). +0395 | * +0396 | * If [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression) returns a +0397 | * [Promise], then +0398 | * [page.$eval(selector, pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-eval-on-selector) +0399 | * would wait for the promise to resolve and return its value. +0400 | * +0401 | * **Usage** +0402 | * +0403 | * ```js +0404 | * const searchValue = await page.$eval('#search', el => el.value); +0405 | * const preloadHref = await page.$eval('link[rel=preload]', el => el.href); +0406 | * const html = await page.$eval('.main-container', (e, suffix) => e.outerHTML + suffix, 'hello'); +0407 | * // In TypeScript, this example requires an explicit type annotation (HTMLLinkElement) on el: +0408 | * const preloadHrefTS = await page.$eval('link[rel=preload]', (el: HTMLLinkElement) => el.href); +0409 | * ``` +0410 | * +0411 | * @param selector A selector to query for. +0412 | * @param pageFunction Function to be evaluated in the page context. +0413 | * @param arg Optional argument to pass to +0414 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression). +0415 | * @param options +0416 | */ +0417 | $eval(selector: string, pageFunction: PageFunctionOn, arg: Arg): Promise; +0418 | /** +0419 | * **NOTE** This method does not wait for the element to pass actionability checks and therefore can lead to the flaky tests. +0420 | * Use +0421 | * [locator.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-locator#locator-evaluate), +0422 | * other [Locator](https://playwright.dev/docs/api/class-locator) helper methods or web-first assertions instead. +0423 | * +0424 | * The method finds an element matching the specified selector within the page and passes it as a first argument to +0425 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression). If no +0426 | * elements match the selector, the method throws an error. Returns the value of +0427 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression). +0428 | * +0429 | * If [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression) returns a +0430 | * [Promise], then +0431 | * [page.$eval(selector, pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-eval-on-selector) +0432 | * would wait for the promise to resolve and return its value. +0433 | * +0434 | * **Usage** +0435 | * +0436 | * ```js +0437 | * const searchValue = await page.$eval('#search', el => el.value); +0438 | * const preloadHref = await page.$eval('link[rel=preload]', el => el.href); +0439 | * const html = await page.$eval('.main-container', (e, suffix) => e.outerHTML + suffix, 'hello'); +0440 | * // In TypeScript, this example requires an explicit type annotation (HTMLLinkElement) on el: +0441 | * const preloadHrefTS = await page.$eval('link[rel=preload]', (el: HTMLLinkElement) => el.href); +0442 | * ``` +0443 | * +0444 | * @param selector A selector to query for. +0445 | * @param pageFunction Function to be evaluated in the page context. +0446 | * @param arg Optional argument to pass to +0447 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression). +0448 | * @param options +0449 | */ +0450 | $eval(selector: K, pageFunction: PageFunctionOn, arg?: any): Promise; +0451 | /** +0452 | * **NOTE** This method does not wait for the element to pass actionability checks and therefore can lead to the flaky tests. +0453 | * Use +0454 | * [locator.evaluate(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-locator#locator-evaluate), +0455 | * other [Locator](https://playwright.dev/docs/api/class-locator) helper methods or web-first assertions instead. +0456 | * +0457 | * The method finds an element matching the specified selector within the page and passes it as a first argument to +0458 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression). If no +0459 | * elements match the selector, the method throws an error. Returns the value of +0460 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression). +0461 | * +0462 | * If [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression) returns a +0463 | * [Promise], then +0464 | * [page.$eval(selector, pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-eval-on-selector) +0465 | * would wait for the promise to resolve and return its value. +0466 | * +0467 | * **Usage** +0468 | * +0469 | * ```js +0470 | * const searchValue = await page.$eval('#search', el => el.value); +0471 | * const preloadHref = await page.$eval('link[rel=preload]', el => el.href); +0472 | * const html = await page.$eval('.main-container', (e, suffix) => e.outerHTML + suffix, 'hello'); +0473 | * // In TypeScript, this example requires an explicit type annotation (HTMLLinkElement) on el: +0474 | * const preloadHrefTS = await page.$eval('link[rel=preload]', (el: HTMLLinkElement) => el.href); +0475 | * ``` +0476 | * +0477 | * @param selector A selector to query for. +0478 | * @param pageFunction Function to be evaluated in the page context. +0479 | * @param arg Optional argument to pass to +0480 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-option-expression). +0481 | * @param options +0482 | */ +0483 | $eval(selector: string, pageFunction: PageFunctionOn, arg?: any): Promise; +0484 | +0485 | /** +0486 | * **NOTE** In most cases, +0487 | * [locator.evaluateAll(pageFunction[, arg])](https://playwright.dev/docs/api/class-locator#locator-evaluate-all), +0488 | * other [Locator](https://playwright.dev/docs/api/class-locator) helper methods and web-first assertions do a better +0489 | * job. +0490 | * +0491 | * The method finds all elements matching the specified selector within the page and passes an array of matched +0492 | * elements as a first argument to +0493 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression). Returns +0494 | * the result of +0495 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression) +0496 | * invocation. +0497 | * +0498 | * If [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression) returns +0499 | * a [Promise], then +0500 | * [page.$$eval(selector, pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all) +0501 | * would wait for the promise to resolve and return its value. +0502 | * +0503 | * **Usage** +0504 | * +0505 | * ```js +0506 | * const divCounts = await page.$$eval('div', (divs, min) => divs.length >= min, 10); +0507 | * ``` +0508 | * +0509 | * @param selector A selector to query for. +0510 | * @param pageFunction Function to be evaluated in the page context. +0511 | * @param arg Optional argument to pass to +0512 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression). +0513 | */ +0514 | $$eval(selector: K, pageFunction: PageFunctionOn, arg: Arg): Promise; +0515 | /** +0516 | * **NOTE** In most cases, +0517 | * [locator.evaluateAll(pageFunction[, arg])](https://playwright.dev/docs/api/class-locator#locator-evaluate-all), +0518 | * other [Locator](https://playwright.dev/docs/api/class-locator) helper methods and web-first assertions do a better +0519 | * job. +0520 | * +0521 | * The method finds all elements matching the specified selector within the page and passes an array of matched +0522 | * elements as a first argument to +0523 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression). Returns +0524 | * the result of +0525 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression) +0526 | * invocation. +0527 | * +0528 | * If [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression) returns +0529 | * a [Promise], then +0530 | * [page.$$eval(selector, pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all) +0531 | * would wait for the promise to resolve and return its value. +0532 | * +0533 | * **Usage** +0534 | * +0535 | * ```js +0536 | * const divCounts = await page.$$eval('div', (divs, min) => divs.length >= min, 10); +0537 | * ``` +0538 | * +0539 | * @param selector A selector to query for. +0540 | * @param pageFunction Function to be evaluated in the page context. +0541 | * @param arg Optional argument to pass to +0542 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression). +0543 | */ +0544 | $$eval(selector: string, pageFunction: PageFunctionOn, arg: Arg): Promise; +0545 | /** +0546 | * **NOTE** In most cases, +0547 | * [locator.evaluateAll(pageFunction[, arg])](https://playwright.dev/docs/api/class-locator#locator-evaluate-all), +0548 | * other [Locator](https://playwright.dev/docs/api/class-locator) helper methods and web-first assertions do a better +0549 | * job. +0550 | * +0551 | * The method finds all elements matching the specified selector within the page and passes an array of matched +0552 | * elements as a first argument to +0553 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression). Returns +0554 | * the result of +0555 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression) +0556 | * invocation. +0557 | * +0558 | * If [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression) returns +0559 | * a [Promise], then +0560 | * [page.$$eval(selector, pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all) +0561 | * would wait for the promise to resolve and return its value. +0562 | * +0563 | * **Usage** +0564 | * +0565 | * ```js +0566 | * const divCounts = await page.$$eval('div', (divs, min) => divs.length >= min, 10); +0567 | * ``` +0568 | * +0569 | * @param selector A selector to query for. +0570 | * @param pageFunction Function to be evaluated in the page context. +0571 | * @param arg Optional argument to pass to +0572 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression). +0573 | */ +0574 | $$eval(selector: K, pageFunction: PageFunctionOn, arg?: any): Promise; +0575 | /** +0576 | * **NOTE** In most cases, +0577 | * [locator.evaluateAll(pageFunction[, arg])](https://playwright.dev/docs/api/class-locator#locator-evaluate-all), +0578 | * other [Locator](https://playwright.dev/docs/api/class-locator) helper methods and web-first assertions do a better +0579 | * job. +0580 | * +0581 | * The method finds all elements matching the specified selector within the page and passes an array of matched +0582 | * elements as a first argument to +0583 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression). Returns +0584 | * the result of +0585 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression) +0586 | * invocation. +0587 | * +0588 | * If [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression) returns +0589 | * a [Promise], then +0590 | * [page.$$eval(selector, pageFunction[, arg])](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all) +0591 | * would wait for the promise to resolve and return its value. +0592 | * +0593 | * **Usage** +0594 | * +0595 | * ```js +0596 | * const divCounts = await page.$$eval('div', (divs, min) => divs.length >= min, 10); +0597 | * ``` +0598 | * +0599 | * @param selector A selector to query for. +0600 | * @param pageFunction Function to be evaluated in the page context. +0601 | * @param arg Optional argument to pass to +0602 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-eval-on-selector-all-option-expression). +0603 | */ +0604 | $$eval(selector: string, pageFunction: PageFunctionOn, arg?: any): Promise; +0605 | +0606 | /** +0607 | * Returns when the +0608 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-wait-for-function-option-expression) returns a +0609 | * truthy value. It resolves to a JSHandle of the truthy value. +0610 | * +0611 | * **Usage** +0612 | * +0613 | * The +0614 | * [page.waitForFunction(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-wait-for-function) +0615 | * can be used to observe viewport size change: +0616 | * +0617 | * ```js +0618 | * const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'. +0619 | * +0620 | * (async () => { +0621 | * const browser = await webkit.launch(); +0622 | * const page = await browser.newPage(); +0623 | * const watchDog = page.waitForFunction(() => window.innerWidth < 100); +0624 | * await page.setViewportSize({ width: 50, height: 50 }); +0625 | * await watchDog; +0626 | * await browser.close(); +0627 | * })(); +0628 | * ``` +0629 | * +0630 | * To pass an argument to the predicate of +0631 | * [page.waitForFunction(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-wait-for-function) +0632 | * function: +0633 | * +0634 | * ```js +0635 | * const selector = '.foo'; +0636 | * await page.waitForFunction(selector => !!document.querySelector(selector), selector); +0637 | * ``` +0638 | * +0639 | * @param pageFunction Function to be evaluated in the page context. +0640 | * @param arg Optional argument to pass to +0641 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-wait-for-function-option-expression). +0642 | * @param options +0643 | */ +0644 | waitForFunction(pageFunction: PageFunction, arg: Arg, options?: PageWaitForFunctionOptions): Promise>; +0645 | /** +0646 | * Returns when the +0647 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-wait-for-function-option-expression) returns a +0648 | * truthy value. It resolves to a JSHandle of the truthy value. +0649 | * +0650 | * **Usage** +0651 | * +0652 | * The +0653 | * [page.waitForFunction(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-wait-for-function) +0654 | * can be used to observe viewport size change: +0655 | * +0656 | * ```js +0657 | * const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'. +0658 | * +0659 | * (async () => { +0660 | * const browser = await webkit.launch(); +0661 | * const page = await browser.newPage(); +0662 | * const watchDog = page.waitForFunction(() => window.innerWidth < 100); +0663 | * await page.setViewportSize({ width: 50, height: 50 }); +0664 | * await watchDog; +0665 | * await browser.close(); +0666 | * })(); +0667 | * ``` +0668 | * +0669 | * To pass an argument to the predicate of +0670 | * [page.waitForFunction(pageFunction[, arg, options])](https://playwright.dev/docs/api/class-page#page-wait-for-function) +0671 | * function: +0672 | * +0673 | * ```js +0674 | * const selector = '.foo'; +0675 | * await page.waitForFunction(selector => !!document.querySelector(selector), selector); +0676 | * ``` +0677 | * +0678 | * @param pageFunction Function to be evaluated in the page context. +0679 | * @param arg Optional argument to pass to +0680 | * [`pageFunction`](https://playwright.dev/docs/api/class-page#page-wait-for-function-option-expression). +0681 | * @param options +0682 | */ +0683 | waitForFunction(pageFunction: PageFunction, arg?: any, options?: PageWaitForFunctionOptions): Promise>; +0684 | +0685 | /** +0686 | * **NOTE** Use web assertions that assert visibility or a locator-based +0687 | * [locator.waitFor([options])](https://playwright.dev/docs/api/class-locator#locator-wait-for) instead. Read more +0688 | * about [locators](https://playwright.dev/docs/locators). +0689 | * +0690 | * Returns when element specified by selector satisfies +0691 | * [`state`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-state) option. Returns `null` if +0692 | * waiting for `hidden` or `detached`. +0693 | * +0694 | * **NOTE** Playwright automatically waits for element to be ready before performing an action. Using +0695 | * [Locator](https://playwright.dev/docs/api/class-locator) objects and web-first assertions makes the code +0696 | * wait-for-selector-free. +0697 | * +0698 | * Wait for the [`selector`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-selector) to +0699 | * satisfy [`state`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-state) option (either +0700 | * appear/disappear from dom, or become visible/hidden). If at the moment of calling the method +0701 | * [`selector`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-selector) already satisfies +0702 | * the condition, the method will return immediately. If the selector doesn't satisfy the condition for the +0703 | * [`timeout`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-timeout) milliseconds, the +0704 | * function will throw. +0705 | * +0706 | * **Usage** +0707 | * +0708 | * This method works across navigations: +0709 | * +0710 | * ```js +0711 | * const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'. +0712 | * +0713 | * (async () => { +0714 | * const browser = await chromium.launch(); +0715 | * const page = await browser.newPage(); +0716 | * for (const currentURL of ['https://google.com', 'https://bbc.com']) { +0717 | * await page.goto(currentURL); +0718 | * const element = await page.waitForSelector('img'); +0719 | * console.log('Loaded image: ' + await element.getAttribute('src')); +0720 | * } +0721 | * await browser.close(); +0722 | * })(); +0723 | * ``` +0724 | * +0725 | * @param selector A selector to query for. +0726 | * @param options +0727 | */ +0728 | waitForSelector(selector: K, options?: PageWaitForSelectorOptionsNotHidden): Promise>; +0729 | /** +0730 | * **NOTE** Use web assertions that assert visibility or a locator-based +0731 | * [locator.waitFor([options])](https://playwright.dev/docs/api/class-locator#locator-wait-for) instead. Read more +0732 | * about [locators](https://playwright.dev/docs/locators). +0733 | * +0734 | * Returns when element specified by selector satisfies +0735 | * [`state`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-state) option. Returns `null` if +0736 | * waiting for `hidden` or `detached`. +0737 | * +0738 | * **NOTE** Playwright automatically waits for element to be ready before performing an action. Using +0739 | * [Locator](https://playwright.dev/docs/api/class-locator) objects and web-first assertions makes the code +0740 | * wait-for-selector-free. +0741 | * +0742 | * Wait for the [`selector`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-selector) to +0743 | * satisfy [`state`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-state) option (either +0744 | * appear/disappear from dom, or become visible/hidden). If at the moment of calling the method +0745 | * [`selector`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-selector) already satisfies +0746 | * the condition, the method will return immediately. If the selector doesn't satisfy the condition for the +0747 | * [`timeout`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-timeout) milliseconds, the +0748 | * function will throw. +0749 | * +0750 | * **Usage** +0751 | * +0752 | * This method works across navigations: +0753 | * +0754 | * ```js +0755 | * const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'. +0756 | * +0757 | * (async () => { +0758 | * const browser = await chromium.launch(); +0759 | * const page = await browser.newPage(); +0760 | * for (const currentURL of ['https://google.com', 'https://bbc.com']) { +0761 | * await page.goto(currentURL); +0762 | * const element = await page.waitForSelector('img'); +0763 | * console.log('Loaded image: ' + await element.getAttribute('src')); +0764 | * } +0765 | * await browser.close(); +0766 | * })(); +0767 | * ``` +0768 | * +0769 | * @param selector A selector to query for. +0770 | * @param options +0771 | */ +0772 | waitForSelector(selector: string, options?: PageWaitForSelectorOptionsNotHidden): Promise>; +0773 | /** +0774 | * **NOTE** Use web assertions that assert visibility or a locator-based +0775 | * [locator.waitFor([options])](https://playwright.dev/docs/api/class-locator#locator-wait-for) instead. Read more +0776 | * about [locators](https://playwright.dev/docs/locators). +0777 | * +0778 | * Returns when element specified by selector satisfies +0779 | * [`state`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-state) option. Returns `null` if +0780 | * waiting for `hidden` or `detached`. +0781 | * +0782 | * **NOTE** Playwright automatically waits for element to be ready before performing an action. Using +0783 | * [Locator](https://playwright.dev/docs/api/class-locator) objects and web-first assertions makes the code +0784 | * wait-for-selector-free. +0785 | * +0786 | * Wait for the [`selector`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-selector) to +0787 | * satisfy [`state`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-state) option (either +0788 | * appear/disappear from dom, or become visible/hidden). If at the moment of calling the method +0789 | * [`selector`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-selector) already satisfies +0790 | * the condition, the method will return immediately. If the selector doesn't satisfy the condition for the +0791 | * [`timeout`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-timeout) milliseconds, the +0792 | * function will throw. +0793 | * +0794 | * **Usage** +0795 | * +0796 | * This method works across navigations: +0797 | * +0798 | * ```js +0799 | * const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'. +0800 | * +0801 | * (async () => { +0802 | * const browser = await chromium.launch(); +0803 | * const page = await browser.newPage(); +0804 | * for (const currentURL of ['https://google.com', 'https://bbc.com']) { +0805 | * await page.goto(currentURL); +0806 | * const element = await page.waitForSelector('img'); +0807 | * console.log('Loaded image: ' + await element.getAttribute('src')); +0808 | * } +0809 | * await browser.close(); +0810 | * })(); +0811 | * ``` +0812 | * +0813 | * @param selector A selector to query for. +0814 | * @param options +0815 | */ +0816 | waitForSelector(selector: K, options: PageWaitForSelectorOptions): Promise | null>; +0817 | /** +0818 | * **NOTE** Use web assertions that assert visibility or a locator-based +0819 | * [locator.waitFor([options])](https://playwright.dev/docs/api/class-locator#locator-wait-for) instead. Read more +0820 | * about [locators](https://playwright.dev/docs/locators). +0821 | * +0822 | * Returns when element specified by selector satisfies +0823 | * [`state`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-state) option. Returns `null` if +0824 | * waiting for `hidden` or `detached`. +0825 | * +0826 | * **NOTE** Playwright automatically waits for element to be ready before performing an action. Using +0827 | * [Locator](https://playwright.dev/docs/api/class-locator) objects and web-first assertions makes the code +0828 | * wait-for-selector-free. +0829 | * +0830 | * Wait for the [`selector`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-selector) to +0831 | * satisfy [`state`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-state) option (either +0832 | * appear/disappear from dom, or become visible/hidden). If at the moment of calling the method +0833 | * [`selector`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-selector) already satisfies +0834 | * the condition, the method will return immediately. If the selector doesn't satisfy the condition for the +0835 | * [`timeout`](https://playwright.dev/docs/api/class-page#page-wait-for-selector-option-timeout) milliseconds, the +0836 | * function will throw. +0837 | * +0838 | * **Usage** +0839 | * +0840 | * This method works across navigations: +0841 | * +0842 | * ```js +0843 | * const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'. +0844 | * +0845 | * (async () => { +0846 | * const browser = await chromium.launch(); +0847 | * const page = await browser.newPage(); +0848 | * for (const currentURL of ['https://google.com', 'https://bbc.com']) { +0849 | * await page.goto(currentURL); +0850 | * const element = await page.waitForSelector('img'); +0851 | * console.log('Loaded image: ' + await element.getAttribute('src')); +0852 | * } +0853 | * await browser.close(); +0854 | * })(); +0855 | * ``` +0856 | * +0857 | * @param selector A selector to query for. +0858 | * @param options +0859 | */ +0860 | waitForSelector(selector: string, options: PageWaitForSelectorOptions): Promise>; +0861 | +0862 | /** +0863 | * The method adds a function called +0864 | * [`name`](https://playwright.dev/docs/api/class-page#page-expose-binding-option-name) on the `window` object of +0865 | * every frame in this page. When called, the function executes +0866 | * [`callback`](https://playwright.dev/docs/api/class-page#page-expose-binding-option-callback) and returns a +0867 | * [Promise] which resolves to the return value of +0868 | * [`callback`](https://playwright.dev/docs/api/class-page#page-expose-binding-option-callback). If the +0869 | * [`callback`](https://playwright.dev/docs/api/class-page#page-expose-binding-option-callback) returns a [Promise], +0870 | * it will be awaited. +0871 | * +0872 | * The first argument of the +0873 | * [`callback`](https://playwright.dev/docs/api/class-page#page-expose-binding-option-callback) function contains +0874 | * information about the caller: `{ browserContext: BrowserContext, page: Page, frame: Frame }`. +0875 | * +0876 | * See +0877 | * [browserContext.exposeBinding(name, callback[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-expose-binding) +0878 | * for the context-wide version. +0879 | * +0880 | * **NOTE** Functions installed via +0881 | * [page.exposeBinding(name, callback[, options])](https://playwright.dev/docs/api/class-page#page-expose-binding) +0882 | * survive navigations. +0883 | * +0884 | * **Usage** +0885 | * +0886 | * An example of exposing page URL to all frames in a page: +0887 | * +0888 | * ```js +0889 | * const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'. +0890 | * +0891 | * (async () => { +0892 | * const browser = await webkit.launch({ headless: false }); +0893 | * const context = await browser.newContext(); +0894 | * const page = await context.newPage(); +0895 | * await page.exposeBinding('pageURL', ({ page }) => page.url()); +0896 | * await page.setContent(` +0897 | * +0902 | * +0903 | *
+0904 | * `); +0905 | * await page.click('button'); +0906 | * })(); +0907 | * ``` +0908 | * +0909 | * @param name Name of the function on the window object. +0910 | * @param callback Callback function that will be called in the Playwright's context. +0911 | * @param options +0912 | */ +0913 | exposeBinding(name: string, playwrightBinding: (source: BindingSource, arg: JSHandle) => any, options: { handle: true }): Promise; +0914 | /** +0915 | * The method adds a function called +0916 | * [`name`](https://playwright.dev/docs/api/class-page#page-expose-binding-option-name) on the `window` object of +0917 | * every frame in this page. When called, the function executes +0918 | * [`callback`](https://playwright.dev/docs/api/class-page#page-expose-binding-option-callback) and returns a +0919 | * [Promise] which resolves to the return value of +0920 | * [`callback`](https://playwright.dev/docs/api/class-page#page-expose-binding-option-callback). If the +0921 | * [`callback`](https://playwright.dev/docs/api/class-page#page-expose-binding-option-callback) returns a [Promise], +0922 | * it will be awaited. +0923 | * +0924 | * The first argument of the +0925 | * [`callback`](https://playwright.dev/docs/api/class-page#page-expose-binding-option-callback) function contains +0926 | * information about the caller: `{ browserContext: BrowserContext, page: Page, frame: Frame }`. +0927 | * +0928 | * See +0929 | * [browserContext.exposeBinding(name, callback[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-expose-binding) +0930 | * for the context-wide version. +0931 | * +0932 | * **NOTE** Functions installed via +0933 | * [page.exposeBinding(name, callback[, options])](https://playwright.dev/docs/api/class-page#page-expose-binding) +0934 | * survive navigations. +0935 | * +0936 | * **Usage** +0937 | * +0938 | * An example of exposing page URL to all frames in a page: +0939 | * +0940 | * ```js +0941 | * const { webkit } = require('playwright'); // Or 'chromium' or 'firefox'. +0942 | * +0943 | * (async () => { +0944 | * const browser = await webkit.launch({ headless: false }); +0945 | * const context = await browser.newContext(); +0946 | * const page = await context.newPage(); +0947 | * await page.exposeBinding('pageURL', ({ page }) => page.url()); +0948 | * await page.setContent(` +0949 | * +0954 | * +0955 | *
+0956 | * `); +0957 | * await page.click('button'); +0958 | * })(); +0959 | * ``` +0960 | * +0961 | * @param name Name of the function on the window object. +0962 | * @param callback Callback function that will be called in the Playwright's context. +0963 | * @param options +0964 | */ +0965 | exposeBinding(name: string, playwrightBinding: (source: BindingSource, ...args: any[]) => any, options?: { handle?: boolean }): Promise; +0966 | +0967 | /** +0968 | * Removes all the listeners of the given type (or all registered listeners if no type given). Allows to wait for +0969 | * async listeners to complete or to ignore subsequent errors from these listeners. +0970 | * +0971 | * **Usage** +0972 | * +0973 | * ```js +0974 | * page.on('request', async request => { +0975 | * const response = await request.response(); +0976 | * const body = await response.body(); +0977 | * console.log(body.byteLength); +0978 | * }); +0979 | * await page.goto('https://playwright.dev', { waitUntil: 'domcontentloaded' }); +0980 | * // Waits for all the reported 'request' events to resolve. +0981 | * await page.removeAllListeners('request', { behavior: 'wait' }); +0982 | * ``` +0983 | * +0984 | * @param type +0985 | * @param options +0986 | */ +0987 | removeAllListeners(type?: string): this; +0988 | /** +0989 | * Removes all the listeners of the given type (or all registered listeners if no type given). Allows to wait for +0990 | * async listeners to complete or to ignore subsequent errors from these listeners. +0991 | * +0992 | * **Usage** +0993 | * +0994 | * ```js +0995 | * page.on('request', async request => { +0996 | * const response = await request.response(); +0997 | * const body = await response.body(); +0998 | * console.log(body.byteLength); +0999 | * }); +1000 | * await page.goto('https://playwright.dev', { waitUntil: 'domcontentloaded' }); +1001 | * // Waits for all the reported 'request' events to resolve. +1002 | * await page.removeAllListeners('request', { behavior: 'wait' }); +1003 | * ``` +1004 | * +1005 | * @param type +1006 | * @param options +1007 | */ +1008 | removeAllListeners(type: string | undefined, options: { +1009 | /** +1010 | * Specifies whether to wait for already running listeners and what to do if they throw errors: +1011 | * - `'default'` - do not wait for current listener calls (if any) to finish, if the listener throws, it may result in unhandled error +1012 | * - `'wait'` - wait for current listener calls (if any) to finish +1013 | * - `'ignoreErrors'` - do not wait for current listener calls (if any) to finish, all errors thrown by the listeners after removal are silently caught +1014 | */ +1015 | behavior?: 'wait'|'ignoreErrors'|'default' +1016 | }): Promise; +1017 | /** +1018 | * Emitted when the page closes. +1019 | */ +1020 | on(event: 'close', listener: (page: Page) => any): this; +1021 | +1022 | /** +1023 | * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. +1024 | * +1025 | * The arguments passed into `console.log` are available on the +1026 | * [ConsoleMessage](https://playwright.dev/docs/api/class-consolemessage) event handler argument. +1027 | * +1028 | * **Usage** +1029 | * +1030 | * ```js +1031 | * page.on('console', async msg => { +1032 | * const values = []; +1033 | * for (const arg of msg.args()) +1034 | * values.push(await arg.jsonValue()); +1035 | * console.log(...values); +1036 | * }); +1037 | * await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); +1038 | * ``` +1039 | * +1040 | */ +1041 | on(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; +1042 | +1043 | /** +1044 | * Emitted when the page crashes. Browser pages might crash if they try to allocate too much memory. When the page +1045 | * crashes, ongoing and subsequent operations will throw. +1046 | * +1047 | * The most common way to deal with crashes is to catch an exception: +1048 | * +1049 | * ```js +1050 | * try { +1051 | * // Crash might happen during a click. +1052 | * await page.click('button'); +1053 | * // Or while waiting for an event. +1054 | * await page.waitForEvent('popup'); +1055 | * } catch (e) { +1056 | * // When the page crashes, exception message contains 'crash'. +1057 | * } +1058 | * ``` +1059 | * +1060 | */ +1061 | on(event: 'crash', listener: (page: Page) => any): this; +1062 | +1063 | /** +1064 | * Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Listener **must** +1065 | * either [dialog.accept([promptText])](https://playwright.dev/docs/api/class-dialog#dialog-accept) or +1066 | * [dialog.dismiss()](https://playwright.dev/docs/api/class-dialog#dialog-dismiss) the dialog - otherwise the page +1067 | * will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the +1068 | * dialog, and actions like click will never finish. +1069 | * +1070 | * **Usage** +1071 | * +1072 | * ```js +1073 | * page.on('dialog', dialog => dialog.accept()); +1074 | * ``` +1075 | * +1076 | * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) or +1077 | * [browserContext.on('dialog')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-dialog) +1078 | * listeners are present, all dialogs are automatically dismissed. +1079 | * +1080 | */ +1081 | on(event: 'dialog', listener: (dialog: Dialog) => any): this; +1082 | +1083 | /** +1084 | * Emitted when the JavaScript +1085 | * [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded) event is dispatched. +1086 | */ +1087 | on(event: 'domcontentloaded', listener: (page: Page) => any): this; +1088 | +1089 | /** +1090 | * Emitted when attachment download started. User can access basic file operations on downloaded content via the +1091 | * passed [Download](https://playwright.dev/docs/api/class-download) instance. +1092 | */ +1093 | on(event: 'download', listener: (download: Download) => any): this; +1094 | +1095 | /** +1096 | * Emitted when a file chooser is supposed to appear, such as after clicking the ``. Playwright can +1097 | * respond to it via setting the input files using +1098 | * [fileChooser.setFiles(files[, options])](https://playwright.dev/docs/api/class-filechooser#file-chooser-set-files) +1099 | * that can be uploaded after that. +1100 | * +1101 | * ```js +1102 | * page.on('filechooser', async fileChooser => { +1103 | * await fileChooser.setFiles(path.join(__dirname, '/tmp/myfile.pdf')); +1104 | * }); +1105 | * ``` +1106 | * +1107 | */ +1108 | on(event: 'filechooser', listener: (fileChooser: FileChooser) => any): this; +1109 | +1110 | /** +1111 | * Emitted when a frame is attached. +1112 | */ +1113 | on(event: 'frameattached', listener: (frame: Frame) => any): this; +1114 | +1115 | /** +1116 | * Emitted when a frame is detached. +1117 | */ +1118 | on(event: 'framedetached', listener: (frame: Frame) => any): this; +1119 | +1120 | /** +1121 | * Emitted when a frame is navigated to a new url. +1122 | */ +1123 | on(event: 'framenavigated', listener: (frame: Frame) => any): this; +1124 | +1125 | /** +1126 | * Emitted when the JavaScript [`load`](https://developer.mozilla.org/en-US/docs/Web/Events/load) event is dispatched. +1127 | */ +1128 | on(event: 'load', listener: (page: Page) => any): this; +1129 | +1130 | /** +1131 | * Emitted when an uncaught exception happens within the page. +1132 | * +1133 | * ```js +1134 | * // Log all uncaught errors to the terminal +1135 | * page.on('pageerror', exception => { +1136 | * console.log(`Uncaught exception: "${exception}"`); +1137 | * }); +1138 | * +1139 | * // Navigate to a page with an exception. +1140 | * await page.goto('data:text/html,'); +1141 | * ``` +1142 | * +1143 | */ +1144 | on(event: 'pageerror', listener: (error: Error) => any): this; +1145 | +1146 | /** +1147 | * Emitted when the page opens a new tab or window. This event is emitted in addition to the +1148 | * [browserContext.on('page')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-page), but +1149 | * only for popups relevant to this page. +1150 | * +1151 | * The earliest moment that page is available is when it has navigated to the initial url. For example, when opening a +1152 | * popup with `window.open('http://example.com')`, this event will fire when the network request to +1153 | * "http://example.com" is done and its response has started loading in the popup. If you would like to route/listen +1154 | * to this network request, use +1155 | * [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) +1156 | * and +1157 | * [browserContext.on('request')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-request) +1158 | * respectively instead of similar methods on the [Page](https://playwright.dev/docs/api/class-page). +1159 | * +1160 | * ```js +1161 | * // Start waiting for popup before clicking. Note no await. +1162 | * const popupPromise = page.waitForEvent('popup'); +1163 | * await page.getByText('open the popup').click(); +1164 | * const popup = await popupPromise; +1165 | * console.log(await popup.evaluate('location.href')); +1166 | * ``` +1167 | * +1168 | * **NOTE** Use +1169 | * [page.waitForLoadState([state, options])](https://playwright.dev/docs/api/class-page#page-wait-for-load-state) to +1170 | * wait until the page gets to a particular state (you should not need it in most cases). +1171 | * +1172 | */ +1173 | on(event: 'popup', listener: (page: Page) => any): this; +1174 | +1175 | /** +1176 | * Emitted when a page issues a request. The [request] object is read-only. In order to intercept and mutate requests, +1177 | * see [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route) or +1178 | * [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route). +1179 | */ +1180 | on(event: 'request', listener: (request: Request) => any): this; +1181 | +1182 | /** +1183 | * Emitted when a request fails, for example by timing out. +1184 | * +1185 | * ```js +1186 | * page.on('requestfailed', request => { +1187 | * console.log(request.url() + ' ' + request.failure().errorText); +1188 | * }); +1189 | * ``` +1190 | * +1191 | * **NOTE** HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request +1192 | * will complete with +1193 | * [page.on('requestfinished')](https://playwright.dev/docs/api/class-page#page-event-request-finished) event and not +1194 | * with [page.on('requestfailed')](https://playwright.dev/docs/api/class-page#page-event-request-failed). A request +1195 | * will only be considered failed when the client cannot get an HTTP response from the server, e.g. due to network +1196 | * error net::ERR_FAILED. +1197 | * +1198 | */ +1199 | on(event: 'requestfailed', listener: (request: Request) => any): this; +1200 | +1201 | /** +1202 | * Emitted when a request finishes successfully after downloading the response body. For a successful response, the +1203 | * sequence of events is `request`, `response` and `requestfinished`. +1204 | */ +1205 | on(event: 'requestfinished', listener: (request: Request) => any): this; +1206 | +1207 | /** +1208 | * Emitted when [response] status and headers are received for a request. For a successful response, the sequence of +1209 | * events is `request`, `response` and `requestfinished`. +1210 | */ +1211 | on(event: 'response', listener: (response: Response) => any): this; +1212 | +1213 | /** +1214 | * Emitted when [WebSocket](https://playwright.dev/docs/api/class-websocket) request is sent. +1215 | */ +1216 | on(event: 'websocket', listener: (webSocket: WebSocket) => any): this; +1217 | +1218 | /** +1219 | * Emitted when a dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is spawned +1220 | * by the page. +1221 | */ +1222 | on(event: 'worker', listener: (worker: Worker) => any): this; +1223 | +1224 | /** +1225 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1226 | */ +1227 | once(event: 'close', listener: (page: Page) => any): this; +1228 | +1229 | /** +1230 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1231 | */ +1232 | once(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; +1233 | +1234 | /** +1235 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1236 | */ +1237 | once(event: 'crash', listener: (page: Page) => any): this; +1238 | +1239 | /** +1240 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1241 | */ +1242 | once(event: 'dialog', listener: (dialog: Dialog) => any): this; +1243 | +1244 | /** +1245 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1246 | */ +1247 | once(event: 'domcontentloaded', listener: (page: Page) => any): this; +1248 | +1249 | /** +1250 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1251 | */ +1252 | once(event: 'download', listener: (download: Download) => any): this; +1253 | +1254 | /** +1255 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1256 | */ +1257 | once(event: 'filechooser', listener: (fileChooser: FileChooser) => any): this; +1258 | +1259 | /** +1260 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1261 | */ +1262 | once(event: 'frameattached', listener: (frame: Frame) => any): this; +1263 | +1264 | /** +1265 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1266 | */ +1267 | once(event: 'framedetached', listener: (frame: Frame) => any): this; +1268 | +1269 | /** +1270 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1271 | */ +1272 | once(event: 'framenavigated', listener: (frame: Frame) => any): this; +1273 | +1274 | /** +1275 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1276 | */ +1277 | once(event: 'load', listener: (page: Page) => any): this; +1278 | +1279 | /** +1280 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1281 | */ +1282 | once(event: 'pageerror', listener: (error: Error) => any): this; +1283 | +1284 | /** +1285 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1286 | */ +1287 | once(event: 'popup', listener: (page: Page) => any): this; +1288 | +1289 | /** +1290 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1291 | */ +1292 | once(event: 'request', listener: (request: Request) => any): this; +1293 | +1294 | /** +1295 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1296 | */ +1297 | once(event: 'requestfailed', listener: (request: Request) => any): this; +1298 | +1299 | /** +1300 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1301 | */ +1302 | once(event: 'requestfinished', listener: (request: Request) => any): this; +1303 | +1304 | /** +1305 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1306 | */ +1307 | once(event: 'response', listener: (response: Response) => any): this; +1308 | +1309 | /** +1310 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1311 | */ +1312 | once(event: 'websocket', listener: (webSocket: WebSocket) => any): this; +1313 | +1314 | /** +1315 | * Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event. +1316 | */ +1317 | once(event: 'worker', listener: (worker: Worker) => any): this; +1318 | +1319 | /** +1320 | * Emitted when the page closes. +1321 | */ +1322 | addListener(event: 'close', listener: (page: Page) => any): this; +1323 | +1324 | /** +1325 | * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. +1326 | * +1327 | * The arguments passed into `console.log` are available on the +1328 | * [ConsoleMessage](https://playwright.dev/docs/api/class-consolemessage) event handler argument. +1329 | * +1330 | * **Usage** +1331 | * +1332 | * ```js +1333 | * page.on('console', async msg => { +1334 | * const values = []; +1335 | * for (const arg of msg.args()) +1336 | * values.push(await arg.jsonValue()); +1337 | * console.log(...values); +1338 | * }); +1339 | * await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); +1340 | * ``` +1341 | * +1342 | */ +1343 | addListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; +1344 | +1345 | /** +1346 | * Emitted when the page crashes. Browser pages might crash if they try to allocate too much memory. When the page +1347 | * crashes, ongoing and subsequent operations will throw. +1348 | * +1349 | * The most common way to deal with crashes is to catch an exception: +1350 | * +1351 | * ```js +1352 | * try { +1353 | * // Crash might happen during a click. +1354 | * await page.click('button'); +1355 | * // Or while waiting for an event. +1356 | * await page.waitForEvent('popup'); +1357 | * } catch (e) { +1358 | * // When the page crashes, exception message contains 'crash'. +1359 | * } +1360 | * ``` +1361 | * +1362 | */ +1363 | addListener(event: 'crash', listener: (page: Page) => any): this; +1364 | +1365 | /** +1366 | * Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Listener **must** +1367 | * either [dialog.accept([promptText])](https://playwright.dev/docs/api/class-dialog#dialog-accept) or +1368 | * [dialog.dismiss()](https://playwright.dev/docs/api/class-dialog#dialog-dismiss) the dialog - otherwise the page +1369 | * will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the +1370 | * dialog, and actions like click will never finish. +1371 | * +1372 | * **Usage** +1373 | * +1374 | * ```js +1375 | * page.on('dialog', dialog => dialog.accept()); +1376 | * ``` +1377 | * +1378 | * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) or +1379 | * [browserContext.on('dialog')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-dialog) +1380 | * listeners are present, all dialogs are automatically dismissed. +1381 | * +1382 | */ +1383 | addListener(event: 'dialog', listener: (dialog: Dialog) => any): this; +1384 | +1385 | /** +1386 | * Emitted when the JavaScript +1387 | * [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded) event is dispatched. +1388 | */ +1389 | addListener(event: 'domcontentloaded', listener: (page: Page) => any): this; +1390 | +1391 | /** +1392 | * Emitted when attachment download started. User can access basic file operations on downloaded content via the +1393 | * passed [Download](https://playwright.dev/docs/api/class-download) instance. +1394 | */ +1395 | addListener(event: 'download', listener: (download: Download) => any): this; +1396 | +1397 | /** +1398 | * Emitted when a file chooser is supposed to appear, such as after clicking the ``. Playwright can +1399 | * respond to it via setting the input files using +1400 | * [fileChooser.setFiles(files[, options])](https://playwright.dev/docs/api/class-filechooser#file-chooser-set-files) +1401 | * that can be uploaded after that. +1402 | * +1403 | * ```js +1404 | * page.on('filechooser', async fileChooser => { +1405 | * await fileChooser.setFiles(path.join(__dirname, '/tmp/myfile.pdf')); +1406 | * }); +1407 | * ``` +1408 | * +1409 | */ +1410 | addListener(event: 'filechooser', listener: (fileChooser: FileChooser) => any): this; +1411 | +1412 | /** +1413 | * Emitted when a frame is attached. +1414 | */ +1415 | addListener(event: 'frameattached', listener: (frame: Frame) => any): this; +1416 | +1417 | /** +1418 | * Emitted when a frame is detached. +1419 | */ +1420 | addListener(event: 'framedetached', listener: (frame: Frame) => any): this; +1421 | +1422 | /** +1423 | * Emitted when a frame is navigated to a new url. +1424 | */ +1425 | addListener(event: 'framenavigated', listener: (frame: Frame) => any): this; +1426 | +1427 | /** +1428 | * Emitted when the JavaScript [`load`](https://developer.mozilla.org/en-US/docs/Web/Events/load) event is dispatched. +1429 | */ +1430 | addListener(event: 'load', listener: (page: Page) => any): this; +1431 | +1432 | /** +1433 | * Emitted when an uncaught exception happens within the page. +1434 | * +1435 | * ```js +1436 | * // Log all uncaught errors to the terminal +1437 | * page.on('pageerror', exception => { +1438 | * console.log(`Uncaught exception: "${exception}"`); +1439 | * }); +1440 | * +1441 | * // Navigate to a page with an exception. +1442 | * await page.goto('data:text/html,'); +1443 | * ``` +1444 | * +1445 | */ +1446 | addListener(event: 'pageerror', listener: (error: Error) => any): this; +1447 | +1448 | /** +1449 | * Emitted when the page opens a new tab or window. This event is emitted in addition to the +1450 | * [browserContext.on('page')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-page), but +1451 | * only for popups relevant to this page. +1452 | * +1453 | * The earliest moment that page is available is when it has navigated to the initial url. For example, when opening a +1454 | * popup with `window.open('http://example.com')`, this event will fire when the network request to +1455 | * "http://example.com" is done and its response has started loading in the popup. If you would like to route/listen +1456 | * to this network request, use +1457 | * [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) +1458 | * and +1459 | * [browserContext.on('request')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-request) +1460 | * respectively instead of similar methods on the [Page](https://playwright.dev/docs/api/class-page). +1461 | * +1462 | * ```js +1463 | * // Start waiting for popup before clicking. Note no await. +1464 | * const popupPromise = page.waitForEvent('popup'); +1465 | * await page.getByText('open the popup').click(); +1466 | * const popup = await popupPromise; +1467 | * console.log(await popup.evaluate('location.href')); +1468 | * ``` +1469 | * +1470 | * **NOTE** Use +1471 | * [page.waitForLoadState([state, options])](https://playwright.dev/docs/api/class-page#page-wait-for-load-state) to +1472 | * wait until the page gets to a particular state (you should not need it in most cases). +1473 | * +1474 | */ +1475 | addListener(event: 'popup', listener: (page: Page) => any): this; +1476 | +1477 | /** +1478 | * Emitted when a page issues a request. The [request] object is read-only. In order to intercept and mutate requests, +1479 | * see [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route) or +1480 | * [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route). +1481 | */ +1482 | addListener(event: 'request', listener: (request: Request) => any): this; +1483 | +1484 | /** +1485 | * Emitted when a request fails, for example by timing out. +1486 | * +1487 | * ```js +1488 | * page.on('requestfailed', request => { +1489 | * console.log(request.url() + ' ' + request.failure().errorText); +1490 | * }); +1491 | * ``` +1492 | * +1493 | * **NOTE** HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request +1494 | * will complete with +1495 | * [page.on('requestfinished')](https://playwright.dev/docs/api/class-page#page-event-request-finished) event and not +1496 | * with [page.on('requestfailed')](https://playwright.dev/docs/api/class-page#page-event-request-failed). A request +1497 | * will only be considered failed when the client cannot get an HTTP response from the server, e.g. due to network +1498 | * error net::ERR_FAILED. +1499 | * +1500 | */ +1501 | addListener(event: 'requestfailed', listener: (request: Request) => any): this; +1502 | +1503 | /** +1504 | * Emitted when a request finishes successfully after downloading the response body. For a successful response, the +1505 | * sequence of events is `request`, `response` and `requestfinished`. +1506 | */ +1507 | addListener(event: 'requestfinished', listener: (request: Request) => any): this; +1508 | +1509 | /** +1510 | * Emitted when [response] status and headers are received for a request. For a successful response, the sequence of +1511 | * events is `request`, `response` and `requestfinished`. +1512 | */ +1513 | addListener(event: 'response', listener: (response: Response) => any): this; +1514 | +1515 | /** +1516 | * Emitted when [WebSocket](https://playwright.dev/docs/api/class-websocket) request is sent. +1517 | */ +1518 | addListener(event: 'websocket', listener: (webSocket: WebSocket) => any): this; +1519 | +1520 | /** +1521 | * Emitted when a dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is spawned +1522 | * by the page. +1523 | */ +1524 | addListener(event: 'worker', listener: (worker: Worker) => any): this; +1525 | +1526 | /** +1527 | * Removes an event listener added by `on` or `addListener`. +1528 | */ +1529 | removeListener(event: 'close', listener: (page: Page) => any): this; +1530 | +1531 | /** +1532 | * Removes an event listener added by `on` or `addListener`. +1533 | */ +1534 | removeListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; +1535 | +1536 | /** +1537 | * Removes an event listener added by `on` or `addListener`. +1538 | */ +1539 | removeListener(event: 'crash', listener: (page: Page) => any): this; +1540 | +1541 | /** +1542 | * Removes an event listener added by `on` or `addListener`. +1543 | */ +1544 | removeListener(event: 'dialog', listener: (dialog: Dialog) => any): this; +1545 | +1546 | /** +1547 | * Removes an event listener added by `on` or `addListener`. +1548 | */ +1549 | removeListener(event: 'domcontentloaded', listener: (page: Page) => any): this; +1550 | +1551 | /** +1552 | * Removes an event listener added by `on` or `addListener`. +1553 | */ +1554 | removeListener(event: 'download', listener: (download: Download) => any): this; +1555 | +1556 | /** +1557 | * Removes an event listener added by `on` or `addListener`. +1558 | */ +1559 | removeListener(event: 'filechooser', listener: (fileChooser: FileChooser) => any): this; +1560 | +1561 | /** +1562 | * Removes an event listener added by `on` or `addListener`. +1563 | */ +1564 | removeListener(event: 'frameattached', listener: (frame: Frame) => any): this; +1565 | +1566 | /** +1567 | * Removes an event listener added by `on` or `addListener`. +1568 | */ +1569 | removeListener(event: 'framedetached', listener: (frame: Frame) => any): this; +1570 | +1571 | /** +1572 | * Removes an event listener added by `on` or `addListener`. +1573 | */ +1574 | removeListener(event: 'framenavigated', listener: (frame: Frame) => any): this; +1575 | +1576 | /** +1577 | * Removes an event listener added by `on` or `addListener`. +1578 | */ +1579 | removeListener(event: 'load', listener: (page: Page) => any): this; +1580 | +1581 | /** +1582 | * Removes an event listener added by `on` or `addListener`. +1583 | */ +1584 | removeListener(event: 'pageerror', listener: (error: Error) => any): this; +1585 | +1586 | /** +1587 | * Removes an event listener added by `on` or `addListener`. +1588 | */ +1589 | removeListener(event: 'popup', listener: (page: Page) => any): this; +1590 | +1591 | /** +1592 | * Removes an event listener added by `on` or `addListener`. +1593 | */ +1594 | removeListener(event: 'request', listener: (request: Request) => any): this; +1595 | +1596 | /** +1597 | * Removes an event listener added by `on` or `addListener`. +1598 | */ +1599 | removeListener(event: 'requestfailed', listener: (request: Request) => any): this; +1600 | +1601 | /** +1602 | * Removes an event listener added by `on` or `addListener`. +1603 | */ +1604 | removeListener(event: 'requestfinished', listener: (request: Request) => any): this; +1605 | +1606 | /** +1607 | * Removes an event listener added by `on` or `addListener`. +1608 | */ +1609 | removeListener(event: 'response', listener: (response: Response) => any): this; +1610 | +1611 | /** +1612 | * Removes an event listener added by `on` or `addListener`. +1613 | */ +1614 | removeListener(event: 'websocket', listener: (webSocket: WebSocket) => any): this; +1615 | +1616 | /** +1617 | * Removes an event listener added by `on` or `addListener`. +1618 | */ +1619 | removeListener(event: 'worker', listener: (worker: Worker) => any): this; +1620 | +1621 | /** +1622 | * Removes an event listener added by `on` or `addListener`. +1623 | */ +1624 | off(event: 'close', listener: (page: Page) => any): this; +1625 | +1626 | /** +1627 | * Removes an event listener added by `on` or `addListener`. +1628 | */ +1629 | off(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; +1630 | +1631 | /** +1632 | * Removes an event listener added by `on` or `addListener`. +1633 | */ +1634 | off(event: 'crash', listener: (page: Page) => any): this; +1635 | +1636 | /** +1637 | * Removes an event listener added by `on` or `addListener`. +1638 | */ +1639 | off(event: 'dialog', listener: (dialog: Dialog) => any): this; +1640 | +1641 | /** +1642 | * Removes an event listener added by `on` or `addListener`. +1643 | */ +1644 | off(event: 'domcontentloaded', listener: (page: Page) => any): this; +1645 | +1646 | /** +1647 | * Removes an event listener added by `on` or `addListener`. +1648 | */ +1649 | off(event: 'download', listener: (download: Download) => any): this; +1650 | +1651 | /** +1652 | * Removes an event listener added by `on` or `addListener`. +1653 | */ +1654 | off(event: 'filechooser', listener: (fileChooser: FileChooser) => any): this; +1655 | +1656 | /** +1657 | * Removes an event listener added by `on` or `addListener`. +1658 | */ +1659 | off(event: 'frameattached', listener: (frame: Frame) => any): this; +1660 | +1661 | /** +1662 | * Removes an event listener added by `on` or `addListener`. +1663 | */ +1664 | off(event: 'framedetached', listener: (frame: Frame) => any): this; +1665 | +1666 | /** +1667 | * Removes an event listener added by `on` or `addListener`. +1668 | */ +1669 | off(event: 'framenavigated', listener: (frame: Frame) => any): this; +1670 | +1671 | /** +1672 | * Removes an event listener added by `on` or `addListener`. +1673 | */ +1674 | off(event: 'load', listener: (page: Page) => any): this; +1675 | +1676 | /** +1677 | * Removes an event listener added by `on` or `addListener`. +1678 | */ +1679 | off(event: 'pageerror', listener: (error: Error) => any): this; +1680 | +1681 | /** +1682 | * Removes an event listener added by `on` or `addListener`. +1683 | */ +1684 | off(event: 'popup', listener: (page: Page) => any): this; +1685 | +1686 | /** +1687 | * Removes an event listener added by `on` or `addListener`. +1688 | */ +1689 | off(event: 'request', listener: (request: Request) => any): this; +1690 | +1691 | /** +1692 | * Removes an event listener added by `on` or `addListener`. +1693 | */ +1694 | off(event: 'requestfailed', listener: (request: Request) => any): this; +1695 | +1696 | /** +1697 | * Removes an event listener added by `on` or `addListener`. +1698 | */ +1699 | off(event: 'requestfinished', listener: (request: Request) => any): this; +1700 | +1701 | /** +1702 | * Removes an event listener added by `on` or `addListener`. +1703 | */ +1704 | off(event: 'response', listener: (response: Response) => any): this; +1705 | +1706 | /** +1707 | * Removes an event listener added by `on` or `addListener`. +1708 | */ +1709 | off(event: 'websocket', listener: (webSocket: WebSocket) => any): this; +1710 | +1711 | /** +1712 | * Removes an event listener added by `on` or `addListener`. +1713 | */ +1714 | off(event: 'worker', listener: (worker: Worker) => any): this; +1715 | +1716 | /** +1717 | * Emitted when the page closes. +1718 | */ +1719 | prependListener(event: 'close', listener: (page: Page) => any): this; +1720 | +1721 | /** +1722 | * Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. +1723 | * +1724 | * The arguments passed into `console.log` are available on the +1725 | * [ConsoleMessage](https://playwright.dev/docs/api/class-consolemessage) event handler argument. +1726 | * +1727 | * **Usage** +1728 | * +1729 | * ```js +1730 | * page.on('console', async msg => { +1731 | * const values = []; +1732 | * for (const arg of msg.args()) +1733 | * values.push(await arg.jsonValue()); +1734 | * console.log(...values); +1735 | * }); +1736 | * await page.evaluate(() => console.log('hello', 5, { foo: 'bar' })); +1737 | * ``` +1738 | * +1739 | */ +1740 | prependListener(event: 'console', listener: (consoleMessage: ConsoleMessage) => any): this; +1741 | +1742 | /** +1743 | * Emitted when the page crashes. Browser pages might crash if they try to allocate too much memory. When the page +1744 | * crashes, ongoing and subsequent operations will throw. +1745 | * +1746 | * The most common way to deal with crashes is to catch an exception: +1747 | * +1748 | * ```js +1749 | * try { +1750 | * // Crash might happen during a click. +1751 | * await page.click('button'); +1752 | * // Or while waiting for an event. +1753 | * await page.waitForEvent('popup'); +1754 | * } catch (e) { +1755 | * // When the page crashes, exception message contains 'crash'. +1756 | * } +1757 | * ``` +1758 | * +1759 | */ +1760 | prependListener(event: 'crash', listener: (page: Page) => any): this; +1761 | +1762 | /** +1763 | * Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Listener **must** +1764 | * either [dialog.accept([promptText])](https://playwright.dev/docs/api/class-dialog#dialog-accept) or +1765 | * [dialog.dismiss()](https://playwright.dev/docs/api/class-dialog#dialog-dismiss) the dialog - otherwise the page +1766 | * will [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking) waiting for the +1767 | * dialog, and actions like click will never finish. +1768 | * +1769 | * **Usage** +1770 | * +1771 | * ```js +1772 | * page.on('dialog', dialog => dialog.accept()); +1773 | * ``` +1774 | * +1775 | * **NOTE** When no [page.on('dialog')](https://playwright.dev/docs/api/class-page#page-event-dialog) or +1776 | * [browserContext.on('dialog')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-dialog) +1777 | * listeners are present, all dialogs are automatically dismissed. +1778 | * +1779 | */ +1780 | prependListener(event: 'dialog', listener: (dialog: Dialog) => any): this; +1781 | +1782 | /** +1783 | * Emitted when the JavaScript +1784 | * [`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded) event is dispatched. +1785 | */ +1786 | prependListener(event: 'domcontentloaded', listener: (page: Page) => any): this; +1787 | +1788 | /** +1789 | * Emitted when attachment download started. User can access basic file operations on downloaded content via the +1790 | * passed [Download](https://playwright.dev/docs/api/class-download) instance. +1791 | */ +1792 | prependListener(event: 'download', listener: (download: Download) => any): this; +1793 | +1794 | /** +1795 | * Emitted when a file chooser is supposed to appear, such as after clicking the ``. Playwright can +1796 | * respond to it via setting the input files using +1797 | * [fileChooser.setFiles(files[, options])](https://playwright.dev/docs/api/class-filechooser#file-chooser-set-files) +1798 | * that can be uploaded after that. +1799 | * +1800 | * ```js +1801 | * page.on('filechooser', async fileChooser => { +1802 | * await fileChooser.setFiles(path.join(__dirname, '/tmp/myfile.pdf')); +1803 | * }); +1804 | * ``` +1805 | * +1806 | */ +1807 | prependListener(event: 'filechooser', listener: (fileChooser: FileChooser) => any): this; +1808 | +1809 | /** +1810 | * Emitted when a frame is attached. +1811 | */ +1812 | prependListener(event: 'frameattached', listener: (frame: Frame) => any): this; +1813 | +1814 | /** +1815 | * Emitted when a frame is detached. +1816 | */ +1817 | prependListener(event: 'framedetached', listener: (frame: Frame) => any): this; +1818 | +1819 | /** +1820 | * Emitted when a frame is navigated to a new url. +1821 | */ +1822 | prependListener(event: 'framenavigated', listener: (frame: Frame) => any): this; +1823 | +1824 | /** +1825 | * Emitted when the JavaScript [`load`](https://developer.mozilla.org/en-US/docs/Web/Events/load) event is dispatched. +1826 | */ +1827 | prependListener(event: 'load', listener: (page: Page) => any): this; +1828 | +1829 | /** +1830 | * Emitted when an uncaught exception happens within the page. +1831 | * +1832 | * ```js +1833 | * // Log all uncaught errors to the terminal +1834 | * page.on('pageerror', exception => { +1835 | * console.log(`Uncaught exception: "${exception}"`); +1836 | * }); +1837 | * +1838 | * // Navigate to a page with an exception. +1839 | * await page.goto('data:text/html,'); +1840 | * ``` +1841 | * +1842 | */ +1843 | prependListener(event: 'pageerror', listener: (error: Error) => any): this; +1844 | +1845 | /** +1846 | * Emitted when the page opens a new tab or window. This event is emitted in addition to the +1847 | * [browserContext.on('page')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-page), but +1848 | * only for popups relevant to this page. +1849 | * +1850 | * The earliest moment that page is available is when it has navigated to the initial url. For example, when opening a +1851 | * popup with `window.open('http://example.com')`, this event will fire when the network request to +1852 | * "http://example.com" is done and its response has started loading in the popup. If you would like to route/listen +1853 | * to this network request, use +1854 | * [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route) +1855 | * and +1856 | * [browserContext.on('request')](https://playwright.dev/docs/api/class-browsercontext#browser-context-event-request) +1857 | * respectively instead of similar methods on the [Page](https://playwright.dev/docs/api/class-page). +1858 | * +1859 | * ```js +1860 | * // Start waiting for popup before clicking. Note no await. +1861 | * const popupPromise = page.waitForEvent('popup'); +1862 | * await page.getByText('open the popup').click(); +1863 | * const popup = await popupPromise; +1864 | * console.log(await popup.evaluate('location.href')); +1865 | * ``` +1866 | * +1867 | * **NOTE** Use +1868 | * [page.waitForLoadState([state, options])](https://playwright.dev/docs/api/class-page#page-wait-for-load-state) to +1869 | * wait until the page gets to a particular state (you should not need it in most cases). +1870 | * +1871 | */ +1872 | prependListener(event: 'popup', listener: (page: Page) => any): this; +1873 | +1874 | /** +1875 | * Emitted when a page issues a request. The [request] object is read-only. In order to intercept and mutate requests, +1876 | * see [page.route(url, handler[, options])](https://playwright.dev/docs/api/class-page#page-route) or +1877 | * [browserContext.route(url, handler[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-route). +1878 | */ +1879 | prependListener(event: 'request', listener: (request: Request) => any): this; +1880 | +1881 | /** +1882 | * Emitted when a request fails, for example by timing out. +1883 | * +1884 | * ```js +1885 | * page.on('requestfailed', request => { +1886 | * console.log(request.url() + ' ' + request.failure().errorText); +1887 | * }); +1888 | * ``` +1889 | * +1890 | * **NOTE** HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request +1891 | * will complete with +1892 | * [page.on('requestfinished')](https://playwright.dev/docs/api/class-page#page-event-request-finished) event and not +1893 | * with [page.on('requestfailed')](https://playwright.dev/docs/api/class-page#page-event-request-failed). A request +1894 | * will only be considered failed when the client cannot get an HTTP response from the server, e.g. due to network +1895 | * error net::ERR_FAILED. +1896 | * +1897 | */ +1898 | prependListener(event: 'requestfailed', listener: (request: Request) => any): this; +1899 | +1900 | /** +1901 | * Emitted when a request finishes successfully after downloading the response body. For a successful response, the +1902 | * sequence of events is `request`, `response` and `requestfinished`. +1903 | */ +1904 | prependListener(event: 'requestfinished', listener: (request: Request) => any): this; +1905 | +1906 | /** +1907 | * Emitted when [response] status and headers are received for a request. For a successful response, the sequence of +1908 | * events is `request`, `response` and `requestfinished`. +1909 | */ +1910 | prependListener(event: 'response', listener: (response: Response) => any): this; +1911 | +1912 | /** +1913 | * Emitted when [WebSocket](https://playwright.dev/docs/api/class-websocket) request is sent. +1914 | */ +1915 | prependListener(event: 'websocket', listener: (webSocket: WebSocket) => any): this; +1916 | +1917 | /** +1918 | * Emitted when a dedicated [WebWorker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) is spawned +1919 | * by the page. +1920 | */ +1921 | prependListener(event: 'worker', listener: (worker: Worker) => any): this; +1922 | +1923 | /** +1924 | * When testing a web page, sometimes unexpected overlays like a "Sign up" dialog appear and block actions you want to +1925 | * automate, e.g. clicking a button. These overlays don't always show up in the same way or at the same time, making +1926 | * them tricky to handle in automated tests. +1927 | * +1928 | * This method lets you set up a special function, called a handler, that activates when it detects that overlay is +1929 | * visible. The handler's job is to remove the overlay, allowing your test to continue as if the overlay wasn't there. +1930 | * +1931 | * Things to keep in mind: +1932 | * - When an overlay is shown predictably, we recommend explicitly waiting for it in your test and dismissing it as +1933 | * a part of your normal test flow, instead of using +1934 | * [page.addLocatorHandler(locator, handler[, options])](https://playwright.dev/docs/api/class-page#page-add-locator-handler). +1935 | * - Playwright checks for the overlay every time before executing or retrying an action that requires an +1936 | * [actionability check](https://playwright.dev/docs/actionability), or before performing an auto-waiting assertion check. When overlay +1937 | * is visible, Playwright calls the handler first, and then proceeds with the action/assertion. Note that the +1938 | * handler is only called when you perform an action/assertion - if the overlay becomes visible but you don't +1939 | * perform any actions, the handler will not be triggered. +1940 | * - After executing the handler, Playwright will ensure that overlay that triggered the handler is not visible +1941 | * anymore. You can opt-out of this behavior with +1942 | * [`noWaitAfter`](https://playwright.dev/docs/api/class-page#page-add-locator-handler-option-no-wait-after). +1943 | * - The execution time of the handler counts towards the timeout of the action/assertion that executed the handler. +1944 | * If your handler takes too long, it might cause timeouts. +1945 | * - You can register multiple handlers. However, only a single handler will be running at a time. Make sure the +1946 | * actions within a handler don't depend on another handler. +1947 | * +1948 | * **NOTE** Running the handler will alter your page state mid-test. For example it will change the currently focused +1949 | * element and move the mouse. Make sure that actions that run after the handler are self-contained and do not rely on +1950 | * the focus and mouse state being unchanged. +1951 | * +1952 | * For example, consider a test that calls +1953 | * [locator.focus([options])](https://playwright.dev/docs/api/class-locator#locator-focus) followed by +1954 | * [keyboard.press(key[, options])](https://playwright.dev/docs/api/class-keyboard#keyboard-press). If your handler +1955 | * clicks a button between these two actions, the focused element most likely will be wrong, and key press will happen +1956 | * on the unexpected element. Use +1957 | * [locator.press(key[, options])](https://playwright.dev/docs/api/class-locator#locator-press) instead to avoid this +1958 | * problem. +1959 | * +1960 | * Another example is a series of mouse actions, where +1961 | * [mouse.move(x, y[, options])](https://playwright.dev/docs/api/class-mouse#mouse-move) is followed by +1962 | * [mouse.down([options])](https://playwright.dev/docs/api/class-mouse#mouse-down). Again, when the handler runs +1963 | * between these two actions, the mouse position will be wrong during the mouse down. Prefer self-contained actions +1964 | * like [locator.click([options])](https://playwright.dev/docs/api/class-locator#locator-click) that do not rely on +1965 | * the state being unchanged by a handler. +1966 | * +1967 | * **Usage** +1968 | * +1969 | * An example that closes a "Sign up to the newsletter" dialog when it appears: +1970 | * +1971 | * ```js +1972 | * // Setup the handler. +1973 | * await page.addLocatorHandler(page.getByText('Sign up to the newsletter'), async () => { +1974 | * await page.getByRole('button', { name: 'No thanks' }).click(); +1975 | * }); +1976 | * +1977 | * // Write the test as usual. +1978 | * await page.goto('https://example.com'); +1979 | * await page.getByRole('button', { name: 'Start here' }).click(); +1980 | * ``` +1981 | * +1982 | * An example that skips the "Confirm your security details" page when it is shown: +1983 | * +1984 | * ```js +1985 | * // Setup the handler. +1986 | * await page.addLocatorHandler(page.getByText('Confirm your security details'), async () => { +1987 | * await page.getByRole('button', { name: 'Remind me later' }).click(); +1988 | * }); +1989 | * +1990 | * // Write the test as usual. +1991 | * await page.goto('https://example.com'); +1992 | * await page.getByRole('button', { name: 'Start here' }).click(); +1993 | * ``` +1994 | * +1995 | * An example with a custom callback on every actionability check. It uses a `` locator that is always visible, +1996 | * so the handler is called before every actionability check. It is important to specify +1997 | * [`noWaitAfter`](https://playwright.dev/docs/api/class-page#page-add-locator-handler-option-no-wait-after), because +1998 | * the handler does not hide the `` element. +1999 | * +2000 | * ```js +2001 | * // Setup the handler. +2002 | * await page.addLocatorHandler(page.locator('body'), async () => { +2003 | * await page.evaluate(() => window.removeObstructionsForTestIfNeeded()); +2004 | * }, { noWaitAfter: true }); +2005 | * +2006 | * // Write the test as usual. +2007 | * await page.goto('https://example.com'); +2008 | * await page.getByRole('button', { name: 'Start here' }).click(); +2009 | * ``` +2010 | * +2011 | * Handler takes the original locator as an argument. You can also automatically remove the handler after a number of +2012 | * invocations by setting [`times`](https://playwright.dev/docs/api/class-page#page-add-locator-handler-option-times): +2013 | * +2014 | * ```js +2015 | * await page.addLocatorHandler(page.getByLabel('Close'), async locator => { +2016 | * await locator.click(); +2017 | * }, { times: 1 }); +2018 | * ``` +2019 | * +2020 | * @param locator Locator that triggers the handler. +2021 | * @param handler Function that should be run once +2022 | * [`locator`](https://playwright.dev/docs/api/class-page#page-add-locator-handler-option-locator) appears. This +2023 | * function should get rid of the element that blocks actions like click. +2024 | * @param options +2025 | */ +2026 | addLocatorHandler(locator: Locator, handler: ((locator: Locator) => Promise), options?: { +2027 | /** +2028 | * By default, after calling the handler Playwright will wait until the overlay becomes hidden, and only then +2029 | * Playwright will continue with the action/assertion that triggered the handler. This option allows to opt-out of +2030 | * this behavior, so that overlay can stay visible after the handler has run. +2031 | */ +2032 | noWaitAfter?: boolean; +2033 | +2034 | /** +2035 | * Specifies the maximum number of times this handler should be called. Unlimited by default. +2036 | */ +2037 | times?: number; +2038 | }): Promise; +2039 | +2040 | /** +2041 | * Adds a ` +2626 | * +2627 | *
+2628 | * `); +2629 | * await page.click('button'); +2630 | * })(); +2631 | * ``` +2632 | * +2633 | * @param name Name of the function on the window object +2634 | * @param callback Callback function which will be called in Playwright's context. +2635 | */ +2636 | exposeFunction(name: string, callback: Function): Promise; +2637 | +2638 | /** +2639 | * **NOTE** Use locator-based [locator.fill(value[, options])](https://playwright.dev/docs/api/class-locator#locator-fill) +2640 | * instead. Read more about [locators](https://playwright.dev/docs/locators). +2641 | * +2642 | * This method waits for an element matching +2643 | * [`selector`](https://playwright.dev/docs/api/class-page#page-fill-option-selector), waits for +2644 | * [actionability](https://playwright.dev/docs/actionability) checks, focuses the element, fills it and triggers an `input` event after +2645 | * filling. Note that you can pass an empty string to clear the input field. +2646 | * +2647 | * If the target element is not an ``, `