@ordojs/mobile
Version:
Mobile and PWA support for OrdoJS applications
145 lines (135 loc) • 16.2 kB
JavaScript
'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