Push draft of 2.0.0rc

master
Ilya.P 8 years ago
parent b5b4f96d6c
commit fdfcc9c5b3

372
.gitignore vendored

@ -0,0 +1,372 @@
# Created by https://www.gitignore.io/api/python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
.venv/
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
# Created by https://www.gitignore.io/api/visualstudio
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
*.vcxproj.filters
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
Properties/launchSettings.json
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/
doc/

104
.vscode/launch.json vendored

@ -0,0 +1,104 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Python",
"type": "python",
"request": "launch",
"stopOnEntry": true,
"pythonPath": "${config.python.pythonPath}",
"program": "${file}",
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit",
"RedirectOutput"
]
},
{
"name": "Integrated Terminal/Console",
"type": "python",
"request": "launch",
"stopOnEntry": true,
"pythonPath": "${config.python.pythonPath}",
"program": "${file}",
"console": "integratedTerminal",
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit"
]
},
{
"name": "External Terminal/Console",
"type": "python",
"request": "launch",
"stopOnEntry": true,
"pythonPath": "${config.python.pythonPath}",
"program": "${file}",
"console": "externalTerminal",
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit"
]
},
{
"name": "Django",
"type": "python",
"request": "launch",
"stopOnEntry": true,
"pythonPath": "${config.python.pythonPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"runserver",
"--noreload"
],
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit",
"RedirectOutput",
"DjangoDebugging"
]
},
{
"name": "Flask",
"type": "python",
"request": "launch",
"stopOnEntry": true,
"pythonPath": "${config.python.pythonPath}",
"program": "${workspaceRoot}/run.py",
"args": [],
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit",
"RedirectOutput"
]
},
{
"name": "Watson",
"type": "python",
"request": "launch",
"stopOnEntry": true,
"pythonPath": "${config.python.pythonPath}",
"program": "${workspaceRoot}/console.py",
"args": [
"dev",
"runserver",
"--noreload=True"
],
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit",
"RedirectOutput"
]
},
{
"name": "Attach (Remote Debug)",
"type": "python",
"request": "attach",
"localRoot": "${workspaceRoot}",
"remoteRoot": "${workspaceRoot}",
"port": 3000,
"secret": "my_secret",
"host": "localhost"
}
]
}

@ -0,0 +1,3 @@
{
"python.linting.pylintEnabled": false
}

@ -1,781 +0,0 @@
# Ambar Web API v1.2.0
Ambar Web API documentation
- [Files](#files)
- [Get File Meta by File Id](#get-file-meta-by-file-id)
- [Get File Source by File Id](#get-file-source-by-file-id)
- [Get Parsed Text From File by File Id](#get-parsed-text-from-file-by-file-id)
- [Download File Content by Secure Uri](#download-file-content-by-secure-uri)
- [Download Parsed Text by Secure Uri](#download-parsed-text-by-secure-uri)
- [Upload File](#upload-file)
- [Hide File](#hide-file)
- [Unhide File](#unhide-file)
- [Search](#search)
- [Search For Documents By Query](#search-for-documents-by-query)
- [Retrieve File Highlight by Query and fileId](#retrieve-file-highlight-by-query-and-fileid)
- [Retrieve Full File Highlight by Query and fileId](#retrieve-full-file-highlight-by-query-and-fileid)
- [Sources](#sources)
- [Get Available Sources](#get-available-sources)
- [Statistics](#statistics)
- [Get Statistics](#get-statistics)
- [Tags](#tags)
- [Delete Tag From File](#delete-tag-from-file)
- [Get Tags](#get-tags)
- [Add Tag For File](#add-tag-for-file)
- [Thumbnails](#thumbnails)
- [Get Thumbnail by Id](#get-thumbnail-by-id)
- [Add or Update Thumbnail](#add-or-update-thumbnail)
- [Users](#users)
- [Login](#login)
- [Logout](#logout)
# Files
## Get File Meta by File Id
GET api/files/direct/:fileId/meta
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email</p> |
| ambar-email-token | String | <p>User token</p> |
### Success Response
HTTP/1.1 200 OK
```
Octet-Stream
```
### Error Response
HTTP/1.1 404 Not Found
```
File meta or content not found
```
## Get File Source by File Id
GET api/files/direct/:fileId/source
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email</p> |
| ambar-email-token | String | <p>User token</p> |
### Success Response
HTTP/1.1 200 OK
```
Octet-Stream
```
### Error Response
HTTP/1.1 404 Not Found
```
File meta or content not found
```
## Get Parsed Text From File by File Id
GET api/files/direct/:fileId/text
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email</p> |
| ambar-email-token | String | <p>User token</p> |
### Success Response
HTTP/1.1 200 OK
```
Octet-Stream
```
### Error Response
HTTP/1.1 404 Not Found
```
File meta or content not found
```
## Download File Content by Secure Uri
GET api/files/:uri
### Success Response
HTTP/1.1 200 OK
```
Octet-Stream
```
### Error Response
HTTP/1.1 404 Not Found
```
File meta or content not found
```
## Download Parsed Text by Secure Uri
GET api/files/:uri/text
### Success Response
HTTP/1.1 200 OK
```
Octet-Stream
```
### Error Response
HTTP/1.1 404 Not Found
```
File meta or content not found
```
## Upload File
<p>New source named <code>uiupload</code> with description <code>Automatically created on UI upload</code> will be created if source didn't exist.</p>
POST api/files/uiupload/:filename
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email</p> |
| ambar-email-token | String | <p>User token</p> |
### Examples
Upload File test.txt
```
curl -X POST \
http://ambar_api_address/api/files/uiupload/test.txt \
-H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
-F file=@test.txt
```
### Success Response
HTTP/1.1 200 OK
```
{ "fileId": xxxxx }
```
### Error Response
HTTP/1.1 400 Bad Request
```
Wrong request data
```
HTTP/1.1 404 Not Found
```
File meta or content not found
```
## Hide File
<p>Hide file by file id</p>
PUT api/files/hide/:fileId
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email</p> |
| ambar-email-token | String | <p>User token</p> |
### Success Response
HTTP/1.1 200 OK
```
HTTP/1.1 200 OK
```
### Error Response
HTTP/1.1 404 NotFound
```
File not found
```
## Unhide File
<p>Unhide file by file id</p>
PUT api/files/unhide/:fileId
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email</p> |
| ambar-email-token | String | <p>User token</p> |
### Success Response
HTTP/1.1 200 OK
```
HTTP/1.1 200 OK
```
### Error Response
HTTP/1.1 404 NotFound
```
File not found
```
# Search
## Search For Documents By Query
GET api/search
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email.</p> |
| ambar-email-token | String | <p>User token.</p> |
### Parameters
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| query | String | <p>URI_ENCODED query string. Check details of query syntax <a href="https://blog.ambar.cloud/mastering-ambar-search-queries/">here</a>.</p> |
| page | Number | **optional** <p>page to return</p> |
| size | Number | **optional** <p>number of results to return per page. Maximum is 100.</p> |
### Examples
Search For `John`
```
curl -i http://ambar_api_address/api/search?query=John
```
### Success Response
HTTP/1.1 200 OK
```
{
"total":1,
"hits":[
{
"sha256":"60a777c59176e98efee98bf16b67983dc981ec4da3eaafcb4d79046d005456f9",
"meta":{
"id":"ac8965ab5e07582e0e57cde0e7c4c2d49b955f8b26c779903191893fcb942fa4",
"full_name":"//mail.nic.ru/hello@ambar.cloud/linus torvalds talk of tech innovation is bullshit shut up and get the work done fcc chairman wants it to be easier to listen to free fm radio on your smartphone.eml",
"short_name":"linus torvalds talk of tech innovation is bullshit shut up and get the work done fcc chairman wants it to be easier to listen to free fm radio on your smartphone.eml",
"extension":".eml",
"extra":[
],
"source_id":"AmbarEmail",
"created_datetime":"2017-02-17 09:22:44.000",
"updated_datetime":"2017-02-17 09:22:44.000",
"download_uri":"b41c4aaa2999ce42957f087db8e7608970efcedb1eaa40c28336390ecb5373849c955f395258f3dfd7482d4b84d543cdfc23cff8df311276a5e111c0504315c60b159cd2fe2cee20c5470789d9d15e4d7e5fb7c2bc60c29bf9a578e47541fb354dcb5109e49ea9019b2d68c3b35e521a418d9c94f0af55dc79c2442188f039c924d0190c72f488ad77647f2a52aaa267"
},
"indexed_datetime":"2017-05-31 13:36:40.400",
"file_id":"aa5e000fd79cfed0e839af7073e1ef135e128408f984b9a8e70e34242b49f01a",
"content":{
"size":49282,
"author":"Slashdot Headlines <slashdot@newsletters.slashdot.org>",
"ocr_performed":false,
"processed_datetime":"2017-05-31 13:36:40.361",
"length":"",
"language":"",
"thumb_available":false,
"state":"processed",
"title":"",
"type":"message/rfc822",
"highlight":{
"text":[
"__________________________________________________________________________<br/>Linus Torvalds: Talk of Tech Innovation is Bullshit. Shut Up and Get the Work Done<br/>http://clicks.slashdot.org/ct.html?ufl=6&rtr=on&s=x8pb08,2qzsp,10sc,d9zf,fh0y,9dml,a0z3<br/><em>Elon Musk</em> Is <em>Really Boring</em><br/>http://clicks.slashdot.org/ct.html?ufl=6&rtr=on&s=x8pb08,2qzsp,10sc,a5s3,9k63,9dml,a0z3<br/>FCC Chairman Wants It To Be Easier To Listen To Free FM Radio On Your Smartphone<br/>http://clicks.slashdot.org/ct.html?ufl=6&rtr=on&s=x8pb08,2qzsp",
"self-serving. From a report on The Register: The term of art he used was more blunt: \"The innovation the industry talks about so much is... Read More http://clicks.slashdot.org/ct.html?ufl=6&rtr=on&s=x8pb08,2qzsp,10sc,aiki,d8f2,9dml,a0z3<br/><em>Elon Musk</em> Is <em>Really Boring</em> http://clicks.slashdot.org/ct.html?ufl=6&rtr=on&s=x8pb08,2qzsp,10sc,ezm,35uk,9dml,a0z3<br/>From the boring-company department<br/>Sometimes it is hard to tell if Elon Musk is serious about the things he says. But as for his \"boring\" claims, that's",
"email to: unsubscribe-47676@elabs10.com<br/>Slashdot | 1660 Logan Ave. Ste A | San Diego, CA 92113<br/>To view our Privacy Policy: http://clicks.slashdot.org/ct.html?ufl=6&rtr=on&s=x8pb08,2qzsp,10sc,8pii,7uiv,9dml,a0z3<br/><em>Elon Musk</em> Is <em>Really Boring</em> | Lost Winston Churchill Essay Reveals His Thoughts On Alien<br/>Life<br/>All the Power of a Windows 10 PC Right In Your Pocket<br/>As the world gets more advanced, technology is getting",
"WiFi and Bluetooth. Plus, with<br/>a wide range of inputs and outputs, you can link with just about any device you want. Learn More!<br/>Linus Torvalds: Talk of Tech Innovation is Bullshit. Shut Up and Get the Work Done <br/><em>Elon Musk</em> Is <em>Really Boring</em> <br/>FCC Chairman Wants It To Be Easier To Listen To Free FM Radio On Your Smartphone <br/>Lost Winston Churchill Essay Reveals His Thoughts On Alien Life <br/>JavaScript Attack Breaks ASLR On 22 CPU Architectures <br/>Ethicists",
"of innovation is smug, self-congratulatory, and self-serving. From a report on The Register: The term of art he used was more blunt: \"The innovation the industry talks about so much is...<br/><em>Elon Musk</em> Is <em>Really Boring</em> <br/>From the boring-company department<br/>Sometimes it is hard to tell if Elon Musk is serious about the things he says. But as for his \"boring\" claims, that's really happening. In a wide-range interview with Bloomberg"
]
}
},
"tags":[
],
"score":1
}
],
"took":24.672135
}
```
### Error Response
HTTP/1.1 400 BadRequest
```
HTTP/1.1 400 BadRequest
```
## Retrieve File Highlight by Query and fileId
<p>This method is useful for getting higlights of large files &gt; 30 MB</p>
GET api/search/:fileId
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email.</p> |
| ambar-email-token | String | <p>User token.</p> |
### Parameters
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| fileId | String | <p>file fileId</p> |
| query | String | <p>query string</p> |
### Examples
Retrieve Higlights for File with fileId `318be2290125e0a6cfb7229133ba3c4632068ae04942ed5c7c660718d9d41eb3`
```
curl -i http://ambar:8004/api/search/318be2290125e0a6cfb7229133ba3c4632068ae04942ed5c7c660718d9d41eb3?query=John
```
### Success Response
HTTP/1.1 200 OK
```
{
"highlight": {
"text": [
"Aesop, by some strange accident it seems to have entirely<br/>disappeared, and to have been lost sight of. His name is<br/>mentioned by Avienus; by Suidas, a celebrated critic, at the<br/>close of the eleventh century, who gives in his lexicon several<br/>isolated verses of his version of the fables; and by <em>John</em><br/>Tzetzes, a grammarian and poet of Constantinople, who lived<br/>during the latter half of the twelfth century. Nevelet, in the<br/>preface to the volume which we have described, points out that<br/>the Fables of Planudes could not be the work of Aesop, as they<br/>contain a reference in two places to \"Holy"
]
}
}
```
### Error Response
HTTP/1.1 400 BadRequest
```
HTTP/1.1 400 BadRequest
```
## Retrieve Full File Highlight by Query and fileId
<p>This method is useful for getting higlights of large files &gt; 30 MB</p>
GET api/search/:fileId/full
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email.</p> |
| ambar-email-token | String | <p>User token.</p> |
### Parameters
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| fileId | String | <p>file fileId</p> |
| query | String | <p>query string</p> |
### Examples
Retrieve Full Higlight for File with fileId `318be2290125e0a6cfb7229133ba3c4632068ae04942ed5c7c660718d9d41eb3`
```
curl -i http://ambar:8004/api/search/318be2290125e0a6cfb7229133ba3c4632068ae04942ed5c7c660718d9d41eb3/full?query=John
```
### Success Response
HTTP/1.1 200 OK
```
Aesop, by some strange accident it seems to have entirely<br/>disappeared, and to have been lost sight of. His name is<br/>mentioned by Avienus; by Suidas, a celebrated critic, at the<br/>close of the eleventh century, who gives in his lexicon several<br/>isolated verses of his version of the fables; and by <em>John</em><br/>Tzetzes, a grammarian and poet of Constantinople, who lived<br/>during the latter half of the twelfth century. Nevelet, in the<br/>preface to the volume which we have described, points out that<br/>the Fables of Planudes could not be the work of Aesop, as they<br/>contain a reference in two places to Holy
```
### Error Response
HTTP/1.1 400 BadRequest
```
HTTP/1.1 400 BadRequest
```
# Sources
## Get Available Sources
<p>Get Available Sources (Crawlers Included)</p>
GET api/sources/
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email.</p> |
| ambar-email-token | String | <p>User token.</p> |
### Success Response
HTTP/1.1 200 OK
```
[
{
"id": "Default",
"description": "Automatically created on UI upload",
"type": "bucket"
},
{
"id": "Books",
"description": "Books crawler",
"type": "crawler"
},
{
"id": "Dropbox",
"description": "Dropbox Crawler",
"type": "crawler"
}
]
```
# Statistics
## Get Statistics
GET api/stats
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email</p> |
| ambar-email-token | String | <p>User token</p> |
### Success Response
HTTP/1.1 200 OK
```
{
"contentType": {
"total": 2,
"minThreshold": 0.1,
"data": [
{
"name": "application/msword",
"value": 1,
"sizeDataInBytes": {
"count": 1,
"min": 91681,
"max": 91681,
"avg": 91681,
"sum": 91681
}
}
]
},
"procRate": {
"data": [
{
"date": "2017-04-13",
"default": 0
},
{
"date": "2017-04-14",
"default": 2
}
],
"names": [
"default"
]
},
"procTotal": {
"totalCount": 2,
"sizeDataInBytes": {
"sum": 147522,
"avg": 73761,
"min": 55841,
"max": 91681
}
}
}
```
# Tags
## Delete Tag From File
DELETE api/tags/:fileId/:tagType/:tagName
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email</p> |
| ambar-email-token | String | <p>User token</p> |
### Parameters
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| fileId | String | <p>File Id to delete tag from.</p> |
| tagType | String | <p>Tag type to delete.</p> |
| tagName | String | <p>Tag name to delete.</p> |
### Success Response
HTTP/1.1 200 OK
```
{
"tags":[
{
"name":"ocr",
"filesCount":3
},
{
"name":"test",
"filesCount":2
},
{
"name":"pdf",
"filesCount":1
}
]
}
```
## Get Tags
GET api/tags/
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email</p> |
| ambar-email-token | String | <p>User token</p> |
### Success Response
HTTP/1.1 200 OK
```
[
{
"name":"ocr",
"filesCount":3
},
{
"name":"test",
"filesCount":2
},
{
"name":"pdf",
"filesCount":1
}
]
```
## Add Tag For File
POST api/tags/:fileId/:tagType/:tagName
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email</p> |
| ambar-email-token | String | <p>User token</p> |
### Parameters
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| fileId | String | <p>File Id to add tag to.</p> |
| tagType | String | <p>Tag type to add.</p> |
| tagName | String | <p>Tag name to add.</p> |
### Success Response
HTTP/1.1 200 OK
```
{
"tagId":"e9536a83e64ff03617ab0379d835ac7bbf213bafb95cb42907a56e735472d4fc",
"tags":[
{
"name":"ocr",
"filesCount":3
},
{
"name":"test",
"filesCount":2
},
{
"name":"pdf",
"filesCount":1
}
]
}
```
# Thumbnails
## Get Thumbnail by Id
GET api/thumbs/:id
### Success Response
HTTP/1.1 200 OK
```
Octet-Stream
```
### Error Response
HTTP/1.1 404 NotFound
```
HTTP/1.1 404 NotFound
```
## Add or Update Thumbnail
POST api/thumbs/:id
### Success Response
HTTP/1.1 200 OK
```
HTTP/1.1 200 OK
```
### Error Response
HTTP/1.1 400 Bad Request
```
Request body is empty
```
# Users
## Login
POST api/users/login
### Parameters
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| email | String | <p>User Email</p> |
| password | String | <p>User Password</p> |
### Success Response
HTTP/1.1 200 OK
```
{
"token": "504d44935c2ccefb557fd49636a73239147b3895db2f2f...",
"ttl": "604800"
}
```
### Error Response
HTTP/1.1 400 BadRequest
```
Bad request
```
HTTP/1.1 404 NotFound
```
User with specified email not found
```
HTTP/1.1 409 Conflict
```
User is not in active state
```
HTTP/1.1 401 Unauthorized
```
Wrong password
```
## Logout
POST api/users/logout
### Headers
| Name | Type | Description |
|---------|-----------|--------------------------------------|
| ambar-email | String | <p>User email</p> |
| ambar-email-token | String | <p>User token</p> |
### Error Response
HTTP/1.1 401 Unauthorized
```
Unauthorized
```

@ -0,0 +1,371 @@
# Created by https://www.gitignore.io/api/python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
.venv/
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
# Created by https://www.gitignore.io/api/visualstudio
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
*.vcxproj.filters
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
Properties/launchSettings.json
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/

@ -0,0 +1,16 @@
FROM elasticsearch:5.6.3
# Set a timezone
ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY elasticsearch.yml ./config/elasticsearch.yml
RUN bin/elasticsearch-plugin install http://dl.bintray.com/content/imotov/elasticsearch-plugins/org/elasticsearch/elasticsearch-analysis-morphology/5.6.3/elasticsearch-analysis-morphology-5.6.3.zip
RUN bin/elasticsearch-plugin install analysis-stempel
RUN bin/elasticsearch-plugin install analysis-smartcn
CMD ["elasticsearch"]
HEALTHCHECK --interval=5s --timeout=30s --retries=50 \
CMD curl -f http://localhost:9200/ || exit 1

@ -0,0 +1,12 @@
network.host: 0.0.0.0
# this value is required because we set "network.host"
# be sure to modify it appropriately for a production cluster deployment
discovery.zen.minimum_master_nodes: 1
http.max_content_length: 1024mb
http.cors.enabled: true
http.cors.allow-origin: "*"
node.ingest: false
action.auto_create_index: false
bootstrap.memory_lock: true

@ -0,0 +1,10 @@
// NOTE: These options are overriden by the babel-loader configuration
// for webpack, which can be found in ~/build/webpack.config.
//
// Why? The react-transform-hmr plugin depends on HMR (and throws if
// module.hot is disabled), so keeping it and related plugins contained
// within webpack helps prevent unexpected errors.
{
"presets": ["es2015", "react", "stage-0"],
"plugins": ["transform-runtime"]
}

@ -0,0 +1,30 @@
# http://editorconfig.org
# A special property that should be specified at the top of the file outside of
# any sections. Set to true to stop .editor config file search on current file
root = true
[*]
# Indentation style
# Possible values - tab, space
indent_style = space
# Indentation size in single-spaced characters
# Possible values - an integer, tab
indent_size = 2
# Line ending file format
# Possible values - lf, crlf, cr
end_of_line = lf
# File character encoding
# Possible values - latin1, utf-8, utf-16be, utf-16le
charset = utf-8
# Denotes whether to trim whitespace at the end of lines
# Possible values - true, false
trim_trailing_whitespace = true
# Denotes whether file should end with a newline
# Possible values - true, false
insert_final_newline = true

@ -0,0 +1,6 @@
blueprints/**/files/**
coverage/**
node_modules/**
dist/**
*.spec.js
src/index.html

@ -0,0 +1,29 @@
{
"parser" : "babel-eslint",
"extends" : [
"standard",
"standard-react"
],
"plugins": [
"babel",
"react",
"promise"
],
"env" : {
"browser" : true
},
"globals" : {
"__DEV__" : false,
"__PROD__" : false,
"__DEBUG__" : false,
"__COVERAGE__" : false,
"__BASENAME__" : false
},
"rules": {
"semi" : [2, "never"],
"max-len": [2, 120, 2],
"generator-star-spacing": 0,
"babel/generator-star-spacing": 1,
"jsx-quotes": [2, "prefer-single"]
}
}

371
FrontEnd/.gitignore vendored

@ -0,0 +1,371 @@
# Created by https://www.gitignore.io/api/python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# dotenv
.env
# virtualenv
.venv/
venv/
ENV/
# Spyder project settings
.spyderproject
# Rope project settings
.ropeproject
# Created by https://www.gitignore.io/api/visualstudio
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
*.vcxproj.filters
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
Properties/launchSettings.json
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/

@ -0,0 +1,7 @@
{
"sourceBase":"src",
"testBase":"tests",
"smartPath":"containers",
"dumbPath":"components",
"fileCasing":"pascal"
}

@ -0,0 +1,15 @@
FROM nginx:latest
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y curl
# Set a timezone
ENV TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY default /etc/nginx/conf.d/default.conf
COPY dist /usr/share/nginx/html
CMD echo $api > /usr/share/nginx/html/apiUrl.txt && nginx -g "daemon off;"
HEALTHCHECK --interval=5s --timeout=30s --retries=50 \
CMD curl -f localhost:80 || exit 1

@ -0,0 +1,24 @@
import fs from 'fs-extra'
import _debug from 'debug'
import webpackCompiler from '../build/webpack-compiler'
import webpackConfig from '../build/webpack.config'
import config from '../config'
const debug = _debug('app:bin:compile')
const paths = config.utils_paths
;(async function () {
try {
debug('Run compiler')
const stats = await webpackCompiler(webpackConfig)
if (stats.warnings.length && config.compiler_fail_on_warning) {
debug('Config set to fail on warning, exiting with status code "1".')
process.exit(1)
}
debug('Copy static assets to dist folder.')
fs.copySync(paths.client('static'), paths.dist())
} catch (e) {
debug('Compiler encountered an error.', e)
process.exit(1)
}
})()

@ -0,0 +1,11 @@
import config from '../config'
import server from '../server/main'
import _debug from 'debug'
const debug = _debug('app:bin:server')
const port = config.server_port
const host = config.server_host
server.listen(port)
debug(`Server is now running at http://${host}:${port}.`)
debug(`Server accessible via localhost:${port} if you are using the project defaults.`)

@ -0,0 +1,11 @@
{
"extends" : "../.eslintrc",
"env" : {
"mocha" : true
},
"globals" : {
"expect" : false,
"should" : false,
"sinon" : false
}
}

@ -0,0 +1,10 @@
import React from 'react'
import classes from './<%= pascalEntityName %>.scss'
export const <%= pascalEntityName %> = () => (
<div className={classes['<%= pascalEntityName %>']}>
<h1><%= pascalEntityName %></h1>
</div>
)
export default <%= pascalEntityName %>

@ -0,0 +1,3 @@
import <%= pascalEntityName %> from './<%= pascalEntityName %>'
export default <%= pascalEntityName %>

@ -0,0 +1,25 @@
// module.exports = {
// locals: function(options) {
// // Return custom template variables here.
// return {};
// },
// fileMapTokens: function(options) (
// // Return custom tokens to be replaced in your files
// return {
// __token__: function(options){
// // logic to determine value goes here
// return 'value';
// }
// }
// },
// Should probably never need to be overriden
//
// filesPath: function() {
// return path.join(this.path, 'files');
// },
// beforeInstall: function(options) {},
// afterInstall: function(options) {},
// };

@ -0,0 +1,10 @@
import React from 'react'
import classes from './<%= pascalEntityName %>.scss'
export const <%= pascalEntityName %> = () => (
<div className={classes['<%= pascalEntityName %>']}>
<h4><%= pascalEntityName %></h4>
</div>
)
export default <%= pascalEntityName %>

@ -0,0 +1,38 @@
import { connect } from 'react-redux'
import { increment, doubleAsync } from '../modules/<%= pascalEntityName %>'
/* This is a container component. Notice it does not contain any JSX,
nor does it import React. This component is **only** responsible for
wiring in the actions and state necessary to render a presentational
component - in this case, the counter: */
import <%= pascalEntityName %> from '../components/<%= pascalEntityName %>'
/* Object of action creators (can also be function that returns object).
Keys will be passed as props to presentational components. Here we are
implementing our wrapper around increment; the component doesn't care */
const mapActionCreators = {
increment: () => increment(1),
doubleAsync
}
const mapStateToProps = (state) => ({
counter: state.counter
})
/* Note: mapStateToProps is where you should use `reselect` to create selectors, ie:
import { createSelector } from 'reselect'
const counter = (state) => state.counter
const tripleCount = createSelector(counter, (count) => count * 3)
const mapStateToProps = (state) => ({
counter: tripleCount(state)
})
Selectors can compute derived data, allowing Redux to store the minimal possible state.
Selectors are efficient. A selector is not recomputed unless one of its arguments change.
Selectors are composable. They can be used as input to other selectors.
https://github.com/reactjs/reselect */
export default connect(mapStateToProps, mapActionCreators)(<%= pascalEntityName %>)

@ -0,0 +1,24 @@
import { injectReducer } from '../../store/reducers'
export default (store) => ({
path: '<%= dashesEntityName %>',
/* Async getComponent is only invoked when route matches */
getComponent (nextState, cb) {
/* Webpack - use 'require.ensure' to create a split point
and embed an async module loader (jsonp) when bundling */
require.ensure([], (require) => {
/* Webpack - use require callback to define
dependencies for bundling */
const <%= pascalEntityName %> = require('./containers/<%= pascalEntityName %>Container').default
const reducer = require('./modules/<%= pascalEntityName %>').default
/* Add the reducer to the store on key 'counter' */
injectReducer(store, { key: '<%= pascalEntityName %>', reducer })
/* Return getComponent */
cb(null, <%= pascalEntityName %>)
/* Webpack named bundle */
}, '<%= pascalEntityName %>')
}
})

@ -0,0 +1,55 @@
// ------------------------------------
// Constants
// ------------------------------------
export const COUNTER_INCREMENT = '<%= pascalEntityName %>.COUNTER_INCREMENT'
// ------------------------------------
// Actions
// ------------------------------------
export function increment (value = 1) {
return {
type: COUNTER_INCREMENT,
payload: value
}
}
/* This is a thunk, meaning it is a function that immediately
returns a function for lazy evaluation. It is incredibly useful for
creating async actions, especially when combined with redux-thunk!
NOTE: This is solely for demonstration purposes. In a real application,
you'd probably want to dispatch an action of COUNTER_DOUBLE and let the
reducer take care of this logic. */
export const doubleAsync = () => {
return (dispatch, getState) => {
return new Promise((resolve) => {
setTimeout(() => {
dispatch(increment(getState().counter))
resolve()
}, 200)
})
}
}
export const actions = {
increment,
doubleAsync
}
// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[COUNTER_INCREMENT]: (state, action) => state + action.payload
}
// ------------------------------------
// Reducer
// ------------------------------------
const initialState = 0
export default function counterReducer (state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]
return handler ? handler(state, action) : state
}

@ -0,0 +1,25 @@
// module.exports = {
// locals: function(options) {
// // Return custom template variables here.
// return {};
// },
// fileMapTokens: function(options) (
// // Return custom tokens to be replaced in your files
// return {
// __token__: function(options){
// // logic to determine value goes here
// return 'value';
// }
// }
// },
// Should probably never need to be overriden
//
// filesPath: function() {
// return path.join(this.path, 'files');
// },
// beforeInstall: function(options) {},
// afterInstall: function(options) {},
// };

@ -0,0 +1,76 @@
import { argv } from 'yargs'
import config from '../config'
import webpackConfig from './webpack.config'
import _debug from 'debug'
const debug = _debug('app:karma')
debug('Create configuration.')
const karmaConfig = {
basePath: '../', // project root in relation to bin/karma.js
files: [
{
pattern: `./${config.dir_test}/test-bundler.js`,
watched: false,
served: true,
included: true
}
],
singleRun: !argv.watch,
frameworks: ['mocha'],
reporters: ['mocha'],
preprocessors: {
[`${config.dir_test}/test-bundler.js`]: ['webpack']
},
browsers: ['PhantomJS'],
webpack: {
devtool: 'cheap-module-source-map',
resolve: {
...webpackConfig.resolve,
alias: {
...webpackConfig.resolve.alias,
sinon: 'sinon/pkg/sinon.js'
}
},
plugins: webpackConfig.plugins,
module: {
noParse: [
/\/sinon\.js/
],
loaders: webpackConfig.module.loaders.concat([
{
test: /sinon(\\|\/)pkg(\\|\/)sinon\.js/,
loader: 'imports?define=>false,require=>false'
}
])
},
// Enzyme fix, see:
// https://github.com/airbnb/enzyme/issues/47
externals: {
...webpackConfig.externals,
'react/addons': true,
'react/lib/ExecutionEnvironment': true,
'react/lib/ReactContext': 'window'
},
sassLoader: webpackConfig.sassLoader
},
webpackMiddleware: {
noInfo: true
},
coverageReporter: {
reporters: config.coverage_reporters
}
}
if (config.globals.__COVERAGE__) {
karmaConfig.reporters.push('coverage')
karmaConfig.webpack.module.preLoaders = [{
test: /\.(js|jsx)$/,
include: new RegExp(config.dir_client),
loader: 'isparta',
exclude: /node_modules/
}]
}
// cannot use `export default` because of Karma.
module.exports = (cfg) => cfg.set(karmaConfig)

@ -0,0 +1,35 @@
import webpack from 'webpack'
import _debug from 'debug'
import config from '../config'
const debug = _debug('app:build:webpack-compiler')
const DEFAULT_STATS_FORMAT = config.compiler_stats
export default function webpackCompiler (webpackConfig, statsFormat = DEFAULT_STATS_FORMAT) {
return new Promise((resolve, reject) => {
const compiler = webpack(webpackConfig)
compiler.run((err, stats) => {
const jsonStats = stats.toJson()
debug('Webpack compile completed.')
debug(stats.toString(statsFormat))
if (err) {
debug('Webpack compiler encountered a fatal error.', err)
return reject(err)
} else if (jsonStats.errors.length > 0) {
debug('Webpack compiler encountered errors.')
debug(jsonStats.errors.join('\n'))
return reject(new Error('Webpack compiler encountered errors'))
} else if (jsonStats.warnings.length > 0) {
debug('Webpack compiler encountered warnings.')
debug(jsonStats.warnings.join('\n'))
} else {
debug('No errors or warnings encountered.')
}
resolve(jsonStats)
})
})
}

@ -0,0 +1,275 @@
import webpack from 'webpack'
import cssnano from 'cssnano'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
import config from '../config'
import _debug from 'debug'
const debug = _debug('app:webpack:config')
const paths = config.utils_paths
const {__DEV__, __PROD__, __TEST__} = config.globals
debug('Create configuration.')
const webpackConfig = {
name: 'client',
target: 'web',
devtool: config.compiler_devtool,
resolve: {
root: paths.client(),
extensions: ['', '.js', '.jsx', '.json']
},
module: {}
}
// ------------------------------------
// Entry Points
// ------------------------------------
const APP_ENTRY_PATHS = [
paths.client('main.js')
]
webpackConfig.entry = {
app: __DEV__
? APP_ENTRY_PATHS.concat(`webpack-hot-middleware/client?path=${config.compiler_public_path}__webpack_hmr`)
: APP_ENTRY_PATHS,
vendor: config.compiler_vendor
}
// ------------------------------------
// Bundle Output
// ------------------------------------
webpackConfig.output = {
filename: `[name].[${config.compiler_hash_type}].js`,
path: paths.dist(),
publicPath: config.compiler_public_path
}
// ------------------------------------
// Plugins
// ------------------------------------
webpackConfig.plugins = [
new webpack.DefinePlugin(config.globals),
new HtmlWebpackPlugin({
template: paths.client('index.html'),
hash: false,
favicon: paths.client('static/favicon.ico'),
filename: 'index.html',
inject: 'body',
minify: {
collapseWhitespace: true
}
})
]
if (__DEV__) {
debug('Enable plugins for live development (HMR, NoErrors).')
webpackConfig.plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
)
} else if (__PROD__) {
debug('Enable plugins for production (OccurenceOrder, Dedupe & UglifyJS).')
webpackConfig.plugins.push(
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
unused: true,
dead_code: true,
warnings: false
}
})
)
}
// Don't split bundles during testing, since we only want import one bundle
if (!__TEST__) {
webpackConfig.plugins.push(
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor']
})
)
}
// ------------------------------------
// Pre-Loaders
// ------------------------------------
/*
[ NOTE ]
We no longer use eslint-loader due to it severely impacting build
times for larger projects. `npm run lint` still exists to aid in
deploy processes (such as with CI), and it's recommended that you
use a linting plugin for your IDE in place of this loader.
If you do wish to continue using the loader, you can uncomment
the code below and run `npm i --save-dev eslint-loader`. This code
will be removed in a future release.
webpackConfig.module.preLoaders = [{
test: /\.(js|jsx)$/,
loader: 'eslint',
exclude: /node_modules/
}]
webpackConfig.eslint = {
configFile: paths.base('.eslintrc'),
emitWarning: __DEV__
}
*/
// ------------------------------------
// Loaders
// ------------------------------------
// JavaScript / JSON
webpackConfig.module.loaders = [{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel',
query: {
cacheDirectory: true,
plugins: ['transform-runtime'],
presets: ['es2015', 'react', 'stage-0']
}
},
{
test: /\.json$/,
loader: 'json'
}]
// ------------------------------------
// Style Loaders
// ------------------------------------
// We use cssnano with the postcss loader, so we tell
// css-loader not to duplicate minimization.
const BASE_CSS_LOADER = 'css?sourceMap&-minimize'
// Add any packge names here whose styles need to be treated as CSS modules.
// These paths will be combined into a single regex.
const PATHS_TO_TREAT_AS_CSS_MODULES = [
// 'react-toolbox', (example)
]
// If config has CSS modules enabled, treat this project's styles as CSS modules.
if (config.compiler_css_modules) {
PATHS_TO_TREAT_AS_CSS_MODULES.push(
paths.client().replace(/[\^\$\.\*\+\-\?\=\!\:\|\\\/\(\)\[\]\{\}\,]/g, '\\$&') // eslint-disable-line
)
}
const isUsingCSSModules = !!PATHS_TO_TREAT_AS_CSS_MODULES.length
const cssModulesRegex = new RegExp(`(${PATHS_TO_TREAT_AS_CSS_MODULES.join('|')})`)
// Loaders for styles that need to be treated as CSS modules.
if (isUsingCSSModules) {
const cssModulesLoader = [
BASE_CSS_LOADER,
'modules',
'importLoaders=1',
'localIdentName=[name]__[local]___[hash:base64:5]'
].join('&')
webpackConfig.module.loaders.push({
test: /\.scss$/,
include: cssModulesRegex,
loaders: [
'style',
cssModulesLoader,
'postcss',
'sass?sourceMap'
]
})
webpackConfig.module.loaders.push({
test: /\.css$/,
include: cssModulesRegex,
loaders: [
'style',
cssModulesLoader,
'postcss'
]
})
}
// Loaders for files that should not be treated as CSS modules.
const excludeCSSModules = isUsingCSSModules ? cssModulesRegex : false
webpackConfig.module.loaders.push({
test: /\.scss$/,
exclude: excludeCSSModules,
loaders: [
'style',
BASE_CSS_LOADER,
'postcss',
'sass?sourceMap'
]
})
webpackConfig.module.loaders.push({
test: /\.css$/,
exclude: excludeCSSModules,
loaders: [
'style',
BASE_CSS_LOADER,
'postcss'
]
})
// ------------------------------------
// Style Configuration
// ------------------------------------
webpackConfig.sassLoader = {
includePaths: paths.client('styles')
}
webpackConfig.postcss = [
cssnano({
autoprefixer: {
add: true,
remove: true,
browsers: ['last 2 versions']
},
discardComments: {
removeAll: true
},
discardUnused: false,
mergeIdents: false,
reduceIdents: false,
safe: true,
sourcemap: true
})
]
// File loaders
/* eslint-disable */
webpackConfig.module.loaders.push(
{ test: /\.woff(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff' },
{ test: /\.woff2(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff2' },
{ test: /\.otf(\?.*)?$/, loader: 'file?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=font/opentype' },
{ test: /\.ttf(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/octet-stream' },
{ test: /\.eot(\?.*)?$/, loader: 'file?prefix=fonts/&name=[path][name].[ext]' },
{ test: /\.svg(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=image/svg+xml' },
{ test: /\.(png|jpg)$/, loader: 'url?limit=8192' }
)
/* eslint-enable */
// ------------------------------------
// Finalize Configuration
// ------------------------------------
// when we don't know the public path (we know it only when HMR is enabled [in development]) we
// need to use the extractTextPlugin to fix this issue:
// http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809
if (!__DEV__) {
debug('Apply ExtractTextPlugin to CSS loaders.')
webpackConfig.module.loaders.filter((loader) =>
loader.loaders && loader.loaders.find((name) => /css/.test(name.split('?')[0]))
).forEach((loader) => {
const [first, ...rest] = loader.loaders
loader.loader = ExtractTextPlugin.extract(first, rest.join('!'))
Reflect.deleteProperty(loader, 'loaders')
})
webpackConfig.plugins.push(
new ExtractTextPlugin('[name].[contenthash].css', {
allChunks: true
})
)
}
export default webpackConfig

@ -0,0 +1,36 @@
// Here is where you can define configuration overrides based on the execution environment.
// Supply a key to the default export matching the NODE_ENV that you wish to target, and
// the base configuration will apply your overrides before exporting itself.
export default {
// ======================================================
// Overrides when NODE_ENV === 'development'
// ======================================================
// NOTE: In development, we use an explicit public path when the assets
// are served webpack by to fix this issue:
// http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809
development: (config) => ({
compiler_public_path: `http://${config.server_host}:${config.server_port}/`,
proxy: {
enabled: false,
options: {
host: 'http://localhost:8000',
match: /^\/api\/.*/
}
}
}),
// ======================================================
// Overrides when NODE_ENV === 'production'
// ======================================================
production: (config) => ({
compiler_public_path: '/',
compiler_fail_on_warning: false,
compiler_hash_type: 'chunkhash',
compiler_devtool: null,
compiler_stats: {
chunks: true,
chunkModules: true,
colors: true
}
})
}

@ -0,0 +1,133 @@
/* eslint key-spacing:0 spaced-comment:0 */
import path from 'path'
import _debug from 'debug'
import { argv } from 'yargs'
import ip from 'ip'
const localip = ip.address()
const debug = _debug('app:config')
debug('Creating default configuration.')
// ========================================================
// Default Configuration
// ========================================================
const config = {
env : process.env.NODE_ENV || 'development',
// ----------------------------------
// Project Structure
// ----------------------------------
path_base : path.resolve(__dirname, '..'),
dir_client : 'src',
dir_dist : 'dist',
dir_server : 'server',
dir_test : 'tests',
// ----------------------------------
// Server Configuration
// ----------------------------------
server_host : localip, // use string 'localhost' to prevent exposure on local network
server_port : process.env.PORT || 3000,
// ----------------------------------
// Compiler Configuration
// ----------------------------------
compiler_css_modules : true,
compiler_devtool : 'source-map',
compiler_hash_type : 'hash',
compiler_fail_on_warning : false,
compiler_quiet : false,
compiler_public_path : '/',
compiler_stats : {
chunks : false,
chunkModules : false,
colors : true
},
compiler_vendor : [
'babel-polyfill',
'history',
'react',
'react-redux',
'react-router',
'react-router-redux',
'redux'
],
// ----------------------------------
// Test Configuration
// ----------------------------------
coverage_reporters : [
{ type : 'text-summary' },
{ type : 'lcov', dir : 'coverage' }
]
}
/************************************************
-------------------------------------------------
All Internal Configuration Below
Edit at Your Own Risk
-------------------------------------------------
************************************************/
// ------------------------------------
// Environment
// ------------------------------------
// N.B.: globals added here must _also_ be added to .eslintrc
config.globals = {
'process.env' : {
'NODE_ENV' : JSON.stringify(config.env)
},
'NODE_ENV' : config.env,
'__DEV__' : config.env === 'development',
'__PROD__' : config.env === 'production',
'__TEST__' : config.env === 'test',
'__DEBUG__' : config.env === 'development' && !argv.no_debug,
'__COVERAGE__' : !argv.watch && config.env === 'test',
'__BASENAME__' : JSON.stringify(process.env.BASENAME || '')
}
// ------------------------------------
// Validate Vendor Dependencies
// ------------------------------------
const pkg = require('../package.json')
config.compiler_vendor = config.compiler_vendor
.filter((dep) => {
if (pkg.dependencies[dep]) return true
debug(
`Package "${dep}" was not found as an npm dependency in package.json; ` +
`it won't be included in the webpack vendor bundle.
Consider removing it from vendor_dependencies in ~/config/index.js`
)
})
// ------------------------------------
// Utilities
// ------------------------------------
const resolve = path.resolve
const base = (...args) =>
Reflect.apply(resolve, null, [config.path_base, ...args])
config.utils_paths = {
base : base,
client : base.bind(null, config.dir_client),
dist : base.bind(null, config.dir_dist)
}
// ========================================================
// Environment Configuration
// ========================================================
debug(`Looking for environment overrides for NODE_ENV "${config.env}".`)
const environments = require('./environments').default
const overrides = environments[config.env]
if (overrides) {
debug('Found overrides, applying to default configuration.')
Object.assign(config, overrides(config))
} else {
debug('No environment overrides found, defaults will be used.')
}
export default config

@ -0,0 +1,9 @@
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
try_files $uri /index.html;
}
}

@ -0,0 +1,4 @@
{
"verbose": false,
"ignore": ["dist", "coverage", "tests", "src"]
}

@ -0,0 +1,144 @@
{
"name": "ambar-frontend",
"version": "0.0.1",
"description": "Ambar Frontend",
"main": "index.js",
"engines": {
"node": ">=4.2.0",
"npm": "^3.0.0"
},
"scripts": {
"clean": "rimraf dist",
"compile": "better-npm-run compile",
"lint": "eslint src tests server",
"lint:fix": "npm run lint -- --fix",
"start": "better-npm-run start",
"dev": "better-npm-run dev",
"dev:no-debug": "npm run dev -- --no_debug",
"deploy": "better-npm-run deploy",
"deploy:dev": "better-npm-run deploy:dev",
"deploy:prod": "better-npm-run deploy:prod"
},
"betterScripts": {
"compile": {
"command": "babel-node bin/compile",
"env": {
"DEBUG": "app:*"
}
},
"dev": {
"command": "nodemon --exec babel-node bin/server",
"env": {
"NODE_ENV": "development",
"DEBUG": "app:*"
}
},
"deploy": {
"command": "npm run clean && npm run compile",
"env": {
"DEBUG": "app:*"
}
},
"deploy:dev": {
"command": "npm run deploy",
"env": {
"NODE_ENV": "development",
"DEBUG": "app:*"
}
},
"deploy:prod": {
"command": "npm run deploy",
"env": {
"NODE_ENV": "production",
"DEBUG": "app:*"
}
},
"start": {
"command": "babel-node bin/server",
"env": {
"DEBUG": "app:*"
}
}
},
"author": "RD17 <hello@ambar.cloud> (https://ambar.cloud)",
"license": "Fair Source",
"dependencies": {
"@blueprintjs/core": "^1.32.0",
"babel-cli": "^6.5.1",
"babel-core": "^6.3.17",
"babel-loader": "^6.2.0",
"babel-plugin-transform-runtime": "^6.3.13",
"babel-polyfill": "^6.9.0",
"babel-preset-es2015": "^6.14.0",
"babel-preset-es2015-loose": "^7.0.0",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-0": "^6.3.13",
"babel-register": "^6.3.13",
"babel-runtime": "^6.3.19",
"better-npm-run": "0.0.10",
"brace": "^0.9.0",
"css-loader": "^0.23.0",
"cssnano": "^3.3.2",
"debug": "^2.2.0",
"deep-equal": "^1.0.1",
"extract-text-webpack-plugin": "^1.0.0",
"file-loader": "^0.9.0",
"fs-extra": "^0.30.0",
"history": "^2.0.0",
"html-webpack-plugin": "^2.7.1",
"immutability-helper": "^2.5.0",
"imports-loader": "^0.6.5",
"ip": "^1.1.2",
"json-loader": "^0.5.4",
"koa": "^2.0.0-alpha.3",
"koa-connect-history-api-fallback": "^0.3.0",
"koa-convert": "^1.2.0",
"koa-proxy": "^0.6.0",
"koa-static": "^3.0.0",
"material-ui": "^0.18.1",
"moment": "^2.14.1",
"node-sass": "^3.7.0",
"postcss-loader": "^0.9.0",
"react": "^15.0.0",
"react-ace": "^4.1.0",
"react-addons-css-transition-group": "^15.6.2",
"react-autosuggest": "^9.0.1",
"react-dom": "^15.0.0",
"react-dropzone": "^3.9.2",
"react-input-autosize": "^1.1.4",
"react-input-mask": "^0.7.2",
"react-redux": "^4.0.0",
"react-responsive": "^1.2.6",
"react-router": "^2.2.0",
"react-router-redux": "^4.0.0",
"react-spinkit": "^1.1.11",
"react-tap-event-plugin": "^2.0.1",
"recharts": "1.0.0-beta.1",
"redux": "^3.0.0",
"redux-thunk": "^2.0.0",
"rimraf": "^2.5.1",
"sass-loader": "^4.0.0",
"style-loader": "^0.13.0",
"url-loader": "^0.5.6",
"validator": "^5.5.0",
"webpack": "^1.12.14",
"whatwg-fetch": "^2.0.1",
"yargs": "^4.0.0"
},
"devDependencies": {
"babel-eslint": "^6.0.0-beta.6",
"eslint": "^3.0.1",
"eslint-config-standard": "^5.1.0",
"eslint-config-standard-react": "^3.0.0",
"eslint-plugin-babel": "^3.2.0",
"eslint-plugin-promise": "^2.0.0",
"eslint-plugin-react": "^6.0.0",
"eslint-plugin-standard": "^2.0.0",
"isparta-loader": "^2.0.0",
"nodemon": "^1.8.1",
"react-addons-test-utils": "^15.0.0",
"redbox-react": "^1.2.10",
"webpack-dev-middleware": "^1.6.1",
"webpack-hot-middleware": "^2.6.0"
}
}

@ -0,0 +1,14 @@
// Based on: https://github.com/dayAlone/koa-webpack-hot-middleware/blob/master/index.js
export default function applyExpressMiddleware (fn, req, res) {
const originalEnd = res.end
return new Promise((resolve) => {
res.end = function () {
originalEnd.apply(this, arguments)
resolve(false)
}
fn(req, res, function () {
resolve(true)
})
})
}

@ -0,0 +1,61 @@
import Koa from 'koa'
import convert from 'koa-convert'
import webpack from 'webpack'
import webpackConfig from '../build/webpack.config'
import historyApiFallback from 'koa-connect-history-api-fallback'
import serve from 'koa-static'
import proxy from 'koa-proxy'
import _debug from 'debug'
import config from '../config'
import webpackDevMiddleware from './middleware/webpack-dev'
import webpackHMRMiddleware from './middleware/webpack-hmr'
const debug = _debug('app:server')
const paths = config.utils_paths
const app = new Koa()
// Enable koa-proxy if it has been enabled in the config.
if (config.proxy && config.proxy.enabled) {
app.use(convert(proxy(config.proxy.options)))
}
// This rewrites all routes requests to the root /index.html file
// (ignoring file requests). If you want to implement isomorphic
// rendering, you'll want to remove this middleware.
app.use(convert(historyApiFallback({
verbose: false
})))
// ------------------------------------
// Apply Webpack HMR Middleware
// ------------------------------------
if (config.env === 'development') {
const compiler = webpack(webpackConfig)
// Enable webpack-dev and webpack-hot middleware
const { publicPath } = webpackConfig.output
app.use(webpackDevMiddleware(compiler, publicPath))
app.use(webpackHMRMiddleware(compiler))
// Serve static assets from ~/src/static since Webpack is unaware of
// these files. This middleware doesn't need to be enabled outside
// of development since this directory will be copied into ~/dist
// when the application is compiled.
app.use(serve(paths.client('static')))
} else {
debug(
'Server is being run outside of live development mode, meaning it will ' +
'only serve the compiled application bundle in ~/dist. Generally you ' +
'do not need an application server for this and can instead use a web ' +
'server such as nginx to serve your static files. See the "deployment" ' +
'section in the README for more information on deployment strategies.'
)
// Serving ~/dist by default. Ideally these files should be served by
// the web server and not the app server, but this helps to demo the
// server in production.
app.use(serve(paths.dist()))
}
export default app

@ -0,0 +1,34 @@
import WebpackDevMiddleware from 'webpack-dev-middleware'
import applyExpressMiddleware from '../lib/apply-express-middleware'
import _debug from 'debug'
import config from '../../config'
const paths = config.utils_paths
const debug = _debug('app:server:webpack-dev')
export default function (compiler, publicPath) {
debug('Enable webpack dev middleware.')
const middleware = WebpackDevMiddleware(compiler, {
publicPath,
contentBase: paths.client(),
hot: true,
quiet: config.compiler_quiet,
noInfo: config.compiler_quiet,
lazy: false,
stats: config.compiler_stats
})
return async function koaWebpackDevMiddleware (ctx, next) {
let hasNext = await applyExpressMiddleware(middleware, ctx.req, {
end: (content) => (ctx.body = content),
setHeader: function () {
ctx.set.apply(ctx, arguments)
}
})
if (hasNext) {
await next()
}
}
}

@ -0,0 +1,18 @@
import WebpackHotMiddleware from 'webpack-hot-middleware'
import applyExpressMiddleware from '../lib/apply-express-middleware'
import _debug from 'debug'
const debug = _debug('app:server:webpack-hmr')
export default function (compiler, opts) {
debug('Enable Webpack Hot Module Replacement (HMR).')
const middleware = WebpackHotMiddleware(compiler, opts)
return async function koaWebpackHMR (ctx, next) {
let hasNext = await applyExpressMiddleware(middleware, ctx.req, ctx.res)
if (hasNext && next) {
await next()
}
}
}

@ -0,0 +1,17 @@
import React from 'react'
import classes from './AmbarResponsiveLogo.scss'
import MediaQuery from 'react-responsive'
export const AmbarResponsiveLogo = ({mode, version}) => (
<div className={classes.ambarResponsiveLogo} title={`Ambar ${mode.toUpperCase()} ${version}`} >
<img alt='Logo'
src={'owl.svg'} />
</div>)
AmbarResponsiveLogo.propTypes = {
mode: React.PropTypes.string.isRequired,
version: React.PropTypes.string.isRequired
}
export default AmbarResponsiveLogo

@ -0,0 +1,8 @@
.ambarResponsiveLogo {
height: 48px;
margin-bottom: 8px;
img {
height: 100%;
}
fill: black;
}

@ -0,0 +1,3 @@
import AmbarResponsiveLogo from './AmbarResponsiveLogo'
export default AmbarResponsiveLogo

@ -0,0 +1,20 @@
import React from 'react'
import Paper from 'material-ui/Paper'
import { Card, CardText } from 'material-ui/Card'
import FullScreenPattern from '../FullScreenPattern'
import classes from './AuthPageTemplate.scss'
export const AuthPageTemplate = ({ children }) => (
<FullScreenPattern>
<Paper zDepth={5} style={{ backgroundColor: 'white' }}>
<Card containerStyle={{ padding: 0, minWidth: '300px' }}>
<CardText style={{ display: 'flex', justifyContent: 'center', paddingBottom: '0' }}>
<a href='https://ambar.cloud' target='_blank'><img height={100} src='owl-green.svg' alt='logo' /></a>
</CardText>
{children}
</Card>
</Paper>
</FullScreenPattern>
)
export default AuthPageTemplate

@ -0,0 +1,2 @@
import AuthPageTemplate from'./AuthPageTemplate'
export default AuthPageTemplate

@ -0,0 +1,25 @@
import React, { Component } from 'react'
import HighlightedSpan from '../HighlightedSpan'
import classes from './AuthorLabel.scss'
const AuthorLabel = ({ content, performSearchByAuthor, ...otherProps }) => {
const authorHighlighted = content.highlight && content.highlight.author ? true : false
return (
<HighlightedSpan
onTouchTap={() => performSearchByAuthor(`${content.author}*`)}
isClickable={true}
isHighlighted={authorHighlighted}
{...otherProps}>
{content.author}
</HighlightedSpan>
)
}
AuthorLabel.propTypes = {
content: React.PropTypes.object.isRequired,
performSearchByAuthor: React.PropTypes.func.isRequired
}
export default AuthorLabel

@ -0,0 +1,3 @@
import AuthorLabel from './AuthorLabel'
export default AuthorLabel

@ -0,0 +1,49 @@
import React, { Component } from 'react'
import classes from './ClickableFilePath.scss'
const ClickableFilePath = (props) => {
const { meta, performSearchByPathToFile } = props
const fullPath = meta.full_name
const fullPathParts = fullPath.split('/')
.filter(part => part != '')
const fullPathPartsExtended = fullPathParts
.map((part, idx) => {
const isLast = idx === fullPathParts.length - 1
const trailingSymbol = isLast ? '' : '/'
const trailingAsterisk = '*'
return {
part: `${part}${trailingSymbol}`,
pathToPart: `//${fullPathParts.filter((part, innerIdx) => innerIdx <= idx).join('/')}${trailingAsterisk}`
}
})
const isHighlighted = meta.highlight && meta.highlight.full_name
return (
<div>
<div className={isHighlighted ? classes.metaFullNameLineContainerHighlighted : classes.metaFullNameLineContainer}>
<span>//</span>
{fullPathPartsExtended.map((part, idx) => <span
className={classes.metaFullNamePart}
key={idx}
onTouchTap={() => performSearchByPathToFile(part.pathToPart)}>
{part.part}
</span>)}
</div>
</div>
)
}
ClickableFilePath.propTypes = {
meta: React.PropTypes.object.isRequired,
performSearchByPathToFile: React.PropTypes.func.isRequired
}
export default ClickableFilePath

@ -0,0 +1,30 @@
div.metaFullNameLineContainer {
color: #9E9E9E;
font-size: 12px;
word-wrap: break-word;
word-break: break-all;
}
div.metaFullNameLineContainerHighlighted {
border-radius: 4px;
background-color: #fff176;
font-size: 12px;
display: inline;
color: rgb(158, 158, 158);
}
span.metaFullNamePart {
cursor: pointer; cursor: hand;
}
span.metaFullNamePart:hover {
cursor: pointer; cursor: hand;
border-radius: 4px;
background-color: #B2EBF2;
}
span.metaFullNamePart:active {
cursor: pointer; cursor: hand;
border-radius: 4px;
background-color: #80DEEA;
}

@ -0,0 +1,3 @@
import ClickableFilePath from './ClickableFilePath'
export default ClickableFilePath

@ -0,0 +1,38 @@
import React, { Component } from 'react'
import classes from './CodeEditor.scss'
import brace from 'brace'
import AceEditor from 'react-ace'
import 'brace/mode/javascript'
import 'brace/theme/monokai'
const CodeEditor = (props) => {
const { uId, value, onChange, readOnly, systemMessage } = props
return (
<div>
<AceEditor
mode="javascript"
theme="monokai"
onChange={onChange}
name={uId}
readOnly={readOnly}
value={value}
width='100%'
heigth= '100%'
fontSize={14}
wrapEnabled={true}
showPrintMargin={false}
/>
<p className={classes.codeEditorSystemMessage}>{systemMessage}</p>
</div>
)
}
CodeEditor.propTypes = {
value: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
readOnly: React.PropTypes.bool.isRequired,
systemMessage: React.PropTypes.string
}
export default CodeEditor

@ -0,0 +1,16 @@
p.codeEditorSystemMessage {
padding: 3px;
text-align: center;
background-color: #272822;
color: #FF3333;
font-family: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
font-size: 14px;
font-stretch: normal;
font-style: normal;
font-variant-caps: normal;
font-variant-ligatures: normal;
font-variant-numeric: normal;
font-weight: normal;
height: 25px;
margin: 0;
}

@ -0,0 +1,3 @@
import CodeEditor from './CodeEditor'
export default CodeEditor

@ -0,0 +1,50 @@
import React, { Component } from 'react'
import Avatar from 'material-ui/Avatar'
import { files } from 'utils/'
import classes from './FileAvatar.scss'
const getHashCode = (str) => {
let hash = 0;
if (str.length == 0) {
return hash
}
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i)
hash = ((hash << 5) - hash) + char
hash = hash & hash // Convert to 32bit integer
}
return hash
}
const FileAvatar = ({ meta, searchFunction }) => {
const colors = [
'#EF5350', '#E53935', '#D81B60', '#EC407A', '#AB47BC', '#7E57C2', '#5C6BC0', '#2196F3', '#43A047', '#EF6C00', '#A1887F', '#78909C', '#FF4081', '#3949AB']
let extension = files.getExtension(meta)
const avatarStyle = {
fontSize: '12px',
textTransform: 'uppercase',
cursor: 'pointer'
}
return (
<Avatar
className={classes.resultAvatar}
onTouchTap={() => searchFunction(`*.${extension}`)}
size={38}
style={avatarStyle}
backgroundColor={colors[getHashCode(extension) % colors.length]}>{extension}</Avatar>
)
}
FileAvatar.propTypes = {
meta: React.PropTypes.object.isRequired,
searchFunction: React.PropTypes.func.isRequired
}
export default FileAvatar

@ -0,0 +1,19 @@
span.clickable {
cursor: pointer; cursor: hand;
}
span.clickable:hover {
cursor: pointer; cursor: hand;
border-radius: 4px;
background-color: #B2EBF2;
}
span.clickable:active {
cursor: pointer; cursor: hand;
border-radius: 4px;
background-color: #80DEEA;
}
span.highlighted {
background-color: #fff176;
}

@ -0,0 +1,3 @@
import FileAvatar from './FileAvatar'
export default FileAvatar

@ -0,0 +1,23 @@
import React, { Component } from 'react'
import { files } from 'utils/'
import HighlightedSpan from '../HighlightedSpan'
import classes from './FileSizeLabel.scss'
const FileSizeLabel = ({ content, searchQuery, ...otherProps }) => {
const SIZE_QUERY = /((^|\s)size(>|<)[=]{0,1})([0-9]*)([k|m]{0,1})/im
const sizeHighlighted = content.size ? SIZE_QUERY.test(searchQuery) : false
const size = content.size
return (
<HighlightedSpan isHighlighted={sizeHighlighted} {...otherProps}>{files.formatFileSize(size)}</HighlightedSpan>
)
}
FileSizeLabel.propTypes = {
content: React.PropTypes.object.isRequired,
searchQuery: React.PropTypes.string.isRequired
}
export default FileSizeLabel

@ -0,0 +1,3 @@
import FileSizeLabel from './FileSizeLabel'
export default FileSizeLabel

@ -0,0 +1,11 @@
import React from 'react'
import CircularProgress from 'material-ui/CircularProgress'
import FullScreenPattern from '../FullScreenPattern'
import classes from './FullScreenLoader.scss'
export const FullScreenLoader = (props) =>
<FullScreenPattern>
<CircularProgress color='white' size={120} thickness={5} />
</FullScreenPattern>
export default FullScreenLoader

@ -0,0 +1,2 @@
import FullScreenLoader from'./FullScreenLoader'
export default FullScreenLoader

@ -0,0 +1,9 @@
import React from 'react'
import classes from './FullScreenPattern.scss'
export const FullScreenPattern = ({children}) =>
<div className={classes.solidBackground}>
{children}
</div>
export default FullScreenPattern

File diff suppressed because one or more lines are too long

@ -0,0 +1,2 @@
import FullScreenPattern from'./FullScreenPattern'
export default FullScreenPattern

@ -0,0 +1,15 @@
import React, { Component } from 'react'
import classes from './HighlightedSpan.scss'
const HighlightedSpan = ({children, isHighlighted, isClickable = false, ...otherProps}) => {
const classNames = `${isHighlighted ? classes.highlighted : '' } ${isClickable ? classes.clickable : ''}`
return <span className={classNames} {...otherProps}>{children}</span>
}
HighlightedSpan.propTypes = {
children: React.PropTypes.any,
isHighlighted: React.PropTypes.bool.isRequired,
isClickable: React.PropTypes.bool
}
export default HighlightedSpan

@ -0,0 +1,20 @@
span.clickable {
cursor: pointer; cursor: hand;
}
span.clickable:hover {
cursor: pointer; cursor: hand;
border-radius: 4px;
background-color: #B2EBF2;
}
span.clickable:active {
cursor: pointer; cursor: hand;
border-radius: 4px;
background-color: #80DEEA;
}
span.highlighted {
background-color: #fff176;
color: rgb(158, 158, 158);
}

@ -0,0 +1,3 @@
import HighlightedSpan from './HighlightedSpan'
export default HighlightedSpan

@ -0,0 +1,74 @@
import React from 'react'
import ReactDOM from 'react-dom'
import deepEqual from 'deep-equal'
import { bindMethods } from './bindMethods.js'
export default class InfiniteScroll extends React.Component {
constructor(props) {
super(props)
bindMethods(this, ['scrollListener', 'attachScrollListener', 'detachScrollListener'])
}
componentDidMount() {
this.attachScrollListener()
}
shouldComponentUpdate(nextProps) {
return !deepEqual(this.props.children, nextProps.children)
}
componentWillUnmount() {
this.detachScrollListener()
}
render() {
return null
}
safeCallOnScrollDown(isFirstPage) {
if (this.props.onScrollDown) {
this.props.onScrollDown(isFirstPage)
}
}
scrollListener() {
const { hasMore, currentPage, anchorEl, threshold, loadMore } = this.props
let el = anchorEl
this.safeCallOnScrollDown(el.scrollTop < el.offsetHeight * 0.75)
if (el.scrollHeight > 0 && (el.scrollHeight - el.scrollTop - el.offsetHeight < threshold)) {
{ hasMore && loadMore(currentPage + 1) }
}
}
attachScrollListener() {
const el = this.props.anchorEl
el.addEventListener('scroll', this.scrollListener)
el.addEventListener('resize', this.scrollListener)
this.scrollListener()
}
detachScrollListener() {
const el = this.props.anchorEl
el.removeEventListener('scroll', this.scrollListener)
el.removeEventListener('resize', this.scrollListener)
}
}
InfiniteScroll.defaultProps = {
currentPage: 0,
hasMore: true,
threshold: 250
}
InfiniteScroll.PropTypes = {
currentPage: React.PropTypes.number.isRequired,
hasMore: React.PropTypes.bool,
loadMore: React.PropTypes.func.isRequired,
threshold: React.PropTypes.number,
onScrollDown: React.PropTypes.func,
anchorEl: React.PropTypes.object.isRequired
}

@ -0,0 +1,8 @@
export function bindMethods(component, names) {
if(typeof names === 'string') {
names = [names];
}
names.forEach((name) =>
component[name] = component[name].bind(component)
);
}

@ -0,0 +1,3 @@
import InfiniteScroll from './InfiniteScroll'
export default InfiniteScroll

@ -0,0 +1,22 @@
import React from 'react'
import CircularProgress from 'material-ui/CircularProgress'
import classes from './LoadingIndicator.scss'
const LoadingIndicator = (props) => {
const { small, medium, large, freezed = false, blurred = false, color = '#00bcd4' } = props
const indicatorSize = (small ? 23 : (medium ? 40 : (large ? 50 : 40)))
const mode = freezed ? 'determinate' : 'indeterminate'
const className = blurred ? classes.circularProgressBlurred : classes.circularProgress
return <div className={className}>
<CircularProgress size={indicatorSize} mode={mode} value={100} color={color} />
</div>
}
LoadingIndicator.propTypes = {
small: React.PropTypes.bool,
medium: React.PropTypes.bool,
large: React.PropTypes.bool,
freezed: React.PropTypes.bool
}
export default LoadingIndicator

@ -0,0 +1,17 @@
div.circularProgress {
display: flex;
justify-content: center;
}
div.circularProgressBlurred {
display: flex;
justify-content: center;
-webkit-filter: blur(3px);
filter: blur(3px);
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}

@ -0,0 +1,3 @@
import LoadingIndicator from './LoadingIndicator'
export default LoadingIndicator

@ -0,0 +1,26 @@
import React from 'react'
import Snackbar from 'material-ui/Snackbar'
import classes from './NotificationIndicator.scss'
export const NotificationIndicator = ({close, isOpen, message, reason}) => {
const ERROR_COLOR = '#FF3333'
const INFO_COLOR = '#8BC34A'
const isError = reason === 'error'
const textColor = isError ? ERROR_COLOR : INFO_COLOR
return (<Snackbar
open={isOpen}
message={message}
onRequestClose={ () => close() }
contentStyle={{ color: textColor, fontWeight: '600', display: 'flex', justifyContent: 'center', userSelect: 'none', cursor: 'default' }}
/>)
}
NotificationIndicator.propTypes = {
close: React.PropTypes.func.isRequired,
message: React.PropTypes.string.isRequired,
isOpen: React.PropTypes.bool.isRequired,
reason: React.PropTypes.string
}
export default NotificationIndicator

@ -0,0 +1,2 @@
import NotificationIndicator from'./NotificationIndicator'
export default NotificationIndicator

@ -0,0 +1,241 @@
import React, { Component } from 'react'
import AutosizeInput from 'react-input-autosize'
import Autosuggest from 'react-autosuggest'
import FontIcon from 'material-ui/FontIcon'
import { Tag } from './components'
import classes from './TagsInput.scss'
class TagsInput extends Component {
constructor() {
super()
this.state = {
inputValue: '',
suggestions: []
}
}
addTag(tagName) {
if (!tagName) {
return
}
const tagType = 'manual'
if (!this.props.tags.find(t => (t.name === tagName))) {
this.props.onAddTag(tagType, tagName)
}
this.setState({ ...this.state, inputValue: '' })
}
removeTag(tagType, tagName) {
const currentTags = this.props.tags.map(t => t.name)
if (!currentTags || currentTags.length === 0) {
return
}
this.props.onRemoveTag(tagType, tagName)
}
removeLastTag() {
const currentTags = this.props.tags
if (!currentTags || currentTags.length === 0) {
return
}
const lastTag = currentTags[currentTags.length - 1]
if (lastTag.isFetching) {
return
}
this.props.onRemoveTag(lastTag.type, lastTag.name)
}
onChange(value) {
this.setState({ ...this.state, inputValue: value.trim() })
value = value.replace(/[\s\,\;]/gim, ',')
const newTags = value.split(',').map(t => t.trim().toLowerCase())
if (newTags.length > 1 && newTags[0] != '') {
this.addTag(newTags[0])
}
}
onKeyPress(event) {
if (event.charCode === 13) { // onEnter
this.addTag(this.state.inputValue)
return
}
}
onKeyDown(event) {
if (event.keyCode === 8) { // onBackspace
if (!this.state.inputValue) {
this.removeLastTag()
return
}
}
}
focusOnInput() {
if (!this.refs.suggestInput) {
return
}
this.refs.suggestInput.input.focus()
}
autosizeInput(inputProps) {
return (
<AutosizeInput
minWidth={40}
inputStyle={{
border: 'none',
backgroundImage: 'none',
backgroundColor: 'transparent',
WebkitBoxShadow: 'none',
MozBoxShadow: 'none',
boxShadow: 'none',
outline: 'none',
margin: '4px',
padding: '0'
}}
type='text'
placeholder=''
{...inputProps}
/>
)
}
getSuggestions(value) {
const inputValue = value.trim().toLowerCase()
const inputLength = inputValue.length
const suggestions = this.props.suggestions
return inputLength === 0 ? suggestions.slice(0, 10) : suggestions.filter(suggestion =>
suggestion.toLowerCase().slice(0, inputLength) === inputValue
).slice(0, 10)
}
onSuggestionsFetchRequested({ value }) {
this.setState({
suggestions: this.getSuggestions(value)
})
}
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
})
}
render() {
const props = this.props
const theme = {
container: {
display: 'inline-block',
position: 'relative',
margin: '4px',
overflow: 'visible'
},
inputOpen: {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0
},
suggestionsContainer: {
display: 'none'
},
suggestionsContainerOpen: {
display: 'block',
position: 'absolute',
top: 25,
width: 120,
backgroundColor: '#fff',
boxShadow: '0 0 7px rgba(0, 0, 0, 0.4)',
borderRadius: '2px',
fontSize: 12,
zIndex: 2
},
suggestionsList: {
margin: 0,
padding: 0,
listStyleType: 'none',
overflow: 'hidden',
textOverflow: 'ellipsis'
},
suggestion: {
cursor: 'pointer',
padding: '2px 4px'
},
suggestionHighlighted: {
backgroundColor: 'rgba(0, 188, 212, 0.15)'
}
}
const { performSearchByTag, showRemoveIcon = true, showAddField = true, style = {} } = this.props
return (
<div
style={{ padding: '5px', cursor: 'text', display: 'flex', flexWrap: 'wrap', ...style }}
className='TAGS_CONTAINER'
onClick={(e) => {
if (e.target.className == 'TAGS_CONTAINER') {
this.focusOnInput()
}
}}>
{this.props.tags.map((tag, idx) =>
<Tag
key={idx}
onRemove={(tagType, tagName) => this.removeTag(tagType, tagName)}
onClick={(tagName) => performSearchByTag(tagName)}
tagName={tag.name}
tagType={tag.type}
isHighlighted={!!tag.highlight && !!tag.highlight.name}
isFetching={tag.isFetching}
showRemoveIcon={showRemoveIcon}
/>)
}
{showAddField && <Autosuggest
ref='suggestInput'
suggestions={this.state.suggestions}
getSuggestionValue={s => s}
renderSuggestion={(suggestion) => <span>{suggestion}</span>}
onSuggestionSelected={(e, { suggestion, suggegstionValue, suggestionIndex, sectionIndex, method }) => {
this.addTag(suggestion)
}}
shouldRenderSuggestions={(value) => true}
onSuggestionsFetchRequested={e => this.onSuggestionsFetchRequested(e)}
onSuggestionsClearRequested={e => this.onSuggestionsClearRequested(e)}
inputProps={{
value: this.state.inputValue,
onChange: (e, { newValue, method }) => this.onChange(newValue),
onKeyPress: (event) => this.onKeyPress(event),
onKeyDown: (event) => this.onKeyDown(event)
}}
theme={theme}
renderInputComponent={(inputProps) => this.autosizeInput(inputProps)}
/>}
</div>
)
}
}
TagsInput.propTypes = {
tags: React.PropTypes.array.isRequired,
suggestion: React.PropTypes.array,
onAddTag: React.PropTypes.func,
onRemoveTag: React.PropTypes.func,
showRemoveIcon: React.PropTypes.bool,
showAddField: React.PropTypes.bool,
performSearchByTag: React.PropTypes.func.isRequired,
style: React.PropTypes.object
}
export default TagsInput

@ -0,0 +1,8 @@
.tagInput {
border:none;
background-image:none;
background-color:transparent;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}

@ -0,0 +1,51 @@
import React, { Component } from 'react'
import ClearIcon from 'material-ui/svg-icons/content/clear'
import { LoadingIndicator } from 'components/BasicComponents'
import classes from './Tag.scss'
const Tag = ({ tagName, tagType, onRemove, onClick, isHighlighted, isFetching, showRemoveIcon = true }) => {
const onRemoveCallback = onRemove
? onRemove
: () => { }
const onClickCallback = onClick
? onClick
: () => { }
return (
<div
style={{ display: 'flex', alignItems: 'center' }}
className={`${classes.tag} ${isHighlighted ? classes.highlight : tagType === 'source' ? classes.source : tagType === 'auto' ? classes.auto : ''} ${isFetching ? classes.loading : ''}`}
onTouchTap={(e) => {
e.stopPropagation()
onClickCallback(tagName)
}}>
<span>{tagName}</span>
{!isFetching && showRemoveIcon && <ClearIcon
className={classes.removeTagButton}
onTouchTap={(e) => {
e.stopPropagation()
onRemoveCallback(tagType, tagName)
}}
hoverColor='#FF5722'
style={{ color: 'inherit', width: '1em', height: '1em' }}
/>}
</div>
)
}
Tag.propTypes = {
tagName: React.PropTypes.string.isRequired,
tagType: React.PropTypes.string.isRequired,
onRemove: React.PropTypes.func,
onClick: React.PropTypes.func,
isHighlighted: React.PropTypes.bool,
showRemoveIcon: React.PropTypes.bool
}
export default Tag

@ -0,0 +1,41 @@
.tag {
margin: 4px;
cursor: pointer;
word-wrap: break-word;
word-break: break-all;
font-size: 12px;
font-family: Roboto, sans-serif;
color: rgb(0, 188, 212);
padding: 2px;
background-color: rgba(0, 188, 212, 0.08);
transition: background-color 0.5s ease;
border-radius: 3px;
}
.loading {
background-color: rgba(0, 188, 212, 0.2);
}
.highlight {
background-color: #fff176;
color: rgb(158, 158, 158);
}
.source {
background-color: #DFFBD8;
color: rgb(158, 158, 158);
}
.auto {
background-color: #F7E2E2;
color: rgb(158, 158, 158);
}
.tag > .removeTagButton {
display: block;
margin: 2px 1px 0 1px;
}
.tag:hover {
background-color: #B2EBF2;
}

@ -0,0 +1,3 @@
import TagsInput from './TagsInput'
export default TagsInput

@ -0,0 +1,24 @@
import React, { Component } from 'react'
import HighlightedSpan from '../HighlightedSpan'
import classes from './UpdatedDateTimeLabel.scss'
const UpdatedDateTimeLabel = ({ meta, searchQuery, formatFunc, ...otherProps }) => {
const WHEN_QUERY = /((^|\s)when:)((today)|(yesterday)|(thisweek)|(thismonth)|(thisyear))/im
const updatedDatetimeHighlighted = searchQuery ? WHEN_QUERY.test(searchQuery) : false
const updatedDatetime = meta.updated_datetime
const displayedUpdatedDateTime = updatedDatetime && formatFunc ? formatFunc(updatedDatetime) : updatedDatetime
return (
<HighlightedSpan isHighlighted={updatedDatetimeHighlighted} {...otherProps}>{displayedUpdatedDateTime}</HighlightedSpan>
)
}
UpdatedDateTimeLabel.propTypes = {
meta: React.PropTypes.object.isRequired,
searchQuery: React.PropTypes.string.isRequired,
formatFunc: React.PropTypes.func
}
export default UpdatedDateTimeLabel

@ -0,0 +1,3 @@
import UpdatedDateTimeLabel from './UpdatedDateTimeLabel'
export default UpdatedDateTimeLabel

@ -0,0 +1,33 @@
import NotificationIndicator from './NotificationIndicator'
import InfiniteScroll from './InfiniteScroll'
import CodeEditor from './CodeEditor'
import AmbarResponsiveLogo from './AmbarResponsiveLogo'
import FullScreenLoader from './FullScreenLoader'
import FullScreenPattern from './FullScreenPattern'
import AuthPageTemplate from './AuthPageTemplate'
import LoadingIndicator from './LoadingIndicator'
import FileAvatar from './FileAvatar'
import HighlightedSpan from './HighlightedSpan'
import TagsInput from './TagsInput'
import ClickableFilePath from './ClickableFilePath'
import AuthorLabel from './AuthorLabel'
import FileSizeLabel from './FileSizeLabel'
import UpdatedDateTimeLabel from './UpdatedDateTimeLabel'
export {
NotificationIndicator,
InfiniteScroll,
CodeEditor,
AmbarResponsiveLogo,
FullScreenLoader,
FullScreenPattern,
AuthPageTemplate,
LoadingIndicator,
FileAvatar,
HighlightedSpan,
TagsInput,
ClickableFilePath,
AuthorLabel,
FileSizeLabel,
UpdatedDateTimeLabel
}

@ -0,0 +1,169 @@
import React, { Component } from 'react'
import { titles } from 'utils/'
import { SearchResults, ImagePreview, SearchInput, SideMenu } from './components'
import { InfiniteScroll } from 'components/BasicComponents'
import UploadContainer from 'routes/SearchPage/containers/UploadModalContainer'
import ImagePreviewContainer from 'routes/SearchPage/containers/ImagePreviewContainer'
import { cyan100, cyan300, cyan400 } from 'material-ui/styles/colors'
import MoreHoriz from 'material-ui/svg-icons/navigation/more-horiz'
import MediaQuery from 'react-responsive'
import ArrowUpward from 'material-ui/svg-icons/navigation/arrow-upward'
import FloatingActionButton from 'material-ui/FloatingActionButton'
import FlatButton from 'material-ui/FlatButton'
import Dialog from 'material-ui/Dialog'
import classes from './Search.scss'
const Desktop = ({ children }) => <MediaQuery query='(min-width: 1024px)' children={children} />
class Search extends Component {
timeoutId = null
componentDidMount() {
const { setPageTitle, setAppHeader, loadTags, search, searchQuery, setQueryFromGetParam, setQuery, localization } = this.props
setPageTitle(localization.searchPage.pageTitle)
setAppHeader({
left: () => <Desktop>{localization.searchPage.pageTitle}</Desktop>,
center: (state) => {
return (
<SearchInput
setQuery={setQuery}
query={state['searchPage'].searchQuery}
search={search}
localization={localization}
/>)
}
})
loadTags()
setQueryFromGetParam()
}
componentWillUnmount() {
const { cleanUpSearchResult } = this.props
cleanUpSearchResult()
}
render() {
const {
search,
searchView,
fetching,
searchQuery,
hasMore,
scrolledDown,
setScrolledDown,
setPageTitle,
currentPage,
mode,
toggleUploadModal,
setAppHeader,
performSearchByQuery,
performSearchBySize,
performSearchByWhen,
performSearchByShow,
performSearchByTag,
setSearchResultView,
allTags,
localization
} = this.props
return (
<div style={{ height: '100%' }}>
<Desktop>
<div style={{
position: 'fixed',
width: '200px',
height: '100%',
left: 0,
right: 0,
boxShadow: '0 0 15px rgba(0, 0, 0, 0.4)',
padding: '0'
}}>
<SideMenu
performSearchByQuery={performSearchByQuery}
performSearchBySize={performSearchBySize}
performSearchByWhen={performSearchByWhen}
performSearchByShow={performSearchByShow}
performSearchByTag={performSearchByTag}
toggleUploadModal={toggleUploadModal}
setSearchResultView={setSearchResultView}
searchView={searchView}
allTags={allTags}
localization={localization}
/>
</div>
</Desktop>
<MediaQuery query='(min-width: 1024px)'>
{
(matches) => {
return (<div style={{ marginLeft: matches ? '200px' : '0', height: '100%', overflowY: 'auto', backgroundColor: 'rgba(0,0,0,0.05)' }}
ref={(container) => { this.containerNode = container }}>
<SearchResults searchView={searchView} />
{this.containerNode && <InfiniteScroll
anchorEl={this.containerNode}
currentPage={currentPage}
threshold={100}
loadMore={(newPage) => {
search(newPage, searchQuery)
}}
hasMore={hasMore}
onScrollDown={(isFirstPage) => setScrolledDown(!isFirstPage)}
/>}
</div>)
}
}
</MediaQuery>
<div>
<div style={{ display: 'flex', flexDirection: 'column', position: 'fixed', bottom: '10%', right: '30px', zIndex: '990' }}>
<FloatingActionButton
zDepth={4}
onTouchTap={() => { this.containerNode.scrollTop = 0 }}
className={scrolledDown ? '' : 'hiddenWithAnimation'}>
<ArrowUpward />
</FloatingActionButton>
</div>
</div>
<UploadContainer />
<ImagePreviewContainer />
</div>
)
}
}
Search.propTypes = {
searchView: React.PropTypes.string.isRequired,
setPageTitle: React.PropTypes.func.isRequired,
setScrolledDown: React.PropTypes.func.isRequired,
scrolledDown: React.PropTypes.bool.isRequired,
fetching: React.PropTypes.bool.isRequired,
currentPage: React.PropTypes.number.isRequired,
hasMore: React.PropTypes.bool.isRequired,
searchQuery: React.PropTypes.string.isRequired,
loadTags: React.PropTypes.func.isRequired,
allTags: React.PropTypes.array.isRequired,
toggleUploadModal: React.PropTypes.func.isRequired,
setQuery: React.PropTypes.func.isRequired,
search: React.PropTypes.func.isRequired,
performSearchByQuery: React.PropTypes.func.isRequired,
performSearchBySize: React.PropTypes.func.isRequired,
performSearchByWhen: React.PropTypes.func.isRequired,
performSearchByShow: React.PropTypes.func.isRequired,
performSearchByTag: React.PropTypes.func.isRequired,
setSearchResultView: React.PropTypes.func.isRequired,
localization: React.PropTypes.object.isRequired
}
export default Search

@ -0,0 +1,15 @@
.header {
font-size: 16px;
font-weight: 700;
}
.filter {
input[type="text"] {
height: 34px;
}
}
.uploadFileBtn {
margin-top: 20px;
}

@ -0,0 +1,34 @@
import React, { Component } from 'react'
import { Card, CardActions, CardHeader, CardText, CardTitle } from 'material-ui/Card'
import Paper from 'material-ui/Paper'
import classes from './EmptyCard.scss'
const EmptyCard = (props) => {
const {title, textElement} = props
return (
<Paper zDepth={1} className={classes.emptyCard}>
<Card>
<CardHeader
className={classes.emptyCardTitle}
title={<span className={classes.emptyCardHeaderTitle}>{title}</span>}
/>
<CardText>
{textElement}
</CardText>
</Card>
</Paper>
)
}
EmptyCard.propTypes = {
title: React.PropTypes.string.isRequired,
textElement: React.PropTypes.object.isRequired
}
export default EmptyCard

@ -0,0 +1,12 @@
.emptyCard {
margin: 10px;
}
.emptyCardHeaderTitle {
font-size: 15px;
font-weight: 600;
}
.emptyCardTitle {
padding-bottom: 0 !important;
}

@ -0,0 +1,3 @@
import EmptyCard from './EmptyCard'
export default EmptyCard

@ -0,0 +1,37 @@
import React, { Component } from 'react'
import HintCard from '../HintCard'
import classes from './EmptySearchResultsCard.scss'
class EmptySearchResultsCard extends Component {
render() {
const { searchQuery, performSearchByQuery, localization } = this.props
if (searchQuery != '') {
return (<div className='pageContainer'>
<HintCard
title={localization.searchPage.nothingFoundLabel}
description={<span><i>{searchQuery}</i>&nbsp;-&nbsp;{localization.searchPage.nothingFoundDescriptionLabel}</span>}
performSearchByQuery={performSearchByQuery}
localization={localization}
/>
</div>)
}
return (<div className='pageContainer'>
<HintCard
title={localization.searchPage.searchTipsLabel}
description={<span>{localization.searchPage.searchTipsDescriptionLabel}</span>}
performSearchByQuery={performSearchByQuery}
localization={localization}
/>
</div>)
}
}
EmptySearchResultsCard.propTypes = {
searchQuery: React.PropTypes.string.isRequired,
performSearchByQuery: React.PropTypes.func.isRequired,
localization: React.PropTypes.object.isRequired
}
export default EmptySearchResultsCard

@ -0,0 +1,3 @@
import EmptySearchResultsCard from './EmptySearchResultsCard'
export default EmptySearchResultsCard

@ -0,0 +1,94 @@
import React, { Component } from 'react'
import FlatButton from 'material-ui/FlatButton'
import EmptyCard from '../EmptyCard'
import classes from './HintCard.scss'
const HintCard = (props) => {
const { title, description, performSearchByQuery, localization } = props
const hintText = (<div><p>{localization.searchPage.refineTipsLabel}</p>
<ul>
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('*') }}>
*
</span> - {localization.searchPage.allFilesQueryLabel}
</li>
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('John Smith') }}>
John Smith
</span> - {localization.searchPage.simpleQueryLabel}
</li>
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('"John Smith"') }}>
"John Smith"
</span> - {localization.searchPage.pharseQueryLabel}
</li>
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('"John Smith"~10') }}>
"John Smith"~10
</span> - {localization.searchPage.pharseQueryWithDistanceLabel}
</li>
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('John~3') }}>
John~3
</span> - {localization.searchPage.fuzzyQueryLabel}
</li>
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('filename:*.txt') }}>
filename:*.txt
</span> - {localization.searchPage.filenameQueryLabel}
</li>
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('size>1M') }}>
size>1M
</span> - {localization.searchPage.sizeQueryLabel}
</li>
<li><span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('when:today') }}>
when:today
</span> - {localization.searchPage.whenQueryLabel}
</li>
<li>
<span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('author:*') }}>
author:*
</span> - {localization.searchPage.authorQueryLabel}
</li>
<li>
<span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('tags:ocr,ui-upload') }}>
tags:ocr,ui-upload
</span> - {localization.searchPage.tagsQueryLabel}
</li>
<li>
<span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('entities:"hello@ambar.cloud"') }}>
entities:"hello@ambar.cloud"
</span> - {localization.searchPage.entitiesQueryLabel}
</li>
<li>
<span className={classes.clickableSpan} onTouchTap={() => { performSearchByQuery('show:removed') }}>
show:removed
</span> - {localization.searchPage.removedQueryLabel}
</li>
</ul>
</div>)
const emailText = (<p>
{localization.searchPage.haveQuestionsLabel}&nbsp;<a className={classes.link} href='mailto:hello@ambar.cloud'>{localization.searchPage.dropMessageLabel}</a>
</p>)
const textElement = (<div>
<p style={{ marginTop: 0 }}>{description}</p>
{hintText}
{emailText}
</div>)
return (
<EmptyCard
title={title}
textElement={textElement} />
)
}
HintCard.propTypes = {
title: React.PropTypes.string.isRequired,
description: React.PropTypes.object.isRequired,
performSearchByQuery: React.PropTypes.func.isRequired,
localization: React.PropTypes.object.isRequired
}
export default HintCard

@ -0,0 +1,22 @@
.link, .link:hover,.link:visited {
color: #00acc1;
text-decoration: underline;
cursor: pointer; cursor: hand;
}
span.clickableSpan {
color: #444477;
display: inline-block;
}
span.clickableSpan:hover {
border-radius: 4px;
background-color: #B2EBF2;
cursor: pointer; cursor: hand;
}
span.clickableSpan:active {
border-radius: 4px;
background-color: #80DEEA;
cursor: pointer; cursor: hand;
}

@ -0,0 +1,3 @@
import HintCard from './HintCard'
export default HintCard

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save