Coder Social home page Coder Social logo

groboclown / p4ic4idea Goto Github PK

View Code? Open in Web Editor NEW
31.0 8.0 11.0 1 GB

Perforce IDEA Community VCS Integration

License: Apache License 2.0

Java 97.65% HTML 1.18% Groovy 0.73% Dockerfile 0.04% Shell 0.25% PHP 0.01% Python 0.15% Ruby 0.01%
perforce plugin intellij android-studio p4

p4ic4idea's Introduction

Perforce IDEA Community Integration

VCS support for Perforce

The plugin allows for associating an IntelliJ IDEA Community Edition IDE project with a Perforce repository through IDEA's built-in VCS support.

Currently Supported IDEA versions: 2020.3 and above For a full list of supported products, please see the JetBrains plugin page

Compatibility with earlier IDE versions is supported up to the 0.9.6 release. It is maintained in the 135-compat branch.

Getting Started

  1. Install the plugin by one of these methods:
    • From within IDEA
      1. Open the IDE settings dialog ( File -> Settings... ).
      2. Navigate to the Plugins panel.
      3. Click the Browse repositories... button.
      4. Select the Perforce IDEA Community Integration plugin.
    • Download and install from disk:
      1. Download from the Jetbrains plugin center or from the Github releases
      2. In IDEA, open the IDE settings dialog ( File -> Settings... ).
      3. Navigate to the Plugins panel.
      4. Click the gear icon, and select the Install plugin from disk... item from the pop-up menu.
      5. Select the downloaded file from the file selection dialog.
    • Build it yourself:
      1. Follow the building guide for details on setting up your environment and building the p4ic4idea-plugin.jar file.
      2. In IDEA, open the IDE settings dialog ( File -> Settings... ).
      3. Navigate to the Plugins panel.
      4. Click the gear icon, and select the Install plugin from disk... item from the pop-up menu.
      5. Select the downloaded file from the file selection dialog.
  2. In IDEA, configure the VCS root directory managed by Perforce and its connection properties.

Setting Up The Plugin

Select VCS Root Directories

To view the Version Control settings, open the IDE settings dialog ( File -> Settings... ) and select the Version Control panel.

Version Control Settings Dialog

In the Version Control settings, you will need to define the root directories that are under Perforce control. Any file in your project outside one of these root directories won't be managed by Perforce. You can have multiple directories here, each with their own or shared server connection settings.

To bring a VCS root directory under Perforce control, use the VCS provider drop-down (marked 1. in the image above) for each root directory, and select "Perforce". To add another directory, use the side toolbar "+" button (marked 2. in the image above).

Then, you will need to configure the connection settings for each Perforce controlled root directory by pressing the pencil button in the side toolbar.

Connecting to Your Perforce Server

The configuration dialog allows you to configure the VCS root directory, the VCS provider, and connection information specific to that provider.

Connection Properties Tab

For the p4ic4idea plugin, this is where you configure how the root directory finds the Perforce server. You can configure each root directory to point to different servers or client workspaces, or have them reference the same workspaces. However, any file outside the root directory will not be considered for Perforce operations.

The Connection Configuration Stack under the Connection Properties tab shows the ordered list of connection settings; the plugin uses the settings at the top before the lower ones.

By default, the plugin selects the environment variables settings. These should connect to the server using the same mechanism as the p4 command line tool. It won't, however, read P4CONFIG values due to limitations in the plugin. On Windows computers, it also won't read any password value you have stored in the registry.

If you need different settings, you can add them with the "plus" button (1. in the image above). This brings up a list of types of settings to add to the stack:

The stack order can be changed with the "remove", "up", and "down" buttons (2. in the image above). You can view detailed information about the settings entry by expanding the arrow button (3. in the image above).

You can see how the plugin evaluates the properties across your project by viewing the Resolved Properties tab. Press the Check Connection button to evaluate the settings for problems, which can be viewed in the Configuration Problems tab, if any are found.

For SSL, you will want to use a P4PORT setting that starts with ssl:// (such as ssl://perforce:1666). If you have the server registered in the authorized hosts, then make sure that P4TRUST setting points to that file; or you can add a SSL server fingerprint setting to explicitly state the expected SSL fingerprint.

Property Values

The Properties configuration allows you to explicitly define the connection details. You can leave fields blank if you don't need to use the value. These values have the same meaning as the Perforce environment variables.

You'll notice that the P4PASSWD setting is not present. If you don't use a P4TICKET for authorization, the plugin will prompt you for your password. This allows the plugin to use the IDE's password storage mechanism.

Client Name

You can type in your client name in the field, or press the refresh button to load the drop-down with the clients in your name on the server. This will use the other settings to connect to the server.

Environment Variables

Loads the properties from the environment variables from the shell that launched the IDE.

In Windows, this will also attempt to inspect your Perforce registry variables.

For users on Macintosh, the plugin does not currently check the user's com.perforce.environment property list in the ~/Library/Preferences folder.

Specific P4CONFIG File

Load the properties from a specific file. The format for the file is the same as a P4CONFIG or P4ENVIRO file.

SSL Server Fingerprint

If you are connecting to an SSL server and are not using a P4TRUST file, then use this field to declare the server fingerprint for authorizing the connection to the server.

Require a Password

This ignores the P4TICKET settings and any password you may have stored in a configuration file or registry, and requires you to enter the password. The password you enter will be stored through the IDE's password storage mechanism.

Checking Connection Properties

The Resolved Properties tab allows you to review the resolved properties for the current VCS root. The Check Connection button will refresh the list. Note that passwords are never shown.

User Preferences

The User Preferences tab allows you to change the general operation of the plugin.

Note: This section needs updates to reflect the new 0.10 version of the plugin.

WARNING This dialog does not reflect the list of user-configurable properties for v0.10 of the plugin. Future releases will fix this. If you want to help, see bug #178.

Workflow

With your working Perforce connections specified, you can use the IDE as usual. Editing files will check them out from the server. Moving or renaming files will trigger a P4 rename operation. Deleting files will delete them from the server. All edited files will show up in the IntelliJ change lists, and those will be associated with the corresponding Perforce change.

From the change lists, you can view a difference against the head revision. You can also view the file history, and compare different revisions against each other.

When submitting a changelist, you may associate Perforce jobs with the changelist, and set the job status. This association will only be used when the changelist is actually submitted.

You can view the active server connections for your project through the Version Control panel, in the "Active Connections" tab.

active connections view

If you are working disconnected from the server, Perforce requests are queued up in the "Pending Actions" list. You can also use this view to reconnect to a server, disconnect from a server, configure a VCS root folder configuration, and manage the pending actions list.

Compatibility Issues

Symlinks on Windows

Perforce supports creating a "symlink" file. If you're running your client on Windows, you can does support the creation of symlinks, but you need to enable special permissions to allow your user to create symlinks. See this article on superuser.com for details on how to do this.

Contributing

You can help make the plugin better!

Submit Bug Reports

Submitting bug reports is the easiest way to help improve the plugin. Bugs can't be fixed if they aren't known.

Please note that issues with the "0.9" label only affect the 135-compat branch.

Fix It Yourself

See BUILDING.md for information on downloading the source and building the plugin. Additional documentation regarding the inner-workings of the plugin are in the docs/dev directory.

Looking For Work?

Please see the bug list for the project, and the [TODO.md] file for details on the future direction of the plugin.

License

The plugin is released under the Apache 2.0 license. It includes modifications to code from:

  • p4java, released under a 2-clause BSD license by Perforce Software, Inc. This code has been modified by the project, but is still under the terms of the original license.

p4ic4idea's People

Contributors

groboclown avatar oleksiimikhieiev 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

p4ic4idea's Issues

Move file across clients should use integrate when clients share a server

Given a project has two directories with different clients, but the clients share a single server.

If the user moves a file across the client directories, the the plugin should realize that they share a server, and perform an integrate / delete (move isn't possible).

Right now, the behavior is just a add / delete, rather than integrate. This loses the history.

Submitting a changelist which causes an error incorrectly reports the submit as successful

If a check-in to the server fails due to merge issues, then the check-in should fail with the reported message ("need to resolve differences"). However, the message is lost and the user just sees their checked-in files still open for edit.

The same thing happens if the changelist is submitted and the set of jobs passed in on submit is invalid, or if the job status is invalid.

Deadlock issue on disconnect

There's a deadlock that can happen when P4Exec.p4RunFor() encounters a ConnectionException. It calls serverStatus.onDisconnect(), which enters a synchronization block on connectionSync when it calls out to a future.get(). This can call out in a different thread (AWT) for ServerStoreSrevice.onDisconnect() which will try to enter the connectionSync block itself, causing a deadlock.

Allow setting job status on commit

When a change list is committed, the user should have the opportunity to select the correct job status to associate with the submitted jobs.

P4JavaAPI error: fetching jobs can incorrectly parse results

IChangelist.getJobs() can throw a RequestException if the resulting map isn't parsed correctly by the underlying P4Java API class. For example:

com.perforce.p4java.exception.RequestException: Error detected at line 31.
Syntax error in 'can'.

    at com.perforce.p4java.impl.mapbased.server.Server.handleErrorStr(Server.java:4989)
    at com.perforce.p4java.impl.mapbased.server.Server.getJob(Server.java:3714)
    at com.perforce.p4java.impl.generic.core.Changelist.getJobs(Changelist.java:426)

UI refresh required when existing IDEA changelist is associated with new p4 changelist

If you create an empty IDEA changelist, it is not associated immediately with a p4 changelist.

If you then drag a file from a p4-backed IDEA changelist into the new IDEA changelist, the plugin will correctly create the new changelist, but the UI will not display the associated connection information until the user does something to refresh the screen (expand/collapse on the changelist, refresh the view, etc).

Move a moved file to another changelist does not update pair

When the user moves a file (or renames it), the plugin treats the two files (the deleted and new one) as a pair in regards to the changelists, just as P4V does. If you move one of the pair of files into another changelist, the other one moves as well. However, the UI does not reflect this change until a refresh happens.

non-numeric perforce server port

Hello, I'm unable to use Your plugin because of this message when I'm trying to check connection: http://screenshot.cz/ZNOHJ/
-port is OK (1666), but the error message says it's wrong, i guess the problem is because of dots in server ip, thanks.

EDIT: the problem was in my p4config file, there was a blank space after my port, so not "1666", but "1666 ". But there is still another problem whether if I try to check connection via cfg file or with just typing port and username, it will always stuck, no pop up window is shown, img: http://screenshot.cz/KN8ZC/ - I was waiting several minutes but nothing happened. Idea version: 14.0.3.Thanks for help.

EDIT2: see last comment, thanks.

Add support for deep version history

Currently, the plugin is restricted to just viewing the version history for the current branch, and it does not go across the inherited branch. This is due to a limitation in the VcsRevisionNumber.Int that restricts the version to just the file revision. This needs to be expanded to allow a revision that goes deep across branches. The diff support also needs to allow for reading files that aren't in the client workspace; however, it looks like the existing code already supports that.

Enhance "compare with" dialog

The current "compare with" dialog was designed around using simple revision numbers, not for full Perforce path information. This needs to be a new dialog with more robust features to make it actually readable.

Changelists refresh event may be incorrectly skipped

There are some circumstances where a series of quick calls to refresh the changelists are made, but a final adjustment is done by Idea that requires that last call to the refresh. Due to the fixes in 0.5.2 that limited the number of calls made, the last refresh can be missed, which results in an incorrect display of the changelists (the user will need a manual refresh).

A watcher thread can be added to keep track of the refreshes, and initiate a call after a longer time if the last refresh was ignored.

It would be nice to include some usability smarts, such as don't initiate a call if the user is interacting with the changelists.

Connection status widget does not show current status correctly

When the plugin receives disconnect or connect messages, the connection status widget does not reflect the correct state; the user must click on it to see the actual state.

It looks like an issue with the MessageBus event being sent at the wrong time.

Connection widget should show status per server

Currently, the connection widget shows the status for all the servers - if any server is disconnected, then it shows disconnected. Likewise, the connect / reconnect buttons are for all the servers. These should instead support connections for all the current project's servers.

Incorrect P4JavaAPI request for job details

(This is related to bug #33, which was fixed in that extra protection was added to better investigate future issues).

The P4JavaAPI is sometimes making incorrect requests to p4d with the job details when it gives the job ID. The source is either the jobs associated with the changelist are incorrectly parsed, returning the wrong values, or the wrong value is passed as the job ID, or something in the P4Java API is adding incorrect data to the request.

"Add file" while disconnected causes NPE

While working in offline mode, if you attempt to add a file, the plugin generates an NPE.

WARN - clown.idea.p4ic.ui.ErrorDialog - Something threw an invalid exception without being properly wrapped 
java.lang.NullPointerException
    at net.groboclown.idea.p4ic.extension.P4VFSListener$3.run(P4VFSListener.java:176)
    at net.groboclown.idea.p4ic.background.Background.runInBackground(Background.java:54)
    at net.groboclown.idea.p4ic.extension.P4VFSListener.performAdding(P4VFSListener.java:166)
    at com.intellij.openapi.vcs.VcsVFSListener.executeAdd(VcsVFSListener.java:173)
    at com.intellij.openapi.vcs.VcsVFSListener.executeAdd(VcsVFSListener.java:132)
    at com.intellij.openapi.vcs.VcsVFSListener$MyCommandAdapter.commandFinished(VcsVFSListener.java:505)
    at com.intellij.openapi.command.impl.CoreCommandProcessor.fireCommandFinished(CoreCommandProcessor.java:187)
    at com.intellij.openapi.command.impl.CoreCommandProcessor.finishCommand(CoreCommandProcessor.java:161)
    at com.intellij.openapi.command.impl.CommandProcessorImpl.finishCommand(CommandProcessorImpl.java:54)
    at com.intellij.openapi.command.impl.CoreCommandProcessor.executeCommand(CoreCommandProcessor.java:130)
    at com.intellij.openapi.command.impl.CoreCommandProcessor.executeCommand(CoreCommandProcessor.java:99)
    at com.intellij.openapi.command.impl.CoreCommandProcessor.executeCommand(CoreCommandProcessor.java:85)
    at com.intellij.refactoring.copy.CopyClassesHandler.copyClassesImpl(CopyClassesHandler.java:305)
    at com.intellij.refactoring.copy.CopyClassesHandler.doCopy(CopyClassesHandler.java:229)
    at com.intellij.refactoring.copy.CopyHandler.doCopy(CopyHandler.java:55)
    at com.intellij.ide.CopyPasteDelegator$MyEditable.performDefaultPaste(CopyPasteDelegator.java:168)
    at com.intellij.ide.CopyPasteDelegator$MyEditable.performPaste(CopyPasteDelegator.java:131)
    at com.intellij.ide.actions.PasteAction.actionPerformed(PasteAction.java:42)
    at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher$3.performAction(IdeKeyEventDispatcher.java:586)
    at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher.processAction(IdeKeyEventDispatcher.java:637)
    at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher.inInitState(IdeKeyEventDispatcher.java:476)
    at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher.dispatchKeyEvent(IdeKeyEventDispatcher.java:212)
    at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:538)
    at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:382)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

Add IDEA 135 (Android Studio) compatibility

The Android Studio tool (v1.0.1) uses IDEA build 135.x. Compatibility with that version of IDEA would be nice.

Additionally, this will start the much needed work of cross-version compatibility layers.

Synchronizing on a checked-out file does not report future merge problem

If a user has a file checked out in their local client, and they perform a "synchronize" that would otherwise cause the file to be updated, the P4 server will report a message about how the file will need to be merged. However, this is not reported to the user.

The synchronize status should report that the file is not merged, but needs to be, before submit.

Add p4 dvcs support

The new versions of Perforce (2015.1) will include support for a new work-disconnected mode, called "dvcs". It would be really nice to add native plugin support for the disconnected mode.

Android Studio: Plugin 'PerforceIC' failed to initialize and will be disabled

I just installed this plugin directly using the Android Studio JetBrains plugin browser. It crashes on startup with the following stacktrace:

com.intellij.diagnostic.PluginException: net/groboclown/idea/p4ic/config/PasswordStoreService : Unsupported major.minor version 51.0 [Plugin: PerforceIC]
    at com.intellij.ide.plugins.cl.PluginClassLoader.loadClassInsideSelf(PluginClassLoader.java:130)
    at com.intellij.ide.plugins.cl.PluginClassLoader.tryLoadingClass(PluginClassLoader.java:77)
    at com.intellij.ide.plugins.cl.PluginClassLoader.loadClass(PluginClassLoader.java:66)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:249)
    at com.intellij.openapi.components.impl.ComponentManagerImpl$ComponentsRegistry.loadClasses(ComponentManagerImpl.java:408)
    at com.intellij.openapi.components.impl.ComponentManagerImpl$ComponentsRegistry.loadClasses(ComponentManagerImpl.java:398)
    at com.intellij.openapi.components.impl.ComponentManagerImpl$ComponentsRegistry.access$000(ComponentManagerImpl.java:384)
    at com.intellij.openapi.components.impl.ComponentManagerImpl.createComponents(ComponentManagerImpl.java:107)
    at com.intellij.openapi.components.impl.ComponentManagerImpl.init(ComponentManagerImpl.java:89)
    at com.intellij.openapi.components.impl.stores.ApplicationStoreImpl.load(ApplicationStoreImpl.java:87)
    at com.intellij.openapi.application.impl.ApplicationImpl.load(ApplicationImpl.java:508)
    at com.intellij.idea.IdeaApplication.run(IdeaApplication.java:151)
    at com.intellij.idea.MainImpl$1$1$1.run(MainImpl.java:46)
    at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
    at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:715)
    at java.awt.EventQueue.access$400(EventQueue.java:82)
    at java.awt.EventQueue$2.run(EventQueue.java:676)
    at java.awt.EventQueue$2.run(EventQueue.java:674)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:86)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:685)
    at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:697)
    at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:524)
    at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:335)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:296)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:211)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:201)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:196)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:188)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)
Caused by: java.lang.UnsupportedClassVersionError: net/groboclown/idea/p4ic/config/PasswordStoreService : Unsupported major.minor version 51.0
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:637)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:621)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:471)
    at com.intellij.util.lang.UrlClassLoader._defineClass(UrlClassLoader.java:195)
    at com.intellij.util.lang.UrlClassLoader.defineClass(UrlClassLoader.java:191)
    at com.intellij.util.lang.UrlClassLoader._findClass(UrlClassLoader.java:167)
    at com.intellij.ide.plugins.cl.PluginClassLoader.loadClassInsideSelf(PluginClassLoader.java:124)
    ... 31 more

This is using Android Studio 1.1.0 on OSX 10.9.5, JDK 1.6.0_65.

Config "resolved values" should better reflect if it has been loaded

The "Resolved values" part of the Config panel starts off uninitialized. It should be initially empty, and only load up values when the "Refresh resolved properties" button is pressed.

It should also be used to indicate whether any .p4config files can be found. See bug #32 for a related issue.

Not finding P4CONFIG in the hierarchy

You watched me do this, Matt, but if I try to have the plugin find my P4CONFIG file (named .p4config in my case, which is what I specified in the dialog) automatically in the hierarchy, it tells me that no such file is found with the EXACT path of the existing file.

Incompatible versions need to have better UI notification

If the user runs a version of the IDE that is not within the compatibility support (the CompatFactory implementations), the user should be notified with a descriptive dialog that explains the problem. Also, it should tell the user to post a bug here asking for support with that version of the api.

Add sync from repository feature

Add the ability to sync to head (default), or choose a revision / changelist to sync from the Perforce repository.

Syncing to a changelist may be tricky when dealing with multiple servers.

Allow adding jobs to changelists

Changelists may need to have jobs associated with them before a commit is allowed. However, the UI currently has no way to associate a job with a changelist.

Files downloaded from server should be properly encoded

The RawServerExecutor does not properly handle the translation of file encoding specified in the file information, nor as the client requests.

This requires special investigation to see what should be done by the client when receiving the file.

Add job search UI

The Job UI needs to implement the search capability, with good filtering support.

Connection to Perforce is lost

My connection to Perforce is periodically lost (every few minutes). When the connection fails, P4V also cannot connect. This lasts for a minute or two, then everything is fine until it happens again. This does not happen on machines that do not have this plugin installed. It also does not happen using the Simple P4 Plugin. My Connection Type is "Specific P4CONFIG File"

Add user preferences

The plugin contains many locations with hard-coded settings, such as maximum number of server connections, which should instead be controlled by the user. The plugin needs a framework for setting and reading these.

Add resolve/merge support

Ater synchronizing the user is not informed about needs for resolve. Also, after submit with files that need resolving does not allow the user to resolve. This part of the VCS extension needs support.

Localize error messages

Error messages end up being seen by the user (in most cases). Because of this, they need to be localized.

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.