Showing posts with label Javascript. Show all posts
Showing posts with label Javascript. Show all posts

Monday, October 23, 2023

React JS Material UI icons for Mastodon and Blogger

Other than the Linkedin icon, I couldn't find the Mastodon and Blogger React Material UI (MUI) icons in the React @mui/icons-material library so I had to make my own with Inkscape and a good old text editor. The results are shown in the screenshot below.

 Here is the MastodonIcon.js code for the Mastodon icon:

import * as React from 'react';
import SvgIcon from '@mui/material/SvgIcon';

export default function MastodonIcon() {
  return (
    <SvgIcon>
      <svg className="mastodon" width="16" height="16" fill="currentColor" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
        <path d="m10.751 11.617c1.7387-0.20699 3.2514-1.2721 3.4412-2.2449 0.30012-1.5334 0.27598-3.7421 0.27598-3.7421 0-2.9926-1.9715-3.8706-1.9715-3.8706-0.99355-0.45451-2.7002-0.6451-4.4734-0.65976h-0.043122c-1.774 0.014661-3.48 0.20526-4.4734 0.65976 0 0-1.9706 0.87709-1.9706 3.8706l-0.00173 0.57093c-0.00345 0.55195-0.00603 1.1643 0.0095 1.8033 0.071582 2.9271 0.53988 5.8128 3.26 6.5286 1.2539 0.33031 2.3311 0.39931 3.1988 0.35188 1.5722-0.08625 2.4554-0.55799 2.4554-0.55799l-0.05176-1.1358s-1.1237 0.3536-2.3864 0.31047c-1.2505-0.04312-2.57-0.13454-2.7728-1.6628a3.1169 3.1169 0 0 1-0.028456-0.42777s1.2281 0.2984 2.784 0.36912c0.95126 0.04312 1.843-0.0552 2.7495-0.163zm1.3911-2.1302h-1.4429v-3.5188c0-0.74083-0.31393-1.1168-0.94091-1.1168-0.69339 0-1.041 0.44587-1.041 1.329v1.9258h-1.4351v-1.9266c0-0.88313-0.34756-1.329-1.041-1.329-0.62699 0-0.94091 0.37602-0.94091 1.1177v3.5179h-1.4429v-3.6239c0-0.74083 0.18973-1.329 0.56921-1.7646 0.39327-0.43553 0.90728-0.6589 1.5463-0.6589 0.73824 0 1.2971 0.28288 1.6671 0.84777l0.35964 0.5994 0.35964-0.5994c0.36998-0.5649 0.92884-0.84777 1.668-0.84777 0.6382 0 1.1522 0.22337 1.5446 0.6589 0.38119 0.43553 0.57007 1.0237 0.57007 1.7646z" strokeWidth=".86244" />
      </svg>
    </SvgIcon>
  );
}

And here is the BloggerIcon.js code for the Blogger icon:

import * as React from 'react';
import SvgIcon from '@mui/material/SvgIcon';

export default function SvgIconChildren() {
  return (
    <SvgIcon>
      <svg width="50.664mm" height="50.575mm" version="1.1" viewBox="0 0 179.52 179.2" xmlns="http://www.w3.org/2001/svg">
        <defs>
          <clipPath id="clipPath8">
            <path d="m111.47 137.84c6.8936-0.96075 12.296-3.7837 17.364-9.0736 3.6662-3.8268 5.9611-7.9689 7.4609-13.466 0.62305-2.2837 0.67529-3.3956 0.78946-16.804 0.0863-10.12 0.0143-14.86-0.24367-16.056-0.37384-1.7344-1.4336-3.345-2.6427-4.0165-0.37209-0.20664-2.7561-0.47002-5.2978-0.58526-4.2591-0.19314-4.7356-0.278-6.08-1.0829-2.1324-1.2768-2.7197-2.6555-2.7255-6.3987-0.0111-7.152-2.9243-13.792-8.6802-19.785-4.1006-4.2694-8.6751-7.1592-13.896-8.7785-1.2498-0.38765-4.0484-0.51957-13.422-0.63274-14.708-0.17757-17.973 0.13047-22.98 2.1682-9.2314 3.7568-15.864 11.674-18.284 21.824-0.45444 1.9064-0.54266 4.9618-0.65 22.513-0.13448 21.988 0.01386 25.217 1.3586 29.575 1.111 3.6002 2.232 5.8063 4.5414 8.9376 4.3994 5.9652 10.993 10.273 17.585 11.49 3.137 0.57911 41.84 0.72399 45.804 0.17161z" display="none" fill="#fff" strokeWidth=".86401" />
            <path className="clip" d="m8.0345 6.3499h163.45v166.5h-163.45zm103.44 131.49c6.8936-0.96075 12.296-3.7837 17.364-9.0736 3.6662-3.8268 5.9611-7.9689 7.4609-13.466 0.62305-2.2837 0.67529-3.3956 0.78946-16.804 0.0863-10.12 0.0143-14.86-0.24367-16.056-0.37384-1.7344-1.4336-3.345-2.6427-4.0165-0.37209-0.20664-2.7561-0.47002-5.2978-0.58526-4.2591-0.19314-4.7356-0.278-6.08-1.0829-2.1324-1.2768-2.7197-2.6555-2.7255-6.3987-0.0111-7.152-2.9243-13.792-8.6802-19.785-4.1006-4.2694-8.6751-7.1592-13.896-8.7785-1.2498-0.38765-4.0484-0.51957-13.422-0.63274-14.708-0.17757-17.973 0.13047-22.98 2.1682-9.2314 3.7568-15.864 11.674-18.284 21.824-0.45444 1.9064-0.54266 4.9618-0.65 22.513-0.13448 21.988 0.01386 25.217 1.3586 29.575 1.111 3.6002 2.232 5.8063 4.5414 8.9376 4.3994 5.9652 10.993 10.273 17.585 11.49 3.137 0.57911 41.84 0.72399 45.804 0.17161z" strokeWidth=".86401" />
          </clipPath>
        </defs>
        <path d="m-82.995 87.838v-171.9h1020v343.8h-1020v-171.9z" fill="none" />
        <g transform="matrix(.92141 0 0 .92141 7.0545 7.0421)" fill="#fff" strokeWidth=".86401">
          <path d="m30.568 167.24c-2.8716-0.77176-5.3495-1.9071-7.6348-3.4982-1.9292-1.3431-4.7488-4.1198-5.8212-5.7323-1.3096-1.9694-2.815-5.342-3.4285-7.6809-0.62576-2.3858-0.6359-3.336-0.64713-60.605-0.01116-56.98 0.0015-58.233 0.61488-60.681 2.169-8.6596 8.8857-15.248 17.548-17.214 2.4907-0.56514 113.75-0.66398 116.44-0.10343 7.2734 1.5175 12.991 5.979 16.299 12.719 2.6303 5.3583 2.3951-0.53816 2.5156 63.081 0.0767 40.479 6e-3 57.473-0.24689 59.59-1.1845 9.9072-7.8732 17.592-17.498 20.103-2.462 0.64225-3.3184 0.65142-59.215 0.63395-54.046-0.0166-56.821-0.0454-58.928-0.61194z" clipPath="url(#clipPath8)" />
          <path d="m70.797 77.476c-3.5242-0.9925-4.841-6.1585-2.2508-8.8303 1.6555-1.7077 2.1132-1.7727 12.476-1.7727 9.3029 0 9.6156 0.02079 10.982 0.72599 1.9748 1.0193 2.8328 2.4564 2.8328 4.7444 0 2.0665-0.80586 3.5146-2.6034 4.6784-0.96518 0.62486-1.542 0.66375-10.657 0.71844-5.6283 0.0338-10.112-0.07615-10.78-0.26416zm-0.44157 34.766c-1.5128-0.67346-2.9215-2.5442-3.165-4.203-0.23192-1.5801 0.54532-3.7524 1.7367-4.854 1.5018-1.3886 2.161-1.435 20.63-1.4498 18.999-0.0152 18.9-0.0234 20.701 1.6952 2.5446 2.4274 2.0078 6.749-1.0585 8.5234l-3.151 0.52354-16.423 0.19641c-14.431 0.17258-18.519-0.0973-19.271-0.43172z" />
        </g>
      </svg>
    </SvgIcon>
  );
}

Hope these help somebody.

Monday, December 28, 2020

How to setup a C++ WebAssembly component to run in a create-react-app React project

I had a hard time trying to get a C/C++ WebAssembly component to work in a React application generated from the create-react-app utility. Some blogs suggested using the react-app-rewired package to override the configuration and use the wasm-loader; but I kept getting magic header not detected errors when loading the wasm file or some ES6 syntax errors. Finally I found a StackOverflow post that gave me some ideas on getting it to work. 

In this post, I outline the steps I did to create, use a C/C++ WebAssembly in a ReactJS application generated by the create-react-app tool, on a Linux Ubuntu machine. This example will call the WebAssembly's function to add in two numbers (1 and 2) and return the resultant value. The resultant value would be displayed at the top of the web page as shown in the screen shot below.

Create a ReactJS application with create-react-app

  1. Open up a Terminal. Change to your project directory and type in the following commands to create the ReactJS application cra-wasm-adder.

    $ cd /path/to/reactprj/
    $ npx create-react-app cra-wasm-adder


    The cra-wasm-adder ReactJS application is created.

  2. Underneath the root of the newly created project, create a source directory, e.g. src_cpp for the C/C++ WebAssembly code.

    $ cd /path/to/reactprj/cra-wasm-adder/
    $ mkdir src_cpp


    The src_cpp directory is created.

Create the C/C++ source code files

  1. In the newly created src_cpp directory, use a favorite source code editor to create a C/C++ file, e.g. adder.cpp. Enter the following code.



    Note: the adder function simply sums up two integer numbers and returns the value.

  2. Create a Shell script file, e.g. make.sh to call the Emscripten compiler emcc to compile the C/C++ source code.

    Note: the emcc compiles the C/C++ source code into a Javascript plumbing helper file and a WebAssembly (*.wasm) file.

  3. In the make.sh file, define the module name and output file names.



  4. Then type in the emcc command to generate a Javascript plumbing helper file and the WebAssembly (*.wasm) file.


    Note: the Javascript generated by the emcc compiler (my version is 2.0.5) is not fully compatible with the ReactJS transpiler. The file has to be edited for compatibility purposes. This modifications may differ for different versions of the compiler

  5. Type in the sed command to insert a line /* eslint-disable */  at the top of the Javascript plumbing file to disable the Ecmascript linting.



  6. Type in the sed command to replace the import.meta.url string to the WebAssembly file relative to the root of the public directory of the ReactJS project, e.g. /path/to/reactprj/public/.


    Note: in this example, the *.wasm file is placed directly underneath the root of the /path/to/reactprj/public/; so the resultant change looks like the code below.


  7. Type in the sed command to add in the window object to the self.location.href string.


    The result change Javascript looks like the snippet below.


  8. Type in the sed commands to comment out the dataURIPrefix declaration  and isDataURI function block.



    The resultant Javascript code is shown below.


  9. Next, type in the sed commands to modify the wasmBinaryFile variable to point to the actual relative location in the ReactJS' public directory and to remove and replace the getBinary and getBinaryPromise function blocks.


    After running the commands, the Javascript should like the code below:



  10.  Finally, type in the sed command to remove the call to the isDataURI function in the instantiateAsync function.


    The resultant Javascript:


  11. The last thing the make.sh script needs to do is to move the generated WebAssembly file to the /path/to/reactprj/public/ directory and the modified Javascript file to the /path/to/reactprj/src/ directory.

    mv $OUTPUT_JS ../src/
    mv $OUTPUT_WASM ../public/



    The directory structure should look like the screen shot below:

Run the Shell script

  1. In a Terminal, change directory to the location of the make.sh shell script.

    $ cd /path/to/reactprj/src_cpp/

  2. Type in the following to run the script.

    $ bash make.sh

    If the compilation is successful, the resultant Javascript and WebAssembly should be placed in the directories as shown below.


Create WebAssembly React component

  1. In a text editor, create the React component under the /path/to/reactprj/src/ directory to use the WebAssembly component, e.g. AddNumbers.js .


  2. Type in the following code to import in the generated Javascript plumbing file and call the WebAssembly adder function.


Running the ReactJS application

  1. In a Terminal, run the React JS application.

    $ npm run start

    The browser opens up the development site.


  2. Note the message at the top which prints out the result of calling the WebAssembly's adder function: Hello 1+2=3.

The source code for this example is freely available on https://gitlab.com/dominoc925/cra-wasm-adder


Monday, December 9, 2019

Fix error 400 bad request when trying to use create-react-app

While attempting to create a new ReactJS app with the create-react-app tool, I encountered the following error: "error 400 Bad Request: run-async@^2.2.0". See the screenshot below.

After some research, I found the node npm cache could be corrupted. So running the following command fixed the issue for me.

$ npm cache verify


The create-react-app tool now can complete successfully, as shown below:
$ npx create-react-app myapp


Monday, October 14, 2019

Simple example of a ReactJS and OpenLayers map component

OpenLayers (https://openlayers.org/) is a "high-performance, feature-packed (Javascript) library for all your mapping needs" for web sites. I spent some time figuring out how to use it with ReactJS. As most of the available examples in the official documentation are for plain vanilla HTML/Javascript, I stumbled a few times trying to get it to work. This post summarises the steps to get a minimal OpenLayers - React component to work. 


Installing OpenLayers for ReactJS
Assuming a ReactJS project has been created e.g. /path/to/React/project/, the first thing is to install the prerequisite software.
  1. Install OpenLayers package for node. Open a Terminal, change directory to the project root directory and type in the following command.

    $ npm install ol --save
  2. Optional. Install Proj4Js and Material-UI.

    $ npm install proj4 @material-ui/core --save
Create the React component for OpenLayers map
Under the ReactJS project e.g. /path/to/React/project/src/components/, create the OpenLayers React component e.g. OLMapFragment.js.

Code listing of OLMapFragment.js
import React from 'react'
import Grid from '@material-ui/core/Grid'

// Start Openlayers imports
import { 
    Map,
    View
 } from 'ol'
import {
    GeoJSON,
    XYZ
} from 'ol/format'
import {
    Tile as TileLayer,
    Vector as VectorLayer
} from 'ol/layer'
import {
    Vector as VectorSource,
    OSM as OSMSource,
    XYZ as XYZSource,
    TileWMS as TileWMSSource
} from 'ol/source'
import {
    Select as SelectInteraction,
    defaults as DefaultInteractions
} from 'ol/interaction'
import { 
    Attribution,
    ScaleLine,
    ZoomSlider,
    Zoom,
    Rotate,
    MousePosition,
    OverviewMap,
    defaults as DefaultControls
} from 'ol/control'
import {
    Style,
    Fill as FillStyle,
    RegularShape as RegularShapeStyle,
    Stroke as StrokeStyle
} from 'ol/style'

import { 
    Projection,
    get as getProjection
 } from 'ol/proj'

// End Openlayers imports

class OLMapFragment extends React.Component {
    constructor(props) {
        super(props)
        this.updateDimensions = this.updateDimensions.bind(this)
    }
    updateDimensions(){
        const h = window.innerWidth >= 992 ? window.innerHeight : 400
        this.setState({height: h})
    }
    componentWillMount(){
        window.addEventListener('resize', this.updateDimensions)
        this.updateDimensions()
    }
    componentDidMount(){

        // Create an Openlayer Map instance with two tile layers
        const map = new Map({
            //  Display the map in the div with the id of map
            target: 'map',
            layers: [
                new TileLayer({
                    source: new XYZSource({
                        url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png',
                        projection: 'EPSG:3857'
                    })
                }),
                new TileLayer({
                    source: new TileWMSSource({
                        url: 'https://ahocevar.com/geoserver/wms',
                        params: {
                            layers: 'topp:states',
                            'TILED': true,
                        },
                        projection: 'EPSG:4326'
                    }),
                    name: 'USA'
                }),
            ],
            // Add in the following map controls
            controls: DefaultControls().extend([
                new ZoomSlider(),
                new MousePosition(),
                new ScaleLine(),
                new OverviewMap()
            ]),
            // Render the tile layers in a map view with a Mercator projection
            view: new View({
                projection: 'EPSG:3857',
                center: [0, 0],
                zoom: 2
            })
        })
    }
    componentWillUnmount(){
        window.removeEventListener('resize', this.updateDimensions)
    }
    render(){
        const style = {
            width: '100%',
            height:this.state.height,
            backgroundColor: '#cccccc',
        }
        return (
            <Grid container>
                <Grid item xs={12}>
                    <div id='map' style={style} >
                    </div>
                </Grid>
            </Grid>
        )
    }
}
export default OLMapFragment

Use the React OpenLayers map component
Now in the ReactJS project's index.js file under /path/to/React/project/src/, import in the newly created OLMapFragment component and render it.

Listing of index.js
import React from 'react'
import { render} from 'react-dom'
import OLMapFragment from './js/components/OLMapFragment'

render(
    <OLMapFragment />
    ,
    document.getElementById('react-container')
)

Create the default index.html file
Finally, include links to OpenLayer's style sheets in the project's index.html file under /path/to/React/project/src/.
Listing of index.html file
<!DOCTYPE html>
<html class="no-js" lang="en">

<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content='ie=edge'>
    <title>Openlayers React</title>
    <meta name="description" content="Explore planet Mars">
    <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" />
    <!-- material-ui prerequisites -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
    <!-- end material-ui prerequisites -->

    <link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css"
        type="text/css" />

</head>

<body>
    <!--[if lte IE 8]
        <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com">upgrade your browser</a></p>
        <![endif]-->
    <div id='react-container'></div>

</body>

</html>

Now, run the ReactJS project with a web server and open the web page with an Internet browser.

The web map tiles are rendered in the React OpenLayers component.

Monday, February 19, 2018

WebApp for calculating the 4x4 transformation matrix between 2 corresponding sets of 3D points

In working with 3D point clouds, one of the task may be to register or transform the point cloud to known ground control points (GCPs) on the ground with corresponding control points in the point cloud. For instance, the screen shot below shows the point cloud with four control points p1, p2, p3, and p4.

Note: At least 3 corresponding set of points are needed to calculate a rigid transformation matrix.

From the point cloud, the from-control points can be measured:

On the ground, the actual ground control points can be obtained, perhaps from a GPS receiver:


With the measured points from the point cloud and the corresponding control points on the ground, a 4x4 homogeneous transformation matrix can be calculated and used to rigidly transform the point cloud to the actual ground coordinate system. 

This WebApp calculator at https://dominoc925-pages.appspot.com/webapp/calc_transf3d/default.html can be used to calculate the 4x4 transformation matrix, as well as the root mean square error (RMSE) as an indicator of the quality of the control points i.e. the smaller the RMSE, the better they are. The WebApp uses the Numeric Javascript matrix library from http://www.numericjs.com/ for performing the required SVD decomposition to calculate the transformation matrix. Note that the library has some unfixed bugs in the SVD decomposition for certain cases and I may probably replace this with an alternative solution in future.  
  1. Paste the "from control points" into the Source Points text area field. 

  2. Paste the ground control point values into the Destination Points text area field.
  3. Click the Calculate button.



    The resultant transformation matrix appears in the 4x4 Transformation Matrix field. The RMSE for the calculation is displayed in the RMSE field.

Monday, January 29, 2018

Display CSV (with WKT geometry field) files on Google Maps with this Mapplet

If you want to display a Comma Separated Values (CSV) file with an embedded geometry field in Well Known Text (WKT) format with just an Internet browser, then this Mapplet may be useful to you. Just go to this url https://dominoc925-pages.appspot.com/mapplets/vcsvfile.html with any modern browser such as Chrome, or FireFox. All the processing is done within your browser, that is the CSV file is never uploaded to a server for processing.


  1. To load a CSV file, click on the Import CSV button (in blue color) as shown in the screenshot below. 



    The Import Comma Separated Values (CSV) file dialog box appears.

  2. To load in a CSV file from a local drive, click the Choose File button. Then browse and select a CSV file as shown below.



    Note: You can also copy and paste WKT text into the CSV file field's text area. Or click the Use Sample Data drop up button to load demo WKT data into the text area.

  3. Click Open.

    The file is read and displayed in the text area below the Choose File button. The WKT Geometry column button is populated with the header fields from the CSV file.


    Note: If necessary, choose an appropriate Coordinate System and projection.
  4. Click Start Import.

    The CSV file is loaded and rendered as overlays on the Google Maps backdrop. The name of the file is added to a CSV files combo box.

  5. Optional. Click on the CSV Command drop up button and choose a command to apply to the current CSV layer.


    Fit - Display the current CSV layer within the map
    Fit All - Display all loaded CSV layers within the map
    Toggle display - Toggle the CSV layer display on or off
    Display off - Don't display the current CSV layer
    Display on - Display the current CSV layer
    Remove layer - Unload the current CSV layer

Monday, March 13, 2017

Batch sample or convert videos to animated GIF files WebApp

I wrote a WebApp VidzGif for batch sampling or converting video files into animated GIF images that works with just a Web browser without uploading the videos to a server for processing.

To use this WebApp, do the following:

  1. Open up the WebApp at https://dominoc925-pages.appspot.com/webapp/vidzgif/default.html with a Web browser.


  2. Optional. Click Settings. Set the desired parameters, e.g. sample interval, duration, etc.
  3. Click the Browse button and select one or more video files. Or drag and drop one or more video files into the dashed box.



    The videos are sampled and rendered into GIFs.
  4. To save out the GIF, right click on the image and choose Save image as.

Monday, February 20, 2017

Clear Javascript, CSS, and HTML web cache in Chrome

When developing Javascript code for web pages, the Chrome browser caches the Javascript code including cascading style sheets and HTML files. Any changes made to the code may not be reflected when the page is refreshed. I found this frustrating. That is, until I discovered that Chrome has a way to clear its cache before reloading the web page.

To enable this, do the following:

  1. Select Options (the 3 vertical dots icon on the top right) | More Tools | Developer Tools.

    The Developer Tools pane appears.
  2. Now, do a mouse-right click on the Reload Page icon, as shown in the screenshot below.

    A popup menu appears.

  3. Choose Empty Cache and Hard Reload.

    The cache is cleared and any any changes made to the code is reflected in the reloaded page.



Monday, March 7, 2016

Highlight the active link on a single HTML page using jQuery and Bootstrap

In a web page, it is useful to indicate the active page (or photo or whatever) in the navigation links by changing some style to differentiate it from the other links. An example is the Photo 4 link in the screenshot below.

When the user clicks on another item, the other link e.g. Photo 5 is highlighted and the previously active link becomes a normal link again, as shown below.

I wanted to do the same but on a single page without resorting to server side programming. This can be done with some help using jQuery, Bootstrap, HTML and some custom CSS styles. Bootstrap can be downloaded from http://getbootstrap.com/. jQuery can be downloaded from https://jquery.com/

1. In the HTML page, set up the links with the nav semantic tag with the Bootstrap classes navbar, navbar-nav as shown below. A unique id e.g. main-nav should be assigned also.


<!doctype html>
<html lang="en">
<head>
<title>My favorite photos</title>
<link href="css/bootstrap.css" rel="stylesheet"/>
<link href="css/styles.css" rel="stylesheet"/>
<script src="js/jquery.min.js" ></script>
<script src="js/bootstrap.min.js" ></script>
</head>
    
<body>
<nav class="navbar">
    <div>
        <ul id="main-nav" class="nav navbar-nav">
            <li><a href="#" class="active photo-link" >Photo 1</a></li>
            <li><a href="#" class="photo-link" >Photo 2</a></li>
            <li><a href="#" class="photo-link" >Photo 3</a></li>
            <li><a href="#" class="photo-link" >Photo 4</a></li>
            <li><a href="#" class="photo-link" >Photo 5</a></li>
            <li><a href="#" class="photo-link" >Photo 6</a></li>
        </ul>
    </div>
</nav>
<!-- etc -->

2. Next define custom CSS styles for the non-active and active links. In this example, the non-active link is the photo-link class style.

.photo-link{
    color: hotpink;
}
.active {
    background-color: yellowgreen;
    font-weight: bold;
}
3. To tie the HTML and the styles, create a Javascript function as shown in the code snippet below with jQuery to search for the navigation link elements with the id (main-nav) assigned previously and add a click event handler to each link. When the user clicks on a link, the function will get the active link element and remove the active class style from it. Then add the active class style to the clicked link.


$('#main-nav li a').on('click', function() {
    var activeLink = $('.active');
    activeLink.removeClass('active'); 
    $(this).parent().addClass('active');
});

Monday, April 21, 2014

A simple Chrome Web App for creating animated GIF files

Recently, it seems animated GIFs have been making a come back and used on social networks like Google+. I thought of making a simple Chrome Web App for creating the animated GIF files (using only Javascript, HTML5 and the gif.js library) without having to upload to a server for processing, i.e. the processing takes place within the web browser only. The resultant WebApp is available here on http://dominoc925-pages.appspot.com/webapp/gifanim/default.html.


Using the WebApp is easy. The following steps show how to create an animated GIF with the default settings.

  1. Simply drag and drop the image frames into the box provided. Or click the Add Image button.

    The Open dialog box appears.

  2. Select one or more image files. Click Open.

    The thumbnails of the selected files appear in the box.

  3. Click Generate GIF.

    The animated GIF file is created.

  4. To save the animated GIF file, mouse right click on the animation. Select Save Image As.



    The Save As dialog appears.
  5. Type in a file name. Click Save.

    The animated GIF file is saved.
Delete or change the order of the image frames
  1. If you want to change the order of the images, simply click on the image thumbnail.

    The Move previous, delete, and Move next icons appear below the thumbnail.

  2. Click Move previous to move the selected image before the previous image frame.
  3. Click Move next to move the selected image after the next image frame.
  4. Click Delete to delete the selected image.
Changing the default settings
  1. If you want to change the default settings, click on Settings on the right pane.


  2. In the Quality field, choose another value.
  3. In the Animation speed field, choose another value.
  4. In the Repeat field, choose another value.
  5. In the Canvas size field, choose another value.
  6. Click Generate GIF to see the effects. 

Monday, February 17, 2014

How to upload, update and save an image file to Google Drive using Javascript

This is a simple Javascript Google Drive example web app to load an image file from a local drive, write some text on the image, then save the edited image to Google Drive in the cloud. The basis of this example came from the Google Drive SDK Javascript Quickstart at this location https://developers.google.com/drive/quickstart-js. Read up the Google Drive SDK Javascript Quickstart to see how to enable the Google Drive API and setup a Google Drive Javascript web app.

Setup the Javascript app
Copy the following source code and save into your own html file. Change the string <YOUR_CLIENT_ID> to your own client ID assigned to you when you created your cloud project in http://cloud.google.com/console. Publish the page to a web server.

In general, this is what the Javascript code is doing: (1) load and draw the local image file in the canvas element, (2) draw the string "Hello World" onto the canvas, (3) save the canvas into an IMG element, (4) upload the IMG element source data into Google Drive.

<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<script type="text/javascript">
 
var CLIENT_ID = '<YOUR_CLIENT_ID>';
var SCOPES = 'https://www.googleapis.com/auth/drive';
 
/**
       * Called when the client library is loaded to start the auth flow.
       */
function handleClientLoad() {
window.setTimeout(checkAuth, 1);
}
 
/**
       * Check if the current user has authorized the application.
       */
function checkAuth() {
gapi.auth.authorize(
{'client_id': CLIENT_ID, 'scope': SCOPES, 'immediate': true},
handleAuthResult);
}
 
/**
       * Called when authorization server replies.
       *
       * @param {Object} authResult Authorization result.
       */
function handleAuthResult(authResult) {
var authButton = document.getElementById('authorizeButton');
var filePicker = document.getElementById('filePicker');
var uploadButton = document.getElementById('uploadButton');

authButton.style.display = 'none';
filePicker.style.display = 'none';
uploadButton.style.display = 'none';
if (authResult && !authResult.error) {
// Access token has been successfully retrieved, requests can be sent to the API.
filePicker.style.display = 'block';
filePicker.onchange = loadImageFile;
uploadButton.onclick = newUploadFile;
} else {
// No access token could be retrieved, show the button to start the authorization flow.
authButton.style.display = 'block';
authButton.onclick = function() {
gapi.auth.authorize(
{'client_id': CLIENT_ID, 'scope': SCOPES, 'immediate': false},
handleAuthResult);
};
}
}
 
function newUploadFile(evt){
gapi.client.load('drive','v2', function(){
var theImage = document.getElementById('editedImage');
var fileTitle = theImage.getAttribute('fileName');
var mimeType = theImage.getAttribute('mimeType');
var metadata = {
'title': fileTitle,
'mimeType': mimeType
};
var pattern = 'data:' + mimeType + ';base64,';
var base64Data = theImage.src.replace(pattern,'');            
newInsertFile(base64Data,metadata);
});
}
/**
       * Insert new file.
       *
       * @param {Image} Base 64 image data
       * @param {Metadata} Image metadata
       * @param {Function} callback Function to call when the request is complete.
       */
function newInsertFile(base64Data, metadata, callback){
const boundary = '-------314159265358979323846';
const delimiter = "\r\n--" + boundary + "\r\n";
const close_delim = "\r\n--" + boundary + "--";
var contentType = metadata.mimeType || 'application/octet-stream';
var multipartRequestBody =
delimiter +
'Content-Type: application/json\r\n\r\n' +
JSON.stringify(metadata) +
delimiter +
'Content-Type: ' + contentType + '\r\n' +
'Content-Transfer-Encoding: base64\r\n' +
'\r\n' +
base64Data +
close_delim;
 
var request = gapi.client.request({
'path' : '/upload/drive/v2/files',
'method' : 'POST',
'params' : {
'uploadType' : 'multipart'
},
'headers' : {
'Content-Type' : 'multipart/mixed; boundary="' + boundary + '"'
},
'body' : multipartRequestBody
});
if (!callback) {
callback = function (file) {
alert('done');
};
}
request.execute(callback);
}
function loadImageFile(evt){
var file = evt.target.files[0];
var reader = new FileReader();
reader.file = file;
reader.onload = onImageReaderLoad;
reader.readAsDataURL(file);            
}
function onImageReaderLoad(evt){
var file = this.file;
var mimeType = file.type;
writeSomeText(file.name,file.type,evt.target.result);        
}
/**
       * Write some Hello World text on an image using the canvas.
       *
       * @param {File Name} The name of the image file
       * @param {MimeType} The mime type of the image e.g. image/png
       * @param {Image} The image data
       */
function writeSomeText(sourceImageName, mimeType, sourceImage){
var resultsDiv = document.getElementById('resultsDiv');
var sourceImg = document.createElement('img');
var resultImg = document.createElement('img');
var canvas = document.createElement('canvas');
sourceImg.onload = function(evt){
canvas.width = this.width;
canvas.height = this.height;
var ctx = canvas.getContext('2d');
ctx.drawImage(this,0,0,canvas.width,canvas.height);
ctx.font = '24px Arial';
ctx.fillText('Hello World',this.width/2,this.height/2);
ctx.restore();
resultImg.onload = function(evt2){
resultImg.setAttribute('id','editedImage');
resultImg.setAttribute('mimeType', mimeType);
resultImg.setAttribute('fileName', sourceImageName);
resultsDiv.appendChild(resultImg);
var uploadButton = document.getElementById('uploadButton');
uploadButton.style.display = 'block';
};
resultImg.src = canvas.toDataURL(mimeType);
};
sourceImg.src = sourceImage;
}    
 
</script>
<script type="text/javascript" src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
</head>
<body>
<!--Add a file picker for the user to choose an image file to be edited -->
<input type="file" id="filePicker" style="display: none" />
<!-- Add a button to start the upload process for loading the edited image file to Google Drive -->
<input type="button" id="uploadButton" style="display:none" value="Upload" />
<input type="button" id="authorizeButton" style="display: none" value="Authorize" />
<!-- div placeholder for displaying the edited image -->
<div id="resultsDiv">
</div>
</body>
</html>


Run the Javascript app

  1. Load the web page in an Internet browser.

    If the Javascript app has not been authorized by you, then the Authorize button will be displayed.
  2. Click Authorize.

    If you are not signed in to your Google account, the following page may display. Just type in your password and sign in.


    The Request for permission page appears.
  3. Click Accept.

    The app is authorized and the browser runs the Javascript app.
  4. Click Choose File.

    The Open dialog box appears.
  5. Browse and select an image file, e.g. ic_launcher.png. Click Open.

    The app writes the text string 'Hello World' in the middle of the selected image. The Upload button appears.

  6. Click the Upload button.

    A done message appears.

     The image is saved into your Google Drive in the cloud.
 

Monday, February 3, 2014

How to display a preview of CSS fonts in a Select Option drop down menu

The HTML Select Option element is normally used to display a list of options for users to make a choice. I wanted to use the option list for users to choose a font to use for further processing. I thought it would be nice to be able to show a preview of the fonts in the list itself, like how word processing software like Microsoft Word does it. I managed to figure how to do it by using some web fonts from Google, cascading style sheets (CSS) and a Web Font Loader.

In general, the following needs to be done in the HTML page: (1) download the web fonts you want to use in the select option list, (2) define style sheet classes for each font,  and (3) wrap each option with the optgroup tag and assign it with the style sheet class.

I managed to get this to work for Chrome and FireFox browsers but not for any of Microsoft's Internet Explorer browsers.

Download web fonts
In the header head tag section of the HTML file, make a link using the link tag to the web fonts you want to use. An example is shown below. Here, I am linking to the Allura and Dynalight fonts from Google.


<!DOCTYPE html> 
<html> 
<head> 
<title>Example font preview</title> 
<meta name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no"> 
<meta charset="UTF-8"> 
<link href="../webappstyles.css" rel="stylesheet" type="text/css" />
<link href='http://fonts.googleapis.com/css?family=Allura' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Dynalight' rel='stylesheet' type='text/css'>        


If you want to use the fonts in Javascript, then it would be wise to use the Web Font Loader to pre-load the fonts. This can be done by appending the font family names to the Web Font load method. The example code shows how this is done. The code can be included in the HTML head tag section.


<script src="//ajax.googleapis.com/ajax/libs/webfont/1.4.7/webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Allura', 'Dynalight']
}
});
</script>    


Define CSS classes
Inside the style tag section of the header, define a style sheet class for each font you want to use.


<style type="text/css">
.Allura { font-family: Allura, cursive; }
.Dynalight { font-family: Dynalight, cursive; }    
</style>

Wrap each option
In the select tag, wrap each option tag with the optgroup tag and assign the appropriate CSS style defined previously.


<select id="fontTypeCombo">
<optgroup class='Allura'>
<option>Allura</option>
</optgroup>
<optgroup class='Dynalight'>
<option>Dynalight</option>
</optgroup>
</select>

Now, when the HTML page is displayed in Chrome or FireFox, clicking on the select option combo box should display a list with a preview of the fonts.