Nativescript how to save image to file in custom component - nativescript

I have created a custom component that access the device's camera to snap a picture, set it as source of an ImageView and then save it to a file.
Here is the Javascript code
CAMERA.JS
Here is the initialization of the imageView
export function cameraLoaded(args):void{
cameraPage = <Page> args.object;
imageView = <Image> cameraPage.getViewById("img_upload");...
}
Here I set the imageViews'source to the just taken picture
export function takePicture():void{
camera.takePicture(
{
})
.then(
function(picture){
imageView.imageSource = picture;
},function(error){
});
}
This works perfectly.
Now I try to save the picture to a file.
export function saveToFile():void{
try {
let saved = imageView.imageSource.saveToFile(path,enums.ImageFormat.png);
HalooseLogger.log(saved,"upload");
})
}catch (e){
...
}
}
Here I get an error cannot read property saveToFile of undefined
This is very unusual, in fact if I console.log(imageView) here is the output :
Image<img_upload>#file:///app/views/camera/camera.xml:4:5;
but if I console.log(imageView.imageSource) i see it is ´undefined`.
How is this possible? What am I doing wrong?
ADDITIONAL INFO
The previous code and relatex xml is included in another view as follows :
MAIN.XML
<Page
xmlns="http://schemas.nativescript.org/tns.xsd"
xmlns:cameraPage="views/camera"
loaded="loaded">
<StackLayout orientation="vertical">
<StackLayout id="mainContainer">
<!-- DYNAMIC CONTENT GOES HERE -->
</StackLayout>
</StackLayout>
</Page>
MAIN.JS
This is were the camera view gets loaded dynamically
export function loaded(args):void{
mainPage = <Page>args.object;
contentWrapper = mainPage.getViewById("mainContainer");
DynamicLoaderService.loadPage(mainPage,contentWrapper,mainViewModel.currentActive);
}
The loadPage method does the following :
public static loadPage(pageElement,parentElement,currentActive):void{
let component = Builder.load({
path : "views/camera",
name : "camera",
page : pageElement
});
parentElement.addChild(component);
}

The thing is that as of NativeScript 2.4.0 the Image created for Android will always return null for its property imageSource. Currently, optimisations are on the way to prevent Out of Memory related issues when working with multiple large images and that is why image-asset was presented in nativeScript 2.4.0.
Now I am not sure if you are using the latest nativescript-camera (highly recommended) but if so you should consider that the promise from takePicture() is returning imageAsset. Due to the memory optimization imageSource will always return undefined (for Android) unless you specifically create one. You can do that with fromAsset() method providing the ImageAsset returned from the camera callback.
Example:
import { EventData } from 'data/observable';
import { Page } from 'ui/page';
import { Image } from "ui/image";
import { ImageSource, fromAsset } from "image-source";
import { ImageAsset } from "image-asset";
import * as camera from "nativescript-camera";
import * as fs from "file-system";
var imageModule = require("ui/image");
var img;
var myImageSource: ImageSource;
// Event handler for Page "navigatingTo" event attached in main-page.xml
export function onLoaded(args: EventData) {
// Get the event sender
let page = <Page>args.object;
img = <Image>page.getViewById("img");
camera.requestPermissions();
}
export function takePhoto() {
camera.takePicture()
.then(imageAsset => {
console.log("Result is an image asset instance");
img.src = imageAsset;
fromAsset(imageAsset).then(res => {
myImageSource = res;
console.log(myImageSource);
})
}).catch(function (err) {
console.log("Error -> " + err.message);
});
}
export function saveToFile(): void {
var knownPath = fs.knownFolders.documents();
var folderPath = fs.path.join(knownPath.path, "CosmosDataBank");
var folder = fs.Folder.fromPath(folderPath);
var path = fs.path.join(folderPath, "Test.png");
var saved = myImageSource.saveToFile(path, "png");
console.log(saved);
}

Related

I'm trying to Use AppLoading to render home screen components but cant get the splash screen to stay until everything is loaded

After optimising my images iv realised that I still need more time for my components to load. They are card like components with images.
I have 2 components to load one is in a flatList, the other just a basic card like component each component contains images. I have been trying in vain to get this to work and have to ask if anyone has a good solution. Here's what I have so far.
import React, { useState } from "react";
import { View, StyleSheet } from "react-native";
import AppLoading from "expo-app-loading";
import Header from "./components/Header";
import HomeScreen from "./screens/HomeScreen";
const fetchHomeScreen = () => {
return HomeScreen.loadAsync({
HomeScreen: require("./screens/HomeScreen"),
Header: require("./components/Header"),
});
};
export default function App() {
const [HomeScreenLoaded, setHomeScreenLoaded] = useState(false);
if (!HomeScreenLoaded) {
return (
<AppLoading
startAsync={fetchHomeScreen}
onFinish={() => setHomeScreenLoaded(true)}
onError={(err) => console.log(err)}
/>
);
}
return (
<View style={styles.screen}>
<Header title="Your Beast Log" />
<HomeScreen />
</View>
);
}
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: "#3E3636",
},
});
Are you using expo status bar ?? then try these;
import splash screen
import * as SplashScreen from 'expo-splash-screen';
add this to the top of main function
SplashScreen.preventAutoHideAsync()
export default function App() {....}
then add this to your screen where you hide the splash screen
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
if(appIsready) {
(async () => {
await SplashScreen.hideAsync();
})()
}
},[appIsReady])
async function prepare() {
try {
await fetchHomeScreen;
//OR
await HomeScreen.loadAsync({
HomeScreen: require("./screens/HomeScreen"),
Header: require("./components/Header"),
});
} catch (e) {
console.warn(e);
} finally {
// Tell the application to render
setAppIsReady(true);
}
}

Copy or download generated QR (vue-qrcode) code with VueJs

I use the plugin "vue-qrcode" to generate a qr code for my users to for a link to their public profile so they can share it e.g. on thei business card.
The Idea is now to give my users the possibility via a button to download the qr code and via another button to copy the qr code to the clipboard to make it easier to send it e.g. via mail (specially for the smartphone users).
Problem: I don't know how i can download or copy the qr code. Does anybody know to copy or download the qr code? Currently I use 'vue-clipboard2' to copy links etc. but it seems it can't copy images.
I use the below code to display the qr code on our site:
<template>
<qrcode-vue
style = "cursor: pointer;"
:value = "linkToProfile"
size = 160
level = "M"
:background = "backgroundcolor_qrcode"
:foreground = "color_qrcode"
></qrcode-vue>
</template>
<script>
import Vue from 'vue'
import QrcodeVue from 'qrcode.vue'
Vue.component('qrcode-vue', QrcodeVue )
export default {
data: () => ({
linkToProfile: "http://www.example.com/johnDoe",
})
</script>
Thanks -
Christian
I figured it out. The solution looks like this:
TEMPLATE AREA:
<qrcode-vue
id="qrblock"
:value = "linkToSki"
size = 220
level = "M"
ref="qrcode"
></qrcode-vue>
FUNCITONS AREA:
// -- FUNCTIONS AREA TO COPY / DOWNLOAD QR CODE - END ---
function selectText(element) {
if (document.body.createTextRange) {
const range = document.body.createTextRange();
range.moveToElementText(element);
range.select();
} else if (window.getSelection) {
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
}
}
function copyBlobToClipboardFirefox(href) {
const img = document.createElement('img');
img.src = href;
const div = document.createElement('div');
div.contentEditable = true;
div.appendChild(img);
document.body.appendChild(div);
selectText(div);
const done = document.execCommand('Copy');
document.body.removeChild(div);
return done;
}
function copyBlobToClipboard(blob) {
// eslint-disable-next-line no-undef
const clipboardItemInput = new ClipboardItem({
'image/png' : blob
});
return navigator.clipboard
.write([clipboardItemInput])
.then(() => true)
.catch(() => false);
}
function downloadLink(name, href) {
const a = document.createElement('a');
a.download = name;
a.href = href;
document.body.append();
a.click();
a.remove();
}
// -- FUNCTIONS AREA TO COPY / DOWNLOAD QR CODE - END ---

Share To Teams Error "Could not load content for https://local.teams.office.com/sourcemaps/../unhashed-assets/launcher.js.map"

We are trying to add "Share To Teams" button to our web application which is an angular app.
but while adding script, it gives an error as
Could not load content for
https://local.teams.office.com/sourcemaps/../unhashed-assets/launcher.js.map:
Connection error: net::ERR_CONNECTION_REFUSED
And the button does not display.
<a class="teams-share-button" data-href="someurl"></a>
Script rendering function in typescript
private appendShareToTeamsScript(): void{
//Add Share to Teams script to the page
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://teams.microsoft.com/share/launcher.js";
document.body.appendChild(script);
}
To load external JavaScript on component use ngOnInit() to append the libraries.
I have tried the below code snippet and worked for me.
Add below anchor tag in home.component.html
<a class="teams-share-button" data-href="yor url" data-icon-px-size="64"></a>
add below code snippet in home.component.ts
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
})
export class HomeComponent implements OnInit {
title = 'app';
ngOnInit() {
this.loadScript('https://teams.microsoft.com/share/launcher.js');
}
public loadScript(url: string) {
const body = <HTMLDivElement> document.body;
var script = document.createElement('script');
script.src = url;
body.appendChild(script);
}
}

Nativescript imagepicker not working in iOS :: not picking up image path?

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.

How to add custom completer in react ace editor from outside of language_tools

How to add custom completer in react based ace editor from index.js using functions like addCompleter or setCompleter
import { render } from "react-dom";
import AceEditor from "../src/ace";
import "brace/mode/jsx";
import 'brace/mode/HCPCustomCalcs'
import 'brace/theme/monokai'
import "brace/snippets/HCPCustomCalcs";
import "brace/ext/language_tools";
const defaultValue = `function onLoad(editor) {
console.log("i've loaded");
}`;
class App extends Component {
constructor(props, context) {
super(props, context);
this.onChange = this.onChange.bind(this);
}
onChange(newValue) {
console.log('changes:', newValue);
}
render() {
return (
<div>
<AceEditor
mode="HCPCustomCalcs"
theme="monokai"
width={ '100%' }
height={ '100vh' }
onChange={this.onChange}
name="UNIQUE_ID_OF_DIV"
editorProps={{
$blockScrolling: true
}}
enableBasicAutocompletion={true}
enableLiveAutocompletion={true}
enableSnippets={true}
/>
</div>
);
}
}
render(<App />, document.getElementById("example"));
i want to add my custom completer from here.my completer is something like this
var myCompleter ={
getCompletions: function(editor, session, pos, prefix, callback) {
var completions = [];
["word1", "word2"].forEach(function(w) {
completions.push({
value: w,
meta: "my completion",
});
});
callback(null, completions);
}
})}
In normal Ace-editor it was straight forward.By simply calling
var langTools = ace.require("ace/ext/language_tools")
langTools.addCompleter(myCompleter1);```
You can do something very similar to the normal Ace-editor.
const langTools = ace.acequire('ace/ext/language_tools');
langTools.addCompleter(myCompleter);
Using ace.acequire allows you to access hidden ACE features. Some documentation here: https://github.com/thlorenz/brace/wiki/Advanced:-Accessing-somewhat-hidden-ACE-features

Resources