easycorp / easyadminbundle Goto Github PK
View Code? Open in Web Editor NEWEasyAdmin is a fast, beautiful and modern admin generator for Symfony applications.
License: MIT License
EasyAdmin is a fast, beautiful and modern admin generator for Symfony applications.
License: MIT License
It's possible add support for files?
thanks... great work here! :)
I'm following this tutorial: http://level7systems.co.uk/en/symfony2-admin-panel-in-30-seconds/
After installing EasyAdmin, I'm stuck and bringing up the admin page:
An exception has been thrown during the rendering of a template ("Could not convert database value "" to Doctrine Type array") in @EasyAdmin/list.html.twig at line 89.
Link 89 in the template is
{% for item in paginator.currentPageResults %}
I have not configured any paginators. EasyAdmin is configured for User admin, DB does contain users to manage:
# FOSUser
fos_user:
db_driver: orm # other valid values are 'mongodb', 'couchdb' and 'propel'
firewall_name: main
# firewall_name: dev
user_class: AppBundle\Entity\User
easy_admin:
entities:
- AppBundle\Entity\User
Would appreciate a hint on what I'm missing here.
in Twig/EasyAdminTwigExtension.php, line 163
Method names should be declared in
camelCase
. You should rename this class to comply with PSR-1.
*
* code: https://github.com/twigphp/Twig-extensions/blob/master/lib/Twig/Extensions/Extension/Text.php
* author: Henrik Bjornskov <[email protected]>
* copyright holder: (c) 2009 Fabien Potencier
*/
public function entity_field_truncate_filter(\Twig_Environment $env, $value, $length = 64, $preserve = false, $separator = '...')
{
if (function_exists('mb_get_info')) {
if (mb_strlen($value, $env->getCharset()) > $length) {
if ($preserve) {
// If breakpoint is on the last word, return the value without separator.
Posted from SensioLabsInsight
Hi,
This would be nice to add a confirm dialog when clicking on the delete button on edit / view.
Such a dialog system can be put, based on bootstrap : http://bootboxjs.com/examples.html
Waiting for your big commit, I can then make a pull request of the feature ;)
Best,
Nicolas
Add options in config file to specify the EntityManager that want to be used for specified entities, or been able to create various "admins", depending on the entity Manager.
For rendering the templates, what about first checking if @EasyAdmin/<entity name>/<template>.html.twig
exists - if it does, render it, if not, render the base template: @EasyAdmin/<template>.html.twig
What about entity specific controllers:
ArticleAdminController extends AdminController
Not sure how the entity specific controller should/could be implemented...
I have played with this type of thing over the years - see some of my experiments for reference:
https://github.com/kbond/ZenstruckResourceBundle
https://github.com/kbond/ZenstruckDataGridBundle
Those two bundles actually work together if they are both in your project. You can configure data grids from within the config of ZenstruckResourceBundle.
Heya,
I noticed the layout shows the connected user's username - this is great!
Do you plan to embed a login / logout management, either based on a simple security.yml in_memory provider, or even a FOSUser integration (in a wayyy farther time) ?
I had to make a simple in_memory login / logout management for a simple project which could be integrated to your bundle's layout if you wish.
Best,
Nicolas
in Configuration/Configurator.php, line 14
The class
Doctrine\Common\Persistence\ObjectManager
is declared but never used. You should remove theuse
statement.
* file that was distributed with this source code.
*/
namespace JavierEguiluz\Bundle\EasyAdminBundle\Configuration;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Bridge\Doctrine\ManagerRegistry;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
class Configurator
Posted from SensioLabsInsight
Unable to find template "bootstrap_3_layout.html.twig" in @EasyAdmin/layout.html.twig at line 83 when i try to edit or create a new entity
In DependencyInjection/Configuration.php
class, the default value of an integer option is set to a string:
->integerNode('list_max_results')->defaultValue('15')
The string '15'
should be the integer 15
.
This error was reported by @stof in javiereguiluz@f08250a#commitcomment-9314044
Make it easier to resue for fx. Silex by not extending the FrameworkBundle Controller etc. etc.
In 1.0.0 version you have the file:
https://github.com/javiereguiluz/EasyAdminBundle/blob/v1.0.0/Resources/views/form/bootstrap_3_horizontal_layout.html.twig
{% extends "bootstrap_3_layout.html.twig" %}
In Master you have:
https://github.com/javiereguiluz/EasyAdminBundle/blob/master/Resources/views/form/bootstrap_3_horizontal_layout.html.twig
{% extends "@EasyAdmin/form/bootstrap_3_layout.html.twig" %}
If you put the 1.0.0 version in your composer.json you will have the next error.
Unable to find template "bootstrap_3_layout.html.twig" in @EasyAdmin/form/bootstrap_3_horizontal_layout.html.twig at line 1.
It can´t find the route.
Merge master in 1.0.0
Hi,
I got an error ( Could not load type "utcdatetime" ) when I'm trying to access to create or modify form.
My entity has a custom data type, defined as below:
doctrine:
dbal:
types:
utcdatetime: mycompany\myvendor\Common\DoctrineExtensions\Types\UTCDateTimeType
Is there a way to load that type in EasyAdmin?
Regards,
Javi
Switching to http://foundation.zurb.com/ would have the following benefits:
Just a thought, because I think it's not or never as once people start building things on top there will be no going back.
Create and edit forms automatically warn you when you try to leave the page without saving changes:
We use the Are You Sure? jQuery plugin and its default JavaScript alert()
-based dialog. Luckily, this library allows to hook into its workflow to display your own dialog.
Let's create a beautiful modal dialog to alert the user about abandoning unsaved forms.
At first I thought about creating a different method for each action (listAction
controller, editAction
controller, etc.) But we don't need that, because the indexAction()
controller routes the requests to the appropriate methods.
In other words, listAction
, editAction
, etc. are no real controllers for us. Thus, we should replace their public
scope for protected
.
This improvement was suggested by @stof in javiereguiluz@f08250a#commitcomment-9314285
Right now the Twig code used to display the entities' associations is ugly and fragile. Even if we don't support yet many-to-many associations, we have to deal with lots of different scenarios (associated collection, associated simple value, associated null values, etc.)
I'm thinking about creating a simple |doctrine_association
Twig filter to move this complex association handling to pure PHP code.
I honestly have no reasons to support the use of DisconnectedMetadataFactory
over other solutions. I saw that this class was used in software similar to ours and I saw the following PHPdoc in DisconnectedMetadataFactory
class:
/**
* This class provides methods to access Doctrine entity class metadata for a
* given bundle, namespace or entity class, for generation purposes
*
* @author Fabien Potencier <[email protected]>
*/
class DisconnectedMetadataFactory
I need some time to investigate if the suggestion made by @stof in this comment to not use DisconnectedMetadataFactory
would bring us enough benefits.
Some of you have expressed interest in helping this project, so I have a task that could be completed by some volunteer.
Right now I'm testing EasyAdmin with my own Symfony applications, but it's a bit chaotic to switch from one application to another just to test field types or Doctrine associations which are only available in that application.
What I really want is a set of Doctrine entities + Doctrine fixtures that covers all the data types and all the possible associations defined by Doctrine. The steps to accomplish this task would be:
1) Check if Doctrine project defines that set of entities and fixtures and reuse it.
2) If not, check if SonataAdmin or any other existing admin generator bundle defines that set of entities and fixtures and reuse it.
3) If none of the previous points succeed, create that set of entities + fixtures for EasyAdmin.
For reference, the entities must define between them the following associations:
And among all the entities, we must have at least one property of each of the following Doctrine types:
Is anyone willing to help us with this task? Thanks!
Found a minor issue with the new feature (#80) I didn't pick up before: https://github.com/javiereguiluz/EasyAdminBundle/blob/master/Twig/EasyAdminTwigExtension.php#L97
If an entity defines a custom primary key not named id
, no link will be generated as the the method getId
probably doesn't exist.
As we currently have no way to access the entity primary_key_field_name
in there, the easiest workaround is to always define a getId
method on entities with a custom pk name.
Maybe this should be a non-fix ?
Now we have this kind of pattern for routes:
{prefix}/?entity={entity}&action={action}
It seems that the pattern always rely on these two elements, which can be "defaulted" if needed, with the method getNameOfTheFirstConfiguredEntity
and the list
action by default.
What about changing the @Route
annotation to have a much nicer pattern ?
After some tests I've come with 3 different patterns that can certainly match the whole thing:
* @Route("/", name="admin_root", defaults={"entity": null, "action": "list", "id": 0})
* @Route("/{entity}/{action}", name="admin_action", defaults={"action": "list", "id": 0})
* @Route("/{entity}/{action}/{id}", name="admin_element", defaults={"action": "list"}, requirements={"id": "\d+"})
What do you think ?
I think is possible to create a FlashMessageListener on DoctrineEvents when create, update or delete an entity.
Then we need to add some front javascript library like 'Pnotify' or 'Toastr' to show flashmessages.
For example, this method doesn't include the array
typehints.
Suggested by @stof in javiereguiluz@f08250a#commitcomment-9312434
Currently, EasyAdmin doesn't have a real backend homepage. When you access the backend, you are redirected to the list
action of the first configured entity
.
I took this decision because 1) I didn't know what to do about the homepage and 2) I didn't have time to do anything.
These are some proposal about having a real homepage:
homepage.html.twig
template. If you want to have a real homepage for your backend, override it. If not, leave it empty and you'll see an empty backend home.homepage
which let you define the action
and the entity
to render as the homepage. For example, some people may find that the most important and common task is to create some entity. These people could define the new
action of that entity
as the homepage to be more productive.What I don't like to add under any circumstance is a dashboard. These are the cool pages with lots of charts, widgets and advanced stuff that you can see in the admin/backend templates. I won't add them because it's extremely complex to do it right and because I believe that a dashboard is completely different from an admin backend.
In controller there is one header, in other classes, another one.
Right now we use the following Delete buttons, which call the actual delete <form>
used to delete entities:
<button onclick="document.getElementById('delete-form').submit()" class="btn btn-danger">
The problem is that some browsers (IE, we're looking at you) consider that a <button>
without an explicit type is in fact a type="submit"
button. So we need to change the Delete buttons by the following:
<button type="button" onclick="document.getElementById('delete-form').submit()" class="btn btn-danger">
This error was spotted by @stof in javiereguiluz@f08250a#commitcomment-9314140
Currently, EasyAdmin interface is only available in English. What is worse: you cannot translate it to your language because content strings are hardcoded.
In the next version, I'll extract all the content strings and put them on a XLIFF file. Then, the community could start volunteering translations.
Right now, when creating a new entity, we just use this trivial code:
$entityFullyQualifiedClassName = $this->entity['class'];
$item = new $entityFullyQualifiedClassName();
As @stof correctly pointed in this comment this code prevents to pass arguments to entity constructors in order to properly initialize them.
I don't know how often do developers pass arguments to entities, so I'm going to think about this. In any case, we probably can use the simple solution suggested by @stof: create a new protected
method that initializes the entities, allowing developers to override that method to suit their needs.
All the information in EasyAdmin is passed via query parameters. The best way to get the value of these parameters is:
$value = $request->query->get('name');
But most of the time, because I'm lazy, I use the following:
$value = $request->get('name');
This works right, but it's a bit slower and it's not strictly correct in our case (we pass data in the query string, then we shouldn't look for in other parts of the request).
This improvement was suggested by @stof in javiereguiluz@f08250a#commitcomment-9312420
Although you cannot select the theme to display in EasyAdmin, you can easily link to your own CSS files to customize the backend to your needs.
However, my guess is that most of the time the backend design is "good enough" for simple backends except for one single thing: the colors of the backend don't match the corporate colors of the company/organization.
From the developer point of view, I'd like to provide something like this:
easy_admin:
design:
color_scheme: {
'content_background': '#FFF',
'main_menu_background': '#CCC',
'main_menu_contents': '#222',
'accent': '#73D631',
'link': '#4083A9'
}
We would define 5 or 6 variables for the colors of the most important page elements and this way, everyone could easily tweak the interface just by changing 5 configuration options.
The problem is that I don't know how to actually do something like this. I don't want to force people to use Assetic and I won't use any frontend tool for this. Anyone know how can we solve this problem?
For example, if I create a "WYSIWYG" or a "Color" field, I want to be able to manage them easily.
If the type is available, we can then manage some nice helpers to create beautiful fields.
I propose two solutions for this:
With each solution, each entity can have a new field in the entity called field_types
where we'll map all entity field types, for instance:
easy_admin:
entities:
User:
class: AppBundle\Entity\User
field_types:
story: wysiwyg
groupColor: color
What do you think about this feature ?
Is it good for the basic EasyAdmin, or is it such an enhancement that it would need a "new bundle" ?
One day it'll become a problem and a cruel need, so:
How do you think the *ToMany
relationships could be handled ?
Change the Router requirement to RouteInterface, for make the easy admin usable for projects with for example the CMF Routing.
Right now the sortDirection
query parameter value isn't validated. If a malicious user provides a value different from ASC
or DESC
, the Doctrine query will fail and the application will crash.
Solution: silently change the sortDirection
parameter to DESC
if its value is different from ASC
or DESC
.
Error reported by @stof in javiereguiluz@f08250a#commitcomment-9312409
If you change the entity name in the URL and that entity isn't managed by EasyAdmin, the initialize()
method would report a PHP notice.
Error reported by @stof in javiereguiluz@f08250a#commitcomment-9312383
I've found out that this bundle depends (maybe obviously ^^) on Symfony, Doctrine and Sensio FrameworkExtraBundle.
What about adding them to the composer.json
file ?
(preferred an issue than a PR because it can be a subject of debates)
"require": {
"php" : ">=5.3.0",
+ "symfony/symfony" : "~2.3",
+ "sensio/framework-extra-bundle": "~3.0",
+ "doctrine/doctrine-bundle": "~1.2",
"doctrine/orm" : "~2.3",
"pagerfanta/pagerfanta" : "~1.0"
}
I created a virtual field on my entity, and whatever it returns (even an empty string!), I can get a value.
When I browse this file and make a var_dump($entity->{$method}())
at line 119, it gives me that error :
Allowed memory size of 268435456 bytes exhausted (tried to allocate 260308992 bytes) in vendor/javiereguiluz/easyadmin-bundle/Twig/EasyAdminTwigExtension.php on line 119
Do you have the same problem ? Did I miss something ?
Thanks,
Nicolas
We support linking to any kind of web asset (internal, external, relative, absolute). That's why in the layout.html.twig
template we use code as follows:
{% for css_asset in config.assets.css %}
{% if css_asset starts with '//' or css_asset starts with 'http://' or css_asset starts with 'https://' %}
<link rel="stylesheet" href="{{ css_asset }}">
{% else %}
<link rel="stylesheet" href="{{ asset(css_asset) }}">
{% endif %}
{% endfor %}
However, Twig's asset()
function already takes care of this because internally it calls this getUrl()
method of the Symfony\Component\Templating\Asset\UrlPackage
class:
public function getUrl($path, $version = null)
{
if (false !== strpos($path, '://') || 0 === strpos($path, '//')) {
return $path;
}
// ...
}
So we can safely use asset()
to link any CSS or JS asset and everything will work as expected.
This improvement was suggested by @stof in javiereguiluz@f08250a#commitcomment-9312840.
Right now we exclude these fields types when guessing which fields to display:
$excludedFieldTypes = array('binary', 'blob', 'guid', 'json_array', 'object', 'text');
As @stof pointed out in this comment we should also exclude simple_array
type. So let's check out if we should add more types to this exclusion list.
The Response
object returned by initialize()
when an error occurs should have a 404
HTTP status code instead of 200
.
Suggested by @stof in javiereguiluz@f08250a#commitcomment-9312391
In order to display entity associations, we use this code to merge associated fields with regular fields:
$metadata->fieldMappings = array_merge($metadata->fieldMappings, $associationFieldMappings);
I though it was a convenient tradeoff, but as @stof pointed in tins comment it can lead to very serious problems, such as:
in Controller/AdminController.php, line 83
This
entity
local variable is declared but never used. You should remove it.
}
$action = $request->get('action', 'list');
// for now, the homepage redirects to the 'list' action of the first entity
if (null === $entity = $request->get('entity')) {
return $this->redirect($this->generateUrl('admin', array('action' => $action, 'entity' => $this->getNameOfTheFirstConfiguredEntity())));
}
return $this->{$action.'Action'}($request);
}
Posted from SensioLabsInsight
Heya ;)
One other nice enhancement would be to give the ability to put a different list_actions than the global one for each entity.
Best,
Nicolas
Our full-text search is as simple as follows:
$query->orWhere("entity.$name LIKE :query")->setParameter('query', '%'.$searchQuery.'%');
One important missing thing, as @stof pointed in this comment, is to escape any character that may be considered special in the context of the LIKE
.
@stof also pointed that this special character list depends on the type of database and to get the appropriate list for each database, we must use:
$em->getConnection()->getDatabasePlatform()->getWildcards();
I know the bundle is young and we need to wait & see what direction the bundle will take.
However, I'd like to open the debate about the ability to add custom actions, as I feel like this would be a priority & must-have feature for this bundle.
Find below a draft of my personal suggestion.
⚠️ Caution: This is a TL;DR. I know. But please, do read :) and let's debate about your concerns/other suggestions.
Instead of having one controller, with all the logic inside, we'd rather have separated classes for each action
.
Those actions
are gathered by a CompilerPass
using _tagged services_.
Therefore, here is what it might look like:
#actions.yml
services:
easy_admin.actions.edit:
class: JavierEguiluz\Bundle\EasyAdminBundle\Action\EditAction
arguments: [@doctrine.orm.entity_manager]
tags:
- { name: easy_admin.action, alias: edit }
easy_admin.actions.show:
class: JavierEguiluz\Bundle\EasyAdminBundle\Action\ShowAction
arguments: [@doctrine.orm.entity_manager]
tags:
- { name: easy_admin.action, alias: show }
...
1. kernel.request
→ 2. Match easy_admin.path
→ 3. ActionHandler
→ 4. getAppropriateAction
→ 5. run
→ 6. return Response
.
kernel.request
event prior to the RouterListener
.Request
match the configured easy_admin.path
, continue. Otherwise, let the RouterListener act.ActionHandler
. This is the service that has gathered every actions
through tags during compilation pass.ActionHandler
, find the appropriate action
. Inject the handler
& the template engine or get the "proxified" version of the action
.ActionInterface::run()
method.Response
. So let's return it directly.📝 NOTE: The step 1 about the
kernel.request
listener, prior to thesymfony/httpkernel
RouterListener is optional. It is semantically easiest to understand the controller. But as the AdminController does only contain one real route/action,and as I want to inject some Twig_Globals and maybe the extensions in a(done), that's a way to kill two targets with one bullet, and remove entirely the AdminController.kernel.request
listener
Having the adminpath
registered in the config, instead of a routing prefix might also be useful further. (we might add dynamically a route only for generation purposes. But this require using a workaround. See [Routing] Routes only for generation, not matching)
When the Response is rendered, multiple hooks are available in the template to add controls & elements on multiple parts of the views.
In order to do that, a new Twig_Function will be available: render_hook('hookName', {})
.
Actions will subscribe to those hooks in order to add their own elements to the views. Create their own hooks is easy as just using the new render_hook('new_hook_name', args)
in their templates.
1. render_hook
→ 2. ActionHandler::getSubscribingHooks()
→ 3. render
render_hook('list.item.actions', { id: 10, entity: 'Product' })
is called.ActionHandler::getSubscribingHooks
method, which returns the list of action
subscribing to this hook.action
subscribing to this hook
, render the specified view and order by priority in an array. The Twig_Function then returns the array.Eventually, for most of the hooks, return a string of the concatenated result instead of an array. (Might be another twig function name in order to avoid confusion)
_e.g:_ When render_hook('list.item.actions', { id: 10, entity: 'Product' })
is called, the Twig_Function will search through the ActionHandler every actions subscribing the list.item.actions
hook and call the corresponding method defined in the getSubscribedHooks
method. Then, the Twig_Function will loop over & display the result in the view:
<td class="actions">
{% for action in render_hook('list.item.actions', { id: 10, entity: 'Product' })) %}
{{ action }}
{% endfor %}
</td>
With the two edit
& show
actions subscribing to the hook list.item.actions
, will generate:
<td class="actions">
<a href="{{ path('easy_admin', { action: 'show', entity: 'Product', id: 10}) }}">Show</a>
<a href="{{ path('easy_admin', { action: 'edit', entity: 'Product', id: 10}) }}">Edit</a>
</td>
From these informations, we could imagine what an Action will look like :
#JavierEguiluz\Bundle\EasyAdminBundle\Action\EditAction
class EditAction extends AbstractAction
{
function __construct(ObjectManager $em)
{
//Inject needed dependencies
$this->em = $em;
}
/**
* Executes the logic about the action.
* The action must be initiated by the ActionHandler prior being executed,
* in order to inject the handler itself & template engine (or find a way to proxyfy this service, with mandatory
* dependencies already injected, or simply using parent services)
* Protected, as it will be executed by the handler through the run method.
*
* @param Request $request
* @param array $config
* @param array $entity
* @return Response
*/
protected function execute(Request $request, array $config, array $entity)
{
if (!$item = $this->em->getRepository($entity['class'])->find($request->query->get('id'))) {
throw $this->createNotFoundException(sprintf('Unable to find entity (%s #%d).', $entity['name'], $request->query->get('id')));
}
$fields = $entity['edit']['fields'];
$editForm = $this->createEditForm($item, $fields);
$deleteForm = $this->actionHandler->getAppropriateAction('delete')->createDeleteForm($entity['name'], $request->query->get('id'));
$editForm->handleRequest($this->request);
if ($editForm->isValid()) {
$this->prepareEditEntityForPersist($item); //Maybe use the eventDispatcher instead
$this->em->flush();
return new RedirectResponse(($this->router->generateUrl('admin', array('action' => 'list', 'entity' => $entity['name']))));
}
return $this->render('@EasyAdmin/edit.html.twig', array(
'config' => $this->config, //Might be injected as a Twig Global if feasible, on kernel.request
'entity' => $this->entity, //Ditto
'form' => $editForm->createView(),
'entity_fields' => $fields,
'item' => $item,
'delete_form' => $deleteForm->createView(),
));
}
public static function getSubscribedHooks()
{
return array(
array('hook' => 'list.item.actions', 'method' => 'renderListItemActionHook', 'priority' => 1),
array('hook' => 'show.actions', 'method' => 'renderShowActionActionHook', 'priority' => 10),
);
}
public function renderListItemActionHook($hook, array $args)
{
//...
//Render a view
}
public function renderShowActionActionHook($hook, array $args)
{
//...
//Render a view
}
public static function getName()
{
return 'edit';
}
}
✨ BONUS: it will allow to easily override how items specific to each action are rendered.
For instance, overriding therenderListItemActionHook
method for the EditAction will allow to render a real bootstrap button instead of the simple link in the entity listing:<a href="{{ path('easy_admin', { action: 'show', entity: 'Product', id: 10}) }}">Show</a> <a class="btn btn-primary" href="{{ path('easy_admin', { action: 'edit', entity: 'Product', id: 10}) }}">Edit</a>📝 NOTE: Should the
getSubscribedHooks
accept a callable at thehook
key, in order to not only validate the hookName it's subscribing, but other arguments asentity
, ... ?
This workflow might not be limited to actions
on entities: See my comment about Extending to custom pages & dashboards.
See actions on entities as a particular case of pages where entity is additionally injected.
What do you think ?
If that makes sense to you, I might work on it when I have some time in order to provide a more concrete draft.
I see very well the bundle but gives me error when trying to manage an entity that relates Many-to-One with another ... I dont know what happens with doctrine mapping but gives error when validating the mapping but is well mapped...
I have found an issue with form entity field type.
If one entity does not have toString() method then when form try to render listbox throws an exception in a ManytoOne relationship.
Currently, having an entity with a custom primary key named anything but 'id' could not be handled:
The 'Acme\DemoBundle\Entity\Airport' entity isn't valid because it doesn't define a primary key called 'id'
In my case, the id
field is replaced by a iata
code.
Will you considered this as a new feature, or do you not intend to implement it ?
Our entity queries don't join collections, but the Paginator by default considers that the query does. If you take a look at Doctrine\ORM\Tools\Pagination\Paginator
class, you'll see that passing false
as the second argument of the paginator slightly increases performance.
This improvement was suggested by @stof in f08250a#commitcomment-9312611 to allow faster pagination.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.