morethanwords
4 years ago
33 changed files with 4353 additions and 343 deletions
@ -1,28 +0,0 @@
@@ -1,28 +0,0 @@
|
||||
MTProto.apiManager.invokeApi('messages.getPeerDialogs', { |
||||
peers: [ |
||||
{ |
||||
_: 'inputDialogPeer', |
||||
peer: {_: 'inputPeerUser', user_id: 296814355, access_hash: '7461657386624868366'} |
||||
} |
||||
] |
||||
}).then(dialogs => console.log(dialogs)); |
||||
|
||||
MTProto.apiManager.invokeApi('messages.getPinnedDialogs', { |
||||
folder_id: 0 |
||||
}).then(dialogs => console.log(dialogs)); |
||||
|
||||
// read_outbox_max_id && read_inbox_max_id are 0!
|
||||
MTProto.apiManager.invokeApi('messages.getDialogs', { |
||||
flags: 0 | 1, |
||||
exclude_pinned: true, |
||||
folder_id: 0, |
||||
offset_date: 0, |
||||
offset_id: 0, |
||||
offset_peer: {_: 'inputPeerEmpty'}, |
||||
limit: 6, |
||||
hash: Date.now() * 5 |
||||
}).then(dialogs => console.log(dialogs)); |
||||
|
||||
// [109, 188, 177, 157, 19, 7, 177, 17, 49, 155, 9, 0, 44, 155, 9, 0, 237, 154, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 157, 80, 175] - works
|
||||
// [109, 188, 177, 157, 19, 7, 177, 17, 49, 155, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 157, 80, 175] - pinned
|
||||
|
@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
|
||||
declare module 'worker-loader!*' { |
||||
class WebpackWorker extends Worker { |
||||
constructor(); |
||||
} |
||||
|
||||
export default WebpackWorker; |
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
export const userAgent = navigator ? navigator.userAgent : null; |
||||
export const isApple = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) != -1; |
||||
export const isAndroid = navigator.userAgent.toLowerCase().indexOf('android') != -1; |
||||
|
||||
/** |
||||
* Returns true when run in WebKit derived browsers. |
||||
* This is used as a workaround for a memory leak in Safari caused by using Transferable objects to |
||||
* transfer data between WebWorkers and the main thread. |
||||
* https://github.com/mapbox/mapbox-gl-js/issues/8771
|
||||
* |
||||
* This should be removed once the underlying Safari issue is fixed. |
||||
*/ |
||||
const ctx = typeof(window) !== 'undefined' ? window : self; |
||||
|
||||
export const isSafari = !!('safari' in ctx) || !!(userAgent && (/\b(iPad|iPhone|iPod)\b/.test(userAgent) || (!!userAgent.match('Safari') && !userAgent.match('Chrome')))); |
@ -1,103 +0,0 @@
@@ -1,103 +0,0 @@
|
||||
class AppWebpManager { |
||||
private webpMachine: any = null; |
||||
private loaded: Promise<void>; |
||||
private busyPromise: Promise<Uint8Array | void>; |
||||
private queue: {bytes: Uint8Array, callback: (res: Uint8Array) => void}[] = []; |
||||
|
||||
private testPromise: Promise<boolean> = null; |
||||
public webpSupport = false; |
||||
|
||||
constructor() { |
||||
this.testWebpSupport(); |
||||
} |
||||
|
||||
private loadWebpHero() { |
||||
if(this.loaded) return this.loaded; |
||||
|
||||
this.loaded = new Promise(async(resolve, reject) => { |
||||
let res = await this.testWebpSupport(); |
||||
|
||||
if(!res) { |
||||
(window as any).webpLoaded = () => { |
||||
//console.log('webpHero loaded');
|
||||
this.webpMachine = new (window as any).WebpMachine(); |
||||
resolve(); |
||||
}; |
||||
|
||||
let sc = document.createElement('script'); |
||||
sc.src = 'npm.webp-hero.chunk.js'; |
||||
sc.async = true; |
||||
sc.onload = (window as any).webpLoaded; |
||||
|
||||
document.body.appendChild(sc); |
||||
} else { |
||||
resolve(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private convert(bytes: Uint8Array): AppWebpManager['busyPromise'] { |
||||
return this.webpMachine.decode(bytes); |
||||
} |
||||
|
||||
private async processQueue() { |
||||
if(this.busyPromise) return; |
||||
|
||||
this.busyPromise = Promise.resolve(); |
||||
|
||||
let {bytes, callback} = this.queue.pop(); |
||||
|
||||
if(!this.loaded) { |
||||
this.loadWebpHero(); |
||||
} |
||||
|
||||
await this.loaded; |
||||
|
||||
this.busyPromise = this.convert(bytes); |
||||
let res = await this.busyPromise; |
||||
|
||||
//console.log('converted webp', res);
|
||||
|
||||
callback(res as Uint8Array); |
||||
|
||||
this.busyPromise = null; |
||||
|
||||
if(this.queue.length) { |
||||
this.processQueue(); |
||||
} |
||||
} |
||||
|
||||
public testWebpSupport() { |
||||
if(this.testPromise) return this.testPromise; |
||||
|
||||
return this.testPromise = new Promise((resolve, reject) => { |
||||
return resolve(this.webpSupport = true); |
||||
let webP = new Image(); |
||||
webP.src = 'data:image/webp;base64,UklGRi4AAABXRUJQVlA4TCEAAAAvAUAAEB8wAiMw' + |
||||
'AgSSNtse/cXjxyCCmrYNWPwmHRH9jwMA'; |
||||
webP.onload = webP.onerror = () => { |
||||
resolve(this.webpSupport = webP.height === 2/* && false */); |
||||
}; |
||||
}); |
||||
} |
||||
|
||||
public isSupported() { |
||||
return this.webpSupport; |
||||
} |
||||
|
||||
public convertToPng(bytes: Uint8Array) { |
||||
//console.warn('convertToPng!');
|
||||
return new Promise<Uint8Array>((resolve, reject) => { |
||||
// @ts-ignore
|
||||
this.queue.push({bytes, callback: resolve}); |
||||
this.processQueue(); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
const appWebpManager = new AppWebpManager(); |
||||
/* // @ts-ignore |
||||
if(process.env.NODE_ENV != 'production') { |
||||
(window as any).appWebpManager = appWebpManager; |
||||
} */ |
||||
export default appWebpManager; |
File diff suppressed because one or more lines are too long
@ -1,55 +0,0 @@
@@ -1,55 +0,0 @@
|
||||
import {Webp} from "webp-hero/libwebp/dist/webp.js" |
||||
|
||||
const relax = () => new Promise(resolve => requestAnimationFrame(resolve)); |
||||
|
||||
export class WebpMachineError extends Error {} |
||||
|
||||
/** |
||||
* Webp Machine |
||||
* - decode and polyfill webp images |
||||
* - can only decode images one-at-a-time (otherwise will throw busy error) |
||||
*/ |
||||
export class WebpMachine { |
||||
private readonly webp: Webp; |
||||
private busy = false; |
||||
|
||||
constructor() { |
||||
this.webp = new Webp(); |
||||
this.webp.Module.doNotCaptureKeyboard = true; |
||||
} |
||||
|
||||
/** |
||||
* Decode raw webp data into a png data url |
||||
*/ |
||||
decode(webpData: Uint8Array): Promise<Uint8Array> { |
||||
if(this.busy) throw new WebpMachineError("cannot decode when already busy"); |
||||
this.busy = true; |
||||
|
||||
try { |
||||
return relax().then(() => { |
||||
const canvas = document.createElement("canvas"); |
||||
this.webp.setCanvas(canvas); |
||||
this.webp.webpToSdl(webpData, webpData.length); |
||||
this.busy = false; |
||||
|
||||
return new Promise((resolve, reject) => { |
||||
canvas.toBlob(blob => { |
||||
let reader = new FileReader(); |
||||
reader.onload = (event) => { |
||||
resolve(new Uint8Array(event.target.result as ArrayBuffer)); |
||||
}; |
||||
reader.onerror = reject; |
||||
reader.readAsArrayBuffer(blob); |
||||
}, 'image/png', 1); |
||||
}); |
||||
}); |
||||
} catch(error) { |
||||
this.busy = false; |
||||
error.name = WebpMachineError.name; |
||||
error.message = `failed to decode webp image: ${error.message}`; |
||||
throw error; |
||||
} |
||||
} |
||||
} |
||||
|
||||
(window as any).WebpMachine = WebpMachine; |
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
import { WebPDecoder } from '../../vendor/libwebp-0.2.0'; |
||||
import { encode } from 'fast-png'; |
||||
|
||||
export function webp2png(data: Uint8Array) { |
||||
const decoder = new WebPDecoder(); |
||||
const config: any = decoder.WebPDecoderConfig; |
||||
const buffer = config.j || config.output; |
||||
const bitstream = config.input; |
||||
|
||||
decoder.WebPInitDecoderConfig(config); |
||||
decoder.WebPGetFeatures(data, data.length, bitstream); |
||||
|
||||
/** MODE_RGBA = 1 MODE_ARGB = 4, */ |
||||
buffer.J = 1; |
||||
|
||||
let status; |
||||
try { |
||||
status = decoder.WebPDecode(data, data.length, config); |
||||
} catch(e) { |
||||
status = e; |
||||
} |
||||
|
||||
if(status === 0) { |
||||
const rgbaData = buffer.Jb; |
||||
const pngData = encode({ |
||||
data: rgbaData, |
||||
width: buffer.width, |
||||
height: buffer.height, |
||||
channels: 4, |
||||
depth: 8, |
||||
}); |
||||
|
||||
return {status, bytes: pngData}; |
||||
} |
||||
|
||||
return {status, bytes: data}; |
||||
} |
||||
|
||||
export function webp2pngAsBlob(data: Uint8Array) { |
||||
const {status, bytes} = webp2png(data); |
||||
return new Blob([bytes], {type: status === 0 ? 'image/png' : 'image/webp'}); |
||||
} |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
import { webp2png } from './webp'; |
||||
import type { WebpConvertTask } from './webpWorkerController'; |
||||
|
||||
const ctx = self as any as DedicatedWorkerGlobalScope; |
||||
const tasks: WebpConvertTask[] = []; |
||||
let isProcessing = false; |
||||
|
||||
function finishTask() { |
||||
isProcessing = false; |
||||
processTasks(); |
||||
} |
||||
|
||||
function processTasks() { |
||||
if(isProcessing) return; |
||||
|
||||
const task = tasks.shift(); |
||||
if(!task) return; |
||||
|
||||
isProcessing = true; |
||||
|
||||
switch(task.type) { |
||||
case 'convertWebp': { |
||||
const {fileName, bytes} = task.payload; |
||||
|
||||
ctx.postMessage({ |
||||
type: 'convertWebp', |
||||
payload: { |
||||
fileName, |
||||
bytes: webp2png(bytes).bytes |
||||
} |
||||
}); |
||||
|
||||
finishTask(); |
||||
|
||||
break; |
||||
} |
||||
|
||||
default: |
||||
finishTask(); |
||||
} |
||||
} |
||||
|
||||
function scheduleTask(task: WebpConvertTask) { |
||||
tasks.push(task); |
||||
processTasks(); |
||||
} |
||||
|
||||
ctx.addEventListener('message', (event) => { |
||||
scheduleTask(event.data); |
||||
}); |
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
import WebpWorker from 'worker-loader!./webp.worker'; |
||||
import { CancellablePromise, deferredPromise } from '../polyfill'; |
||||
|
||||
export type WebpConvertTask = { |
||||
type: 'convertWebp', |
||||
payload: { |
||||
fileName: string, |
||||
bytes: Uint8Array |
||||
} |
||||
}; |
||||
|
||||
export class WebpWorkerController { |
||||
private worker: Worker; |
||||
private convertPromises: {[fileName: string]: CancellablePromise<Uint8Array>} = {}; |
||||
|
||||
init() { |
||||
this.worker = new WebpWorker(); |
||||
this.worker.addEventListener('message', (e) => { |
||||
const payload = (e.data as WebpConvertTask).payload; |
||||
|
||||
if(payload.fileName.indexOf('main-') === 0) { |
||||
const promise = this.convertPromises[payload.fileName]; |
||||
if(promise) { |
||||
promise.resolve(payload.bytes); |
||||
delete this.convertPromises[payload.fileName]; |
||||
} |
||||
} else { |
||||
navigator.serviceWorker.controller.postMessage(e.data); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
postMessage(data: WebpConvertTask) { |
||||
if(this.init) { |
||||
this.init(); |
||||
this.init = null; |
||||
} |
||||
|
||||
this.worker.postMessage(data); |
||||
} |
||||
|
||||
convert(fileName: string, bytes: Uint8Array) { |
||||
const convertPromise = deferredPromise<Uint8Array>(); |
||||
|
||||
fileName = 'main-' + fileName; |
||||
|
||||
this.postMessage({type: 'convertWebp', payload: {fileName, bytes}}); |
||||
|
||||
return this.convertPromises[fileName] = convertPromise; |
||||
} |
||||
} |
||||
|
||||
export const webpWorkerController = new WebpWorkerController(); |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue