Code Monkey home page Code Monkey logo

webwriter's People

Contributors

salmenf avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

webwriter's Issues

Widget Support Roadmap

H5P Content Types

Layout (9/52)

Tasks (24/52)

Standalone (5/52)

Custom (10/52)

Not planned (5/52)

PhET Simulations

Widget code not bundled on save

Describe the bug
Widget code is not bundled on saving.

To Reproduce
Steps to reproduce the behavior:

  1. Add any widget to an explorable.
  2. Save the explorable.
  3. Open the explorable with a web browser -> The widget is not shown and the bundle code is missing.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: Windows
  • WebWriter Version 0.7.0

Sandbox documents

Right now, any document content can affect the whole app. This is both a security and a usability issue: Attackers can gain control over the app and faulty widgets can break the whole app.

Sandboxing on the document level seems to be the best choice. Widgets having access to the whole document can be useful, and the damage attackers can cause is limited to an acceptable level. Performance loss is also kept limited, since each open document has its own iframe, but the user most likely won't have many open documents (<10).

The document would need to exist in an iframe. The editor needs to be modified so all content exists in the iframe. Bundles need to be loaded into the iframe. The editor toolbox with its widget previews needs to be reconsidered, as well.

HTML Support Roadmap

Supporting most of the HTML spec to be able to write/read any HTML document

Read

  • Read any document fragment

Write

Document metadata

  • <html>, <head>, <body> (automatically as <html><head></head><body></body></html>)
  • <link>
    • author, icon, license (mirror <meta>)
    • stylesheet (Stylesheet Picker)
  • <meta> (Metadata Form)
    • author, description, keywords
    • generator (automatic)
  • <title> (mirror <meta>)
  • <base> (literal only)

Content sectioning

  • <address> (Person Container)
  • <article> (not applicable)
  • <aside> (Aside Container)
  • <header>, <footer> (?)
  • <hgroup> (automatic with headings if neccessary)
  • <main> (literal only)
  • <nav> (literal only)
  • <section> (automatic with headings by default?)
  • <search> (literal only)

Text content

  • <blockquote> (Blockquote Container)
  • <dl>, <dd>, <dt> (Glossary Container)
  • <figure>, <figcaption> (Figure Container)
  • <hr> (Separator Leaf)
  • <ol>, <ul>, <li> (List Container)
  • <menu> (literal only)
  • <p> (Paragraph Container)
  • <pre> (literal only, fulfilled by visible <script>)

Inline text semantics

  • <a> (Link Mark)
  • <abbr> (Acronym Mark)
  • <b>, <strong> (Bold Mark)
  • <i>, <em> (Italic Mark)
  • <u> (Underline Mark)
  • <s> (Strikethrough Mark)
  • <cite> (Reference Mark)
  • <code> (Code Mark)
  • <data>, <time> (Data Mark)
  • <dfn> (literal only)
  • <q> (Quote Mark)
  • <samp> (Output Mark)
  • <small> (literal only)
  • <var> (Inline Math Mark)
  • <br>, <wbr> (automatic)
  • <bdi>, <bdo>, <rp>, <rt>, <ruby> <span> (literal only)

Image and multimedia

  • <area>, <map> (literal only)
  • <img>, <picture>, <audio>, <video>, <track> (Media Element)

Embedded content

  • <embed> (Media Element - PDF)
  • <iframe> (Media Element - Website)
  • <source> (automatic)
  • <object>, <portal> (literal only)

SVG and MathML

  • <svg> (Vector Drawing Container)
  • <math> (Formula Container)

Scripting

  • <script> (Media Element - Textual and other types)
  • <noscript> (automatic)
  • <canvas> (literal only)

Demarcating content

  • <del>, <ins> (literal only)

Table content

  • <caption>, <col>, <colgroup>, <table>, <tbody>, <td>, <tfoot>, <th>, <thead>, <tr> (Table Container)

Forms

  • <button>, <input>, <select>, <meter>, <datalist>, <fieldset>, <form>, <label>, <legend>, <optgroup>, <option> (Quiz Container Elements)
  • <output>, <progress> (literal only)

Interactive elements

  • <details>, <summary> (Drawer Container)
  • <dialog> (Dialog Container)

Web Components

  • <my-element>, <slot>, <template> (Widget System)

  • Fallback: HTML literal

Widget Features API

There are some general features which make sense for a large subset of widgets.

Some features should be available on the editing level, some on the usage level, some on both levels.

Use cases

  1. User Stylesheets (usage + editing)
  2. Fullscreen (usage)
  3. Drag & Drop (usage/editing)
  4. Copy & Paste (usage + editing)
  5. Metadata Editing (editing)
  6. Sharing (usage)
  7. Analytics (usage)

Specification by developers

Editing by teachers

Usage by students

ID System (Widgets, Explorables)

Allowing widgets to be addressed with an ID would enable some useful features:

  • Anchor links to jump to the widget in the explorable
  • Identifying the widget in xAPI statements
  • Automatically linking/referencing sources when copy-pasting

URL-based approach

  • Explorable ID: The explorable itself is already defined by the URL, e.g. example.com/myexplorable.html. For HTTP/S, this can be expected to be unique. For local files, the username needs to be inserted into the file URL.
  • Widget ID: The widget ID must be unique across all elements in the document. The set of widget IDs should also be stable, meaning that inserting or removing a widget ID should not change the existing widget IDs.

Editing by teachers
The editor automatically assigns IDs when creating widgets. The ID is a Base 36 ASCII string encoded number, preceded by ww_. For example, the first widget ID would be ww_0, while "last" widget ID would be ww_2gosa7pa2gv, based on Number.MAX_SAFE_INTEGER.toString(36). On creation, possible widget IDs are checked one by one, until an unassigned one is found. This theoretically allows for a stable set of Number.MAX_SAFE_INTEGER widgets in the document, while checking for available IDs is performant.

Advanced Rich Text Features

Text

  • Text highlighting
  • Text color
  • Text outline

Block

  • Block justification
  • Indentation
  • Line height
  • Block padding
  • Block border
  • Block margin
  • Block background

In-editor widget development

It would be nice to allow developers to use WebWriter directly as the development preview. This would also remove the need to set up a development environment for widget developers, so only WebWriter, npm and a text editor are needed to develop widgets.

Basic

  • Use a file system watcher to automatically reimport the widget when code changes (Enable/disable in "Packages")
  • Allow accessing folder of specific package
  • Add package initialization ('npm init ...')
  • Analyzing package.json to give feedback/error messages about the package structure
  • Make it easier to publish development versions (hide versions with MAJOR < 1 by default? Add 'npm publish' interface to local packages)

Advanced

  • Baseline Bundle Size: Sum of the size of all dependencies -> Just get the file size of the import test file
  • Lighthouse Score: Loading performance of widget -> Get lighthouse score & report
  • Accessibility: WCAG compliance -> Use axe-core

Problems:

  • Tauri's file system scoping wouldn't allow for arbitrary package paths -> Developer version? Extra allowed path for packages? Just force development in data dir? --> Persisted Scope, remember every package path ever selected

Edit-mode widgets API

Some widgets could provide advanced editing capabilities, such as a video widget allowing video editing or a H5P widget that can install H5P content types locally.

Specification by developers
The mechanism to specify advanced editing capabilities is to create a second entry point file edit.ts, additional to the standard entry point index.ts. Using the "exports" field, the developer can map the edit.ts file to my-widget/edit such that it can be imported with import MyWidget from "my-widget/edit".

my-widget/package.json

{
  "main": "index.ts",
  "exports": {
    ".": "index.ts",
    "./edit": "edit.ts"
  }
}

The main entrypoint should contain the normal widget with no dependency on the editing entry point.
my-widget/index.ts

// Imports omitted
@customElement("my-widget")
export class MyWidget extends LitElementWw {
...
}

The "edit" entrypoint can depend on the main entrypoint.
my-widget/edit.ts

import MyWidget from "."
...

Editing by teachers
The editor automatically imports the editing widget from the edit entrypoint.

Usage by students
None

Widget history (undo/redo)

Undoing/redoing changes is desirable for any stateful widget. It's better for UX to implement this on the editor level instead of the widget level.

To implement this on the widget level, the widget needs to interface with the editor. A simple approach would be running a MutationObserver on every element - When the element's attributes or children change, an event is triggered to change the editor state accordingly. This adds a small performance overhead, but integrates fluently with the existing implementation and doesn't need any changes on the widget level.

Security of Packages

Widgets are distributed as NPM packages, and WebWriter installs these packages on the user's machine using a package manager. As such, all scenarios and mitigations of NPM packages in general apply. https://cheatsheetseries.owasp.org/cheatsheets/NPM_Security_Cheat_Sheet.html

Scenarios:

  1. Malicious Package: An attacker authors a malicious package and publishes it on NPM so that WebWriter can find it. The user installs it and a script is executed on the user machine.
    1a. Typo-squatting/Trojan: An attacker typo-squats an existing widget package and copies the metadata to appear the same.
  2. Dependency chain attack: An attacker gains control of a package that widget packages depend upon, leading to the same issues as (1).
  3. Package Spam: An attacker may publish spam packages that get picked up by WebWriter and are displayed to authors. This may even happen unintentionally if the webwriter-widget keyword is used.

Mitigations:

  • Do not execute scripts on package installation (--ignore-scripts) -> This would allow malicious package to execute arbitrary code. (mitigates 1, 2)
  • Deny installation if lockfile and package.json differ (--frozen-lockfile) -> This makes builds deterministic and avoids security issues introduced by updates anywhere in the dependency tree (mitigates 2) BUT introduces the burden of consistent lockfiles on developers
  • Publish an allowlist and hide results not on the allowlist by default, e.g. allowing by organization name (mitigates 3) BUT adds a maintenance burden

WebWriter Cloud

While WebWriter can function mostly decentralized, some features need a central instance:

  • Storing analytics data (LRS)
  • CRUD of explorables
  • Interoperability with LMS (via LTI)

Storage of analytics and explorables could be combined into one LRS using the xAPI State API, but dependencies should be stored separately as an optimization.

WebWriter Cloud

Registered Handler API

Widgets should be able to specify that they can handle certain media types. This allows advanced drag n' drop handling in the editor, such as automatically creating a ww-figure widget out of an image dragged into the editor.

Specification by developers
A static property mediaTypes on the widget's constructor is used to specify which media types the widget can handle. This is an array of valid MIME type strings.

// Imports are omitted
@customElement("my-widget")
class MyWidget extends LitElementWw {
  static mediaTypes = ["image/png", "image/svg"]
}

Editing by teachers
When something is dropped or pasted into the editor, the media type should be checked. If the media type has no registered handler, a warning should be shown. If the media type has exactly one registered handler, a new handler element is created at the drop location and a drop or paste event is triggered synthetically on the newly created element. If the media type has two or more registered handlers, a dialog should be shown where the user can pick a handler.

Usage by students
None

Add test basic test coverage

While @webwriter/core/view is difficult to test automatically (many platforms), other parts of @webwriter/core could be tested automatically:

  • state: Provide an example state for the store and dispatch actions on it
  • marshal: Serialize/unserialize example documents
  • utility: Straightforward testing for expected outputs

Security of Explorables

WebWriter opens all explorables in iframes, which provides some isolation between the explorable document and the editor. While the default iframe configuration is enough to avoid unintentional leakage of styles and scripts into the editor, intentional attacks using scripts remain a potential issue.

Scenarios:

  • An attacker creates a malicious explorable with a script that accesses WebWriter's Tauri API for file system access, script execution, etc. . They distribute this explorable as a trojan on a OER portal.

Mitigations:

  • Limit file system access to the app directory (and the currently opened file) - this is Tauri's default which is currently disabled because of the way local packages are implemented
  • Set permissions so that the the iframe is considered a different origin (Same Origin Policy), effectively preventing any script access between the iframe and the main (editor) document - this would require re-implementing the editor to use postMessage for setup and configuration OR this might be possible with the new csp property (https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement/csp)

Parity Roadmap

Text Processor

  • Multimedia
  • Text Marks
  • Block Styling
  • Lists
  • Tables
  • Vector Drawing
  • Formulas
  • Document Layout
  • References
  • Spell Checking

Presentation Editor

  • Canvas Layout
  • Slide Layout
  • Slide Transitions
  • Animations
  • Recording

Code Notebook

  • Code Cells

Explorable Dependency Versioning

  • On saving an explorable:
  1. Write an importmap with the versions of all widgets used in the explorable (based on installed).
  2. Create bundle based on importmap.
  • On loading an explorable:
  1. Load importmap of explorable if available.
  2. For each import:
  3. If versions match, pass.
  4. If explorable < installed on a minor or patch version, silently upgrade explorable.
  5. If versions don't match: Let user choose between keeping both, aligning installed, or aligning explorable. Warn if aligning a major version mismatch.
  • Also add a form in the metadata editor to change versions.

Widget Development Dashboard

Some widget metrics can be recorded automatically and instantly.

Metrics

  • Baseline Bundle Size: Sum of the size of all dependencies
  • Lighthouse Score: Loading performance of widget

Usage Testing

  • Show configurable, auto-reloading widget matrix

Validation

  • Check package.json for validity and provide hints

Attributes are not populated with their default values

Describe the bug
When adding a widget the default values of attributes are not set.

To Reproduce
Add Property:
@property({ type: String, attribute: true, reflect: true }) public testString: string = 'Test';
Try to render Property:

render(){
    return html`${this.testString}`;
}

Screenshots

Desktop (please complete the following information):

  • OS: Windows
  • WebWriter Version 0.9.0

Additional context
Only happens in Editor not in exported File.
Attributes are correct in the firstUpdated but are lost in the render Function

Package & Widget Concept

Package & Widget Concept

Phases

  1. Package Development
  2. Explorable Authoring
  3. Explorable Usage

Structure

A package is a set of elements to be used in WebWriter, implemented by a developer. It contains several parts with a package.json file serving as the entrypoint. It should follow NPM's conventions to provide metadata for the widget package. Specifically, these rules apply:

  • A field name MUST be present. The name MUST be scoped (in the form @{SCOPE}/{NAME}).
  • A field version MUST be present. It MUST follow the syntax of SemVer and SHOULD follow the conventions laid out with SemVer.
  • A field exports MUST be present. It MUST contain a record mapping identifiers of the pattern {TYPE}/{ID} to relative paths of source files.
    • {TYPE} MUST be one of themes, utilities, snippets or widgets.
    • {ID} MUST be a sequence of lowercase, URL-safe characters.
    • If {TYPE} is widgets, {ID} MUST follow the form {SCOPE}-{ELEMENT}, where {SCOPE} is the package scope and {ELEMENT} is a string such that {SCOPE}-{ELEMENT} is a valid custom element identifier. The file referenced MUST be a JavaScript or TypeScript file that registers a custom element of the same name {SCOPE}-{ELEMENT} with window.customElements.define
    • If {TYPE} is snippets, {ID} MAY be a string such that widgets/{ID} is also defined in exports.
    • If {TYPE} is themes, the file referenced MUST be a CSS file.
  • A field editingConfig MAY be present. It MUST be an object where each key is also present in exports, and each value is an object providing editing settings depending on {TYPE}

Elements

Widgets

Widgets are interactive elements visible to both authors and users.

Custom Elements Manifest (CEM) is a way to describe custom elements, adding metadata. For WebWriter, this could serve several functions.The @custom-elements-manifest/analyzer library is useful for this - During widget development, this can be run when neccessary.

Attributes & Events

Attributes represent the permanent state of the widget. WebWriter tracks changes in attributes and is able to undo/redo them. For non-permanent state, properties should be used.
Attributes of the widget and events that may be emitted can be annotated for purposes of documentation.

Supported attributes & events
class - State of the editor
  • ww-beforeprint: Before printing, this class is set, allowing widgets to change their presentation to be print-ready.
contenteditable - Differentiate author and user behavior

The value contenteditable="true" is set when editing, allowing widgets to provide affordances to authors change their state.

lang - Language for localization

WebWriter sets the user's chosen document language on each widget during editing as the lang attribute. The widget should process the set locale and change its presentation accordingly.

@experience - Custom events for xAPI statements

Widget may emit custom experience events that report interactions with the widget.

Slots

Slots are elements inside the widget's shadow tree in which content can be placed. This allows for a nesting of elements in WebWriter. Developers can specify the the allowed content of the slot with a content expression. The content expression is added as a custom JSDoc annotation on the component.

Parts & CSS variables

Parts and CSS variables enable styling the internals of the widget by WebWriter or developed stylesheets. Developers can annotate CSS variables with a CSS type that enables WebWriter to generate a widget allowing the user to manipulate the CSS variable. For example, the widget may have a --canvas-color variable that is of the CSS type <color>, allowing authors to use a color picker to choose a color.

Themes

Themes are stylesheets intended to be applied to the whole explorable.

Snippets

Snippets are arbitrary HTML fragments to be inserted into the explorable.

Theming

  • Edit CSS properties of overall document
  • Persist as embedded stylesheet
  • Save and load themes
  • Publish themes as packages with "main": "theme.css"

CSS Support Roadmap

Marks

Text Decoration, Font

  • font: L1 Picker
  • font-synthesis: L2 Form
  • font-variant: L2 Form
  • font-kerning: L2 Form
  • font-optical-sizing: L2 Form
  • font-stretch: L1 Picker
  • color: L1 Picker
  • text-decoration: L1 Picker
  • text-emphasis: L1 Picker
  • text-transform: L1 Picker
  • text-shadow: L1 Picker

Element Attributes

Box Model, Backgrounds and Borders

  • display: L1 Picker
  • background: L1 Picker
  • min-height: L1 Picker
  • height: L1 Picker
  • max-height: L1 Picker
  • min-width: L1 Picker
  • width: L1 Picker
  • max-width: L1 Picker
  • padding: L1 Picker
  • border: L1 Picker
  • border-left: L1 Picker
  • border-right: L1 Picker
  • border-top: L1 Picker
  • border-bottom: L1 Picker
  • margin: L1 Picker
  • box-shadow: L1 Picker

Overflow

  • overflow: L2 Form
  • overflow-clip-margin: L2 Form
  • overscroll-behavior: L2 Form
  • scroll-behavior: L2 Form
  • scrollbar-gutter: L2 Form
  • text-overflow: L2 Form

Text, Generated content

  • letter-spacing: L2 Form
  • text-align: L1 Picker
  • text-align-last: L2 Form
  • text-indent: L2 Form
  • text-justify: L2 Form
  • text-size-adjust: L2 Form
  • vertical-align: L2 Form
  • white-space: L2 Form
  • word-spacing: L2 Form
  • word-break: L2 Form
  • line-break: L2 Form
  • overflow-wrap: L2 Form
  • tab-size: L2 Form
  • quotes: L2 Form

Positioned layout

  • float: L2 Form
  • clip: L2 Form
  • position: L2 Form
  • inset: L2 Form
  • z-index: L2 Form

Multi-column layout

  • columns: L1 Picker
  • column-span: L2 Form
  • column-gap: L2 Form
  • column-fill: L2 Form
  • column-rule: L2 Form
  • break-after: L2 Form
  • break-before: L2 Form
  • break-inside: L2 Form

Basic user interface

  • cursor: L2 Form
  • user-select: L2 Form
  • accent-color: L2 Form
  • appearance: L2 Form
  • caret-color: L2 Form
  • outline: L2 Form
  • outline-offset: L2 Form
  • pointer-events: L2 Form
  • resize: L2 Form

Animations, Motion path

  • animation: L3 Literal
  • offset: L3 Literal

Composition and blending

  • background-blend-mode: L2 Form
  • isolation: L2 Form
  • mix-blend-mode: L2 Form

Filter effects

  • backdrop-filter: L2 Form
  • filter: L2 Form

Transforms

  • transform: L3 Literal
  • transform-box: L3 Literal
  • transform-origin: L3 Literal
  • transform-style: L3 Literal
  • translate: L3 Literal
  • rotate: L3 Literal
  • scale: L3 Literal
  • perspective: L3 Literal
  • perspective-origin: L3 Literal
  • backface-visibility: L3 Literal

Fragmentation

  • box-decoration-break: L2 Form
  • break-before, break-after, break-inside: L2 Form
  • orphans, widows: L2 Form

Masking

  • clip-path: L2 Form
  • mask: L2 Form
  • mask-border: L2 Form
  • mask-type: L2 Form

Ruby, [Writing modes]

  • ruby-align: L3 Literal
  • ruby-position: L3 Literal
  • direction: L2 Form
  • text-orientation: L2 Form
  • unicode-bidi: L2 Form
  • writing-mode: L2 Form

Shapes

  • shape-image-threshold: L3 Literal
  • shape-margin: L3 Literal
  • shape-outside: L3 Literal

Transitions

  • transition: L3 Literal

Elements with display: table

Table (Node attribute <table>)

  • border-collapse: L1 Picker
  • border-spacing: L1 Picker
  • caption-side: L1 Picker
  • empty-cells: L1 Picker
  • table-layout: L1 Picker
  • vertical-align: L1 Picker

Elements with display: flex

Flexible Box Layout, Grid layout

  • flex-flow: L1 Picker
  • grid-template: L1 Picker
  • grid-auto-flow: L1 Picker
  • gap: L1 Picker
  • place-items: L1 Picker
  • place-content: L1 Picker
  • flex: L1 Picker
  • grid-area: L1 Picker
  • place-self: L1 Picker
  • order: L1 Picker

Elements with display: list-item

Lists (Node attribute <ul>|<ol>)

  • list-style: L1 Picker

Replaced Elements

Images

  • image-orientation: L3 Literal
  • image-rendering: L3 Literal
  • image-resolution: L3 Literal
  • object-fit: L3 Literal
  • object-position: L3 Literal

Container Switching

Some nodes in the model are containers, for example the built-in lists and headings. Furthermore, widgets can also work as containers with slots.
Authors should be able to switch between (compatible) types of containers.

How can this be implemented?
Fundamentally, there is a target node and zero or more original nodes which need to be fit inside the target node.

  1. Parse and resolve the target node's content expression. This yields a content tree in which the original nodes can be fitted.
  2. Based on the content, find a way to fill the target node with the original nodes. Try several strategies for this:
  3. If there are no original nodes: Insert an empty target node (with default content according to spec).
  4. If there are one or more original nodes: Try to fit???

Changing Attributes Deletes the Widget

Describe the bug
Changing attributes while using the widget results in a reload of the widget and it gets deleted.
Only happens in the editor, not in the exported file.

To Reproduce
Add a simple Attribute e.g.
@property({ type: String, attribute: true, reflect: true }) public testString: string = '';
Change the Attribute e.g. with a button
<button @click=${() => this.testString = 'test'}>Test</button>

Screenshots
Screenshot 2024-01-30 121204

Desktop (please complete the following information):

  • OS: Windows
  • WebWriter Version 0.9.0

Additional context
Tested with a Local Widget

Widget Analytics API

Analytics data about user interaction with widgets can be useful both to enable insights into learner behavior for teachers and to generate research data.

Specification by developers

Editing by teachers

Usage by students

Slot API

More complex widgets may need to contain children.

There are multiple use cases:

  1. Inline: Text segments in the widget that should allow for any inline content
  2. Block: Layout widgets such as grid, slides, etc.

Specification by developers
Slots are specified with the <slot> element. A name attribute may be provided which will also be used as an input hint by the editor. Additionally, a data-type attribute may be provided. If it is provided, the editor narrows the possible slot children to this value. Possible values are: "inline" for inline widgets, "block" for block widgets, any valid element name (e.g. "other-widget"), or a space-separated list of those previous three (e. g. "other-widget another-widget" or "inline other-widget"). The <slot> element should be filled with a fallback input so the widget remains fully usable outside of a WebWriter context. The fallback input should store its state as widget attributes.

my-widget/index.ts

// Imports omitted
@customElement("my-widget")
class MyWidget extends LitElementWw {
  render() {
    return html`
      <img src="..." />
      <slot name="caption" data-type="inline">
        <input />
      </slot>
    `
  }
}

Editing by teachers
When the widget is rendered by the editor, each slot is first filled with a wrapper element. When the wrapper element is selected, it becomes the actively edited slot. Any editor input is now placed into the wrapper element. Optionally, the possible input is limited by the slot's data-type attribute. Furthermore, the name attribute is used as an input hint.

Usage by students
How the element is updated and saved depends on whether there is a slot child present. If there is not slot content, the state is updated and saved as attributes. If there is slot content, the children are updated and saved.

A) Widget without slot content

<my-widget caption="great image"></my-widget>

B) Widget with slot content

<my-widget>
  <span slot="caption"><b>great</b> image</span>
</my-widget>

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.