framework7io / framework7-vue

Deprecated! Build full featured iOS & Android apps using Framework7 & Vue
http://framework7.io/vue/
MIT License
674 stars 150 forks source link

Image caching for all images #103

Closed centrual closed 6 years ago

centrual commented 7 years ago

Hello Vladimir and Framework7 community;

I trying to cache all images in phone's storage (or localStorage).

Caching easy in vanilla javascript( with some ready to use scripts ) but it's hard in vuejs (especially on lists, and custom components like photo viewer), i thinking how can be easier.

I know it's not framework7's main target, it's another project maybe but;

Maybe we can make

<f7-image
timeout="1000" /* in ms */
[bg-]src="http://imageurl.com/image.jpg"
[bg-]default="assets/static/default.png"
[bg-]placeholder="assets/static/placeholder.png" />

component for cordova environments?

it saves image to [localstorage|sdcard|phone] and displays from there if it can't load from url.

Have a great day!

centrual commented 7 years ago

I wrote my code for my project:

Requirements

1) Waits for deviceready event. 2) Tries to find in phone cache first 3) If not found, tries to download from remote url, if it success, saves to cache and loads from cache on next. 4) If it can't download, displays local-fallback-image.

It supports placeholder image for before load.

Sample Usage:

<script>
  export default {
    data() {
      return {
        defaultPlaceholderImage: require('assets/static/img/defaultPlaceholderImage.jpg')
      }
    },

    methods: {
      openBigImage() {
        // Opens lightbox etc.
      }
    }
  }
</script>

<template>
  <offline-image @click="openBigImage" src="https://avatars3.githubusercontent.com/u/2001424?v=3&s=460" :placeholder="defaultPlaceholderImage" :local-fallback-image="defaultPlaceholderImage"></offline-image>
<template>

offlineImage.vue

<script>
    export default {
        name: 'offlineImage',
        render: function (c) {
            if (this.img != '') {
                return c('img', {
                    attrs: {
                        src: this.img
                    },
                    on: {
                        click: this.onClick
                    }
                })
            }

            let attrs = {}

            if (this.placeholderClass != "")
                attrs = { "class": this.placeholderClass }

            return c('div', {
                "attrs": attrs,
                on: { click: this.onClick }
            })
        },

        props: {
            localFallbackImage: {"type": String},
            placeholder: {"type": String},
            placeholderClass: {
                "type": String,
                "default": ""
            },
            timeout: {
                "type": Number,
                "default": 3000
            },
            src: {
                "type": String,
                "required": true
            }
        },

        data() {
            return {
                img: "",
                timer: null,
                cacheFailCalled: false
            }
        },

        methods: {
            onClick(event) {
                this.$emit('click', event)
            },

            getRandomInt(min, max) {
                return Math.floor(Math.random() * (max - min + 1)) + min
            },

            randomFileName(imageUrl) {
                let fileExt = "",
                    fullFileName = "",
                    allChars = "qwertyuopasdfghjklizxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789"

                if (imageUrl.indexOf('?') > -1) {
                    let splitted = imageUrl.split('?', 1)

                    if (Array.isArray(splitted)) {
                        splitted = splitted[0].split('.')
                        fileExt = splitted[splitted.length - 1]
                    }
                } else {
                    let splitted = imageUrl.split('.')
                    fileExt = splitted[splitted.length - 1]
                }

                if (fileExt.endsWith('/'))
                    fileExt = fileExt.substr(0, fileExt.length - 1)

                for (let x = 0; x < 20; x++) {
                    fullFileName += allChars.charAt(this.getRandomInt(0, allChars.length - 1))
                }

                return `${fullFileName}.${fileExt}`
            },

            downloadAndCacheImage() {
                let newFilePath = cordova.file.dataDirectory + this.randomFileName(this.src),
                    ft = new FileTransfer()

                ft.download(this.src, newFilePath, (entry) => {
                    localStorage.setItem(this.src, newFilePath)
                    this.img = entry.toInternalURL()
                }, () => {
                    this.img = this.localFallbackImage
                })
            },

            testImageAndSetIfSucceed( src, onError ) {
                let image = new Image()

                image.onload = () => {
                    console.log('onload called')
                    this.img = src

                    if (this.timer != null) {
                        console.log('timer cleaned')
                        clearTimeout(this.timer)
                        this.timer = null
                    }
                }

                image.onerror = onError.bind(this)

                this.timer = setTimeout(() => {
                    console.log('timer started')
                    image.onload = function () {}
                    image.onerror()
                    image = null
                }, this.timeout)

                image.src = src
            },

            cachedOnlineImage() {
                if (this.src != "") {
                    if (this.src.startsWith('http')) {
                        console.log('http found in link')
                        let filePath = localStorage.getItem(this.src)

                        if (filePath != null) {
                            console.log('localStorage file path is NOT null!')

                            resolveLocalFileSystemURL(filePath, (file) => {
                                console.log('local file system url resolved.')
                                this.testImageAndSetIfSucceed(file.toInternalURL(), () => {
                                    console.log('local file system url is NOT exists, fail returned from testImageAndSetIfSucceed.')
                                    console.log(`Downloading ${this.src}...`)
                                    this.downloadAndCacheImage()
                                })
                            }, () => {
                                console.log('local file system url NOT resolved.')
                                console.log(`Downloading ${this.src}...`)
                                this.downloadAndCacheImage()
                            })
                        } else {
                            console.log('localStorage filePath is null.')
                            console.log(`Downloading ${this.src}...`)
                            this.downloadAndCacheImage()
                        }
                    } else {
                        console.log(`http not found in url. Testing image: ${this.src}`)
                        this.testImageAndSetIfSucceed(this.src, () => {
                            console.log('Image test failed. Setting to fallback image.')
                            this.img = this.localFallbackImage
                        })
                    }
                }
            }
        },

        created() {
            if (this.placeholder != "")
                this.img = this.placeholder

            document.addEventListener("deviceready", this.cachedOnlineImage.bind(this))
        }
    }
</script>
macdonst commented 7 years ago

@centrual have you ever looked at the content-sync plugin? Seems to do a lot of what you want.

https://github.com/phonegap/phonegap-plugin-contentsync

centrual commented 7 years ago

Hello @macdonst

This plugin is good too, but it's hard to use for dynamic mobile apps.

The code i sent working good for dynamic images. I using it currently, and i just shared for everybody.

juliusbangert commented 6 years ago

@centrual I am very excited by this code you have shared. I really want something to handle offline images but where I can still use simple html tags referencing remote sources such that a remote update forces a change; but otherwise the app should use local images. Do you know if phonegap stores the cached images indefinitely? Please could you give some pointers for setting this up and getting your example up and running. Do you have a working example I could download and run to get my head around what's going on? I struggle with where I should set this up in my Framework7 component/template?

Also, would this work within a virtualList?