GithubHelp home page GithubHelp logo

electron-dl's Introduction

electron-dl

Simplified file downloads for your Electron app

Why?

  • One function call instead of having to manually implement a lot of boilerplate.
  • Saves the file to the users Downloads directory instead of prompting.
  • Bounces the Downloads directory in the dock when done. (macOS)
  • Handles multiple downloads.
  • Support for BrowserWindow and BrowserView.
  • Shows badge count (macOS & Linux only) and download progress. Example on macOS:

Install

npm install electron-dl

Requires Electron 7 or later.

Usage

Register it for all windows

This is probably what you want for your app.

const {app, BrowserWindow} = require('electron');
const electronDl = require('electron-dl');

electronDl();

let win;
(async () => {
	await app.whenReady();
	win = new BrowserWindow();
})();

Use it manually

This can be useful if you need download functionality in a reusable module.

const {BrowserWindow, ipcMain} = require('electron');
const {download} = require('electron-dl');

ipcMain.on('download-button', async (event, {url}) => {
 	const win = BrowserWindow.getFocusedWindow();
     try {
         console.log(await download(win, url));
     } catch (error) {
         if (error instanceof electronDl.CancelError) {
         	console.info('item.cancel() was called');
		 } else {
		 	console.error(error);
		 }
	 }
});

API

It can only be used in the main process.

electronDl(options?)

electronDl.download(window, url, options?): Promise<DownloadItem>

window

Type: BrowserWindow | BrowserView

Window to register the behavior on. Alternatively, a BrowserView can be passed.

url

Type: string

URL to download.

options

Type: object

saveAs

Type: boolean
Default: false

Show a Save As… dialog instead of downloading immediately.

Note: Only use this option when strictly necessary. Downloading directly without a prompt is a much better user experience.

directory

Type: string
Default: User's downloads directory

The directory to save the file in.

Must be an absolute path.

filename

Type: string
Default: downloadItem.getFilename()

Name of the saved file.

This option only makes sense for electronDl.download().

errorTitle

Type: string
Default: 'Download Error'

Title of the error dialog. Can be customized for localization.

Note: Error dialog will not be shown in electronDl.download(). Please handle error manually.

errorMessage

Type: string
Default: 'The download of {filename} was interrupted'

Message of the error dialog. {filename} is replaced with the name of the actual file. Can be customized for localization.

Note: Error dialog will not be shown in electronDl.download(). Please handle error manually.

onStarted

Type: Function

Optional callback that receives the download item. You can use this for advanced handling such as canceling the item like item.cancel() which will throw electronDl.CancelError from the electronDl.download() method.

onProgress

Type: Function

Optional callback that receives an object containing information about the progress of the current download item.

{
	percent: 0.1,
	transferredBytes: 100,
	totalBytes: 1000
}

onTotalProgress

Type: Function

Optional callback that receives an object containing information about the combined progress of all download items done within any registered window.

Each time a new download is started, the next callback will include it. The progress percentage could therefore become smaller again. This callback provides the same data that is used for the progress bar on the app icon.

{
	percent: 0.1,
	transferredBytes: 100,
	totalBytes: 1000
}

onCancel

Type: Function

Optional callback that receives the download item for which the download has been cancelled.

onCompleted

Type: Function

Optional callback that receives an object with information about an item that has been completed. It is called for each completed item.

{
	filename: 'file.zip',
	path: '/path/file.zip',
	fileSize: 503320,
	mimeType: 'application/zip',
	url: 'https://example.com/file.zip'
}

openFolderWhenDone

Type: boolean
Default: false

Reveal the downloaded file in the system file manager, and if possible, select the file.

showBadge

Type: boolean
Default: true

Show a file count badge on the macOS/Linux dock/taskbar icon when a download is in progress.

showProgressBar

Type: boolean
Default: true

Show a progress bar on the dock/taskbar icon when a download is in progress.

overwrite

Type: boolean
Default: false

Allow downloaded files to overwrite files with the same name in the directory they are saved to.

The default behavior is to append a number to the filename.

dialogOptions

Type: SaveDialogOptions
Default: {}

Customize the save dialog.

If defaultPath is not explicity defined, a default value is assigned based on the file path.

Development

After making changes, run the automated tests:

$ npm test

And before submitting a pull request, run the manual tests to manually verify that everything works:

npm start

Related

electron-dl's People

Contributors

bendingbender avatar bolivir avatar cvx avatar da1nerd avatar dated avatar dcai169 avatar declanchiu avatar deependrax avatar djalmaaraujo avatar fregante avatar joshuapinter avatar jrainlau avatar jwheare avatar kevva avatar limebell avatar louis-cf-lin avatar motiz88 avatar mquevill avatar nickromano avatar papb avatar paviro avatar pierbover avatar richienb avatar sindresorhus avatar sondremare avatar thegnuu avatar theogravity avatar timborden avatar willyfrog avatar wong2 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

electron-dl's Issues

Getting progress to renderer process...

Hi,

Thanks for the awesome package. I was wondering how can I show the progress in the renderer process. I could not find the right syntax to send it via ipcMain. I would appreciate your help.

Regards,
Bardia

so is there any way to stop run onProgress function?

https://stackoverflow.com/questions/46102851/electron-download-a-file-to-a-specific-location

i try to use electron-dl, everything does well, my main.js code :

ipcMain.on("download", (event, info) => {
    console.dir(event);
    console.log(info);
    info.properties.onProgress = status => {
        console.log('progress');
        return event.sender.send("download progress", status)
    };

    download(BrowserWindow.getFocusedWindow(), info.url, info.properties)
        .then(dl => {
            console.log("dl.getSavePath()", dl.getSavePath());
            return event.sender.send('download complete', dl.getSavePath());
        }).catch((error)=>{
        console.log('error');
        console.log(error);
    });
});

but when i turn off my wifi, the code console.log('progress'); keep run, never stop.
so is there any way to stop run onProgress function?

get single file download state

the default onProgess can only get all files total download state.
i think we should create a callback to handle single file download state.

example: 8ddf676 @disoul disoul committed 31 minutes ago

Can't save images

This happens when trying to save an image through the context menu. (At least on development, I haven't tried with a packaged executable).

It says the problem is in electron-dl/index.js line 138
"Cannot read property 'session' of undefined"

Electron: 5.0.2
electron-context-menu: 0.12.1

Can you provide enhanced doc ?

Hello,
I'm new in electron community. And I'm having issue using you lib', if you could help me ?

I've done in my main process.

import ElectronDL from 'electron-dl';

ElectronDL();

and now I would like to use it in my renderer process, but I can't figure out, am I missing something ?

<template>
<div>
  <button @click="download"> Click ME </button>
</div>
</template>

<script>
import ElectronDL from 'electron-dl';

export default {
  name: 'landing-page',
  components: {},
  methods: {
    download() {
      const remoteURL = 'http://i.imgur.com/CuVQGg3.jpg';

      ElectronDL.download(this.$electron.remote.getCurrentWindow(), remoteURL)
        .then((dl) => {
          debugger;
          console.log(dl.getSavePath());
        })
        .catch(console.error);
    },
  },
};
</script>

<style>

</style>

Best regards

File Dialogue Appears if the window does not have focus.

Hello. So I am using electron-dl, and it works fine if the window has focus. However, if the window does not have focus, nothing downloads and the save dialogue appears, which I don't want it to. I attempted to pass saveAs:false into the options, but still this behaviour persists.

This app needs to check for files and if they are present, download the files every hour. Thus, I need the app to not download without prompting, even if the window does not have focus. Is there any way to overcome this? My snippet is below:

ipcMain.on('dl-files', (event, args) => {
return Promise.all(
args.files.map(async (file)=>{
logger.log(file.url)
return await download(mainWindow, file.url,{saveAs:false})
})
)
.then(...)

Cannot read property 'getType' of null

electron-dl version:1.10
electron version:1.7.8

Give me message:
TypeError: Cannot read property 'getType' of null
at Session.listener (/Users/rain/workspace/xd-oa2/xd-oa/node_modules/electron-dl/index.js:34:18)

I found webContents is NULL.

const listener = (e, item, webContents) => {
   console.log(webContents)    //  is NULL 
    ....
}

I call download in this code

ipcMain.on('download-btn', (e, args) => {
  download(BrowserWindow.getFocusedWindow(), args.url, { saveAs: true })
    .then(dl => console.log(dl.getSavePath()))
    .catch(console.error);
})

Can you help me fix this problem, please.

Modify filename for saveAs dialog

The files on my cdn Server are stored with random filenames. The actual filenames are stored in a database. How can I predefine a filename (that is different from the filename on the server) for the saveAs dialog?

Defining the filename option does not work. The option is ignored and the filename in the URL is used instead.

This is my code snippet:

const { ipcMain } = require('electron');
const { download } = require('electron-dl');

// ... some code to create BrowserWindow etc. ... //

ipcMain.on('file-download', (event, eventPayload) => {
	const downloadOptions = {
		saveAs: true,
		filename: 'differentFilename.txt'
	};
	
	download(win, eventPayload.FileURL, downloadOptions)
		.then((dl) => {
			console.log(dl.getSavePath())
		})
		.catch(console.error);
});

Downloading multiple files

Hello, I try to use this package for downloading multiple files. I send the ipc download-subtitle multiple times but electron downloads just 1 (of 4 files, for example).

Renderer:

ipcRenderer.send("download-subtitle", { file, subtitle });

Main:

  ipcMain.on("download-subtitle", async (event, args) => {
    const downloadLocation = path.dirname(args.file.path);
    const filename = args.file.name.replace(/\.[^/.]+$/, "");
    const options = {
      saveAs: false,
      directory: downloadLocation,
      filename: `${filename}.srt`
    };
    const dl = await download(mainWindow, args.subtitle.url, options);
    console.log(dl.getSavePath());
  });

Directory option being sometimes ignored

Description

I'm queuing multiple downloads to happen very quickly with code similar to the following

const download = (files: string[]) => {
    const directory = path.join(app.getPath('userData'), 'downloadCache');
    files.forEach(file => {
        download(file, {
            saveAs: false,
            directory
        }
    });
}

Expected Behavior

All files will be downloaded into the folder downloadCache inside the userData directory.

Actual Behavior

The first several files will download as expected, with all remaining downloads going to the default download directory specified in the registration function

Multi file download progress error

While downloading multiple files and downloading progress events, the return value will interfere with each other. Is this an BUG of Electron?
download.zip

Click second times after a short while to observe the download progress

multiple simultaneous downloads possible ?

It is mentioned in docs that in download function , url is string
download(BrowserWindow.getFocusedWindow(), args.url)

Is there a way to download a given array of files simultaneously ? and have one promise/callback for all

Can anyone help with promise chain to achieve this ?
I asked the question here stackoverflow
Can someone suggest fix for the code ?

Download Location

I cannot specify download location, please help. I want it in the music floder.

Upload?

It could bee cool to have an upload() function in this or another module.

I'd like to see more error details when "download...was interrupted"

Is it possible to return error message/code information (if available) on what caused a download interruption?

I was trying to interact with a server that was returning error 403 because i needed to include the authorization header. However, the error "Error: The download of [filename] was interrupted" was not helpful for debugging what went wrong.

Thanks for any help.

[Suggestion with sample code] Add an option to hide error message box

@sindresorhus please add an option to display the error message box.
If make more sense allow users to deal with the download errors. I quickly created some code, so you can copy/paste the text without loosing to much time.

LINE 55 ADD:

const showErrorDialog = options.showErrorDialog != null ? options.showErrorDialog : true; // default is true

LINE 108 CHANGE TO:

if (showErrorDialog) {
   electron.dialog.showErrorBox(errorTitle, message);
}

README.md ADD

#### showErrorDialog

Type: `boolean`<br>
Default: `true`

Show error dialog

Thank you for your good work on many modules

Not able to pack app while using electron-dl

For details check attached.

    "electron": "4.0.4",
    "electron-builder": "^20.40.2",
    "electron-connect": "0.6.3",
    "electron-connect-webpack-plugin": "0.1.1",
    "electron-debug": "2.1.0",
    "electron-devtools-installer": "2.2.3",
    "electron-publisher-s3": "^20.9.0",

image

image

Overwriting files

Hi

Currently using this code, it downloads the file great but if it does it again it adds it as a new version, not overwriting the existing file. Can this be achieved using this? If so please could you show me a code example on how to do this.

Thanks.

Regards
Chris

Downloading local file

Im downloading a bunch of files, and doing some processing on those. Then creating a zip file and sending that to the user. The zipfile is located in getPath('temp') but the content of the file is:

Failed to compile /var/folders/pl/cgw8lyrn03d50cb8c7npxqxr0000gn/T/MYZIPFILE.ZIP: Asked for /var/folders/pl/cgw8lyrn03d50cb8c7npxqxr0000gn/T/MYZIPFILE.ZIP but it was not precompiled!
Error: Asked for /var/folders/pl/cgw8lyrn03d50cb8c7npxqxr0000gn/T/MYZIPFILE.ZIP but it was not precompiled!
    at FileChangedCache.getCacheEntryForPath (/Users/MYUSERNAME/Development/jpd/out/MYAPPNAME-darwin-x64/MYAPPNAME.app/Contents/Resources/app/node_modules/electron-compile/lib/file-change-cache.js:218:15)
    at /Users/MYUSERNAME/Development/jpd/out/MYAPPNAME-darwin-x64/MYAPPNAME.app/Contents/Resources/app/node_modules/electron-compile/lib/file-change-cache.js:127:41
    at Generator.next (<anonymous>)
    at step (/Users/MYUSERNAME/Development/jpd/out/MYAPPNAME-darwin-x64/MYAPPNAME.app/Contents/Resources/app/node_modules/electron-compile/lib/file-change-cache.js:27:191)
    at /Users/MYUSERNAME/Development/jpd/out/MYAPPNAME-darwin-x64/MYAPPNAME.app/Contents/Resources/app/node_modules/electron-compile/lib/file-change-cache.js:27:437
    at new Promise (<anonymous>)
    at /Users/MYUSERNAME/Development/jpd/out/MYAPPNAME-darwin-x64/MYAPPNAME.app/Contents/Resources/app/node_modules/electron-compile/lib/file-change-cache.js:27:99
    at FileChangedCache.getHashForPath (/Users/MYUSERNAME/Development/jpd/out/MYAPPNAME-darwin-x64/MYAPPNAME.app/Contents/Resources/app/node_modules/electron-compile/lib/file-change-cache.js:177:7)
    at /Users/MYUSERNAME/Development/jpd/out/MYAPPNAME-darwin-x64/MYAPPNAME.app/Contents/Resources/app/node_modules/electron-compile/lib/compiler-host.js:298:51
    at Generator.next (<anonymous>)

From what i could find, this happens because of a security measure not allowing access to files that isnt a part of the app it self. Is there any workaround for this issue, or should i handle local files a different way?

Downloads from within webview

@sindresorhus have you tried to make the download link from within a webview work? If I load the entire webpage then electron-dl works really great but when I load a remote url inside webview then the downloads dont work.

Cannot read property 'fromWebContents' of undefined

i use below codes :

download(BrowserWindow.getFocusedWindow(), url ,{saveAs:true,openFolderWhenDone:true,onProgress:function(pr){
//console.log(pr+" - ");
}})
.then(dl => console.log(dl.getSavePath()))
.catch(console.error);

but i get this error in consol :

E:\nodejs\bireport\node_modules\electron-dl\index.js:37 Uncaught TypeError: Cannot read property 'fromWebContents' of undefined
at listener (E:\nodejs\bireport\node_modules\electron-dl\index.js:37)
at CallbacksRegistry.apply (E:\nodejs\bireport\node_modules\electron\dist\resources\electron.asar\common\api\callbacks-registry…:48)
at EventEmitter. (E:\nodejs\bireport\node_modules\electron\dist\resources\electron.asar\renderer\api\remote.js:283)
at emitThree (events.js:116)
at EventEmitter.emit (events.js:194)
listener @ E:\nodejs\bireport\node_modules\electron-dl\index.js:37
apply @ E:\nodejs\bireport\node_modules\electron\dist\resources\electron.asar\common\api\callbacks-registry…:48
(anonymous) @ E:\nodejs\bireport\node_modules\electron\dist\resources\electron.asar\renderer\api\remote.js:283
emitThree @ events.js:116
emit @ events.js:194

how can i fix it ?

How to download multiple files at once?

I'm trying to download all the files from a gist and I'm having inconsistent behavior. A basic repro case can be found at this repo. My desired behavior would be that I could set a directory for all the files in a gist, but could change the directory on a gist-by-gist basis (so that directory would change multiple times within the app's lifetime).

The failed behavior manifests as repeated "Save as..." prompts, despite setting the directory option. If I cancel out of the prompts, my files do not download. Other times, the app will not prompt me, but will download one file (e.g. thumbnail.png) N times, where N is the number of files in the gist.

I suspect that this line, which calls download anew each iteration through an array is the root of my problem, but it's unclear to me what the intended call pattern is for my use case.

Provide access to receivedBytes and totalBytes

Perhaps in the onProgress handler? It could receive a second parameter with a details object. The signature could look something like this:

{
  receivedBytes,
  totalBytes,
  item,
}

Further expanding on this idea, it could also include useful info like eta (estimated time remaining in milliseconds) and bps (current bytes per second). These would be handy but are less important as they can be calculated outside of the module using receivedBytes, totalBytes and a setTimeout.

BrowserWindow.getFocusedWindow() is undefined

I am trying to bypass the save dialog box and directly wants to download file to particular location as to have path of the file getting downloaded to be with me.

Code snippet:
const BrowserWindow = require('electron').remote.BrowserWindow;
var download = require('electron-dl');
download(BrowserWindow.getFocusedWindow(),workbook.toDataURL())
.then(function(dl){
console.log(dl.getSavePath());
})
.catch(console.error);

In above code BrowserWindow.getFocusedWindow() is always undefined and getting exception as Cannot read property 'on' of undefined.

Anything I am missing here ?

Alert when download completes

Hi - nice lib - really usefull!

I am not extremely experienced so would appreciate if you could show an example or explain how to handle the completion event?

I currently have the following in my main.dev.js file and would like to have some way of handling the completion of the download (instead of opening the folder):

import { app, BrowserWindow } from 'electron';
...
require('electron-dl')({openFolderWhenDone:true});
...

how to download undirect links ?

how to download undirect links ? for example : http://localhost:8080/jpctasks/download/?t=0&d=25 .
it does not work with electron-dl , but it works fine on browser.
here is error:

E:\nodejs\taskdan\node_modules\electron-dl\index.js:34 Uncaught TypeError: Cannot read property 'getType' of null
    at listener (E:\nodejs\taskdan\node_modules\electron-dl\index.js:34)
    at CallbacksRegistry.apply (E:\nodejs\taskdan\node_modules\electron\dist\resources\electron.asar\common\api\callbacks-registry.…:48)
    at EventEmitter.<anonymous> (E:\nodejs\taskdan\node_modules\electron\dist\resources\electron.asar\renderer\api\remote.js:299)
    at emitThree (events.js:116)
    at EventEmitter.emit (events.js:197)

BUG: Promise is not working correctly

#66

download
This library works correctly for same syntax

Promise
.all(['https://download.filezilla-project.org/client/FileZilla_3.34.0_win64-setup_bundled.exe', 'http://the.earth.li/~sgtatham/putty/latest/w32/putty-0.70-installer.msi', 'http://speedtest.ftp.otenet.gr/files/test10Mb.db']
.map(function (x) {
    return download(x, 'dist');
})).then(function () {
    console.log('files downloaded!');
});

electron-dl:
while using same with electron-dl does not.
Promise returns before downloads

  Promise
.all(['https://download.filezilla-project.org/client/FileZilla_3.34.0_win64-setup_bundled.exe', 'http://the.earth.li/~sgtatham/putty/latest/w32/putty-0.70-installer.msi', 'http://speedtest.ftp.otenet.gr/files/test10Mb.db']
.map(function (x) {
    return download(BrowserWindow.getFocusedWindow(), x);
  })).then(function (x) {
    console.log('files downloaded!');
  });

Download window opens and stays there

I am having an issue and cannot seem to find a fix - when I click on a link that I have in the window, the download starts and finishes fine without a save prompt but a new window is open and it is blank. This new window stays there and has to be manually closed. Have others seen this?

Force stop ongoing download.

Hi, very good lib indeed,

I have got a small feature request, is there an option to stop an ongoing download forcefully.

TypeError: Cannot read property 'webContents' of null

I am getting this error while downloading XML file.

Main.js

ipcMain.on('download-file', function (event, data) {
	console.log(data);
	download(BrowserWindow.getFocusedWindow(), data.url, {
		directory: "G:\\Orders\\"
	}).then(function (dl) {
		console.log(dl.getSavePath());
	}).catch(console.error);
});

Error

TypeError: Cannot read property 'webContents' of null
	at Promise (G:\fc-electron\node_modules\electron-dl\index.js:125:22)
	at Promise (<anonymous>)
	at module.exports.download (G:\fc-electron\node_modules\electron-dl\index.js:122:47)
	at EventEmitter.<anonymous> (G:\fc-electron\main.js:53:9)
	at emitTwo (events.js:125:13)
	at EventEmitter.emit (events.js:213:7)
	at WebContents.<anonymous> (G:\fc-electron\node_modules\electron\dist\resources\electron.asar\browser\api\web-contents.js:249:13)
	at emitTwo (events.js:125:13)
	at WebContents.emit (events.js:213:7)

Dynamically changing file path?

Can I dynamically change the file path from the renderer to electron-dl? Kind of new to Electron and this whole deal so I'm having trouble figuring some stuff out.

Combination of Windows and HTTP headers causes download to hang

This download works in macOS but not Windows. I'm receiving NaN from my onProgress handler.

HTTP/1.1 200 OK
Content-Type: application/zip
Content-Length: 1147148
Connection: keep-alive
Date: Wed, 21 Feb 2018 01:47:42 GMT
Last-Modified: Sat, 17 Feb 2018 03:27:43 GMT
ETag: "d674353de23b4513908cf46d90cb89a7"
x-amz-version-id: null
Accept-Ranges: bytes
Server: AmazonS3
Age: 148
X-Cache: Hit from cloudfront
Via: 1.1 6efe4cf59648bc53c4136b0291d22d56.cloudfront.net (CloudFront)
X-Amz-Cf-Id: sOTO1CCLML52l5S0rvU-2x7Xem6ilbjugL-r4XrZD0r8aFfsujdVZg==

I had to switch to S3.

Support for HTTP headers

Would it be possible to add HTTP Header support in general, or at least a way to add an Authorization header?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.