I need to show loading screen with Pixijs before all assets are loaded. How can I do this? Is there any way/method to render something in canvas container before loading the assets?
try this sample snippet that works
const loader = new PIXI.Loader();
loadAssets() {
for (var i in resources) {
loader.add(i, resources[i]);
}
loader.on("progress", handleLoadProgress)
.on("load", handleLoadAsset)
.on("error", handleLoadError)
.load(handleLoadComplete);
function handleLoadProgress(loader, resource) { }
function handleLoadAsset(loader, resource) { }
function handleLoadError() { }
function handleLoadComplete() { }
loader.onProgress.add(() => {
this.loadingText.text = "Loading " + loader.progress.toFixed(2) + "%";
});
loader.onError.add(() => { }); // called once per errored file
loader.onLoad.add(() => { }); // called once per loaded file
loader.onComplete.add(() => {
this.loadingComplete();
}); // called once when the queued resources all load.
}
I think the answer to this,
const loader = new PIXI.Loader();
loader.add('bunny', 'data/bunny.png')
loader.add('scoreFont', 'assets/score.fnt');
loader.onProgress.add(() => {}); // called once per loaded/errored file
loader.onLoad.add(() => {}); // called once per loaded file
loader.onComplete.add(() => {}); // called once when the queued resources all load.
source: https://pixijs.download/dev/docs/PIXI.Loader.html
Related
I'm new to service workers and I'm running into an issue with my implementation. My goal is to create a runtime cache for images and videos. I've looked at the workbox implementation but it hasn't worked for me. I see that my service worker successfully registers at the top-level scope of my app but for some reason, it seems like some of the code in my service worker file doesn't get executed. The main issue is that the event listeners from my service worker don't seem to get called (including registerRoute), and therefore, the Cache doesn't ever get created.
I'm not sure if this is related to the issue I'm having but when I look at the console messages, it seems like the code from sw.js may be run prior to the service worker registration:
console messages
I've been stuck on this problem for a while so I would appreciate some help if anyone has run into this issue, thanks!
// main.js (in a Vue 2 app)
if (process.env.NODE_ENV === "production") {
window.addEventListener("load", () => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register(`/sw.js`)
.then(() => {
console.log("Service worker registered!");
navigator.serviceWorker.ready.then((registration) => {
registration.update();
console.log('Service Worker: ready');
});
})
.catch((error) => {
console.warn("Error registering service worker:");
console.warn(error);
});
}
});
}
// sw.js
import { registerRoute } from "workbox-routing";
import { CacheFirst } from "workbox-strategies";
import { CacheableResponsePlugin } from "workbox-cacheable-response";
import { RangeRequestsPlugin } from "workbox-range-requests";
import { clientsClaim } from "workbox-core";
const CACHE_PREFIX = "background-slideshow-cache";
const CACHE_VERSION = "v1";
const CACHE_RUNTIME = "runtime";
const BACKGROUND_SLIDESHOW_CACHE = `${CACHE_PREFIX}-${CACHE_RUNTIME}-${CACHE_VERSION}`;
clientsClaim();
const addToCache = async (url) => {
const cache = await caches.open(BACKGROUND_SLIDESHOW_CACHE);
if (!(await cache.match(url))) {
await cache.add(url);
}
};
const cacheFirstStrategy = new CacheFirst({
cacheName: BACKGROUND_SLIDESHOW_CACHE,
plugins: [
new CacheableResponsePlugin({
statuses: [200],
}),
new RangeRequestsPlugin(),
],
});
self.addEventListener("message", (event) => {
if (event.data && event.data.message) {
if (event.data.message === "SKIP_WAITING") {
self.skipWaiting();
}
}
});
self.addEventListener("install", (event) => {
console.log('Service worker: fetch event', event);
})
console.log("Service Worker: in file");
registerRoute(
({ request }) => {
const { destination } = request;
console.log("Service Worker:", "request", request);
return destination === "video" || destination === "image";
},
({ event, request }) => {
// console.log("Service Worker: in the 2nd param", event, request);
event.respondWith(async () => {
await addToCache(request.url);
return cacheFirstStrategy.handle({ request });
});
}
);
After many hours of debugging, I realized that the minification of sw.js at build time was the reason this code wasn't able to execute. I decided to use uglifyjs-webpack-plugin in my webpack config and this solved the issue!
I create a site in nuxt and got data from worpdress api.
I have a few store: home.js, solutions.js, tipo.js, portfolio.js and options.js.
In fetch i check, if the store array is empty, than call dispatch and fill arrays.
export default {
async fetch({ store }) {
try {
if (store.getters['home/home'].length === 0) {
await store.dispatch('home/fetchHome');
}
if (store.getters["solutions/getSolutions"].length === 0) {
await store.dispatch('solutions/fetchSolutions');
}
if (store.getters["tipo/getTipo"].length === 0) {
await store.dispatch('tipo/fetchTipo');
}
if (store.getters["portfolio/getPortfolio"].length === 0) {
await store.dispatch('portfolio/fetchPortfolio');
}
if(store.getters["options/getOptions"].length === 0){
await store.dispatch('options/fetchOptions');
}
} catch (e) {
console.log(e, 'e no data')
}
},
components: { HomeContacts, PortofolioSlider, Clients, ChiSiamo, Solutions, HomeIntro }
}
But the problem is, that the page is loading to long time. Because i call dispatches throw await, and i think, this is the problem.
How can i call all dispatches in fethc, without async, but parallel?
I see the advantage of working with fetch over asyncData in that only the first time when I load the page, I need to wait a little, the arrays will fill up and when I get to the current page from another page, there will be no requests through the api, and the data will be output from the store.
It's just that there is very little information on nuxt in terms of ideology, how to work and what is better to use and when. In next, this is better.
This method doesn't work.
fetch({ store }) {
const promise1 = new Promise((resolve, reject) => {
if (store.getters['home/home'].length === 0) {
resolve(store.dispatch('home/fetchHome'));
}
});
const promise2 = new Promise((resolve, reject) => {
if (store.getters["solutions/getSolutions"].length === 0) {
resolve(store.dispatch('solutions/fetchSolutions'));
}
});
const promise3 = new Promise((resolve, reject) => {
if (store.getters["tipo/getTipo"].length === 0) {
resolve(store.dispatch('tipo/fetchTipo'));
}
});
const promise4 = new Promise((resolve, reject) => {
if (store.getters["portfolio/getPortfolio"].length === 0) {
resolve(store.dispatch('portfolio/fetchPortfolio'));
}
});
const promise5 = new Promise((resolve, reject) => {
if (store.getters["options/getOptions"].length === 0) {
resolve(store.dispatch('options/fetchOptions'));
}
});
Promise.all([promise1, promise2, promise3, promise4, promise5])
.then((data) => console.log(data))
.catch((error) => console.log(error));
Assuming that:
store.dispatch() returns Promise,
the first attempt in the question is generally correct,
the objective is to perform relevant dispatches in parallel,
then:
elimitate await from the store.dispatch() sequence,
accumulate the promises returned by store.dispatch() in an array,
don't use a new Promise() wrapper,
await the Promise returned by Promise.all(promises).
export default {
async fetch({ store }) {
try {
let promises = [];
if (store.getters['home/home'].length === 0) {
promises.push(store.dispatch('home/fetchHome'));
}
if (store.getters['solutions/getSolutions'].length === 0) {
promises.push(store.dispatch('solutions/fetchSolutions'));
}
if (store.getters['tipo/getTipo'].length === 0) {
promises.push(store.dispatch('tipo/fetchTipo'));
}
if (store.getters['portfolio/getPortfolio'].length === 0) {
promises.push(store.dispatch('portfolio/fetchPortfolio'));
}
if(store.getters['options/getOptions'].length === 0) {
promises.push(store.dispatch('options/fetchOptions'));
}
let data = await Promise.all(promises);
console.log(data);
} catch (e) {
console.log(e);
}
},
components: { HomeContacts, PortofolioSlider, Clients, ChiSiamo, Solutions, HomeIntro }
}
For convenience, this can be proceduralised as follows:
export default {
async fetch({ store }) {
try {
let paths = [
{ get: 'home/home', fetch: 'home/fetchHome' },
{ get: 'solutions/getSolutions', fetch: 'solutions/fetchSolutions' },
{ get: 'tipo/getTipo', fetch: 'tipo/fetchTipo' },
{ get: 'portfolio/getPortfolio', fetch: 'portfolio/fetchPortfolio' },
{ get: 'options/getOptions', fetch: 'options/fetchOptions' }
];
let promises = paths.filter(p => store.getters[p.get].length === 0).map(p => store.dispatch(p.fetch));
let data = await Promise.all(promises);
console.log(data);
} catch (e) {
console.log(e);
}
},
components: { HomeContacts, PortofolioSlider, Clients, ChiSiamo, Solutions, HomeIntro }
}
It may make more sense to define the paths array elsewhere in the code and pass it to a simplified fetch(), giving it the profile :
fetch({ store, paths })
If it still doesn't work, then there's something your're not telling us.
Promise.all can be useful here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Or even Promise.allSettled(), depending on what you're trying to do: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
Then, this is a matter of displaying something while your page does the fetching. You could use a v-if="$fetchState.pending" at the top level of your page to display a loader while the whole thing is being fetched.
There is nothing related to ideology here, there are 2 hooks that do data fetching by either blocking the render of the page (asyncData()) or allowing you to render it while the data is fetched (fetch()).
Nothing related to the framework by itself, you're free to do as you'd like.
I am using Nativescript with Angular and have a page where I photograph a receipt or add from gallery and add a couple of text inputs and send to server.
The Add from gallery is working fine in Android but not in iOS.
Here is the template code:
<Image *ngIf="imageSrc" [src]="imageSrc" [width]="previewSize" [height]="previewSize" stretch="aspectFit"></Image>
<Button text="Pick from Gallery" (tap)="onSelectGalleryTap()" class="btn-outline btn-photo"> </Button>
and the component:
public onSelectGalleryTap() {
let context = imagepicker.create({
mode: "single"
});
let that = this;
context
.authorize()
.then(() => {
that.imageAssets = [];
that.imageSrc = null;
return context.present();
})
.then((selection) => {
alert("Selection done: " + JSON.stringify(selection));
that.imageSrc = selection.length > 0 ? selection[0] : null;
// convert ImageAsset to ImageSource
fromAsset(that.imageSrc).then(res => {
var myImageSource = res;
var base64 = myImageSource.toBase64String("jpeg", 20);
this.expense.receipt_data=base64;
})
that.cameraImage=null;
that.imageAssets = selection;
that.galleryProvided=true;
// set the images to be loaded from the assets with optimal sizes (optimize memory usage)
selection.forEach(function (element) {
element.options.width = that.previewSize;
element.options.height = that.previewSize;
});
}).catch(function (e) {
console.log(e);
});
}
I have posted below the Android and iOS screenshots of the line:
alert("Selection done: " + JSON.stringify(selection));
In Android there is a path to the location of the image in the file system but in iOS there are just empty curly brackets where I'd expect to see the path and then when submitted the message back is "Unable to save image" although the image preview is displaying on the page in Image.
Here are the screenshots:
Android:
iOS:
Any ideas why it is failing in iOS?
Thanks
==========
UPDATE
I am now saving the image to a temporary location and it is still not working in iOS. It works in Android.
Here is my code now.
import { ImageAsset } from 'tns-core-modules/image-asset';
import { ImageSource, fromAsset, fromFile } from 'tns-core-modules/image-source';
import * as fileSystem from "tns-core-modules/file-system";
...
...
public onSelectGalleryTap() {
alert("in onSelectGalleryTap");
var milliseconds=(new Date).getTime();
let context = imagepicker.create({
mode: "single"
});
let that = this;
context
.authorize()
.then(() => {
that.imageAssets = [];
that.previewSrc = null;
that.imageSrc = null;
return context.present();
})
.then((selection) => {
that.imageSrc = selection.length > 0 ? selection[0] : null;
// convert ImageAsset to ImageSource
fromAsset(that.imageSrc)
.then(res => {
var myImageSource = res;
let folder=fileSystem.knownFolders.documents();
var path=fileSystem.path.join(folder.path, milliseconds+".jpg");
var saved=myImageSource.saveToFile(path, "jpg");
that.previewSrc=path;
const imageFromLocalFile: ImageSource = <ImageSource> fromFile(path);
var base64 = imageFromLocalFile.toBase64String("jpeg", 20);
this.expense.receipt_data=base64;
})
that.cameraImage=null;
that.imageAssets = selection;
that.galleryProvided=true;
// set the images to be loaded from the assets with optimal sizes (optimize memory usage)
selection.forEach(function (element) {
element.options.width = that.previewSize;
element.options.height = that.previewSize;
});
}).catch(function (e) {
console.log(e);
});
}
Any ideas? Thanks.
It is an already communicated issue, several of us subscribed for, check here issue #321
for updates.
I'm using the plugin image-picker for nativescript and I copied the example code to see how it works and to adapt it to my code. But the code doesn't work. When I tap the button it's supposed that the screen gallery from my device should be opened, but nothing happen when I tap the button.
The code below is how I implements this.
album_list.component.ts
import { Component } from '#angular/core';
import { RouterExtensions } from 'nativescript-angular/router';
//image picker
var imagepicker = require("nativescript-imagepicker");
#Component({
selector:'album_list',
moduleId: module.id,
templateUrl: "album_list.component.html",
})
export class AlbumListComponent{
constructor(private routerExt: RouterExtensions ){}
ngOnInit() {
}
onSelectMultipleTap() {
console.log('Im in');
function selectImages() {
var context = imagepicker.create({
mode: "multiple"
});
context
.authorize()
.then(function() {
return context.present();
})
.then(function(selection) {
console.log("Selection done:");
selection.forEach(function(selected) {
console.log(" - " + selected.uri);
});
}).catch(function (e) {
console.log(e);
});
}
}
}
album_list.component.html
<StackLayout>
<Button text="Pick Multiple Images" (tap)="onSelectMultipleTap()" > </Button>
</StackLayout>
As I said, when I tap the button in the html the log from the function onSelectMultipleTap appears, but nothing else.
Thanks!!
You arent calling selectImages(), you just declare it. Replace with this:
onSelectMultipleTap() {
console.log('Im in');
function selectImages() {
var context = imagepicker.create({
mode: "multiple"
});
context
.authorize()
.then(function() {
return context.present();
})
.then(function(selection) {
console.log("Selection done:");
selection.forEach(function(selected) {
console.log(" - " + selected.uri);
});
}).catch(function (e) {
console.log(e);
});
}
selectImages()
}
I had a slightly different issue that only occurred on iOS. I'm working on an upgraded Nativescript project from 4 to 6, and yes I know NS 8 is out right now, but some of the libraries being used aren't supported on the latest NS.
My application had a modal list view that popped up to allow the user to select between camera and gallery, and once the user clicked one of the options the list modal would close. At that time the camera or gallery modal should have appeared but it didn't. What was happening was the closing of the first model was somehow blocking the second modal from opening. My fix was to add a conditional async timeout in my method before calling the context.present(). See my code below:
public takePicture() {
// const options = { width: 1280, height: 720, keepAspectRatio: false, saveToGallery: false};
const self = this;
camera.requestPermissions()
.then(async function () {
//This iOS pause is needed so the camera modal popup will not be stopped by the list option modal closing
if (isIOS) {
await new Promise(resolve => setTimeout(() => resolve(), 1000));
}
})
.then (function() {
camera.takePicture()
.then((imageAsset) => {
const imagePost = new TripMessagePostModel();
ImageSource.fromAsset(imageAsset).then((result) => {
const time = new Date();
imagePost.image = result.toBase64String("jpeg", 50);
imagePost.imageFileName = `${self.userId}-${time.getTime()}.jpeg`;
self.addPost(imagePost);
});
}).catch((err) => {
console.log("Error -> " + err.message);
});
}
)
}
public selectImage() {
const context = imagepicker.create({
mode: "single",
});
const imagePost = new TripMessagePostModel();
context
.authorize()
.then(async function() {
//This iOS pause is needed so the camera modal popup will not be stopped by the list option modal closing
if (isIOS) {
await new Promise(resolve => setTimeout(() => resolve(), 1000));
}
return context.present();
})
.then(function(selection) {
selection.forEach(async (selected) => {
ImageSource.fromAsset(selected).then((result) => {
//console.log(selected.android.toString());
const time = new Date();
imagePost.image = result.toBase64String("jpeg", 40);
imagePost.imageFileName = `${this.userId}-${time.getTime()}.jpeg`;
this.addPost(imagePost);
});
});
}).catch((e) => {
console.log(e);
});
}
In my app, i have the regions as header,content,footer - in which on the login page, I don't want to use the header, and footer. for that, on onRender i remove the regions what i don't want to be.
But I am getting an error saying: Cannot read property 'empty' of undefined.
here is my template : (i use jade )
div#wrapper
script(type='text/template', id="appTemplate")
div#header
div#content
div#footer
script(type='text/template', id="loginTemplate")
div this is login template
here is my layout.js:
socialApp.AppLayout = Backbone.Marionette.LayoutView.extend({
el:'#wrapper',
template:'#appTemplate',
regions: {
header : '#header',
content : '#content',
footer : '#footer'
},
onRender : function () {
this.removeRegion("header", "#header"); //i am removing header alone here.
}
});
here is my controller.js
socialApp.loginController = Marionette.Controller.extend({
_initialize:function(){
this.loginView = new loginView({model:new loginModel});
this.layout.onRender(); //calling onRender from here...
this.layout.content.show(this.loginView);
}
});
But it's all not working. any one help me the correct way please?
You should never call methods that are prefixed with on manually. Those are there for your code to react to given events, in this case that the view’s render method was invoked.
I would suggest that you instead of trying to remove and then later re-add regions, you create two different layouts. Then when your router hits the login route, you render LoginLayout into your App’s root region, and for other routes, the ‘normal’ layout. Here’s how I solved something similar:
app.js:
var App = new Marionette.Application;
App.addRegions({ root: '#acme' });
// Instantiate User model
App.addInitializer(function()
{
this.user = new UserModel;
});
// Render App layout
App.addInitializer(function()
{
this.layout = this.user.get('id') ? new ContentLayoutView({ identifier: 'content' }) : new UserLayoutView({ identifier: 'user' });
this.root.show(this.layout);
// And let the routers decide what goes in the content region of each layout
this.router = {
content: new ContentRouter,
user: new UserRouter
};
});
layout/content.js
var ContentLayout = Marionette.LayoutView.extend(
{
identifier: 'content',
template: ContentLayoutTemplate,
regions: {
content: '[data-region="content"]',
panelLeft: '[data-region="panel-left"]',
panelRight: '[data-region="panel-right"]'
},
initialize: function()
{
this.content.once('show', function(view)
{
this.panelLeft.show(new PanelLeftView);
this.panelRight.show(new PanelRightView);
}.bind(this));
}
});
layout/user.js
var UserLayout = Marionette.LayoutView.extend(
{
identifier: 'user',
template: UserLayoutTemplate,
regions: {
content: '[data-region="content"]'
}
});
router/content.js
var ContentRouter = Marionette.AppRouter.extend(
{
routes: {
'(/)': '...'
},
createLayout: function(callback)
{
if(App.root.currentView.options.identifier != 'content')
{
var layout = new ContentLayoutView({ identifier: 'content' });
this.region = layout.content;
this.listenTo(layout, 'show', callback);
App.root.show(layout);
}
else
{
this.region = App.root.currentView.content;
callback();
}
},
execute: function(callback, args)
{
if(App.user.get('id'))
{
this.createLayout(function()
{
callback.apply(this, args);
}.bind(this));
}
else
App.router.user.navigate('login', true);
}
});
router/user.js
var UserRouter = Marionette.AppRouter.extend(
{
routes: {
'login(/)': 'showLogin',
'logout(/)': 'showLogout'
},
createLayout: function(callback)
{
if(App.root.currentView.options.identifier != 'user')
{
var layout = new UserLayoutView({ identifier: 'user' });
this.region = layout.content;
this.listenTo(layout, 'show', callback);
App.root.show(layout);
}
else
{
this.region = App.root.currentView.content;
callback();
}
},
execute: function(callback, args)
{
this.createLayout(function()
{
callback.apply(this, args);
}.bind(this));
},
showLogin: function()
{
var LoginView = require('view/detail/login');
this.region.show(new LoginView);
},
showLogout: function()
{
var LogoutView = require('view/detail/logout');
this.region.show(new LogoutView);
}
});