UNPKG

@ordojs/mobile

Version:

Mobile and PWA support for OrdoJS applications

145 lines (135 loc) 16.2 kB
'use strict';var l=class{config;element;touchPoints=new Map;gestureStartTime=0;gestureStartPoints=[];listeners=new Map;constructor(e,t={}){this.element=e,this.config={enabled:true,threshold:10,velocity:.3,direction:"both",preventDefault:true,...t},this.initialize();}initialize(){this.config.enabled&&(this.element.addEventListener("touchstart",this.handleTouchStart.bind(this),{passive:false}),this.element.addEventListener("touchmove",this.handleTouchMove.bind(this),{passive:false}),this.element.addEventListener("touchend",this.handleTouchEnd.bind(this),{passive:false}),this.element.addEventListener("touchcancel",this.handleTouchCancel.bind(this),{passive:false}));}handleTouchStart(e){this.config.preventDefault&&e.preventDefault(),this.gestureStartTime=Date.now(),this.gestureStartPoints=[],this.touchPoints.clear(),Array.from(e.touches).forEach(t=>{let i={x:t.clientX,y:t.clientY,identifier:t.identifier,timestamp:Date.now()};this.touchPoints.set(t.identifier,i),this.gestureStartPoints.push({...i});}),this.touchPoints.size===1&&(this.detectTap(),this.detectLongPress());}handleTouchMove(e){this.config.preventDefault&&e.preventDefault(),Array.from(e.touches).forEach(t=>{let i=this.touchPoints.get(t.identifier);i&&(i.x=t.clientX,i.y=t.clientY,i.timestamp=Date.now());}),this.touchPoints.size===1?(this.detectSwipe(),this.detectPan()):this.touchPoints.size===2&&(this.detectPinch(),this.detectRotate());}handleTouchEnd(e){this.config.preventDefault&&e.preventDefault();let t=Date.now()-this.gestureStartTime,i=Array.from(this.touchPoints.values());if(i.length===1){let n=i[0];if(!n)return;let r=this.gestureStartPoints.find(o=>o.identifier===n.identifier);if(r){let o=n.x-r.x,s=n.y-r.y,a=Math.sqrt(o*o+s*s),c=a/t;if(a<this.config.threshold&&t<300&&this.emitGesture("tap",{type:"tap",points:[n],deltaX:o,deltaY:s,velocity:c,direction:"none",duration:t}),a>this.config.threshold&&c>this.config.velocity){let g=this.getDirection(o,s);this.emitGesture("swipe",{type:"swipe",points:[n],deltaX:o,deltaY:s,velocity:c,direction:g,duration:t});}}}this.touchPoints.clear(),this.gestureStartPoints=[];}handleTouchCancel(e){this.touchPoints.clear(),this.gestureStartPoints=[];}detectTap(){}detectLongPress(){setTimeout(()=>{if(this.touchPoints.size===1){let e=Array.from(this.touchPoints.values())[0];if(!e)return;this.emitGesture("longpress",{type:"longpress",points:[e],deltaX:0,deltaY:0,velocity:0,direction:"none",duration:500});}},500);}detectSwipe(){}detectPan(){let e=Array.from(this.touchPoints.values());if(e.length===1){let t=e[0];if(!t)return;let i=this.gestureStartPoints.find(n=>n.identifier===t.identifier);if(i){let n=t.x-i.x,r=t.y-i.y,o=Math.sqrt(n*n+r*r);if(o>this.config.threshold){let s=this.getDirection(n,r);this.emitGesture("pan",{type:"pan",points:[t],deltaX:n,deltaY:r,velocity:o/(Date.now()-this.gestureStartTime),direction:s,duration:Date.now()-this.gestureStartTime});}}}}detectPinch(){let e=Array.from(this.touchPoints.values());if(e.length===2){let t=e[0],i=e[1],n=this.gestureStartPoints[0],r=this.gestureStartPoints[1];if(!t||!i||!n||!r)return;let o=this.getDistance(n,r),a=this.getDistance(t,i)/o;this.emitGesture("pinch",{type:"pinch",points:e,deltaX:0,deltaY:0,scale:a,velocity:0,direction:"none",duration:Date.now()-this.gestureStartTime});}}detectRotate(){let e=Array.from(this.touchPoints.values());if(e.length===2){let t=e[0],i=e[1],n=this.gestureStartPoints[0],r=this.gestureStartPoints[1];if(!t||!i||!n||!r)return;let o=this.getAngle(n,r),a=this.getAngle(t,i)-o;this.emitGesture("rotate",{type:"rotate",points:e,deltaX:0,deltaY:0,rotation:a,velocity:0,direction:"none",duration:Date.now()-this.gestureStartTime});}}getDistance(e,t){let i=e.x-t.x,n=e.y-t.y;return Math.sqrt(i*i+n*n)}getAngle(e,t){return Math.atan2(t.y-e.y,t.x-e.x)*180/Math.PI}getDirection(e,t){let i=Math.abs(e),n=Math.abs(t);return i>n?e>0?"right":"left":t>0?"down":"up"}emitGesture(e,t){let i=this.listeners.get(e);i&&i.forEach(n=>{try{n(t);}catch(r){console.error(`Error in gesture listener for ${e}:`,r);}});}on(e,t){this.listeners.has(e)||this.listeners.set(e,[]),this.listeners.get(e).push(t);}off(e,t){let i=this.listeners.get(e);if(i){let n=i.indexOf(t);n>-1&&i.splice(n,1);}}updateConfig(e){this.config={...this.config,...e};}getConfig(){return {...this.config}}destroy(){this.element.removeEventListener("touchstart",this.handleTouchStart.bind(this)),this.element.removeEventListener("touchmove",this.handleTouchMove.bind(this)),this.element.removeEventListener("touchend",this.handleTouchEnd.bind(this)),this.element.removeEventListener("touchcancel",this.handleTouchCancel.bind(this)),this.listeners.clear(),this.touchPoints.clear();}};var h=class{config;features=new Map;constructor(e={}){this.config={features:{camera:true,geolocation:true,contacts:true,calendar:true,notifications:true,storage:true,network:true,device:true},permissions:[],plugins:[],...e},this.initializeFeatures();}initializeFeatures(){this.config.features.camera&&this.features.set("camera",{name:"Camera",available:this.isFeatureAvailable("camera"),permission:"camera",description:"Access device camera for photo/video capture"}),this.config.features.geolocation&&this.features.set("geolocation",{name:"Geolocation",available:this.isFeatureAvailable("geolocation"),permission:"geolocation",description:"Access device location services"}),this.config.features.contacts&&this.features.set("contacts",{name:"Contacts",available:this.isFeatureAvailable("contacts"),permission:"contacts",description:"Access device contacts"}),this.config.features.calendar&&this.features.set("calendar",{name:"Calendar",available:this.isFeatureAvailable("calendar"),permission:"calendar",description:"Access device calendar"}),this.config.features.notifications&&this.features.set("notifications",{name:"Notifications",available:this.isFeatureAvailable("notifications"),permission:"notifications",description:"Send push notifications"}),this.config.features.storage&&this.features.set("storage",{name:"Storage",available:this.isFeatureAvailable("storage"),description:"Access device storage"}),this.config.features.network&&this.features.set("network",{name:"Network",available:this.isFeatureAvailable("network"),description:"Access network information"}),this.config.features.device&&this.features.set("device",{name:"Device",available:this.isFeatureAvailable("device"),description:"Access device information"});}isFeatureAvailable(e){if(typeof window>"u")return false;switch(e){case "camera":return "mediaDevices"in navigator&&"getUserMedia"in navigator.mediaDevices;case "geolocation":return "geolocation"in navigator;case "contacts":return "contacts"in navigator||"ContactsManager"in window;case "calendar":return "Calendar"in window;case "notifications":return "Notification"in window;case "storage":return "localStorage"in window||"indexedDB"in window;case "network":return "connection"in navigator||"onLine"in navigator;case "device":return "deviceMemory"in navigator||"hardwareConcurrency"in navigator;default:return false}}async getDeviceInfo(){let e={platform:"web",version:"",model:"",manufacturer:"",screenWidth:window.screen.width,screenHeight:window.screen.height,pixelRatio:window.devicePixelRatio||1,orientation:window.screen.width>window.screen.height?"landscape":"portrait",isOnline:navigator.onLine,connectionType:"unknown",batteryLevel:0,isCharging:false},t=navigator.userAgent.toLowerCase();if(t.includes("iphone")||t.includes("ipad")?e.platform="ios":t.includes("android")&&(e.platform="android"),"connection"in navigator){let i=navigator.connection;i&&(e.connectionType=i.effectiveType||"unknown");}if("getBattery"in navigator)try{let i=await navigator.getBattery();e.batteryLevel=i.level,e.isCharging=i.charging;}catch(i){console.warn("Battery API not available:",i);}return e}async requestPermission(e){let t=this.features.get(e);if(!t)throw new Error(`Feature ${e} not found`);if(!t.available)throw new Error(`Feature ${e} not available on this device`);switch(e){case "camera":return this.requestCameraPermission();case "geolocation":return this.requestGeolocationPermission();case "notifications":return this.requestNotificationPermission();default:return true}}async requestCameraPermission(){try{return (await navigator.mediaDevices.getUserMedia({video:!0})).getTracks().forEach(t=>t.stop()),!0}catch(e){return console.error("Camera permission denied:",e),false}}async requestGeolocationPermission(){return new Promise(e=>{navigator.geolocation.getCurrentPosition(()=>e(true),()=>e(false),{timeout:5e3});})}async requestNotificationPermission(){return "Notification"in window?await Notification.requestPermission()==="granted":false}async takePhoto(e={}){if(!this.features.get("camera")?.available)throw new Error("Camera not available");if(!await this.requestPermission("camera"))throw new Error("Camera permission denied");return new Promise((i,n)=>{let r=document.createElement("input");r.type="file",r.accept="image/*",r.capture=e.source==="camera"?"camera":void 0,r.onchange=o=>{let s=o.target.files?.[0];if(s){let a=new FileReader;a.onload=()=>{i({dataUrl:a.result,file:s,width:0,height:0});},a.onerror=()=>n(new Error("Failed to read file")),a.readAsDataURL(s);}else n(new Error("No file selected"));},r.click();})}async getLocation(e={}){if(!this.features.get("geolocation")?.available)throw new Error("Geolocation not available");if(!await this.requestPermission("geolocation"))throw new Error("Geolocation permission denied");return new Promise((i,n)=>{navigator.geolocation.getCurrentPosition(r=>{i({latitude:r.coords.latitude,longitude:r.coords.longitude,accuracy:r.coords.accuracy,altitude:r.coords.altitude??void 0,heading:r.coords.heading??void 0,speed:r.coords.speed??void 0,timestamp:r.timestamp});},r=>{n(new Error(`Geolocation error: ${r.message}`));},{enableHighAccuracy:e.highAccuracy||false,timeout:e.timeout||1e4,maximumAge:e.maximumAge||6e4});})}async sendNotification(e){if(!this.features.get("notifications")?.available)throw new Error("Notifications not available");if(!await this.requestPermission("notifications"))throw new Error("Notification permission denied");"Notification"in window&&new Notification(e.title,e);}getAvailableFeatures(){return Array.from(this.features.values())}updateConfig(e){this.config={...this.config,...e},this.initializeFeatures();}getConfig(){return {...this.config}}};var u=class{config;swConfig;constructor(e,t){this.config=e,this.swConfig=t;}generateManifest(){let e={name:this.config.name,short_name:this.config.shortName,description:this.config.description,start_url:this.config.startUrl,display:this.config.display,theme_color:this.config.themeColor,background_color:this.config.backgroundColor,icons:this.config.icons,categories:this.config.categories,lang:this.config.lang,dir:this.config.dir,orientation:this.config.orientation,scope:this.config.scope,prefer_related_applications:this.config.preferRelatedApplications,related_applications:this.config.relatedApplications};return JSON.stringify(e,null,2)}generateServiceWorker(){return ` // OrdoJS Service Worker const CACHE_NAME = 'ordojs-cache-v1'; const STATIC_CACHE = 'static-cache-v1'; const DYNAMIC_CACHE = 'dynamic-cache-v1'; // Install event self.addEventListener('install', (event) => { console.log('Service Worker installing...'); event.waitUntil( caches.open(STATIC_CACHE).then((cache) => { return cache.addAll([ '/', '/offline.html', '/static/css/main.css', '/static/js/main.js' ]); }) ); self.skipWaiting(); }); // Activate event self.addEventListener('activate', (event) => { console.log('Service Worker activating...'); event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { if (cacheName !== STATIC_CACHE && cacheName !== DYNAMIC_CACHE) { return caches.delete(cacheName); } }) ); }) ); self.clients.claim(); }); // Fetch event self.addEventListener('fetch', (event) => { const { request } = event; const url = new URL(request.url); // Handle different cache strategies ${this.generateCacheStrategies()} // Background sync ${this.generateBackgroundSync()} // Push notifications ${this.generatePushNotifications()} }); ${this.generateCacheStrategies()} ${this.generateBackgroundSync()} ${this.generatePushNotifications()} `.trim()}generateCacheStrategies(){let e="";return this.swConfig.cacheStrategies.forEach(t=>{e+=` // ${t.name} strategy if (${this.generatePatternMatcher(t.pattern)}) { event.respondWith( ${this.generateStrategyHandler(t)} ); return; } `;}),e}generatePatternMatcher(e){return e.includes("*")?`url.pathname.match(/^${e.replace(/\*/g,".*")}$/)`:`url.pathname === '${e}'`}generateStrategyHandler(e){switch(e.strategy){case "cache-first":return ` caches.match(request).then((response) => { return response || fetch(request).then((fetchResponse) => { return caches.open(DYNAMIC_CACHE).then((cache) => { cache.put(request, fetchResponse.clone()); return fetchResponse; }); }); }) `;case "network-first":return ` fetch(request).then((response) => { return caches.open(DYNAMIC_CACHE).then((cache) => { cache.put(request, response.clone()); return response; }); }).catch(() => { return caches.match(request); }) `;case "stale-while-revalidate":return ` caches.match(request).then((cachedResponse) => { const fetchPromise = fetch(request).then((networkResponse) => { return caches.open(DYNAMIC_CACHE).then((cache) => { cache.put(request, networkResponse.clone()); return networkResponse; }); }); return cachedResponse || fetchPromise; }) `;case "network-only":return "fetch(request)";case "cache-only":return "caches.match(request)";default:return "fetch(request)"}}generateBackgroundSync(){return this.swConfig.backgroundSync.enabled?` // Background sync self.addEventListener('sync', (event) => { if (event.tag === '${this.swConfig.backgroundSync.syncName}') { event.waitUntil( // Perform background sync operations console.log('Background sync triggered'); ); } }); `:""}generatePushNotifications(){return this.swConfig.pushNotifications.enabled?` // Push notifications self.addEventListener('push', (event) => { const options = { body: event.data ? event.data.text() : 'New notification', icon: '/icon-192x192.png', badge: '/badge-72x72.png', vibrate: [100, 50, 100], data: { dateOfArrival: Date.now(), primaryKey: 1 }, actions: [ { action: 'explore', title: 'View', icon: '/icon-192x192.png' }, { action: 'close', title: 'Close', icon: '/icon-192x192.png' } ] }; event.waitUntil( self.registration.showNotification('${this.config.name}', options) ); }); self.addEventListener('notificationclick', (event) => { event.notification.close(); if (event.action === 'explore') { event.waitUntil( clients.openWindow('/') ); } }); `:""}registerServiceWorker(){return "serviceWorker"in navigator?navigator.serviceWorker.register("/sw.js",{scope:this.swConfig.scope,updateViaCache:this.swConfig.updateViaCache}):Promise.resolve(null)}isInstalled(){return window.matchMedia("(display-mode: standalone)").matches||window.navigator.standalone===true}showInstallPrompt(){return new Promise((e,t)=>{if("BeforeInstallPromptEvent"in window){let i=new Event("beforeinstallprompt");window.dispatchEvent(i),window.addEventListener("beforeinstallprompt",n=>{n.preventDefault(),n.prompt(),e();});}else t(new Error("Install prompt not supported"));})}updateConfig(e){this.config={...this.config,...e};}updateServiceWorkerConfig(e){this.swConfig={...this.swConfig,...e};}getConfig(){return {...this.config}}getServiceWorkerConfig(){return {...this.swConfig}}};exports.GestureManager=l;exports.NativeManager=h;exports.PWAManager=u;//# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map