Trying to type return of generic Function [Flow] - flowtype

i'm want to create mapperFunction and type it return with generics;
But i'm catching an error;
Error - Cannot return true because boolean [1] is incompatible with T [2]
/* #flow */
const someMapper = <T>(param): T => {
if (typeof param === 'boolean') {
return true;
}
if (typeof param === 'string') {
return 'string';
}
return undefined;
}
Working version, but without generics
const someMapper = (param): boolean | string | void => {
if (typeof param === 'boolean') {
return true;
}
if (typeof param === 'string') {
return 'string';
}
return undefined;
}
try flow link

Related

Vue 3 Composition API broken after re-rendering the component

thanks for reading my post.
It's hard to describe my issue.
I have a reactive object cached globally(like a store and let's call it 'store' here) to preserve the states together with some actions. It's working all good initially. But when I added more pages & switching between the pages, the components are broken after re-rendering. The components can still read correctly from the store and call the actions to update the store data and can console.log the 'fingerprint' to show it's the same store. But the updated data in store is no longer updated in the UI. I can console.log the data in store which looks all good, but the UI is frozen with the data since last unmounted.
I'm providing my component code below.
<template>
<div class="min-w-full relative" ref="container">
<hi-transition slow>
<center-box
v-if="loading"
class="absolute w-full h-full left-0 top-0 text-primary-light opacity-50"
>
<hi-spinner class="w-1/6 h-1/6" :stroke="2" />LOADING...
</center-box>
<div v-else-if="noResult">
No Result
</div>
<hi-markable
v-else
class="w-full h-full divide-y divide-primary-lighter overflow-y-auto block"
:search="searchValue"
>
<div
v-for="item in displayItems"
:key="item.id"
class="hover:bg-primary-lightest transition-colors duration-500 flex items-center block"
#click="selectItem(item)"
:style="{
'min-height': minItemHeight + 'px',
height: itemHeight + 'px'
}"
:class="{ active: currentItem === item }"
>
<slot :item="item" />
</div>
</hi-markable>
</hi-transition>
</div>
</template>
<script>
import { inject, ref } from "vue";
import HiSpinner from "#/ui/HiSpinner";
import CenterBox from "#/ui/elements/CenterBox";
import HiTransition from "#/ui/HiTransition";
import HiMarkable from "#/ui/HiMarkable";
import { computed } from "vue";
export default {
name: "HiDataList",
components: { HiMarkable, HiTransition, CenterBox, HiSpinner },
props: {
storeToken: String,
minItemHeight: [Number, String],
autoItemsPerPage: Boolean
},
emits: ["select"],
setup(props, { emit }) {
//this store is a reactive object cached somewhere
const store = inject(props.storeToken);
//to make sure it's the same store
console.log("fingerprint", store.fingerprint);
const { displayItems, currentItem, loading, searchValue, noResult } = store;
const container = ref(null);
const itemHeight = computed(() => {
return props.autoItemsPerPage
? store.autoItemHeight.value
: props.minItemHeight;
});
if (props.autoItemsPerPage) {
store.autoItemsPerPage(container, props.minItemHeight);
}
function selectItem(item) {
console.log(item);
emit("select", item);
store.setCurrentItem(item);
}
return {
displayItems,
selectItem,
container,
itemHeight,
currentItem,
loading,
searchValue,
noResult
};
}
};
</script>
<style scoped>
.active,
.active:hover {
#apply bg-primary-lighter border-primary-light;
}
</style>
The listStore
import { computed, reactive, watch, toRefs } from "vue";
import { useElementSize } from "#vueuse/core";
import { watchProps } from "#/utils/reactiveHelpers";
function filter(item, filters) {
const f = (key, filter) => {
const itemVal = item[key];
if (Array.isArray(itemVal)) return itemVal.indexOf(filter) >= 0;
else return itemVal === filter;
};
for (let key in filters) {
const filterVal = filters[key];
if (Array.isArray(filterVal)) {
for (let i = 0; i < filterVal.length; i++) {
if (f(key, filterVal[i])) return true;
}
} else {
return f(key, filterVal);
}
}
return false;
}
const getTime = date => {
if (date.milliseconds) return date.milliseconds;
if (date.seconds) return date.seconds;
if (date.getTime) return date.getTime();
};
const createListStore = (source, settings = {}) => {
const state = reactive({
source: source,
currentPage: 0,
itemsPerPage: 0, //zero means display all
loading: true,
ready: false,
noData: false,
noResult: false,
filters: {},
search: null,
searchables: settings.searchables || [],
sortBy: null,
sortType: "alpha",
desc: false,
currentItem: null,
autoItemHeight: 0,
fingerprint: Math.random() * 10000000000000000
});
// const { itemsPerPage,source,filters,search,searchables,sortBy,sortType,desc } = toRefs(state);
const {
itemsPerPage,
ready,
loading,
noResult,
search,
autoItemHeight
} = toRefs(state);
watchProps(state, "source", v => {
if (typeof v !== "undefined") {
state.ready = true;
state.loading = false;
if (!v.length) state.noData = true;
}
});
const currentPage = computed(() => state.currentPage + 1);
// const itemsPerPage = computed(() => state.itemsPerPage);
const totalItems = computed(() => results.asc.length);
const from = computed(() => {
if (totalItems.value === 0) return 0;
return state.currentPage * state.itemsPerPage + 1;
});
const to = computed(() => {
const t = from.value + displayItems.value.length - 1;
return t > totalItems.value ? totalItems.value : t;
});
const totalPages = computed(() => {
if (totalItems.value === 0 || state.itemsPerPage === 0) return 1;
return Math.ceil(totalItems.value / state.itemsPerPage);
});
const gotoPage = page => {
console.log("gotoPage", page);
state.currentPage = page - 1;
console.log(state.currentPage);
};
const prevPage = () => {
if (state.currentPage > 0) state.currentPage--;
};
const nextPage = () => {
if (state.currentPage < totalPages.value) state.currentPage++;
};
const updateFilters = (filter, val) => {
state.filters[filter] = val;
};
/**
*
* #param column
* #param desc
* #param type "alpha"|"number"|"date"|"time"
*/
const sortBy = (column, desc = false, type = "alpha") => {
state.sortBy = column;
state.desc = desc;
state.sortType = type;
};
function doSearch(item) {
const searchables = state.searchables;
for (let i = 0; i < searchables.length; i++) {
const key = searchables[i];
let value = item[key];
if (value && typeof value === "string") {
value = value.toLowerCase();
}
if (value && value.indexOf(state.search) >= 0) {
return true;
}
}
return false;
}
const results = reactive({
desc: [],
asc: []
});
function calcResults() {
if (!state.ready || state.noData) return null;
// console.log("re-calc results....");
const hasFilters = Object.keys(state.filters).length > 0;
// console.log(Object.keys(state.filters));
let items = [];
if (hasFilters || (state.search && state.search.length)) {
//do filter & search
const source = state.source;
for (let i = 0; i < source.length; i++) {
const item = source[i];
// console.log(filter(item, state.filters));
if (hasFilters && !filter(item, state.filters)) {
continue;
}
if (state.search && state.search.length && !doSearch(item)) {
continue;
}
items.push(item);
}
if (!items.length) {
results.desc = results.asc = [];
return null;
}
} else {
items = state.source;
}
if (state.sortBy) {
//do sort
const sort = state.sortBy;
// const desc = state.desc ? -1 : 1;
const type = state.sortType.toLowerCase();
items.sort((a, b) => {
a = a[sort];
b = b[sort];
if (type === "date" || type === "time") {
return getTime(a) - getTime(b);
} else {
if (typeof a === "string") a = a.trim();
if (typeof b === "string") b = b.trim();
if (state.sortType.toLowerCase() === "number") {
return a - b;
} else {
return a.localeCompare(b, "en", { sensitivity: "base" });
}
}
});
}
results.asc = items;
results.desc = [...items].reverse();
// return items;
}
//changed to watch for the wired vue error.
watchProps(
state,
["source", "filters", "search", "searchables", "sortBy", "sortType"],
() => {
calcResults();
state.noResult = results.asc.length === 0;
}
);
const displayItems = computed(() => {
if (!results.asc.length) return [];
const re = state.desc ? results.desc : results.asc;
if (state.itemsPerPage === 0) return re;
const from = state.currentPage * state.itemsPerPage;
const to = from + state.itemsPerPage;
return re.slice(from, to);
});
/**
*
* #param elementRef ref
* #param minHeight Number
* #param itemHeightRef ref
*/
const autoItemsPerPage = (elementRef, minHeight) => {
const { height } = useElementSize(elementRef);
// console.log(elementRef);
watch(height, v => {
const items = Math.floor(v / minHeight);
// itemHeightRef.value = v / items;
// console.log(v / items);
state.itemsPerPage = items;
state.autoItemHeight = v / items;
});
};
const setCurrentItem = item => {
console.log("set current", state.fingerprint);
state.currentItem = item;
};
const currentItem = computed(() => state.currentItem);
return {
currentPage,
itemsPerPage,
totalItems,
displayItems,
from,
to,
totalPages,
gotoPage,
prevPage,
nextPage,
ready,
loading,
updateFilters,
sortBy,
search: v => (state.search = v),
searchValue: search,
autoItemsPerPage,
setCurrentItem,
currentItem,
noResult,
autoItemHeight,
fingerprint: state.fingerprint
};
};
export { createListStore };
The store provider
import { provide } from "vue";
import { createListStore } from "#/ui/storeProvider/listStore";
const storeDepot = {};
export const provideListStore = (token, source, settings = {}) => {
if (!storeDepot[token]) {
console.log("create new store", token);
storeDepot[token] = createListStore(source, settings);
}
provide(token, storeDepot[token]);
return storeDepot[token];
};

Flow JS - How to use Generics in an Object as a Map with a Class

I have the following class:
// #flow
type Entities<EntityType> = {
[number]: EntityType
}
export default class NormalizedCollection<EntityType> {
entities: Entities<EntityType>
result: Array<number>
constructor(entities: Entities<EntityType>, result: Array<number>) {
this.entities = entities
this.result = result
}
getItems = (): Array<Object> => {
try {
if (this.result) {
return this.result.map((index, key) => this.entities[index]).filter((item)=>typeof item !== 'undefined')
}
} catch (e) {}
return []
}
find = (id: number): Entities<EntityType> | null => {
if(typeof this.entities === 'undefined' || typeof this.entities[id] === 'undefined') {
return null
}
return this.entities[id]
}
}
This class takes two parameters, Entities (an Object which functions as a Map) and Results which is an Array of numbers. I'd like consumers of the class to be able to indicate the entity type they are passing.
This gives me two errors.
From getItems:
Cannot return this.result.map(...).filter(...) because EntityType [1] is incompatible with object type [2] in array element.
From find:
Cannot return this.entities[id] because EntityType [1] is incompatible with Entities [2]
What is the proper way to pass the entity type to NormalizedCollection?
I forgot to change the return types! (Array<EntityType> and EntityType respectively).
// #flow
type Entities<EntityType> = {
[id: number]: EntityType
}
export default class NormalizedCollection<EntityType> {
entities: Entities<EntityType>
result: Array<number>
constructor(entities: Entities<EntityType>, result: Array<number>) {
this.entities = entities
this.result = result
}
getItems = (): Array<EntityType> => {
try {
if (this.result) {
return this.result.map(index => this.entities[index]).filter((item)=>typeof item !== 'undefined')
}
} catch (e) {}
return []
}
find = (id: number): EntityType | null => {
if(typeof this.entities === 'undefined' || typeof this.entities[id] === 'undefined') {
return null
}
return this.entities[id]
}
}

Window object in nativescript

I need to create a window object so that the external file, which is loaded inside iframe, can call nativescript function.
I, specifically need window object because the file I am loading can be any file that follows conformant (SCORM) related to LMS.
Edit :
The file I have loaded is a SCORM conformant file. They search for window.API object/window.parent.API and so on, for starting communication with the container in which they have been loaded. I can't alter that file.
Tell me if more details needed.
We are successfully handling SCORM content in our NativeScript application, but it does require a little bit of hackery to accomplish.
I authored a utility class that will inject into the main entry file (index) of the SCORM content an override for the window API.
Background:
In our app we decompress the zip courses to the devices: documents/courses/UUID (that's what identity is referencing)
You can change the path as needed for your implementation
Example usage of the utility class:
const documents = fs.knownFolders.documents();
const destination = fs.path.join(documents.path, 'courses', this.media.identity);
this._readAccessUrl = destination;
const src = `file://${destination}`;
if (fs.File.exists(destination)) {
SCORMUtils.readSCORMManifest(this.media.identity).then(fileName => {
this._src = `${src}/${fileName}`;
SCORMUtils.makeOfflineCompatible(fs.path.join(destination, fileName))
.then(() => {
this._loading = false;
});
this._loading = false;
});
}
Utility Class:
import { File, knownFolders } from 'tns-core-modules/file-system';
const SCORM_API = `
<script type="text/javascript">
(function () {
window.parent.API = (function() {
return {
LMSInitialize: function () {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSInitialize");
}
return "true";
},
LMSCommit: function () {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSCommit");
}
return "true";
},
LMSFinish: function () {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSFinish");
}
return "true";
},
LMSGetValue: function (key) {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSGetValue");
}
return "";
},
LMSSetValue: function (key, value) {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSSetValue");
}
return "true";
},
LMSGetLastError: function () {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSGetLastError");
}
return "0";
},
LMSGetErrorString: function (errorCode) {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSGetErrorString");
}
return "No error";
},
LMSGetDiagnostic: function (errorCode) {
if (window && window.webkit) {
window.webkit.messageHandlers.print.postMessage("LMSGetDiagnostic");
}
return "No error";
}
}
})();
})();
</script>
</head>
`;
export class SCORMUtils {
/**
* Converts a SCORM course to be opened offline
* #param entryFile The main entry point determined by imsmanifest.xml
*/
static makeOfflineCompatible(entryFile: string) {
return new Promise((resolve, reject) => {
// Rewrite the entry file first
this._rewriteFile(entryFile)
.then(() => {
this._discoverHTMLEntry(entryFile)
.then(() => {
resolve();
}, () => {
console.error('Unable to rewrite alternative HTML entry');
reject();
});
}, () => {
console.error(`Unable to rewrite primary entry point: ${entryFile}`);
reject();
});
});
}
/**
* Digests a SCORM Manifest file to determine the main point of entry
* for the course viewer. Normally this is a index.html file.
*/
static readSCORMManifest(identity: string): Promise<string> {
return new Promise((resolve, reject) => {
const manifestFile = knownFolders.documents()
.getFolder('courses')
.getFolder(identity)
.getFile('imsmanifest.xml');
if (!File.exists(manifestFile.path)) {
alert({
title: 'Error',
message: 'Course is missing imsmanifest.xml file',
okButtonText: 'Ok'
});
return reject();
}
const data = manifestFile.readTextSync(() => {
alert({
title: 'Error',
message: 'Cannot open course.',
okButtonText: 'Ok'
});
return reject();
});
const matches = data.match(/type="webcontent"+.+?href="(.*?)"/);
if (matches === null || matches.length < 1) {
alert({
title: 'Error',
message: 'Invalid imsmanifest.xml file',
okButtonText: 'Ok'
});
}
else {
resolve(matches[1]);
}
});
}
/**
* Rewrites a file to be SCORM offline-compliant
* #param path The path of the file to re-write
*/
private static _rewriteFile(path: string) {
return new Promise((resolve, reject) => {
const entryFile = File.fromPath(path);
entryFile.readText()
.then(htmlText => {
this._injectOfflineAPI(htmlText)
.then(updatedHtml => {
entryFile.writeText(updatedHtml).then(() => {
resolve();
}, () => {
console.error(`Error writing to file: ${path}`);
reject();
});
});
}, () => {
console.error(`There was an entry reading the entry file at: ${path}`);
reject();
});
});
}
/**
* Attempts to find another SCORM entry point for re-write
* #param mainEntry The main entry point to branch from
*/
private static _discoverHTMLEntry(mainEntry: string): Promise<any> {
return new Promise((resolve, reject) => {
const entryFile = File.fromPath(mainEntry);
entryFile.readText()
.then(htmlText => {
let htmlEntry = htmlText.match(/{"type":"html5","url":"(.*?)"}/);
if (htmlEntry === null || htmlEntry.length < 1) {
// Check for Articulate
htmlEntry = htmlText.match(/location\.href\.replace\("index_lms", "(.*?)"/);
}
if (htmlEntry !== null && htmlEntry.length > 0) {
let fileName = htmlEntry[1];
if (fileName.indexOf('.html') === -1) {
fileName = `${fileName}.html`;
}
const directory = mainEntry.substr(0, mainEntry.lastIndexOf('/'));
const entryPoint = `${directory}/${fileName}`;
if (File.exists(entryPoint)) {
this._rewriteFile(entryPoint)
.then(() => {
resolve();
}, () => {
console.error('Error discovering main entry point.');
reject();
});
}
else {
console.error(`Cannot find alternative entry point: ${entryPoint}`);
reject();
}
}
else {
// This course does not have an alternative entry point
console.error('Course does not have an alternative entry, skipping...');
resolve();
}
}, () => {
reject();
});
});
}
/**
* Injects the extended SCORM API for offline-compatible viewing
* #param text The unmodified HTML source text
*/
private static _injectOfflineAPI(text: string): Promise<string> {
return new Promise((resolve, reject) => {
// Prevent multiple rewrites of the same file
if (this._isConverted(text)) {
return resolve(text);
}
// Finds the end of the head tag for script injection
const head = text.match(/<\/head>/gi);
if (head !== null && head.length > 0) {
resolve(text.replace(head.toString(), SCORM_API));
}
else {
console.error('Unable to parse incoming HTML for head tag.');
reject({
message: 'Unable to parse HTML'
});
}
});
}
/**
* Checks if the HTML has already been converted for offline-viewing
* #param text The incoming HTML source text
*/
private static _isConverted(text: string) {
const match = text.match(/window.parent.API/);
return match !== null && match.length > 0;
}
}

Cloud functions for firebase with transaction operation finished with status: 'timeout'

Function with transaction operation is timing out. I am not able find out what is wrong. I am not sure if it is an issue with firebase.
exports.ComputeUserReviews = functions.database.ref("/reviews/{userid}/{jobid}").onWrite((event) => {
const userid = event.params.userid;
const parentRefPromise = admin.database().ref(`reviews/${userid}`).once('value');
const UserReviewRef = admin.database().ref(`users/${userid}/account`);
return Promise.all([parentRefPromise]).then(results => {
const userReviewSnapshot = results[0];
const hasreviewPromise = UserReviewRef.child("hasreview").transaction(current => {
if (userReviewSnapshot.numChildren() === 0) {
return false;
} else if (userReviewSnapshot.numChildren() > 0) {
return true;
}
});
const reviewcountPromise = UserReviewRef.child("reviewcount").transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
} else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
});
return Promise.all([hasreviewPromise, reviewcountPromise]);
});
});
There is an issue with the firebase SDK, as a workaround adding .then(()=>{console.log('message');}); fix the timeout.
const reviewcountPromise = UserReviewRef.child("reviewcount").transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
} else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
}).then(()=>{console.log('message');});

Cloud Functions for Firebase return array of promises to GCF

In my onWrite event handler I perform data updates in different database paths. The function gets interrupted without finishing performing tasks. I am implementing Promise.all() to send array of promises to GCF and await for all outstanding work/tasks to complete. Console log is not showing errors. I am trying to find out if I am doing the right implementation.
exports.ObserveProposals = functions.database.ref("/proposals/{jobid}/{propid}").onWrite((event) => {
const jobid = event.params.jobid;
const userId = event.params.propid;
const promises = [];
const isinvitation = event.data.child("isinvitation").val();
if (!isinvitation) {
return userRef = admin.database().ref(`users/${userId}/proposals/sent`);
if (event.data.exists() && !event.data.previous.exists()) {
return userRef.child(jobid).set({
timestamp: admin.database.ServerValue.TIMESTAMP
});
} else if (!event.data.exists() && event.data.previous.exists()) {
return userRef.child(jobid).remove();
}
promises.push(userRef);
}
return collectionRef = admin.database().ref(`/jobs/${jobid}`).once('value').then(snapshot => {
if (snapshot.val() !== null) {
return countRef = collectionRef.child("proposals").transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
} else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
});
}
promises.push(collectionRef);
});
return Promise.all(promises);
});
Bellow function is working properly, array of promises is being sent to GCF and all tasks are executed.
exports.ObserveProposals = functions.database.ref("/proposals/{jobid}/{propid}").onWrite((event) => {
const jobid = event.params.jobid;
const userid = event.params.propid;
const promises = [];
let userRef = admin.database().ref(`users/${userid}/proposals`);
let jobRef = admin.database().ref(`/jobs/${jobid}/proposals`);
jobRef.once('value').then(snapshot => {
if (snapshot.val() !== null) {
jobRef.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
} else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
});
}
});
promises.push(jobRef);
if (event.data.exists() && !event.data.previous.exists()) {
const isInvitation = event.data.child("isinvitation").val();
if (!isInvitation) {
return userRef.child(`/sent/${jobid}`).set({
timestamp: admin.database.ServerValue.TIMESTAMP
});
} else if (isInvitation) {
return userRef.child(`/received/${jobid}`).set({
timestamp: admin.database.ServerValue.TIMESTAMP
});
}
} else if (!event.data.exists() && event.data.previous.exists()) {
const isInvitation = event.data.previous.child("isinvitation").val();
if (!isInvitation) {
return userRef.child(`/sent/${jobid}`).remove();
}else if (isInvitation) {
return userRef.child(`/received/${jobid}`).remove();
}
}
promises.push(userRef);
return Promise.all(promises);
});

Resources