├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── analytics.js
├── dist
├── fonts
│ ├── MaterialIcons-Regular.eot
│ ├── MaterialIcons-Regular.svg
│ ├── MaterialIcons-Regular.ttf
│ ├── MaterialIcons-Regular.woff
│ ├── MaterialIcons-Regular.woff2
│ ├── Roboto-Bold.ttf
│ ├── Roboto-Light.ttf
│ ├── Roboto-Medium.ttf
│ ├── Roboto-Regular.ttf
│ └── Roboto-Thin.ttf
├── icons
│ ├── mac
│ │ └── icon.png
│ ├── png
│ │ ├── 128x128.png
│ │ ├── 16x16.png
│ │ ├── 24x24.png
│ │ ├── 256x256.png
│ │ ├── 32x32.png
│ │ ├── 48x48.png
│ │ ├── 512x512.png
│ │ ├── 64x64.png
│ │ └── 96x96.png
│ └── win
│ │ └── icon.png
├── img
│ ├── logo.svg
│ └── logo_inv.svg
├── index.html
└── templates
│ ├── modules
│ ├── joystick.coffee
│ └── joystick
│ │ ├── App.coffee
│ │ ├── Background.coffee
│ │ ├── Broadcaster.coffee
│ │ ├── FocusSystem.coffee
│ │ ├── Focusable.coffee
│ │ ├── Gamepad.coffee
│ │ ├── Grid.coffee
│ │ ├── Transitions.coffee
│ │ ├── View.coffee
│ │ └── stores
│ │ ├── ActionStore.coffee
│ │ ├── FocusStore.coffee
│ │ └── ViewStore.coffee
│ ├── project-coffeescript
│ ├── Main.coffee
│ ├── app.js
│ ├── components
│ │ └── CardCarousel.component.coffee
│ ├── framer
│ │ ├── framer.js
│ │ ├── framer.js.map
│ │ └── images
│ │ │ ├── cursor-active.png
│ │ │ ├── cursor-active@2x.png
│ │ │ ├── cursor.png
│ │ │ └── cursor@2x.png
│ ├── generic
│ │ └── .gitkeep
│ ├── images
│ │ ├── Icon.png
│ │ └── background.png
│ ├── index.html
│ └── views
│ │ └── Home.view.coffee
│ └── project-javascript
│ ├── Main.js
│ ├── app.js
│ ├── components
│ └── CardCarousel.component.js
│ ├── framer
│ ├── framer.js
│ ├── framer.js.map
│ └── images
│ │ ├── cursor-active.png
│ │ ├── cursor-active@2x.png
│ │ ├── cursor.png
│ │ └── cursor@2x.png
│ ├── generic
│ └── .gitkeep
│ ├── images
│ ├── Icon.png
│ └── background.png
│ ├── index.html
│ └── views
│ └── Home.view.js
├── docs
└── Joystick.md
├── main.js
├── package-lock.json
├── package.json
├── server.js
├── src
├── components
│ ├── Button.tsx
│ ├── Editor.tsx
│ ├── Icon.tsx
│ ├── Popup.tsx
│ ├── Sidebar.tsx
│ ├── Tabs.tsx
│ ├── Welcome.tsx
│ ├── _Editor.scss
│ ├── _Popup.scss
│ ├── _Sidebar.scss
│ ├── _Tabs.scss
│ └── _Welcome.scss
├── index.tsx
├── misc
│ ├── app-icon.png
│ └── framer-theme.json
├── scss
│ ├── _fonts.scss
│ ├── _generic.scss
│ ├── _reset.scss
│ ├── _variables.scss
│ └── main.scss
├── snippets.ts
├── stores
│ ├── EditorStore.ts
│ ├── PopupStore.ts
│ ├── ProjectStore.ts
│ └── index.ts
└── types.ts
├── tsconfig.json
├── webpack.config.js
├── yarn-error.log
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /build/
3 |
4 | /dist/types/
5 |
6 | /dist/*.js
7 | /dist/**.js
8 |
9 | /dist/*.js.map
10 | /dist/**.js.map
11 |
12 | /dist/*.json
13 | /dist/**.json
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | matrix:
2 | include:
3 | - os: osx
4 | osx_image: xcode9.4
5 | language: node_js
6 | node_js: "10"
7 | env:
8 | - ELECTRON_CACHE=$HOME/.cache/electron
9 | - ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
10 | cache:
11 | directories:
12 | - node_modules
13 | - $HOME/.cache/electron
14 | - $HOME/.cache/electron-builder
15 | script: yarn release
16 | before_cache:
17 | - rm -rf $HOME/.cache/electron-builder/wine
18 |
19 | branches:
20 | except:
21 | - "/^v\\d+\\.\\d+\\.\\d+$/"
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Emil Widlund
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Carousel
2 | [](https://travis-ci.org/emilwidlund/carousel)
3 | [](https://opensource.org/licenses/MIT)
4 | [](https://lgtm.com/projects/g/emilwidlund/carousel/alerts/)
5 | [](https://lgtm.com/projects/g/emilwidlund/carousel/context:javascript)
6 |
7 | Carousel is a prototyping tool powered by [Framer Library](https://github.com/koenbok/Framer). It provides a simple way to build large-scale, Gamepad-driven prototypes on both macOS and Windows. Prototypes are built using the [Framer Library API](https://classic.framer.com/docs) and may be written in JavaScript (ES6) or CoffeeScript.
8 |
9 | New Carousel-projects comes pre-loaded with [Framer Joystick](https://blog.framer.com/introducing-framer-joystick-28359287bef0) and simple boilerplate code which makes it very easy to get up and running without setting things up.
10 |
11 | [Here's a quick demo](https://www.youtube.com/watch?v=5UR9pkTTJvY&feature=youtu.be)
12 |
13 | ### Download
14 | You can find download-links for the latest release [here](https://github.com/emilwidlund/carousel/releases/latest). If you'd like to see all releases, you can find them [here](https://github.com/emilwidlund/carousel/releases).
15 | Download the .dmg file for macOS or the .exe file for Windows.
16 |
17 | #### Important Notice
18 | Code Signing Certificates has not yet been integrated with the builds for neither Windows or macOS. This means that both Windows and macOS (and probably a couple of anti-virus programs) will flag the Carousel installer as untrusted. Carousel will however run fine if you proceed despite the warnings.
19 |
20 | This is of course on our radar, and we'll hopefully have certificates integrated with builds on both platforms soon.
21 |
22 | ### Documentation
23 | - [Joystick Documentation](https://github.com/emilwidlund/carousel/blob/master/docs/Joystick.md)
24 | - Joystick Guides - Coming Soon
25 |
26 | ### Issues & Contributing
27 | If you find an issue or a bug, please let us know by [submitting an issue](https://github.com/emilwidlund/carousel/issues). You could also [open a pull request](https://github.com/emilwidlund/carousel/pulls). If you have ideas of improvement and feature-requests, open an issue and add the "enhancement"-label.
28 |
29 | ### Contact
30 | For any questions, highfives or concerns - contact me on [Twitter](https://twitter.com/emilwidlund).
31 |
--------------------------------------------------------------------------------
/analytics.js:
--------------------------------------------------------------------------------
1 | const ua = require('universal-analytics');
2 | const uuid = require('uuid/v4');
3 | const Store = require('electron-store');
4 | const store = new Store();
5 |
6 | // Setup User Analytics
7 | const userId = store.get('userId') || uuid();
8 | store.set('userId', userId);
9 |
10 | const user = ua('UA-75056533-4', userId);
11 |
12 |
13 |
14 | const trackEvent = (category, action, label, value) => {
15 | user.event({
16 | ec: category,
17 | ea: action,
18 | el: label,
19 | ev: value
20 | })
21 | .send();
22 | }
23 |
24 |
25 | module.exports = {
26 | trackEvent
27 | }
--------------------------------------------------------------------------------
/dist/fonts/MaterialIcons-Regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/fonts/MaterialIcons-Regular.eot
--------------------------------------------------------------------------------
/dist/fonts/MaterialIcons-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/fonts/MaterialIcons-Regular.ttf
--------------------------------------------------------------------------------
/dist/fonts/MaterialIcons-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/fonts/MaterialIcons-Regular.woff
--------------------------------------------------------------------------------
/dist/fonts/MaterialIcons-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/fonts/MaterialIcons-Regular.woff2
--------------------------------------------------------------------------------
/dist/fonts/Roboto-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/fonts/Roboto-Bold.ttf
--------------------------------------------------------------------------------
/dist/fonts/Roboto-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/fonts/Roboto-Light.ttf
--------------------------------------------------------------------------------
/dist/fonts/Roboto-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/fonts/Roboto-Medium.ttf
--------------------------------------------------------------------------------
/dist/fonts/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/fonts/Roboto-Regular.ttf
--------------------------------------------------------------------------------
/dist/fonts/Roboto-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/fonts/Roboto-Thin.ttf
--------------------------------------------------------------------------------
/dist/icons/mac/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/icons/mac/icon.png
--------------------------------------------------------------------------------
/dist/icons/png/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/icons/png/128x128.png
--------------------------------------------------------------------------------
/dist/icons/png/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/icons/png/16x16.png
--------------------------------------------------------------------------------
/dist/icons/png/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/icons/png/24x24.png
--------------------------------------------------------------------------------
/dist/icons/png/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/icons/png/256x256.png
--------------------------------------------------------------------------------
/dist/icons/png/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/icons/png/32x32.png
--------------------------------------------------------------------------------
/dist/icons/png/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/icons/png/48x48.png
--------------------------------------------------------------------------------
/dist/icons/png/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/icons/png/512x512.png
--------------------------------------------------------------------------------
/dist/icons/png/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/icons/png/64x64.png
--------------------------------------------------------------------------------
/dist/icons/png/96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/icons/png/96x96.png
--------------------------------------------------------------------------------
/dist/icons/win/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/icons/win/icon.png
--------------------------------------------------------------------------------
/dist/img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
--------------------------------------------------------------------------------
/dist/img/logo_inv.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
23 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Carousel
4 |
5 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/dist/templates/modules/joystick.coffee:
--------------------------------------------------------------------------------
1 | {App} = require './joystick/App.coffee'
2 | {Focusable} = require './joystick/Focusable.coffee'
3 | {GamepadSystem} = require './joystick/Gamepad.coffee'
4 | {Transitions} = require './joystick/Transitions.coffee'
5 | {View} = require './joystick/View.coffee'
6 | {Grid} = require './joystick/Grid.coffee'
7 |
8 | {focusStore} = require './joystick/stores/FocusStore.coffee'
9 | {viewStore} = require './joystick/stores/ViewStore.coffee'
10 | {actionStore} = require './joystick/stores/ActionStore.coffee'
11 |
12 | joystick =
13 | App: App
14 | Focusable: Focusable
15 | Gamepad: new GamepadSystem(false)
16 | Transitions: Transitions
17 | View: View
18 | Grid: Grid
19 | FocusStore: focusStore
20 | ViewStore: viewStore
21 | ActionStore: actionStore
22 |
23 | module.exports = joystick
--------------------------------------------------------------------------------
/dist/templates/modules/joystick/App.coffee:
--------------------------------------------------------------------------------
1 | {FocusSystem} = require './FocusSystem.coffee'
2 | {Focusable} = require './Focusable.coffee'
3 | {Background} = require './Background.coffee'
4 | {viewStore} = require './stores/ViewStore.coffee'
5 | {actionStore} = require './stores/ActionStore.coffee'
6 | _ = Framer._
7 |
8 | Utils.isFramerStudio = () -> true
9 |
10 | Device = new DeviceComponent
11 | hideBezel: true
12 |
13 | Device.setupContext()
14 |
15 | Device.customize
16 | screenWidth: 1920
17 | screenHeight: 1080
18 | devicePixelRatio: 1
19 | deviceZoom: 1
20 |
21 | Device.screenBackground.visible = false
22 |
23 | # Disable Hints
24 | Framer.Extras.Hints.disable()
25 |
26 | class exports.App extends FlowComponent
27 |
28 | constructor: (properties={}) ->
29 | background = new Background
30 |
31 | super _.defaults properties,
32 | backgroundColor: 'transparent'
33 |
34 | @focusSystem = new FocusSystem()
35 | @background = background
36 | @setupTransitionListener()
37 |
38 | # Manipulate FlowComponent's ScrollComponent content insets
39 | @on 'transitionstart', (previousView, nextView, direction) =>
40 | if @_wrappedLayer
41 | sc = @_wrappedLayer nextView
42 | else
43 | sc = @_tempScroll
44 | sc.contentInset = 0
45 |
46 | @on 'transitionend', (previousView, nextView, direction) =>
47 | if @_wrappedLayer
48 | sc = @_wrappedLayer nextView
49 | else
50 | sc = @_tempScroll
51 | sc.draggable.enabled = false
52 |
53 | viewStore.transition properties.view
54 |
55 | setupTransitionListener: ->
56 | viewStore.on 'transitionEvent', (transitionEvent) =>
57 | if transitionEvent.viewTransition
58 | @transition transitionEvent.view, transitionEvent.viewTransition
59 | else
60 | @showNext transitionEvent.view
--------------------------------------------------------------------------------
/dist/templates/modules/joystick/Background.coffee:
--------------------------------------------------------------------------------
1 | {viewStore} = require './stores/ViewStore.coffee'
2 | _ = Framer._
3 |
4 | class exports.Background extends Layer
5 | constructor: (properties={}) ->
6 | super properties
7 |
8 | @bgLayer = new Layer
9 | parent: @
10 | size: Screen.size
11 | backgroundColor: '#000'
12 |
13 | @gradientLayer = {}
14 |
15 | viewStore.on 'transitionEvent', (transitionEvent) =>
16 | if (transitionEvent.view.background)
17 | @populateBackground(transitionEvent.view.background)
18 |
19 | populateBackground: (background) ->
20 |
21 | tempVar = new Layer
22 | parent: @
23 | size: Screen.size
24 | image: background.image || null
25 | backgroundColor: background.backgroundColor || '#000'
26 | blur: background.blur || null
27 | opacity: 0
28 |
29 | tempVar.animate
30 | opacity: 1
31 | options:
32 | time: .4
33 |
34 | Utils.delay .4, =>
35 | @bgLayer.destroy()
36 | @bgLayer = tempVar
--------------------------------------------------------------------------------
/dist/templates/modules/joystick/Broadcaster.coffee:
--------------------------------------------------------------------------------
1 | _ = Framer._
2 |
3 | class BroadcasterSystem extends Framer.EventEmitter
4 | viewTransitionEvent: (view) ->
5 | @emit 'viewTransitionEvent', view
6 |
7 | focusEvent: (focusable) ->
8 | @emit 'focusEvent', focusable
9 |
10 | exports.Broadcaster = new BroadcasterSystem()
--------------------------------------------------------------------------------
/dist/templates/modules/joystick/FocusSystem.coffee:
--------------------------------------------------------------------------------
1 | {GamepadSystem} = require './Gamepad.coffee'
2 | {focusStore} = require './stores/FocusStore.coffee'
3 | _ = Framer._
4 |
5 | exports.Gamepad = Gamepad = new GamepadSystem(true)
6 |
7 | class exports.FocusSystem
8 | constructor: () ->
9 |
10 | # Setup event listeners for navigation
11 | Gamepad.on 'gamepadevent', (event) =>
12 | if event.keyCode > 36 && event.keyCode < 41 && focusStore.focusableElements.length
13 | @navigate event.keyCode
14 | else if event.keyCode > 11 && event.keyCode < 16 && focusStore.focusableElements.length
15 | @navigate event.keyCode
16 |
17 | window.addEventListener 'keydown', (event) =>
18 | if event.keyCode > 36 && event.keyCode < 41 && focusStore.focusableElements.length
19 | @navigate(event.keyCode)
20 |
21 | navigate: (keyCode) ->
22 |
23 | focusedPosition =
24 | x: focusStore.focusedElement.screenFrame.x + (focusStore.focusedElement.screenFrame.width / 2),
25 | y: focusStore.focusedElement.screenFrame.y + (focusStore.focusedElement.screenFrame.height / 2)
26 |
27 | relevantFocusables = _.filter focusStore.focusableElements, (focusable) ->
28 |
29 | if focusable.parent.parent.visible == false || focusable.parent.parent.opacity == 0
30 | return false
31 | if focusable.visible == false || focusable.opacity == 0
32 | return false
33 |
34 | switch keyCode
35 | when 13
36 | focusablePosition =
37 | x: focusable.screenFrame.x + focusable.screenFrame.width
38 | y: focusable.screenFrame.y + (focusable.screenFrame.height / 2)
39 |
40 | if focusablePosition.x < focusedPosition.x
41 | return true
42 | else
43 | return false
44 |
45 | when 11
46 | focusablePosition =
47 | x: focusable.screenFrame.x + (focusable.screenFrame.width / 2)
48 | y: focusable.screenFrame.y + focusable.screenFrame.height
49 |
50 | if focusablePosition.y < focusedPosition.y
51 | return true
52 | else
53 | return false
54 |
55 | when 14
56 | focusablePosition =
57 | x: focusable.screenFrame.x
58 | y: focusable.screenFrame.y + (focusable.screenFrame.height / 2)
59 |
60 | if focusablePosition.x > focusedPosition.x
61 | return true
62 | else
63 | return false
64 |
65 | when 12
66 | focusablePosition =
67 | x: focusable.screenFrame.x + (focusable.screenFrame.width / 2)
68 | y: focusable.screenFrame.y
69 |
70 | if focusablePosition.y > focusedPosition.y
71 | return true
72 | else
73 | return false
74 |
75 | when 37
76 | focusablePosition =
77 | x: focusable.screenFrame.x + focusable.screenFrame.width
78 | y: focusable.screenFrame.y + (focusable.screenFrame.height / 2)
79 |
80 | if focusablePosition.x < focusedPosition.x
81 | return true
82 | else
83 | return false
84 |
85 | when 38
86 | focusablePosition =
87 | x: focusable.screenFrame.x + (focusable.screenFrame.width / 2)
88 | y: focusable.screenFrame.y + focusable.screenFrame.height
89 |
90 | if focusablePosition.y < focusedPosition.y
91 | return true
92 | else
93 | return false
94 |
95 | when 39
96 | focusablePosition =
97 | x: focusable.screenFrame.x,
98 | y: focusable.screenFrame.y + (focusable.screenFrame.height / 2)
99 |
100 | if focusablePosition.x > focusedPosition.x
101 | return true
102 | else
103 | return false
104 |
105 | when 40
106 | focusablePosition =
107 | x: focusable.screenFrame.x + (focusable.screenFrame.width / 2)
108 | y: focusable.screenFrame.y
109 |
110 | if focusablePosition.y > focusedPosition.y
111 | return true
112 | else
113 | return false
114 |
115 |
116 | sortedFocusables = _.sortBy relevantFocusables, (focusable) ->
117 |
118 | switch keyCode
119 | when 14
120 | angleOffset = 90
121 | break
122 |
123 | when 12
124 | angleOffset = 0
125 | break
126 |
127 | when 15
128 | angleOffset = 90
129 | break
130 |
131 | when 13
132 | angleOffset = 180
133 | break
134 |
135 | when 37
136 | angleOffset = 90
137 | break
138 |
139 | when 38
140 | angleOffset = 0
141 | break
142 |
143 | when 39
144 | angleOffset = 90
145 | break
146 |
147 | when 40
148 | angleOffset = 180
149 | break
150 |
151 |
152 | focusablePosition =
153 | x: focusable.screenFrame.x + (focusable.screenFrame.width / 2)
154 | y: focusable.screenFrame.y + (focusable.screenFrame.height / 2)
155 |
156 | Y = Math.abs(focusedPosition.y - focusablePosition.y)
157 | X = Math.abs(focusedPosition.x - focusablePosition.x)
158 |
159 | distance = Math.sqrt(Math.abs(X*X + Y*Y))
160 | angle = Math.abs(Math.atan2(focusedPosition.x - focusablePosition.x, focusedPosition.y - focusablePosition.y) * 180 / Math.PI)
161 | score = distance + Math.abs(angleOffset - angle)
162 | return score
163 |
164 | if sortedFocusables.length
165 | focusStore.focus sortedFocusables[0]
--------------------------------------------------------------------------------
/dist/templates/modules/joystick/Focusable.coffee:
--------------------------------------------------------------------------------
1 | _ = Framer._
2 |
3 | class exports.Focusable extends Layer
4 | constructor: (properties={}) ->
5 | super properties
6 |
7 | @meta = properties.meta
8 | @actions = properties.actions || []
9 |
10 | @states.default.animationOptions = properties.animationOptions
11 | @states.focused = properties.focusProperties
--------------------------------------------------------------------------------
/dist/templates/modules/joystick/Gamepad.coffee:
--------------------------------------------------------------------------------
1 | _ = Framer._
2 |
3 | Function::define = (prop, desc) ->
4 | Object.defineProperty this.prototype, prop, desc
5 |
6 | class exports.GamepadSystem extends Framer.EventEmitter
7 | constructor: (throttle) ->
8 |
9 | super()
10 |
11 | @connectedGamepad = undefined
12 | @loopRequest = undefined
13 | @pollingGP = undefined
14 | @buttonsPressed = []
15 |
16 | # Poll loop X times a second for new states
17 | @loopInterval = 500
18 |
19 | # Threshold for approved axis values - Values above X will be registered as an input
20 | @axisSensitivity = if throttle then .7 else .2
21 |
22 | # Amount of button events occuring in a sequence
23 | @eventsInSequence = 0
24 |
25 | # Should events be throttled?
26 | @throttled = throttle
27 |
28 | if navigator.getGamepads()[0]
29 | @connectedGamepad = navigator.getGamepads()[0]
30 | @loopRequest = window.requestAnimationFrame @eventLoop.bind(@)
31 |
32 | window.addEventListener 'gamepadconnected', (e) =>
33 | @connectedGamepad = navigator.getGamepads()[0]
34 | @loopRequest = window.requestAnimationFrame @eventLoop.bind(@)
35 |
36 | window.addEventListener 'gamepaddisconnected', (e) =>
37 | @connectedGamepad = null
38 | window.cancelAnimationFrame @loopRequest
39 |
40 | @define 'throttle',
41 | set: (bool) ->
42 | @throttled = bool
43 |
44 | if bool == true
45 | @axisSensitivity = .7
46 | else
47 | @axisSensitivity = .2
48 |
49 | eventLoop: () =>
50 |
51 | setTimeout () =>
52 |
53 | @pollingGP = navigator.getGamepads()[0]
54 |
55 | for button, index in @pollingGP.buttons
56 | button.type = 'button'
57 | button.keyCode = index
58 |
59 | @buttonsPressed = _.filter @pollingGP.buttons, {pressed: true}
60 |
61 | for axis, index in @pollingGP.axes
62 |
63 | if (index <= 3)
64 | activeAxis = {}
65 |
66 | if axis > @axisSensitivity || axis < -@axisSensitivity
67 | activeAxis.type = 'axis'
68 | activeAxis.value = axis
69 |
70 | switch index
71 | when 0
72 | if axis > 0
73 | activeAxis.keyCode = 39
74 | @buttonsPressed.push activeAxis
75 | else
76 | activeAxis.keyCode = 37
77 | @buttonsPressed.push activeAxis
78 | when 1
79 | if axis > 0
80 | activeAxis.keyCode = 40
81 | @buttonsPressed.push activeAxis
82 | else
83 | activeAxis.keyCode = 38
84 | @buttonsPressed.push activeAxis
85 | when 2
86 | if axis > 0
87 | activeAxis.keyCode = 44
88 | @buttonsPressed.push activeAxis
89 | else
90 | activeAxis.keyCode = 42
91 | @buttonsPressed.push activeAxis
92 | when 3
93 | if axis > 0
94 | activeAxis.keyCode = 43
95 | @buttonsPressed.push activeAxis
96 | else
97 | activeAxis.keyCode = 41
98 | @buttonsPressed.push activeAxis
99 |
100 | if @buttonsPressed.length
101 | for buttonPressed, index in @buttonsPressed
102 | @emit 'gamepadevent', buttonPressed
103 |
104 | if @throttled
105 | switch @eventsInSequence
106 | when 0
107 | @loopInterval = 3
108 | when 1
109 | @loopInterval = 8
110 | else
111 | @loopInterval = 1000
112 |
113 | @eventsInSequence++
114 | else
115 | if @throttled
116 | @eventsInSequence = 0
117 | @loopInterval = 500
118 | else
119 | @eventsInSequence = 0
120 | @loopInterval = 1000
121 |
122 | @loopRequest = window.requestAnimationFrame @eventLoop.bind(@)
123 |
124 | , 1000 / @loopInterval
--------------------------------------------------------------------------------
/dist/templates/modules/joystick/Grid.coffee:
--------------------------------------------------------------------------------
1 | _ = Framer._
2 |
3 | class GridSystem
4 | constructor: ->
5 | @safezoneBounds =
6 | width: Screen.width * .9
7 | height: Screen.height * .9
8 |
9 | @dangerzoneBounds =
10 | width: (Screen.width * .1) / 2
11 | height: (Screen.height * .1) / 2
12 |
13 | @columnCount = 24
14 | @columnGutterCount = @columnCount - 1
15 | @columnGutter = 10
16 | @columnWidth = (@safezoneBounds.width - (@columnGutterCount * @columnGutter)) / @columnCount
17 |
18 | @rowHeight = 10
19 |
20 | getWidth: (cells) ->
21 | return (@columnWidth * cells) + (@columnGutter * (cells - 1))
22 |
23 | getHeight: (rows) ->
24 | return @rowHeight * rows
25 |
26 | getSafezone: (parent) ->
27 | return new Layer
28 | parent: parent
29 | name: 'safezone'
30 | width: @safezoneBounds.width
31 | height: @safezoneBounds.height
32 | y: Align.center
33 | x: Align.center
34 | backgroundColor: 'transparent'
35 |
36 | exports.Grid = new GridSystem()
--------------------------------------------------------------------------------
/dist/templates/modules/joystick/Transitions.coffee:
--------------------------------------------------------------------------------
1 | exports.Transitions =
2 | goIn: ->
3 | options =
4 | curve: 'cubic-bezier(0.645, 0.045, 0.355, 1)'
5 | time: .4
6 |
7 | return (
8 | layerA:
9 | show:
10 | opacity: 1
11 | scale: 1
12 | options: options
13 | hide:
14 | opacity: 0
15 | scale: 1.2
16 | options: options
17 | layerB:
18 | show:
19 | opacity: 1
20 | scale: 1
21 | options: options
22 | hide:
23 | opacity: 0
24 | scale: .8
25 | options: options
26 | options: options
27 | )
28 |
29 | goOut: ->
30 | options =
31 | curve: 'cubic-bezier(0.645, 0.045, 0.355, 1)'
32 | time: .4
33 |
34 | return (
35 | layerA:
36 | show:
37 | opacity: 1
38 | scale: 1
39 | options: options
40 | hide:
41 | opacity: 0
42 | scale: .8
43 | options: options
44 | layerB:
45 | show:
46 | opacity: 1
47 | scale: 1
48 | options: options
49 | hide:
50 | opacity: 0
51 | scale: 1.2
52 | options: options
53 | options: options
54 | )
55 |
56 | fade: ->
57 | options =
58 | curve: 'cubic-bezier(0.645, 0.045, 0.355, 1)'
59 | time: .4
60 |
61 | return (
62 | layerA:
63 | show:
64 | opacity: 1
65 | options: options
66 | hide:
67 | opacity: 0
68 | options: options
69 | layerB:
70 | show:
71 | opacity: 1
72 | options: options
73 | hide:
74 | opacity: 0
75 | options: options
76 | options: options
77 | )
--------------------------------------------------------------------------------
/dist/templates/modules/joystick/View.coffee:
--------------------------------------------------------------------------------
1 | {Grid} = require './Grid.coffee'
2 | _ = Framer._
3 |
4 | class exports.View extends Layer
5 | constructor: (properties={}) ->
6 |
7 | properties.backgroundColor = 'transparent'
8 |
9 | super _.defaults properties,
10 | width: Screen.width
11 | height: Screen.height
12 |
13 | @safezone = Grid.getSafezone(@)
14 |
15 | @actions = properties.actions || []
16 | @background = properties.background || {}
--------------------------------------------------------------------------------
/dist/templates/modules/joystick/stores/ActionStore.coffee:
--------------------------------------------------------------------------------
1 | {Gamepad} = require '../FocusSystem.coffee';
2 | {viewStore} = require './ViewStore.coffee'
3 | {focusStore} = require './FocusStore.coffee'
4 | _ = Framer._
5 |
6 | Function::define = (prop, desc) ->
7 | Object.defineProperty this.prototype, prop, desc
8 |
9 | class ActionStore extends Framer.EventEmitter
10 | constructor: ->
11 | super()
12 |
13 | @focusableActions = []
14 | @viewActions = []
15 |
16 | window.addEventListener 'keydown', (event) =>
17 | if event.keyCode == 13
18 | action = _.find @actions, {keyCode: 0}
19 | if action
20 | action.function()
21 | else if event.keyCode == 8
22 | action = _.find @actions, {keyCode: 1}
23 | if action
24 | action.function()
25 |
26 | Gamepad.on 'gamepadevent', (event) =>
27 | action = _.find @actions, {keyCode: event.keyCode}
28 | if action
29 | action.function()
30 |
31 | viewStore.on 'transitionEvent', (transitionEvent) =>
32 | @populateViewActions(transitionEvent.view.actions)
33 |
34 | focusStore.on 'focusEvent', (focusEvent) =>
35 | if focusEvent.focusedElement
36 | @populateFocusableActions(focusEvent.focusedElement.actions)
37 |
38 | focusStore.on 'focusedElementCleared', =>
39 | @clearFocusableActions()
40 |
41 | @define 'actions',
42 | get: -> @focusableActions.concat @viewActions
43 |
44 | populateFocusableActions: (focusableActions) ->
45 | @focusableActions = focusableActions
46 | @emit 'focusableActionsPopulated', focusableActions
47 |
48 | clearFocusableActions: (viewActions) ->
49 | @focusableActions = []
50 | @emit 'focusableActionsCleared'
51 |
52 | populateViewActions: (viewActions) ->
53 | @viewActions = viewActions
54 | @emit 'viewActionsPopulated', viewActions
55 |
56 | exports.actionStore = new ActionStore
--------------------------------------------------------------------------------
/dist/templates/modules/joystick/stores/FocusStore.coffee:
--------------------------------------------------------------------------------
1 | {Gamepad} = require '../Gamepad.coffee'
2 |
3 | class FocusStore extends Framer.EventEmitter
4 | constructor: ->
5 | super()
6 |
7 | @focusableElements = []
8 | @focusedElement = null
9 | @previouslyFocusedElement = null
10 |
11 | focus: (focusable) ->
12 |
13 | # If an element is focused, set it as previouslyFocused and change state to default
14 | # Loop through descendant elements and update their states as well
15 |
16 | if @focusedElement
17 | @previouslyFocusedElement = @focusedElement
18 | @previouslyFocusedElement.animate 'default'
19 |
20 | @previouslyFocusedElement.descendants.map (desc, index) ->
21 | if desc.states.focused
22 | desc.animate 'default'
23 |
24 | # Focus focusable and change state to focused
25 | # Loop through descendant elements and update their states as well
26 |
27 | @focusedElement = focusable
28 | @focusedElement.animate 'focused'
29 |
30 | for desc, index in @focusedElement.descendants
31 | if desc.states.focused
32 | desc.animate 'focused'
33 |
34 | @emit 'focusEvent',
35 | previouslyFocusedElement: @previouslyFocusedElement
36 | focusedElement: @focusedElement
37 |
38 | addFocusable: (focusable) ->
39 | @focusableElements.push focusable
40 |
41 | clearFocusables: ->
42 | @focusableElements = []
43 | @emit 'focusableElementsCleared'
44 |
45 | clearFocus: ->
46 | if @focusedElement
47 | @previouslyFocusedElement = @focusedElement
48 | @previouslyFocusedElement.animate 'default'
49 |
50 | @previouslyFocusedElement.descendants.map (desc, index) ->
51 | if desc.states.focused
52 | desc.animate 'default'
53 |
54 | @focusedElement = null
55 | @emit 'focusedElementCleared'
56 |
57 | exports.focusStore = new FocusStore
--------------------------------------------------------------------------------
/dist/templates/modules/joystick/stores/ViewStore.coffee:
--------------------------------------------------------------------------------
1 | {focusStore} = require './FocusStore.coffee'
2 | {Focusable} = require '../Focusable.coffee'
3 | {Transitions} = require '../Transitions.coffee'
4 |
5 | class ViewStore extends Framer.EventEmitter
6 | constructor: ->
7 | super()
8 |
9 | @currentView = null
10 | @previousView = null
11 | @historyStack = []
12 | @viewTransition = null
13 |
14 | transition: (view, transition) ->
15 | @previousView = @currentView
16 | @currentView = view
17 | @viewTransition = transition
18 |
19 | focusStore.clearFocusables()
20 |
21 | # Handle the new view
22 |
23 | viewObj =
24 | view: @currentView
25 | transition: @viewTransition
26 |
27 | @historyStack.push viewObj
28 |
29 | view.descendants.map (child, index) ->
30 | if child instanceof Focusable
31 | focusStore.addFocusable child
32 |
33 | if focusStore.focusableElements.length
34 | focusStore.focus focusStore.focusableElements[0]
35 | else
36 | focusStore.clearFocus()
37 |
38 | @emit 'transitionEvent',
39 | view: view
40 | previousView: @previousView
41 | viewTransition: transition
42 |
43 | goBack: ->
44 | @previousView = @currentView
45 |
46 | if @historyStack[@historyStack.length - 1].transition == Transitions.goIn
47 | @viewTransition = Transitions.goOut
48 | else if @historyStack[@historyStack.length - 1].transition == Transitions.goOut
49 | @viewTransition = Transitions.goIn
50 | else if @historyStack[@historyStack.length - 1].transition == Transitions.fade
51 | @viewTransition = Transitions.fade
52 |
53 | @historyStack.pop()
54 | @currentView = @historyStack[@historyStack.length - 1].view
55 |
56 | focusStore.clearFocusables()
57 |
58 | # Handle the new view
59 |
60 | @currentView.descendants.map (child, index) ->
61 | if child instanceof Focusable
62 | focusStore.addFocusable child
63 |
64 | if focusStore.focusableElements.length
65 | focusStore.focus focusStore.focusableElements[0]
66 | else
67 | focusStore.clearFocus()
68 |
69 | @emit 'transitionEvent',
70 | view: if @historyStack.length then @historyStack[@historyStack.length - 1].view else undefined
71 | previousView: @previousView
72 | viewTransition: @viewTransition
73 |
74 | exports.viewStore = new ViewStore
--------------------------------------------------------------------------------
/dist/templates/project-coffeescript/Main.coffee:
--------------------------------------------------------------------------------
1 | {HomeView} = require './views/Home.view'
2 |
3 | app = new App
4 | view: HomeView
--------------------------------------------------------------------------------
/dist/templates/project-coffeescript/app.js:
--------------------------------------------------------------------------------
1 | const joystick = require('./modules/joystick');
2 |
3 | Object.keys(joystick).forEach((k, i) => {
4 | window[k] = joystick[k];
5 | });
6 |
7 | require('./Main');
--------------------------------------------------------------------------------
/dist/templates/project-coffeescript/components/CardCarousel.component.coffee:
--------------------------------------------------------------------------------
1 | class exports.CardCarousel extends ScrollComponent
2 | constructor: (props={}) ->
3 | super(props)
4 |
5 | @content.clip = props.clip
6 | @content.draggable.enabled = false
7 |
8 | FocusStore.on 'focusEvent', (focusEvent) =>
9 | if focusEvent.focusedElement && focusEvent.focusedElement.parent == @content
10 | @scrollToCard focusEvent.focusedElement
11 |
12 | scrollToCard: (focusable) ->
13 | if (focusable.screenFrame.x + focusable.screenFrame.width) > (@screenFrame.x + @screenFrame.width)
14 | @scrollToLayer(focusable, 1, 0, true)
15 | else if focusable.screenFrame.x < @screenFrame.x
16 | @scrollToLayer(focusable, 0, 0, true)
--------------------------------------------------------------------------------
/dist/templates/project-coffeescript/framer/images/cursor-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-coffeescript/framer/images/cursor-active.png
--------------------------------------------------------------------------------
/dist/templates/project-coffeescript/framer/images/cursor-active@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-coffeescript/framer/images/cursor-active@2x.png
--------------------------------------------------------------------------------
/dist/templates/project-coffeescript/framer/images/cursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-coffeescript/framer/images/cursor.png
--------------------------------------------------------------------------------
/dist/templates/project-coffeescript/framer/images/cursor@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-coffeescript/framer/images/cursor@2x.png
--------------------------------------------------------------------------------
/dist/templates/project-coffeescript/generic/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-coffeescript/generic/.gitkeep
--------------------------------------------------------------------------------
/dist/templates/project-coffeescript/images/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-coffeescript/images/Icon.png
--------------------------------------------------------------------------------
/dist/templates/project-coffeescript/images/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-coffeescript/images/background.png
--------------------------------------------------------------------------------
/dist/templates/project-coffeescript/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/dist/templates/project-coffeescript/views/Home.view.coffee:
--------------------------------------------------------------------------------
1 | {CardCarousel} = require '../components/CardCarousel.component'
2 |
3 | view = new View
4 | size: Screen.size
5 | background:
6 | backgroundColor: '#000'
7 |
8 | cc = new CardCarousel
9 | parent: view
10 | width: Grid.getWidth(23)
11 | height: Grid.getHeight(60)
12 | y: Align.center()
13 | x: Align.center()
14 | clip: false
15 |
16 | for i in [0..10]
17 | l = new Focusable
18 | parent: cc.content
19 | width: 400
20 | height: 600
21 | x: (400 + 20) * i
22 | backgroundColor: 'rgba(255, 255, 255, .1)'
23 | focusProperties:
24 | backgroundColor: '#fff'
25 | scale: 1.1
26 | options:
27 | time: .2
28 | animationOptions:
29 | time: .2
30 |
31 |
32 |
33 | exports.HomeView = view
--------------------------------------------------------------------------------
/dist/templates/project-javascript/Main.js:
--------------------------------------------------------------------------------
1 | import HomeView from './views/Home.view';
2 |
3 | const app = new App({
4 | view: HomeView
5 | });
--------------------------------------------------------------------------------
/dist/templates/project-javascript/app.js:
--------------------------------------------------------------------------------
1 | const joystick = require('./modules/joystick');
2 |
3 | Object.keys(joystick).forEach((k, i) => {
4 | window[k] = joystick[k];
5 | });
6 |
7 | require('./Main');
--------------------------------------------------------------------------------
/dist/templates/project-javascript/components/CardCarousel.component.js:
--------------------------------------------------------------------------------
1 | export default class CardCarousel extends ScrollComponent {
2 | constructor(props={}) {
3 | super(props);
4 |
5 | this.content.clip = props.clip;
6 | this.content.draggable.enabled = false;
7 |
8 | FocusStore.on('focusEvent', (focusEvent) => {
9 | if (focusEvent.focusedElement && focusEvent.focusedElement.parent == this.content) {
10 | this.scrollToCard(focusEvent.focusedElement);
11 | }
12 | });
13 | }
14 |
15 | scrollToCard(focusable) {
16 | if ((focusable.screenFrame.x + focusable.screenFrame.width) > (this.screenFrame.x + this.screenFrame.width)) {
17 | this.scrollToLayer(focusable, 1, 0, true);
18 | }
19 | else if (focusable.screenFrame.x < this.screenFrame.x) {
20 | this.scrollToLayer(focusable, 0, 0, true);
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/dist/templates/project-javascript/framer/images/cursor-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-javascript/framer/images/cursor-active.png
--------------------------------------------------------------------------------
/dist/templates/project-javascript/framer/images/cursor-active@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-javascript/framer/images/cursor-active@2x.png
--------------------------------------------------------------------------------
/dist/templates/project-javascript/framer/images/cursor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-javascript/framer/images/cursor.png
--------------------------------------------------------------------------------
/dist/templates/project-javascript/framer/images/cursor@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-javascript/framer/images/cursor@2x.png
--------------------------------------------------------------------------------
/dist/templates/project-javascript/generic/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-javascript/generic/.gitkeep
--------------------------------------------------------------------------------
/dist/templates/project-javascript/images/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-javascript/images/Icon.png
--------------------------------------------------------------------------------
/dist/templates/project-javascript/images/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/dist/templates/project-javascript/images/background.png
--------------------------------------------------------------------------------
/dist/templates/project-javascript/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/dist/templates/project-javascript/views/Home.view.js:
--------------------------------------------------------------------------------
1 | import CardCarousel from '../components/CardCarousel.component';
2 |
3 | const view = new View({
4 | size: Screen.size,
5 | background: {
6 | backgroundColor: '#000'
7 | }
8 | });
9 |
10 | const cc = new CardCarousel({
11 | parent: view,
12 | width: Grid.getWidth(23),
13 | height: Grid.getHeight(60),
14 | y: Align.center(),
15 | x: Align.center(),
16 | clip: false
17 | });
18 |
19 | Array(10).fill(0).forEach((x, i) => {
20 | const l = new Focusable({
21 | parent: cc.content,
22 | width: 400,
23 | height: 600,
24 | x: (400 + 20) * i,
25 | backgroundColor: 'rgba(255, 255, 255, .1)',
26 | focusProperties: {
27 | backgroundColor: '#fff',
28 | scale: 1.1,
29 | options: {
30 | time: .2
31 | }
32 | },
33 | animationOptions: {
34 | time: .2
35 | }
36 | });
37 | });
38 |
39 | export default view;
40 |
41 |
--------------------------------------------------------------------------------
/docs/Joystick.md:
--------------------------------------------------------------------------------
1 | # Joystick Documentation
2 |
3 | #### Table of Contents
4 | - [App](#app)
5 | - [View](#view)
6 | - [Focusable](#focusable)
7 | - [Grid](#grid)
8 | - [Gamepad](#gamepad)
9 | - [FocusStore](#focusstore)
10 | - [ViewStore](#viewstore)
11 |
12 | ## App
13 | An app-instance is the brain of the prototype. It handles view-transitions, manages focusables and takes care of all views.
14 | The App class extends the FlowComponent class, which means that all applicable properties & methods for FlowComponents are supported on App-instances.
15 |
16 | ```
17 | app = new App
18 | view: myView
19 | ```
20 |
21 | ### Properties
22 | All regular FlowComponent-properties are applicable on an App.
23 | - view: `View` - The initial view to show.
24 |
25 |
26 | ## View
27 | A View is exactly what you think it is. It is a layer that covers the screen and holds content.
28 | The View class extends the Layer class, which means that all applicable properties & methods for Layers are supported on View-instances.
29 |
30 | ```
31 | myView = new View
32 | background:
33 | backgroundColor: '#f66'
34 | actions: [
35 | {
36 | keyCode: 41
37 | function: () ->
38 | print 'Something happens when moving the right stick up on my Gamepad, on this view'
39 | }
40 | ]
41 |
42 | l = new Layer
43 | parent: myView.safezone
44 | backgroundColor: '#fff'
45 | ```
46 |
47 | ### Properties
48 | All regular Layer-properties are applicable on Views.
49 | - safezone: `Layer` - Returns the safezone-layer that covers 90% of the view. Safezones are widely used in TV-applications to prevent clipping of content. You should usually add children to this property. For example: `parent: myView.safezone`.
50 | - background?: `object` - An object containing some properties on what the background should be. Important to use this instead of setting the backgroundColor/image on the View-instance.
51 | - image?: `string` - Path to image to use as background.
52 | - backgroundColor?: `string` - Color to use as background.
53 | - blur?: `number` - Useful if you want to blur the specified image.
54 | - actions?: `object[]` - An array of objects defining what should happen when a key-event occurs. Used to trigger functions when a pressing a button on a Gamepad or a keyboard.
55 | - keyCode: `number` - The key-code that should trigger the action. Can be a Keyboard-keycode or Gamepad-keycode.
56 | - function: `function` - The function to execute when desired keycode is pressed.
57 |
58 |
59 | ## Focusable
60 | A Focusable is a navigatable entity. It has a default state and a focused state defined, which are cycled between depending on if it's focused or not.
61 | Focusables are navigatable with keyboard-arrows and gamepads.
62 | The Focusable class extends the Layer class, which means that all applicable properties & methods for Layers are supported on Focusable-instances.
63 |
64 | ```
65 | foc = new Focusable
66 | parent: myView.safezone
67 | width: Grid.getWidth(4)
68 | height: Grid.getHeight(45)
69 | backgroundColor: 'rgba(255, 255, 255, .5)'
70 | focusProperties:
71 | scale: 1.1
72 | backgroundColor: '#fff'
73 | animationOptions:
74 | time: .2
75 | animationOptions:
76 | time: .2
77 | actions: [
78 | {
79 | keyCode: 41
80 | function: () ->
81 | print 'Something happens when moving the right stick up on my Gamepad, while focusing this focusable'
82 | }
83 | ]
84 | ```
85 |
86 | ### Properties
87 | All regular Layer-properties are applicable on Focusables.
88 | - focusProperties: `object` - An object containing properties to use when the Focusable is focused. This is pretty much just a state. This can hold animationOptions if you want to define the animation going from the default state to the focused state.
89 | - animationOptions?: `object` - An object that defines the animation options when going from the focused state to the default state.
90 | - actions?: `object[]` - An array of objects defining what should happen when a key-event occurs. Used to trigger functions when a pressing a button on a Gamepad or a keyboard.
91 | - keyCode: `number` - The key-code that should trigger the action. Can be a Keyboard-keycode or Gamepad-keycode.
92 | - function: `function` - The function to execute when desired keycode is pressed.
93 |
94 | ### Tips
95 | - If a focusable has opacity set to 0 or has visible set to false, that Focusable won't be navigatable. This means that it is handy to set one of these properties if you need to temporarily disable the navigation to a Focusable.
96 |
97 |
98 | ## Grid
99 | The Grid is used to achieve coherent and perfectly aligned prototypes. It primarly utilizes two methods that should be used whenever you want to define sizes & margins.
100 |
101 | The Grid contain 24 columns, 23 gutters and infinite vertical rows. Gutters & Rows are always 10px. Columns have a variable width, depending on how big the Screen is. When you want to define a size for an element, you can simply do like this:
102 |
103 | ```
104 | new Layer
105 | width: Grid.getWidth(4) # Returns the width of 4 columns + 3 gutters
106 | height: Grid.getHeight(15) # Returns the height of 15 rows
107 | ```
108 |
109 | ### Properties
110 | All these properties are readonly.
111 | - safezoneBounds: `object` - Returns width & height of the Grid safezone
112 | - dangerzoneBounds: `object` - Returns the width of the dangergap between the left screen edge and the horizontal safezone start point & height of the dangergap between the top screen edge and the vertical safezone start point.
113 | - columnCount: `number` - Returns the amount of columns in the Grid
114 | - columnGutterCount: `number` - Returns the amount of column gutters in the Grid
115 | - columnGutter: `number` - Returns the column gutter width
116 | - columnWidth: `number` - Returns the width of a single column
117 | - rowHeight: `number` - Returns the height of a row
118 |
119 | ### Methods
120 | - getWidth(columnCount: number) - Returns the pixels of the amount of columns + (amount of columns - 1) gutters
121 | - getHeight(rowCount: number) - Returns the pixels of rowCount * 10px
122 |
123 |
124 | ## Gamepad
125 | The Gamepad instance emits gamepadevents whenever you trigger a button on your gamepad.
126 | The Gamepad is an EventEmitter under the hood. To handle gamepad events, simply do like this:
127 |
128 | ```
129 | # If you want throttled events, uncomment the line below
130 | # Gamepad.throttle = true
131 |
132 | Gamepad.on 'gamepadevent', (e) ->
133 | # Gamepad Right Stick Going Up
134 | if e.keyCode == 41
135 | print 'Up'
136 |
137 | # Gamepad Right Stick Going Left
138 | if e.keyCode == 42
139 | print 'Left'
140 |
141 | # Gamepad Right Stick Going Down
142 | if e.keyCode == 43
143 | print 'Down'
144 |
145 | # Gamepad Right Stick Going Right
146 | if e.keyCode == 44
147 | print 'Right'
148 | ```
149 |
150 | ### Properties
151 | - throttle: `boolean` - By default, all gamepad events are emitted multiple times a second. Setting this value to true will limit the events to be emitted less frequently.
152 |
153 |
154 | ## FocusStore
155 | The FocusStore holds the state of all focusables within the current view. It emits events when focusing occurs.
156 | The FocusStore is an EventEmitter under the hood.
157 |
158 | ```
159 | FocusStore.on 'focusEvent', (focusEvent) ->
160 | # The focusEvent contain the previously focused focusable and the new, current focused focusable
161 | ```
162 |
163 | ## ViewStore
164 | The ViewStore holds the state of all focusables within the current view. It emits events when view-transitions occurs.
165 | The ViewStore is an EventEmitter under the hood.
166 |
167 | ```
168 | # Listen for transition events
169 | ViewStore.on 'transitionEvent', (transitionEvent) ->
170 | # The transitionEvent contain the previous view, the current view and the transition that was used
171 |
172 | # Transition to a view using the goIn transition
173 | ViewStore.transition(myView, Transitions.goIn)
174 |
175 | # Pop the history stack and go back to the previous screen
176 | ViewStore.goBack()
177 | ```
178 |
179 | ### Methods
180 | - transition(view: View, transition?: Transition) - Transitions to the specified view. Transition can be specified. You can use [these pre-defined transitions](https://github.com/emilwidlund/carousel/blob/master/dist/templates/modules/joystick/Transitions.coffee) or read more about them [here](https://medium.com/the-school-of-do/framer-cheat-sheet-flow-components-f7ca94632227#c7f7).
181 | - goBack() - Pops the history stack and goes back to the previous screen
182 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | const {app, ipcMain, dialog, BrowserWindow} = require('electron');
2 | const path = require('path');
3 |
4 | const server = require('./server');
5 |
6 | // Setup User Analytics
7 | const {trackEvent} = require('./analytics');
8 | global.trackEvent = trackEvent;
9 |
10 | let mainWindow;
11 | let previewWindow;
12 | let projectServerRunning;
13 |
14 | app.on('window-all-closed', () => {
15 | if (process.platform != 'darwin') {
16 | app.quit();
17 | }
18 |
19 | trackEvent('Window', 'All Windows Closed');
20 | });
21 |
22 | app.on('ready', () => {
23 | mainWindow = new BrowserWindow({
24 | title: 'Carousel',
25 | width: 1360,
26 | height: 800,
27 | icon: path.join(__dirname, 'dist/icons/png/64x64.png')
28 | });
29 | trackEvent('Window', 'Open', 'Main Window');
30 |
31 | mainWindow.loadFile('./dist/index.html');
32 |
33 | if (!app.isPackaged) {
34 | mainWindow.openDevTools();
35 | trackEvent('Window', 'Open Developer Tools', 'Main Window');
36 | }
37 |
38 | mainWindow.on('close', (e) => {
39 | e.preventDefault();
40 |
41 | if (projectServerRunning) {
42 | previewWindow.destroy();
43 | }
44 |
45 | dialog.showMessageBox({
46 | type: 'warning',
47 | buttons: ['Save', 'Don\'t Save', 'Cancel'],
48 | message: 'Your project has not been saved. How do you want to proceed?',
49 | noLink: true
50 | }, (response) => {
51 | if (response === 0) {
52 | mainWindow.webContents.send('save-project');
53 | trackEvent('Window', 'Save Project On Close', 'Main Window');
54 | } else if (response === 1) {
55 | mainWindow.destroy();
56 | trackEvent('Window', 'Don\'t Save Project On Close', 'Main Window');
57 | } else {
58 | trackEvent('Window', 'Cancel On Close', 'Main Window');
59 | }
60 | });
61 |
62 | });
63 |
64 | mainWindow.on('close', () => {
65 | if (previewWindow) {
66 | previewWindow.destroy();
67 | trackEvent('Window', 'Inheritly Closed', 'Preview Window');
68 | }
69 | });
70 |
71 | mainWindow.on('closed', () => {
72 | mainWindow = null;
73 | trackEvent('Window', 'Closed', 'Main Window');
74 | });
75 | });
76 |
77 | ipcMain.on('start-preview', (event, arg) => {
78 | if (projectServerRunning) return;
79 |
80 | server.start(arg, () => {
81 | projectServerRunning = true;
82 | trackEvent('Project Server', 'Start');
83 |
84 | previewWindow = new BrowserWindow({
85 | title: 'Preview',
86 | width: 640,
87 | height: 360,
88 | alwaysOnTop: true,
89 | experimentalFeatures: true,
90 | autoHideMenuBar: true,
91 | icon: path.join(__dirname, 'dist/icons/png/64x64.png')
92 | });
93 | trackEvent('Window', 'Open', 'Preview Window');
94 |
95 | previewWindow.loadURL('http://localhost:8010');
96 |
97 | if (!app.isPackaged) {
98 | previewWindow.openDevTools();
99 | trackEvent('Window', 'Open Developer Tools', 'Preview Window');
100 | }
101 |
102 | previewWindow.on('closed', () => {
103 | previewWindow = null;
104 | trackEvent('Window', 'Closed', 'Preview Window');
105 |
106 | projectServerRunning = false;
107 | server.close();
108 | trackEvent('Project Server', 'Closed', 'Preview Window');
109 | });
110 |
111 | mainWindow.on('closed', () => {
112 | projectServerRunning = false;
113 | server.close();
114 | trackEvent('Project Server', 'Closed', 'Main Window');
115 | });
116 | });
117 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "carousel",
3 | "productName": "Carousel",
4 | "version": "0.1.2",
5 | "description": "",
6 | "main": "main.js",
7 | "scripts": {
8 | "start": "electron .",
9 | "server": "./node_modules/.bin/webpack-dev-server",
10 | "watch": "webpack --watch",
11 | "bundle": "webpack",
12 | "build": "electron-builder",
13 | "release": "webpack && electron-builder -m -w"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/emilwidlund/carousel.git"
18 | },
19 | "build": {
20 | "appId": "com.carousel.framer",
21 | "win": {
22 | "icon": "dist/icons/win/icon.png"
23 | },
24 | "mac": {
25 | "category": "public.app-category.graphics-design",
26 | "icon": "dist/icons/mac/icon.png"
27 | },
28 | "directories": {
29 | "output": "build"
30 | },
31 | "fileAssociations": [
32 | {
33 | "ext": [
34 | "crsl"
35 | ],
36 | "name": "Carousel",
37 | "description": "Carousel Project File"
38 | }
39 | ]
40 | },
41 | "files": [
42 | "*.js",
43 | "dist",
44 | "node_modules"
45 | ],
46 | "keywords": [],
47 | "author": "Emil Widlund",
48 | "license": "ISC",
49 | "bugs": {
50 | "url": "https://github.com/emilwidlund/carousel/issues"
51 | },
52 | "homepage": "https://github.com/emilwidlund/carousel#readme",
53 | "dependencies": {
54 | "@babel/core": "^7.1.2",
55 | "@babel/plugin-proposal-class-properties": "^7.1.0",
56 | "@babel/plugin-proposal-decorators": "^7.1.2",
57 | "@babel/preset-env": "^7.1.0",
58 | "@types/classnames": "^2.2.6",
59 | "@types/electron-store": "^1.3.0",
60 | "@types/react": "^16.4.14",
61 | "@types/react-dom": "^16.0.8",
62 | "archiver": "^3.0.0",
63 | "babel-cli": "^6.26.0",
64 | "babel-loader": "^8.0.4",
65 | "classnames": "^2.2.6",
66 | "coffee-loader": "^0.9.0",
67 | "coffeescript": "^2.3.2",
68 | "css-loader": "^1.0.0",
69 | "electron-store": "^2.0.0",
70 | "mobx": "^5.5.0",
71 | "mobx-react": "^5.2.8",
72 | "monaco-editor": "^0.14.3",
73 | "monaco-editor-webpack-plugin": "^1.5.4",
74 | "node-sass": "^4.9.3",
75 | "react": "^16.5.2",
76 | "react-dom": "^16.5.2",
77 | "sass-loader": "^7.1.0",
78 | "style-loader": "^0.23.0",
79 | "temp": "^0.8.3",
80 | "universal-analytics": "^0.4.17",
81 | "unzip": "^0.1.11",
82 | "unzip-stream": "^0.3.0",
83 | "uuid": "^3.3.2",
84 | "walk": "^2.3.14",
85 | "webpack": "^4.20.2",
86 | "webpack-cli": "^3.1.1",
87 | "webpack-dev-server": "^3.1.9"
88 | },
89 | "devDependencies": {
90 | "awesome-typescript-loader": "^5.2.1",
91 | "electron": "^3.0.2",
92 | "electron-builder": "^20.28.4",
93 | "source-map-loader": "^0.2.4",
94 | "typescript": "^3.1.1"
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const WebpackDevServer = require('webpack-dev-server');
3 | const path = require('path');
4 | require('babel-loader');
5 |
6 | const PORT = 8010;
7 |
8 | let server;
9 |
10 | module.exports = {
11 | start: (servePath, cb) => {
12 |
13 | const config = {
14 | entry: {
15 | app: [path.resolve(__dirname, 'node_modules/webpack/hot/dev-server'), `${servePath}/app.js`]
16 | },
17 | resolve: {
18 | extensions: ['.js', '.coffee']
19 | },
20 | mode: 'development',
21 | target: 'web',
22 | output: {
23 | path: servePath,
24 | filename: 'bundle.js'
25 | },
26 | module: {
27 | rules: [
28 | {
29 | test: /\.js$/,
30 | loader: path.resolve(__dirname, 'node_modules/babel-loader'),
31 | options: {
32 | babelrc: false,
33 | presets: [path.resolve(__dirname, 'node_modules/@babel/preset-env')],
34 | plugins: [
35 | [path.resolve(__dirname, 'node_modules/@babel/plugin-proposal-decorators'), {legacy: true}],
36 | path.resolve(__dirname, 'node_modules/@babel/plugin-proposal-class-properties')
37 | ]
38 | }
39 | },
40 | {
41 | test: /\.coffee$/,
42 | use: [path.resolve(__dirname, 'node_modules/coffee-loader')]
43 | }
44 | ]
45 | },
46 | plugins: [new webpack.HotModuleReplacementPlugin()]
47 | }
48 |
49 | const options = {
50 | contentBase: servePath
51 | };
52 |
53 | server = new WebpackDevServer(webpack(config), options);
54 |
55 |
56 | return server.listen(PORT, 'localhost', (err) => {
57 | if (err) return console.error(err);
58 |
59 | console.log('Serrver running on port', PORT);
60 |
61 | if (cb) cb();
62 | });
63 | },
64 | close: (cb) => {
65 | server.close(cb);
66 | }
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/src/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import classnames from 'classnames';
3 |
4 | import {IButtonProps} from '../types';
5 |
6 | export default class Button extends React.Component {
7 | render() {
8 | return (
9 |
15 | );
16 | }
17 | }
--------------------------------------------------------------------------------
/src/components/Editor.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as monaco from 'monaco-editor';
3 | import {reaction} from 'mobx';
4 | import {inject, observer} from 'mobx-react';
5 |
6 | import {IEditorProps} from '../types';
7 |
8 | @inject('ProjectStore', 'EditorStore')
9 | @observer
10 | export default class Editor extends React.Component {
11 |
12 | _node: HTMLElement;
13 | _editor: monaco.editor.IStandaloneCodeEditor;
14 |
15 | componentDidMount() {
16 | const {options} = this.props;
17 | this._editor = monaco.editor.create(this._node, options);
18 | this.props.EditorStore.editor = this._editor;
19 |
20 | this._editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S, () => {
21 | this.props.ProjectStore.saveProject();
22 | }, '');
23 |
24 | reaction(
25 | () => this.props.EditorStore.selectedFile,
26 | (file) => {
27 | this._editor.setModel(file.model);
28 | }
29 | );
30 | }
31 |
32 | componentWillUnmount() {
33 | this._editor && this._editor.dispose();
34 | }
35 |
36 | renderMeta() {
37 | return (
38 |
39 |
{this.props.EditorStore.selectedFile.shortName}
40 | {this.props.EditorStore.selectedFile.name}
41 |
42 | );
43 | }
44 |
45 | render() {
46 | return (
47 |
48 | {this.props.EditorStore.selectedFile ? this.renderMeta() : null}
49 |
this._node = c}
52 | />
53 |
54 | );
55 | }
56 | }
--------------------------------------------------------------------------------
/src/components/Icon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import classnames from 'classnames';
3 |
4 | import {IIconProps} from '../types';
5 |
6 | const Icon: React.SFC
= (props) => (
7 |
16 | {props.name}
17 |
18 | );
19 |
20 | export default Icon;
--------------------------------------------------------------------------------
/src/components/Popup.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default class Popup extends React.Component {
4 | render() {
5 | return (
6 |
7 |
8 | {this.props.children}
9 |
10 |
11 | );
12 | }
13 | }
--------------------------------------------------------------------------------
/src/components/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as classnames from 'classnames';
3 | import {inject, observer} from 'mobx-react';
4 |
5 | import {remote, ipcRenderer} from 'electron';
6 | const shell = remote.shell;
7 |
8 | import Tabs from './Tabs';
9 | import Icon from './Icon';
10 | import Button from './Button';
11 | import snippets from '../snippets';
12 |
13 | import {
14 | ISidebarProps,
15 | IProjectFileCategoryHeaderProps,
16 | IProjectFileItemProps,
17 | IProjectFile,
18 | ProjectFileType
19 | } from '../types';
20 |
21 | @inject('ProjectStore', 'PopupStore')
22 | class CreateFilePopup extends React.Component {
23 |
24 | state = {
25 | filename: ''
26 | }
27 |
28 | render() {
29 |
30 | let title;
31 |
32 | switch(this.props.type) {
33 | case ProjectFileType.View:
34 | title = 'Create New View';
35 | break;
36 | case ProjectFileType.Component:
37 | title = 'Create New Component';
38 | break;
39 | case ProjectFileType.Generic:
40 | title = 'Create New Generic File';
41 | break;
42 | default:
43 | title = 'Create New Generic File';
44 | }
45 |
46 | return (
47 |
48 |
{title}
49 |
{
54 | this.setState({
55 | filename: e.target.value.trim()
56 | });
57 | }}
58 | />
59 |
60 |
76 |
77 | );
78 | }
79 | }
80 |
81 | @inject('PopupStore')
82 | class ProjectFileCategoryHeader extends React.Component {
83 | render() {
84 | return (
85 |
88 |
{this.props.title}
89 | {
90 | this.props.type === ProjectFileType.Image ?
91 | null :
92 | {
95 | this.props.PopupStore.displayPopup();
96 | }}
97 | />
98 | }
99 |
100 | );
101 | }
102 | }
103 |
104 |
105 | @inject('EditorStore')
106 | @observer
107 | class ProjectFileItem extends React.Component {
108 | handleClick() {
109 | this.props.EditorStore.selectFile(this.props.file);
110 | }
111 |
112 | render() {
113 |
114 | let iconName;
115 |
116 | switch (this.props.file.type) {
117 | case ProjectFileType.Main:
118 | iconName = 'all_inclusive';
119 | break;
120 | case ProjectFileType.View:
121 | iconName = 'panorama_horizontal';
122 | break;
123 | case ProjectFileType.Component:
124 | iconName = 'bubble_chart';
125 | break;
126 | case ProjectFileType.Image:
127 | iconName = 'image';
128 | break;
129 | default:
130 | iconName = 'description';
131 | }
132 |
133 | return (
134 |
141 |
142 |
143 | {
144 | this.props.file.type === ProjectFileType.Generic || this.props.file.type === ProjectFileType.Image ?
145 | this.props.file.name :
146 | this.props.file.shortName
147 | }
148 |
149 |
150 | );
151 | }
152 | }
153 |
154 |
155 |
156 | @inject('ProjectStore', 'EditorStore')
157 | @observer
158 | class ProjectPanelNavigator extends React.Component {
159 | render() {
160 | return (
161 |
162 |
Project Navigator
163 |
169 |
170 |
174 | {this.props.ProjectStore.viewFiles.map((file: IProjectFile, index: number) => {
175 | return (
176 |
181 | );
182 | })}
183 |
184 |
185 |
189 | {this.props.ProjectStore.componentFiles.map((file: IProjectFile, index: number) => {
190 | return (
191 |
196 | );
197 | })}
198 |
199 |
200 |
204 | {this.props.ProjectStore.imageFiles.map((file: IProjectFile, index: number) => {
205 | return (
206 |
211 | );
212 | })}
213 |
214 |
215 |
219 | {this.props.ProjectStore.genericFiles.map((file: IProjectFile, index: number) => {
220 | return (
221 |
226 | );
227 | })}
228 |
229 |
230 | );
231 | }
232 | }
233 |
234 |
235 | class ProjectPanel extends React.Component {
236 | render() {
237 | return (
238 |
241 | );
242 | }
243 | }
244 |
245 |
246 |
247 | @inject('ProjectStore', 'EditorStore')
248 | @observer
249 | class SnippetItem extends React.Component {
250 | handleClick() {
251 | if (this.props.ProjectStore.fileFormat == 'js') {
252 | this.props.EditorStore.pasteSnippet(this.props.snippet.jsSnippet());
253 | } else if (this.props.ProjectStore.fileFormat == 'coffee') {
254 | this.props.EditorStore.pasteSnippet(this.props.snippet.csSnippet());
255 | }
256 | }
257 |
258 | render() {
259 | return (
260 |
264 |
265 | {this.props.snippet.name}
266 |
267 | );
268 | }
269 | }
270 |
271 |
272 |
273 | class SnippetPanel extends React.Component {
274 | render() {
275 | return (
276 |
277 |
Snippets
278 |
279 | {snippets.map((s, i) => {
280 | return ;
281 | })}
282 |
283 |
284 | );
285 | }
286 | }
287 |
288 |
289 | @inject('ProjectStore')
290 | @observer
291 | export default class Sidebar extends React.Component {
292 | componentDidMount() {
293 | ipcRenderer.on('save-project', () => {
294 | this.props.ProjectStore.saveProject(() => {
295 | remote.getCurrentWindow().destroy();
296 | });
297 | });
298 | }
299 |
300 | render() {
301 | return (
302 |
318 | );
319 | }
320 | }
--------------------------------------------------------------------------------
/src/components/Tabs.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import classnames from 'classnames';
3 |
4 | import Icon from './Icon';
5 |
6 | class Tab extends React.Component {
7 | render() {
8 | const classNames = classnames([
9 | 'tab',
10 | this.props.active ? 'active' : null
11 | ]);
12 |
13 | return (
14 | this.props.selectTab(this.props.index)}
17 | >
18 |
21 |
22 | );
23 | }
24 | }
25 |
26 | class TabContent extends React.Component {
27 | render() {
28 | return (
29 |
30 | {this.props.content}
31 |
32 | );
33 | }
34 | }
35 |
36 | export default class Tabs extends React.Component {
37 |
38 | state = {
39 | selectedIndex: 0
40 | }
41 |
42 | constructor(props: any) {
43 | super(props);
44 |
45 | this.selectTab = this.selectTab.bind(this);
46 | }
47 |
48 | selectTab(index: number) {
49 | this.setState({
50 | selectedIndex: index
51 | });
52 | }
53 |
54 | render() {
55 | return (
56 |
57 |
58 | {this.props.tabs.map((tab: any, index: number) => {
59 | return (
60 |
67 | );
68 | })}
69 |
70 |
74 |
75 | );
76 | }
77 | }
--------------------------------------------------------------------------------
/src/components/Welcome.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import {inject, observer} from 'mobx-react';
3 | import classnames from 'classnames';
4 | import {remote} from 'electron';
5 | const dialog = remote.dialog;
6 | const shell = remote.shell;
7 | const os = remote.require('os');
8 | const path = remote.require('path');
9 | const trackEvent = remote.getGlobal('trackEvent');
10 |
11 | import Button from './Button';
12 | import Icon from './Icon';
13 | import {IRecentProjectsProps, IRecentProject} from '../types';
14 |
15 | const version = require('../../package.json').version;
16 |
17 |
18 | @inject('ProjectStore', 'PopupStore')
19 | @observer
20 | class NewProjectPopup extends React.Component {
21 |
22 | defaultFilename: string = 'Untitled.crsl';
23 |
24 | state = {
25 | filename: path.resolve(os.homedir(), 'Documents', this.defaultFilename),
26 | projectType: 'js'
27 | }
28 |
29 | handleProjectTypeChange(e: any) {
30 | this.setState({
31 | projectType: e.target.value
32 | });
33 | }
34 |
35 | render() {
36 | return (
37 |
38 |
Create New Project
39 |
40 | {
44 | this.setState({
45 | filename: e.target.value
46 | });
47 | }}
48 | />
49 | {
53 | dialog.showSaveDialog({defaultPath: this.defaultFilename}, (filename: string) => {
54 | if (!filename) return;
55 | this.setState({
56 | filename: filename
57 | });
58 | });
59 | }}
60 | />
61 |
62 |
82 |
83 | {
86 | this.props.ProjectStore.createProject(this.state.projectType, this.state.filename, () => {
87 | this.props.PopupStore.disposePopup();
88 | trackEvent('Popup', 'Dispose', 'New Project');
89 | });
90 | }}
91 | />
92 | {
96 | this.props.PopupStore.disposePopup();
97 | trackEvent('Popup', 'Cancel', 'New Project');
98 | }}
99 | />
100 |
101 |
102 | );
103 | }
104 | }
105 |
106 |
107 |
108 | @inject('ProjectStore')
109 | @observer
110 | class RecentProjects extends React.Component {
111 | render() {
112 | return (
113 |
114 | {this.props.ProjectStore.recentProjects.map((p: IRecentProject, i: number) => {
115 | return (
116 |
{
120 | this.props.ProjectStore.initializeProject(p.path);
121 | }}
122 | >
123 |
{p.name}.crsl
124 | {p.path}
125 |
126 | );
127 | })}
128 |
129 | );
130 | }
131 | }
132 |
133 |
134 | @inject('ProjectStore', 'PopupStore')
135 | @observer
136 | export default class Welcome extends React.Component {
137 |
138 | handleNewProject() {
139 | this.props.PopupStore.displayPopup();
140 | trackEvent('Popup', 'Display', 'New Project');
141 | }
142 |
143 | handleOpenProject() {
144 | dialog.showOpenDialog({
145 | filters: [
146 | {
147 | name: 'Carousel',
148 | extensions: ['crsl']
149 | }
150 | ]
151 | }, (filepaths: string[]) => {
152 | if (!filepaths) return;
153 | this.props.ProjectStore.initializeProject(filepaths[0]);
154 | });
155 | }
156 |
157 | openLink(url: string) {
158 | shell.openExternal(url);
159 | trackEvent('Outbound Link', 'Click', url);
160 | }
161 |
162 | render() {
163 | return (
164 |
165 |
166 |
167 |

168 |
Welcome to Carousel
169 |
Version {version}
170 |
171 |
175 |
179 |
New Project
180 |
181 |
185 |
189 |
Open Project
190 |
191 |
192 |
193 |
194 |
this.openLink('https://twitter.com/emilwidlund')}>Built by Emil Widlund
195 |
this.openLink('https://github.com/koenbok/Framer')}>Powered by Framer Library
196 |
197 |
198 |
199 |
200 | );
201 | }
202 | }
--------------------------------------------------------------------------------
/src/components/_Editor.scss:
--------------------------------------------------------------------------------
1 | #editor-wrapper {
2 | display: flex;
3 | flex-direction: column;
4 | flex-grow: 1;
5 | background-color: #000;
6 | width: 100%;
7 | height: 100%;
8 |
9 | .editor-meta {
10 | display: flex;
11 | flex-direction: column;
12 | padding: 30px 30px;
13 | // border-bottom: 1px solid $color-divider;
14 |
15 | h2 {
16 | margin-top: 10px;
17 | }
18 | }
19 |
20 | .editor {
21 | display: flex;
22 | flex-direction: column;
23 | flex-grow: 1;
24 | flex-shrink: 1;
25 | }
26 | }
--------------------------------------------------------------------------------
/src/components/_Popup.scss:
--------------------------------------------------------------------------------
1 | .popup-container {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: center;
5 | align-items: center;
6 | position: absolute;
7 | top: 0;
8 | right: 0;
9 | bottom: 0;
10 | left: 0;
11 | background-color: rgba(0, 0, 0, .8);
12 | z-index: 999999;
13 |
14 | .popup-content {
15 | display: flex;
16 | flex-direction: column;
17 | min-width: 500px;
18 | min-height: 250px;
19 | border-radius: 20px;
20 | background-color: #050505;
21 | padding: 40px;
22 | text-align: center;
23 | }
24 | }
--------------------------------------------------------------------------------
/src/components/_Sidebar.scss:
--------------------------------------------------------------------------------
1 | $sidebar-padding-horizontal: 20px;
2 |
3 | #sidebar {
4 | display: flex;
5 | flex-direction: column;
6 | flex-grow: 0;
7 | flex-shrink: 0;
8 | overflow-y: auto;
9 | width: 380px;
10 | background-color: $color-bg-sidebar;
11 | border-right: 1px solid $color-divider;
12 | user-select: none;
13 |
14 | .project-panel-navigator {
15 | display: flex;
16 | flex-direction: column;
17 | flex-grow: 1;
18 | padding: 10px 0 0;
19 |
20 | h3 {
21 | padding: 5px $sidebar-padding-horizontal;
22 | }
23 |
24 | .views, .components, .images, .generic {
25 | padding: 10px 0;
26 |
27 | .file-category-header {
28 | display: flex;
29 | flex-direction: row;
30 | justify-content: space-between;
31 | align-items: center;
32 | padding: 0 $sidebar-padding-horizontal;
33 |
34 | .icon {
35 | color: #555;
36 | }
37 | }
38 | }
39 | }
40 |
41 | .snippet-panel {
42 | padding: 10px 0 0;
43 |
44 | h3 {
45 | padding: 5px $sidebar-padding-horizontal;
46 | }
47 |
48 | .snippet-items {
49 | display: flex;
50 | flex-direction: column;
51 |
52 | .sidebar-interactive-item {
53 | span {
54 | color: #fff;
55 | }
56 | }
57 | }
58 | }
59 |
60 | .sidebar-interactive-item {
61 | display: flex;
62 | align-items: center;
63 | padding: 10px $sidebar-padding-horizontal;
64 |
65 | span {
66 | margin-left: 15px;
67 | }
68 |
69 | .icon {
70 | color: $color-accent;
71 | }
72 |
73 | &.active {
74 | color: #fff;
75 | background-color: rgba(255, 255, 255, .05);
76 |
77 | &:hover {
78 | background-color: rgba(255, 255, 255, .05);
79 | }
80 | }
81 |
82 | &:hover {
83 | background-color: rgba(255, 255, 255, .05);
84 | }
85 | }
86 | }
87 |
88 | .create-new-file-popup {
89 | display: flex;
90 | flex-direction: column;
91 | align-items: center;
92 |
93 | input {
94 | margin-top: 20px;
95 | width: 200px;
96 | }
97 |
98 | .new-file-actions {
99 | display: flex;
100 | flex-direction: row;
101 | justify-content: center;
102 | user-select: none;
103 | margin-top: 40px;
104 |
105 | .secondary {
106 | margin-left: 20px;
107 | }
108 | }
109 | }
--------------------------------------------------------------------------------
/src/components/_Tabs.scss:
--------------------------------------------------------------------------------
1 | .tabs {
2 | display: flex;
3 | flex-direction: row;
4 | flex-grow: 1;
5 |
6 | .tabs-header {
7 | display: flex;
8 | flex-direction: column;
9 | cursor: default;
10 | border-right: 1px solid $color-divider;
11 |
12 | .tab {
13 | display: flex;
14 | flex-direction: column;
15 | justify-content: center;
16 | align-items: center;
17 | width: 65px;
18 | height: 65px;
19 |
20 | .icon {
21 | color: rgba(255, 255, 255, .33);
22 | }
23 |
24 | &.active {
25 | background-color: $color-accent;
26 |
27 | .icon {
28 | color: #fff;
29 | }
30 |
31 | &:hover {
32 | background-color: $color-accent;
33 | }
34 | }
35 |
36 | &:hover {
37 | background-color: rgba(255, 255, 255, .1);
38 | }
39 | }
40 | }
41 |
42 | .tab-content {
43 | display: flex;
44 | flex-direction: column;
45 | overflow-y: auto;
46 | flex-grow: 1;
47 | }
48 | }
--------------------------------------------------------------------------------
/src/components/_Welcome.scss:
--------------------------------------------------------------------------------
1 | #welcome {
2 | display: flex;
3 | justify-content: center;
4 | flex-grow: 1;
5 | background-color: $color-bg-welcome;
6 | user-select: none;
7 |
8 | .welcome-info {
9 | display: flex;
10 | align-items: center;
11 | flex-direction: column;
12 | flex-grow: 1;
13 |
14 | .welcome-info-content {
15 | display: flex;
16 | flex-direction: column;
17 | justify-content: center;
18 | align-items: center;
19 | text-align: center;
20 | flex-grow: 1;
21 |
22 | img {
23 | height: 150px;
24 | }
25 |
26 | .welcome-actions {
27 | display: flex;
28 | flex-direction: row;
29 | justify-content: space-between;
30 | width: 400px;
31 | margin-top: 40px;
32 |
33 | .action {
34 | display: flex;
35 | flex-direction: column;
36 | align-items: center;
37 | padding: 30px 40px;
38 | background-color: #111;
39 | border-radius: 10px;
40 | margin-top: 20px;
41 |
42 | .icon {
43 | color: $color-accent;
44 | }
45 |
46 | h3 {
47 | margin-top: 30px;
48 | text-align: center;
49 | }
50 |
51 | &:hover {
52 | background-color: #151515;
53 | cursor: pointer;
54 | }
55 | }
56 | }
57 | }
58 |
59 | .welcome-meta {
60 | padding: 30px 0;
61 | text-align: center;
62 |
63 | h4, p {
64 | cursor: pointer;
65 | }
66 | }
67 | }
68 |
69 | .recent-projects {
70 | display: flex;
71 | flex-direction: column;
72 | flex-shrink: 0;
73 | width: 300px;
74 | border-left: 1px solid $color-divider;
75 |
76 | .recent-project {
77 | display: flex;
78 | flex-direction: column;
79 | padding: 15px 20px;
80 |
81 | &:hover {
82 | background-color: rgba(255, 255, 255, .05);
83 | }
84 |
85 | h4 {
86 | margin-top: 0;
87 | }
88 |
89 | span {
90 | white-space: nowrap;
91 | overflow: hidden;
92 | text-overflow: ellipsis;
93 | font-size: $font-size-xs;
94 | }
95 | }
96 | }
97 | }
98 |
99 | .new-project-popup {
100 | display: flex;
101 | flex-direction: column;
102 | flex-grow: 1;
103 |
104 | .new-project-path {
105 | display: flex;
106 | flex-direction: row;
107 | justify-content: center;
108 | margin-top: 20px;
109 |
110 | input {
111 | width: 200px;
112 | }
113 |
114 | button {
115 | margin-left: 10px;
116 | }
117 | }
118 |
119 | .new-project-type {
120 | display: flex;
121 | flex-direction: row;
122 | justify-content: center;
123 | padding: 30px 0 40px;
124 | user-select: none;
125 |
126 | div {
127 | display: flex;
128 | flex-direction: row;
129 | justify-content: baseline;
130 |
131 | span {
132 | margin-left: 10px;
133 | margin-top: 2px;
134 | }
135 |
136 | &:last-of-type {
137 | margin-left: 30px;
138 | }
139 |
140 | &.active {
141 | span {
142 | color: #fff;
143 | }
144 | }
145 | }
146 | }
147 |
148 | .new-project-actions {
149 | display: flex;
150 | flex-direction: row;
151 | justify-content: center;
152 | user-select: none;
153 |
154 | .secondary {
155 | margin-left: 20px;
156 | }
157 | }
158 | }
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import {Provider, observer} from 'mobx-react';
4 | import * as monaco from 'monaco-editor';
5 | import {ipcRenderer, shell} from 'electron';
6 |
7 | import * as stores from './stores';
8 | import Icon from './components/Icon';
9 | import Popup from './components/Popup';
10 | import Sidebar from './components/Sidebar';
11 | import Welcome from './components/Welcome';
12 | import Editor from './components/Editor';
13 |
14 | import {remote} from 'electron';
15 |
16 | import './scss/main.scss';
17 |
18 |
19 |
20 | const framerTheme = require('./misc/framer-theme.json');
21 | monaco.editor.defineTheme('syntax', framerTheme);
22 |
23 |
24 | const AppAction = (props: any) => {
25 | return (
26 |
27 |
28 | {props.text}
29 |
30 | );
31 | }
32 |
33 |
34 | @observer
35 | class App extends React.Component {
36 |
37 | componentDidMount() {
38 | if (remote.app.isPackaged) {
39 | if (remote.process.argv.length >= 2) {
40 | const filePath = remote.process.argv[1];
41 | if (filePath) {
42 | stores.ProjectStore.initializeProject(filePath);
43 | }
44 | }
45 | }
46 | }
47 |
48 | renderContent() {
49 | return (
50 |
51 |
52 |
53 |

54 |
{stores.ProjectStore.projectName}
55 |
56 |
57 |
{
61 | ipcRenderer.send('start-preview', stores.ProjectStore.projectTempPath);
62 | }}
63 | />
64 | {
68 | shell.openItem(stores.ProjectStore.projectTempPath);
69 | }}
70 | />
71 | {
75 | shell.openExternal('https://classic.framer.com/docs');
76 | }}
77 | />
78 |
79 |
80 |
81 |
82 |
95 |
96 |
97 | );
98 | }
99 |
100 | render() {
101 | return (
102 |
103 |
104 | {
105 | stores.PopupStore.popupContent ?
106 |
{stores.PopupStore.popupContent} :
107 | null
108 | }
109 | {
110 | stores.ProjectStore.projectInitialized ?
111 | this.renderContent() :
112 |
113 | }
114 |
115 |
116 | );
117 | }
118 | }
119 |
120 | ReactDOM.render(
121 | ,
122 | document.getElementById('carousel')
123 | );
--------------------------------------------------------------------------------
/src/misc/app-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emilwidlund/carousel/1ab0e12800cdbe84c160e471bb7b98bc8b37b005/src/misc/app-icon.png
--------------------------------------------------------------------------------
/src/misc/framer-theme.json:
--------------------------------------------------------------------------------
1 | {
2 | "base": "vs-dark",
3 | "inherit": true,
4 | "rules": [],
5 | "colors": {
6 | "activityBar.background": "#111",
7 | "activityBar.border": "#111",
8 | "activityBar.dropBackground": "#000",
9 | "activityBar.foreground": "#fff",
10 | "activityBarBadge.foreground": "#FFF",
11 |
12 | "activityBarBadge.background": "#00AEFF",
13 | "list.activeSelectionForeground": "#fff",
14 | "list.inactiveSelectionForeground": "#fff",
15 | "list.highlightForeground": "#00AEFF",
16 | "scrollbarSlider.activeBackground": "#777",
17 | "editorSuggestWidget.highlightForeground": "#fff",
18 | "textLink.foreground": "#00AEFF",
19 | "progressBar.background": "#00AEFF",
20 | "pickerGroup.foreground": "#00AEFF",
21 | "tab.activeBorder": "#00AEFF",
22 | "notificationLink.foreground": "#00AEFF",
23 |
24 | "badge.foreground": "#fff",
25 |
26 | "button.background": "#272727",
27 | "button.foreground": "#fff",
28 | "button.hoverBackground": "#333",
29 |
30 | "contrastBorder": "#000",
31 |
32 | "debugExceptionWidget.background": "#272727",
33 | "debugExceptionWidget.border": "#2D2D2D",
34 | "debugToolBar.background": "#141414",
35 |
36 | "descriptionForeground": "#808080",
37 |
38 | "diffEditor.insertedTextBackground": "#333",
39 | "diffEditor.insertedTextBorder": "#2D2D2D",
40 | "diffEditor.removedTextBackground": "#333",
41 | "diffEditor.removedTextBorder": "#2D2D2D",
42 |
43 | "dropdown.background": "#333",
44 | "dropdown.border": "#2D2D2D",
45 | "dropdown.foreground": "#fff",
46 |
47 | "editor.background": "#000",
48 |
49 | "editor.foreground": "#fff",
50 |
51 | "editor.findMatchBackground": "#555",
52 |
53 | "editor.findMatchHighlightBackground": "#333",
54 |
55 | "editor.findRangeHighlightBackground": "#333",
56 |
57 | "editor.hoverHighlightBackground": "#333",
58 |
59 | "editor.inactiveSelectionBackground": "#333",
60 |
61 | "editor.lineHighlightBackground": "#111",
62 | "editor.lineHighlightBorder": "#111",
63 | "editor.rangeHighlightBackground": "#222",
64 |
65 | "editor.selectionBackground": "#111",
66 |
67 | "editor.selectionHighlightBackground": "#111",
68 |
69 | "editor.wordHighlightStrongBackground": "#111",
70 |
71 | "editor.wordHighlightBackground": "#111",
72 | "editorBracketMatch.background": "#333",
73 | "editorBracketMatch.border": "#333",
74 | "editorCodeLens.foreground": "#333",
75 | "editorCursor.background": "#111",
76 | "editorCursor.foreground": "#fff",
77 | "editorError.border": "#2D2D2D",
78 | "editorError.foreground": "#e27e8d",
79 |
80 | "editorGutter.background": "#000",
81 | "editorGutter.deletedBackground": "#e27e8d",
82 | "editorGutter.modifiedBackground": "#111",
83 |
84 | "editorGroup.background": "#111",
85 | "editorGroup.border": "#2D2D2D",
86 | "editorGroup.dropBackground": "#111",
87 |
88 | "editorGroupHeader.tabsBackground": "#111",
89 | "editorGroupHeader.tabsBorder": "#2d2d2d",
90 |
91 | "editorHoverWidget.background": "#111",
92 | "editorHoverWidget.border": "#2d2d2d",
93 | "editorIndentGuide.background": "#111",
94 | "editorLineNumber.foreground": "#333",
95 | "editorLineNumber.activeForeground": "#777",
96 | "editorLink.activeForeground": "#888",
97 |
98 | "editorMarkerNavigation.background": "#111",
99 | "editorMarkerNavigationError.background": "#111",
100 | "editorMarkerNavigationWarning.background": "#2d2d2d",
101 |
102 | "editorOverviewRuler.border": "#2d2d2d",
103 | "editorOverviewRuler.commonContentForeground": "#000",
104 | "editorOverviewRuler.currentContentForeground": "#000",
105 | "editorOverviewRuler.incomingContentForeground": "#000",
106 | "editorRuler.foreground": "#000",
107 |
108 | "editorSuggestWidget.background": "#111",
109 | "editorSuggestWidget.border": "#2d2d2d",
110 | "editorSuggestWidget.foreground": "#888",
111 | "editorSuggestWidget.selectedBackground": "#333",
112 |
113 | "editorWarning.border": "#ffffff00",
114 | "editorWarning.foreground": "#e27e8d",
115 | "editorWhitespace.foreground": "#444",
116 | "editorWidget.background": "#111",
117 | "editorWidget.border": "#2d2d2d",
118 | "errorForeground": "#e27e8d",
119 |
120 | "extensionButton.prominentBackground": "#333",
121 | "extensionButton.prominentForeground": "#fff",
122 | "extensionButton.prominentHoverBackground": "#222",
123 | "focusBorder": "#2d2d2d",
124 | "foreground": "#888",
125 |
126 | "input.background": "#111",
127 | "input.border": "#111",
128 | "input.foreground": "#fff",
129 | "input.placeholderForeground": "#888",
130 | "inputOption.activeBorder": "#eee",
131 | "inputValidation.errorBackground": "#FF5459",
132 | "inputValidation.errorBorder": "#4C292A",
133 | "inputValidation.infoBackground": "#5ec4ff",
134 | "inputValidation.infoBorder": "#539afc",
135 | "inputValidation.warningBackground": "#fff",
136 | "inputValidation.warningBorder": "#d98e48",
137 |
138 | "list.activeSelectionBackground": "#333",
139 | "list.dropBackground": "#000",
140 | "list.focusBackground": "#333",
141 | "list.focusForeground": "#888",
142 | "list.hoverBackground": "#333",
143 | "list.hoverForeground": "#fff",
144 | "list.inactiveSelectionBackground": "#333",
145 |
146 | "merge.border": "#ffffff00",
147 | "merge.commonContentBackground": "#ffffff00",
148 | "merge.commonHeaderBackground": "#ffffff00",
149 | "merge.currentContentBackground": "#ffffff00",
150 | "merge.currentHeaderBackground": "#ffffff00",
151 | "merge.incomingContentBackground": "#ffffff00",
152 | "merge.incomingHeaderBackground": "#ffffff00",
153 |
154 | "panel.background": "#111",
155 | "panel.border": "#2D2D2D",
156 | "panelTitle.activeBorder": "#2D2D2D",
157 | "panelTitle.activeForeground": "#888",
158 | "panelTitle.inactiveForeground": "#333",
159 |
160 | "peekView.border": "#2D2D2D",
161 | "peekViewEditor.background": "#111",
162 | "peekViewEditor.matchHighlightBackground": "#333",
163 | "peekViewEditorGutter.background": "#111",
164 | "peekViewResult.background": "#111",
165 | "peekViewResult.fileForeground": "#fff",
166 | "peekViewResult.lineForeground": "#fff",
167 | "peekViewResult.matchHighlightBackground": "#333",
168 | "peekViewResult.selectionBackground": "#333",
169 | "peekViewResult.selectionForeground": "#fff",
170 | "peekViewTitle.background": "#111",
171 | "peekViewTitleDescription.foreground": "#888",
172 | "peekViewTitleLabel.foreground": "#00AEFF",
173 |
174 | "pickerGroup.border": "#333",
175 |
176 | "scrollbar.shadow": "#000",
177 | "scrollbarSlider.background": "#333",
178 | "scrollbarSlider.hoverBackground": "#333",
179 |
180 | "selection.background": "#fff",
181 |
182 | "sideBar.background": "#111",
183 | "sideBar.border": "#111",
184 | "sideBar.foreground": "#888",
185 | "sideBarSectionHeader.background": "#111",
186 | "sideBarSectionHeader.foreground": "#888",
187 | "sideBarTitle.foreground": "#888",
188 |
189 | "statusBar.background": "#111",
190 | "statusBar.border": "#111",
191 | "statusBar.debuggingBackground": "#111",
192 | "statusBar.debuggingForeground": "#666",
193 | "statusBar.foreground": "#666",
194 | "statusBar.noFolderBackground": "#111",
195 | "statusBar.noFolderForeground": "#666",
196 | "statusBarItem.activeBackground": "#09f",
197 | "statusBarItem.hoverBackground": "#333",
198 |
199 | "tab.activeBackground": "#111",
200 | "tab.activeForeground": "#fff",
201 | "tab.border": "#111",
202 | "tab.inactiveBackground": "#111",
203 | "tab.inactiveForeground": "#888",
204 | "tab.unfocusedActiveForeground": "#888",
205 | "tab.unfocusedInactiveForeground": "#888",
206 |
207 | "textBlockQuote.background": "#111",
208 | "textBlockQuote.border": "#111",
209 | "textCodeBlock.background": "#111",
210 | "textLink.activeForeground": "#00AEFF",
211 | "textPreformat.foreground": "#888",
212 | "textSeparator.foreground": "#888",
213 | "titleBar.activeBackground": "#111",
214 | "titleBar.activeForeground": "#fff",
215 | "titleBar.inactiveBackground": "#111",
216 | "titleBar.inactiveForeground": "#777",
217 | "walkThrough.embeddedEditorBackground": "#111",
218 | "welcomePage.buttonBackground": "#111",
219 | "welcomePage.buttonHoverBackground": "#333",
220 | "widget.shadow": "#000"
221 | },
222 | "encodedTokenColors": [
223 | {
224 | "name": "Comment",
225 | "scope": ["comment", "punctuation.definition.comment"],
226 | "settings": {
227 | "foreground": "#888"
228 | }
229 | },
230 | {
231 | "name": "Constant",
232 | "scope": "constant",
233 | "settings": {
234 | "foreground": "#FFD14A"
235 | }
236 | },
237 | {
238 | "name": "Entity",
239 | "scope": "entity",
240 | "settings": {
241 | "foreground": "#00AEFF"
242 | }
243 | },
244 | {
245 | "name": "Keyword",
246 | "scope": "keyword",
247 | "settings": {
248 | "foreground": "#FFD14A"
249 | }
250 | },
251 | {
252 | "name": "Keywords",
253 | "scope": ["keyword.control.conditional", "keyword.control.import"],
254 | "settings": {
255 | "foreground": "#FFD14A"
256 | }
257 | },
258 | {
259 | "name": "Punctuation",
260 | "scope": "punctuation",
261 | "settings": {
262 | "foreground": "#888"
263 | }
264 | },
265 | {
266 | "name": "Punctuation Parameters",
267 | "scope": "punctuation.definition.parameters",
268 | "settings": {
269 | "foreground": "#888"
270 | }
271 | },
272 | {
273 | "name": "Function Parameters",
274 | "scope": [
275 | "entity.name.variable.parameter",
276 | "meta.at-rule.function variable",
277 | "meta.at-rule.mixin variable",
278 | "meta.function.arguments",
279 | "meta.selectionset.graphql meta.arguments.graphql variable.arguments.graphql",
280 | "variable.parameter"
281 | ],
282 | "settings": {
283 | "foreground": "#fff"
284 | }
285 | },
286 | {
287 | "name": "Punctuation Template Expression",
288 | "scope": "punctuation.definition.template-expression",
289 | "settings": {
290 | "foreground": "#FFD14A"
291 | }
292 | },
293 | {
294 | "name": "Storage",
295 | "scope": "storage",
296 | "settings": {
297 | "foreground": "#8df"
298 | }
299 | },
300 | {
301 | "name": "Storage Type Arrow Function",
302 | "scope": "storage.type.function.arrow",
303 | "settings": {
304 | "foreground": "#FFD14A"
305 | }
306 | },
307 | {
308 | "name": "String",
309 | "scope": ["string", "punctuation.definition.string"],
310 | "settings": {
311 | "foreground": "#91DD64"
312 | }
313 | },
314 | {
315 | "name": "String Template",
316 | "scope": ["string.template", "punctuation.definition.string.template"],
317 | "settings": {
318 | "foreground": "#91DD64"
319 | }
320 | },
321 | {
322 | "name": "Support",
323 | "scope": "support",
324 | "settings": {
325 | "foreground": "#00AEFF"
326 | }
327 | },
328 | {
329 | "name": "Support Function",
330 | "scope": ["support.function"],
331 | "settings": {
332 | "foreground": "#00AEFF"
333 | }
334 | },
335 | {
336 | "name": "[JAVASCRIPT JSX] React Class",
337 | "scope": ["support.class.component.js"],
338 | "settings": {
339 | "foreground": "#00AEFF"
340 | }
341 | },
342 | {
343 | "name": "Variable",
344 | "scope": "variable",
345 | "settings": {
346 | "foreground": "#fff"
347 | }
348 | },
349 | {
350 | "name": "[CSS] - Entity",
351 | "scope": [
352 | "source.css",
353 | "source.stylus",
354 | "source.scss",
355 | "entity.other.attribute-name.class.css"
356 | ],
357 | "settings": {
358 | "foreground": "#00AEFF"
359 | }
360 | },
361 | {
362 | "name": "[CSS] - Hex hex",
363 | "scope": ["punctuation.definition.constant.css"],
364 | "settings": {
365 | "foreground": "#a8f"
366 | }
367 | },
368 | {
369 | "name": "[CSS] - keys",
370 | "scope": ["support.type.property-name.css"],
371 | "settings": {
372 | "foreground": "#fff"
373 | }
374 | },
375 | {
376 | "name": "[CSS] - Keyword",
377 | "scope": [
378 | "source.css punctuation.definition.keyword",
379 | "source.css keyword.control"
380 | ],
381 | "settings": {
382 | "foreground": "#f6b"
383 | }
384 | },
385 | {
386 | "name": "[CSS] - Important",
387 | "scope": "keyword.other.important.scss",
388 | "settings": {
389 | "foreground": "#f6b"
390 | }
391 | },
392 | {
393 | "name": "[CSS] - Punctiation",
394 | "scope": ["punctuation.definition.entity.css"],
395 | "settings": {
396 | "foreground": "#00AEFF"
397 | }
398 | },
399 | {
400 | "name": "[CSS] - ID hastags",
401 | "scope": ["entity.other.attribute-name.pseudo-element.css"],
402 | "settings": {
403 | "foreground": "#00AEFF"
404 | }
405 | },
406 | {
407 | "name": "[CSS] - support misc function",
408 | "scope": ["support.function.misc.scss"],
409 | "settings": {
410 | "foreground": "#a8f"
411 | }
412 | },
413 | {
414 | "name": "[CSS] - ID Selector",
415 | "scope": ["entity.other.attribute-name.id.css"],
416 | "settings": {
417 | "foreground": "#8df"
418 | }
419 | },
420 | {
421 | "name": "[CSS] - Element Selector",
422 | "scope": "entity.name.tag.css",
423 | "settings": {
424 | "foreground": "#00AEFF"
425 | }
426 | },
427 | {
428 | "name": "[CSS] - Support",
429 | "scope": [
430 | "source.css support",
431 | "source.stylus support",
432 | "source.css support.constant"
433 | ],
434 | "settings": {
435 | "foreground": "#fff"
436 | }
437 | },
438 | {
439 | "name": "[CSS] - Constant",
440 | "scope": ["source.stylus constant", "source.css constant"],
441 | "settings": {
442 | "foreground": "#a8f"
443 | }
444 | },
445 | {
446 | "name": "[CSS] - support constant",
447 | "scope": ["support.constant.property-value.css"],
448 | "settings": {
449 | "foreground": "#FFD14A"
450 | }
451 | },
452 | {
453 | "name": "[CSS] - units",
454 | "scope": [
455 | "keyword.other.unit.css",
456 | "keyword.other.unit.px.css",
457 | "keyword.other.unit.percentage.css",
458 | "constant.other.color.rgb-value.hex.css",
459 | "keyword.other.unit.ms.css",
460 | "keyword.other.unit.s.css",
461 | "keyword.other.unit.vh.css",
462 | "keyword.other.unit.vw.css",
463 | "keyword.other.unit.deg.css",
464 | "keyword.other.unit.fr.css"
465 | ],
466 | "settings": {
467 | "foreground": "#a8f"
468 | }
469 | },
470 | {
471 | "name": "[CSS] - String",
472 | "scope": [
473 | "source.css string",
474 | "source.css punctuation.definition.string",
475 | "source.stylus string",
476 | "source.stylus punctuation.definition.string"
477 | ],
478 | "settings": {
479 | "foreground": "#98EC65"
480 | }
481 | },
482 | {
483 | "name": "[CSS] - Variable",
484 | "scope": ["source.css variable", "source.stylus variable"],
485 | "settings": {
486 | "foreground": "#FFD14A"
487 | }
488 | },
489 | {
490 | "name": "[HTML] - Entity",
491 | "scope": ["entity.other"],
492 | "settings": {
493 | "foreground": "#8df"
494 | }
495 | },
496 | {
497 | "name": "[HTML] - Elements",
498 | "scope": [
499 | "entity.name.tag.block.any.html",
500 | "entity.name.tag.inline.any.html",
501 | "entity.name.tag.structure.any.html",
502 | "entity.name.tag.html",
503 | "meta.jsx.children.tsx"
504 | ],
505 | "settings": {
506 | "foreground": "#00AAEF"
507 | }
508 | },
509 | {
510 | "name": "[HTML] - Keywords",
511 | "scope": ["punctuation.definition.keyword", "keyword.control"],
512 | "settings": {
513 | "foreground": "#FFD14A"
514 | }
515 | },
516 | {
517 | "name": "[HTML] - ID value",
518 | "scope": "meta.toc-list.id.html",
519 | "settings": {
520 | "foreground": "#91DD64"
521 | }
522 | },
523 | {
524 | "name": "[HTML] - Meta tags",
525 | "scope": ["meta.tag.block.any.html", "meta.tag.inline.any.html"],
526 | "settings": {
527 | "foreground": "#00AEFF"
528 | }
529 | },
530 | {
531 | "name": "[HTML] - Tags",
532 | "scope": [
533 | "punctuation.definition.tag.end.html",
534 | "punctuation.definition.tag.begin.html"
535 | ],
536 | "settings": {
537 | "foreground": "#777"
538 | }
539 | },
540 | {
541 | "name": "[HTML] - Text basic",
542 | "scope": "text.html.basic",
543 | "settings": {
544 | "foreground": "#c3c3c3"
545 | }
546 | },
547 | {
548 | "name": "[HTML] - Quotes",
549 | "scope":
550 | "punctuation.definition.string.begin, punctuation.definition.string.end",
551 | "settings": {
552 | "foreground": "#91DD64"
553 | }
554 | },
555 | {
556 | "name": "[JAVASCRIPT] - Numbers",
557 | "scope": "source.js constant",
558 | "settings": {
559 | "foreground": "#a8f"
560 | }
561 | },
562 | {
563 | "name": "[JAVASCRIPT] - Keywords",
564 | "scope": "keyword.operator",
565 | "settings": {
566 | "foreground": "#f6b"
567 | }
568 | },
569 | {
570 | "name": "[JAVASCRIPT] - Keywords",
571 | "scope": "source.js keyword",
572 | "settings": {
573 | "foreground": "#f6b"
574 | }
575 | },
576 |
577 | {
578 | "name": "[JAVASCRIPT] - Storage Type Function",
579 | "scope": "source.js storage.type.function",
580 | "settings": {
581 | "foreground": "#fff"
582 | }
583 | },
584 | {
585 | "name": "[JAVASCRIPT] - Keywords",
586 | "scope": [
587 | "source.js punctuation.definition.keyword",
588 | "source.js keyword.control"
589 | ],
590 | "settings": {
591 | "foreground": "#FFD14A"
592 | }
593 | },
594 | {
595 | "name": "[JAVASCRIPT] - Keywords",
596 | "scope": [
597 | "source.js punctuation.definition.keyword",
598 | "source.js keyword.control"
599 | ],
600 | "settings": {
601 | "foreground": "#FFD14A"
602 | }
603 | },
604 | {
605 | "name": "[JAVASCRIPT] - Variable Language",
606 | "scope":
607 | "variable.language, entity.name.type.class.js, meta.tag.js, entity.name.tag.js",
608 | "settings": {
609 | "foreground": "#FFD14A"
610 | }
611 | },
612 | {
613 | "name": "[JAVASCRIPT] - Inherited Component",
614 | "scope": [
615 | "entity.other.inherited-class.js",
616 | "variable.language.this.js",
617 | "variable.other.readwrite.alias.js",
618 | "meta.import.js"
619 | ],
620 | "settings": {
621 | "foreground": "#FFD14A"
622 | }
623 | },
624 | {
625 | "name": "Meta",
626 | "scope": "meta",
627 | "settings": {
628 | "foreground": "#fff"
629 | }
630 | },
631 | {
632 | "name": "Meta Brace",
633 | "scope": "meta.brace",
634 | "settings": {
635 | "foreground": "#888"
636 | }
637 | },
638 | {
639 | "name": "Support Variable Property DOM",
640 | "scope": "support.variable.property.dom",
641 | "settings": {
642 | "foreground": "#00AEFF"
643 | }
644 | },
645 | {
646 | "name": "[JSON] - Support",
647 | "scope": "source.json support",
648 | "settings": {
649 | "foreground": "#fff"
650 | }
651 | },
652 | {
653 | "name": "[JSON] - String",
654 | "scope": [
655 | "source.json string",
656 | "source.json punctuation.definition.string",
657 | "punctuation.definition.string.end.json",
658 | "punctuation.definition.string.begin.json"
659 | ],
660 | "settings": {
661 | "foreground": "#a8f"
662 | }
663 | }
664 | ]
665 | }
--------------------------------------------------------------------------------
/src/scss/_fonts.scss:
--------------------------------------------------------------------------------
1 |
2 |
3 | h1, h2, h3, h4 {
4 | font-weight: 400;
5 | margin: .8em 0;
6 | color: #fff;
7 | }
8 |
9 | h1 {
10 | font-size: $font-size-xxl;
11 | }
12 |
13 | h2 {
14 | font-size: $font-size-l;
15 | }
16 |
17 | h3 {
18 | font-size: $font-size-m;
19 | }
20 |
21 | h4 {
22 | font-size: $font-size-s;
23 | }
24 |
25 | .material-icon {
26 | position: relative;
27 | font-family: 'Material Icons';
28 | font-weight: normal;
29 | font-style: normal;
30 | font-size: 24px; /* Preferred icon size */
31 | display: inline-block;
32 | line-height: 1;
33 | text-transform: none;
34 | letter-spacing: normal;
35 | word-wrap: normal;
36 | white-space: nowrap;
37 | direction: ltr;
38 |
39 | /* Support for all WebKit browsers. */
40 | -webkit-font-smoothing: antialiased;
41 | /* Support for Safari and Chrome. */
42 | text-rendering: optimizeLegibility;
43 |
44 | /* Support for Firefox. */
45 | -moz-osx-font-smoothing: grayscale;
46 |
47 | /* Support for IE. */
48 | font-feature-settings: 'liga';
49 |
50 | &.action {
51 |
52 | &:before {
53 | content: '';
54 | position: absolute;
55 | top: 50%;
56 | left: 50%;
57 | width: 150%;
58 | height: 150%;
59 | border-radius: 75%;
60 | background-color: rgba(255, 255, 255, .1);
61 | transform: scale(.8) translate(-50%, -50%);
62 | opacity: 0;
63 |
64 | -webkit-transition: all .1s ease-in-out;
65 | }
66 |
67 | &:hover {
68 | color: #fff !important;
69 |
70 | &:before {
71 | transform: scale(1) translate(-50%, -50%);
72 | opacity: 1;
73 | }
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/src/scss/_generic.scss:
--------------------------------------------------------------------------------
1 | button {
2 | background-color: $color-accent;
3 | color: #fff;
4 | border: none;
5 | font-family: inherit;
6 | font-size: $font-size-xs;
7 | padding: 10px 15px 8px;
8 | border-radius: 50px;
9 |
10 | &.secondary {
11 | background-color: #222;
12 | }
13 |
14 | &:hover {
15 | opacity: .8;
16 | }
17 |
18 | &:focus {
19 | outline: none !important;
20 | }
21 | }
22 |
23 | input {
24 | background-color: transparent;
25 | padding: 0 0 3px;
26 | border: 0;
27 | border-bottom: 1px solid rgba(255, 255, 255, .2);
28 | color: #fff;
29 |
30 | &:focus {
31 | outline: none !important;
32 | border-bottom: 1px solid #fff;
33 | }
34 | }
35 |
36 | input[type="radio"] {
37 | position: relative;
38 | width: 16px;
39 | height: 16px;
40 | border-radius: 8px;
41 | background-color: #333;
42 | border: none;
43 | margin: 0;
44 | -webkit-appearance: none;
45 |
46 | &:checked {
47 | background-color: $color-accent;
48 | }
49 |
50 | &:checked:after {
51 | content: '';
52 | position: absolute;
53 | top: 5px;
54 | left: 5px;
55 | height: 6px;
56 | width: 6px;
57 | border-radius: 3px;
58 | background-color: #fff;
59 | }
60 | }
--------------------------------------------------------------------------------
/src/scss/_reset.scss:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
7 | margin: 0;
8 | padding: 0;
9 | border: 0;
10 | font-size: 100%;
11 | font: inherit;
12 | vertical-align: baseline;
13 | }
14 |
15 | /* HTML5 display-role reset for older browsers */
16 |
17 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
18 | display: block; }
19 |
20 | body {
21 | line-height: 1; }
22 |
23 | ol, ul {
24 | list-style: none; }
25 |
26 | blockquote, q {
27 | quotes: none; }
28 |
29 | blockquote {
30 | &:before, &:after {
31 | content: '';
32 | content: none; } }
33 |
34 | q {
35 | &:before, &:after {
36 | content: '';
37 | content: none; } }
38 |
39 | table {
40 | border-collapse: collapse;
41 | border-spacing: 0; }
42 |
43 |
44 | div {
45 | box-sizing: border-box;
46 | }
--------------------------------------------------------------------------------
/src/scss/_variables.scss:
--------------------------------------------------------------------------------
1 |
2 | // ----- COLORS
3 |
4 | $color-accent: #4258ff;
5 | $color-text: #888;
6 | $color-divider: #111;
7 | $color-bg-sidebar: #000;
8 | $color-bg-welcome: #000;
9 |
10 |
11 | // ----- FONT SIZES
12 |
13 | $font-size-xxl: 48px;
14 | $font-size-xl: 32px;
15 | $font-size-l: 22px;
16 | $font-size-m: 18px;
17 | $font-size-s: 16px;
18 | $font-size-xs: 14px;
19 | $font-size-xxs: 12px;
--------------------------------------------------------------------------------
/src/scss/main.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 | @import 'reset';
3 | @import 'fonts';
4 | @import 'generic';
5 |
6 | body {
7 | display: flex;
8 | margin: 0;
9 | font-family: 'Roboto', sans-serif;
10 | font-size: $font-size-s;
11 | color: $color-text;
12 | height: 100%;
13 | width: 100%;
14 |
15 | #carousel, #carousel-content, #app {
16 | display: flex;
17 | flex-direction: column;
18 | width: 100%;
19 | height: 100%;
20 |
21 | .app-header {
22 | display: flex;
23 | align-items: center;
24 | height: 80px;
25 | padding: 0 20px;
26 | background-color: #000;
27 | user-select: none;
28 | border-bottom: 1px solid $color-divider;
29 |
30 | .app-project {
31 | display: flex;
32 | flex-direction: row;
33 | align-items: center;
34 | width: 360px;
35 |
36 | img {
37 | height: 25px;
38 | }
39 |
40 | .project-name {
41 | margin: 0 0 0 20px;
42 | padding: 5px 0 0 20px;
43 | border-left: 1px solid rgba(255, 255, 255, .2);
44 | font-size: $font-size-m;
45 | white-space: nowrap;
46 | overflow: hidden;
47 | text-overflow: ellipsis;
48 | }
49 | }
50 |
51 | .app-actions {
52 | display: flex;
53 | height: 100%;
54 |
55 | .action {
56 | display: flex;
57 | align-items: center;
58 | padding: 0 20px;
59 | color: #888;
60 |
61 | .icon {
62 | font-size: 18px;
63 | }
64 |
65 | .label {
66 | margin-left: 10px;
67 | padding-top: 5px;
68 | font-size: $font-size-xs;
69 | }
70 |
71 | &:hover {
72 | color: #fff;
73 | background-color: rgba(255, 255, 255, .05);
74 | }
75 | }
76 | }
77 | }
78 |
79 | .app-content {
80 | display: flex;
81 | width: 100%;
82 | height: 100%;
83 | }
84 | }
85 | }
86 |
87 |
88 | // Scrollbar Style
89 | ::-webkit-scrollbar {
90 | width: 6px;
91 | height: 6px;
92 | background-color: #131313;
93 | }
94 |
95 | ::-webkit-scrollbar-thumb {
96 | background-color: #333;
97 | }
98 |
99 |
100 | @import '../components/Sidebar';
101 | @import '../components/Welcome';
102 | @import '../components/Editor';
103 | @import '../components/Popup';
104 | @import '../components/Tabs';
--------------------------------------------------------------------------------
/src/snippets.ts:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | name: 'Gamepad',
4 | icon: 'gamepad',
5 | jsSnippet: () => {
6 | return [
7 | `// If you want throttled events, uncomment the line below`,
8 | `// Gamepad.throttle = true;`,
9 | ``,
10 | `Gamepad.on('gamepadevent', (e) => {`,
11 | `\t// Gamepad Right Stick Going Up`,
12 | `\tif (e.keyCode == 41) {`,
13 | `\t\tprint('Up');`,
14 | `\t}`,
15 | `\t`,
16 | `\t// Gamepad Right Stick Going Left`,
17 | `\tif (e.keyCode == 42) {`,
18 | `\t\tprint('Left');`,
19 | `\t}`,
20 | `\t`,
21 | `\t// Gamepad Right Stick Going Down`,
22 | `\tif (e.keyCode == 43) {`,
23 | `\t\tprint('Down');`,
24 | `\t}`,
25 | `\t`,
26 | `\t// Gamepad Right Stick Going Right`,
27 | `\tif (e.keyCode == 44) {`,
28 | `\t\tprint('Right');`,
29 | `\t}`,
30 | `});`
31 | ].join('\n');
32 | },
33 | csSnippet: () => {
34 | return [
35 | `# If you want throttled events, uncomment the line below`,
36 | `# Gamepad.throttle = true`,
37 | ``,
38 | `Gamepad.on 'gamepadevent', (e) ->`,
39 | `\t# Gamepad Right Stick Going Up`,
40 | `\tif e.keyCode == 41`,
41 | `\t\tprint 'Up'`,
42 | ``,
43 | `\t# Gamepad Right Stick Going Left`,
44 | `\tif e.keyCode == 42`,
45 | `\t\tprint 'Left'`,
46 | ``,
47 | `\t# Gamepad Right Stick Going Down`,
48 | `\tif e.keyCode == 43`,
49 | `\t\tprint 'Down'`,
50 | ``,
51 | `\t# Gamepad Right Stick Going Right`,
52 | `\tif e.keyCode == 44`,
53 | `\t\tprint 'Right'`
54 | ].join('\n');
55 | }
56 | },
57 |
58 | {
59 | name: 'Focusable',
60 | icon: 'view_carousel',
61 | jsSnippet: () => {
62 | return [
63 | `new Focusable({`,
64 | `\tparent: null, // Insert parent`,
65 | `\twidth: Grid.getWidth(5),`,
66 | `\theight: Grid.getHeight(35),`,
67 | `\tbackgroundColor: 'rgba(255, 255, 255, .2)',`,
68 | `\tfocusProperties: {`,
69 | `\t\tscale: 1.1,`,
70 | `\t\tbackgroundColor: '#fff',`,
71 | `\t\tanimationOptions: {`,
72 | `\t\t\ttime: .2`,
73 | `\t\t}`,
74 | `\t},`,
75 | `\tanimationOptions: {`,
76 | `\t\ttime: .2`,
77 | `\t}`,
78 | `});`
79 | ].join('\n');
80 | },
81 | csSnippet: () => {
82 | return [
83 | `new Focusable`,
84 | `\tparent: null # Insert parent`,
85 | `\twidth: Grid.getWidth(5)`,
86 | `\theight: Grid.getHeight(35)`,
87 | `\tbackgroundColor: 'rgba(255, 255, 255, .2)'`,
88 | `\tfocusProperties:`,
89 | `\t\tscale: 1.1`,
90 | `\t\tbackgroundColor: '#fff'`,
91 | `\t\tanimationOptions:`,
92 | `\t\t\ttime: .2`,
93 | `\tanimationOptions:`,
94 | `\t\ttime: .2`
95 | ].join('\n');
96 | }
97 | }
98 | ]
99 |
100 |
101 |
--------------------------------------------------------------------------------
/src/stores/EditorStore.ts:
--------------------------------------------------------------------------------
1 | import {observable} from 'mobx';
2 | import * as monaco from 'monaco-editor';
3 |
4 | import ProjectStore from './ProjectStore';
5 | import {IProjectFile} from '../types';
6 |
7 | export class EditorStore {
8 | @observable selectedFile: IProjectFile = null;
9 | @observable editor: monaco.editor.IStandaloneCodeEditor = null;
10 |
11 | selectFile(file: IProjectFile) {
12 | if (this.selectedFile && this.selectedFile.model) {
13 | ProjectStore.saveFile(this.selectedFile, () => {
14 | this.selectedFile = file;
15 | });
16 | } else {
17 | this.selectedFile = file;
18 | }
19 | }
20 |
21 | pasteSnippet(snippet: string) {
22 | const line = this.editor.getPosition();
23 |
24 | const range = new monaco.Range(line.lineNumber, line.column, line.lineNumber, line.column);
25 | const id = { major: 1, minor: 1 };
26 | const text = snippet;
27 | const op = {identifier: id, range: range, text: text, forceMoveMarkers: true};
28 | this.editor.executeEdits('', [op]);
29 | }
30 | }
31 |
32 | const editorStore = new EditorStore();
33 | export default editorStore;
--------------------------------------------------------------------------------
/src/stores/PopupStore.ts:
--------------------------------------------------------------------------------
1 | import {observable} from 'mobx';
2 |
3 | export class PopupStore {
4 | @observable popupContent: JSX.Element = null;
5 |
6 | displayPopup(content: JSX.Element) {
7 | this.popupContent = content;
8 | }
9 |
10 | disposePopup() {
11 | this.popupContent = null;
12 | }
13 | }
14 |
15 | const popupStore = new PopupStore();
16 | export default popupStore;
--------------------------------------------------------------------------------
/src/stores/ProjectStore.ts:
--------------------------------------------------------------------------------
1 | import {observable} from 'mobx';
2 | import * as monaco from 'monaco-editor';
3 |
4 | import {remote, ipcRenderer} from 'electron';
5 | const trackEvent = remote.getGlobal('trackEvent');
6 | const fs = remote.require('fs');
7 | const walk = remote.require('walk');
8 | const path = remote.require('path');
9 | const archiver = remote.require('archiver');
10 | const unzip = remote.require('unzip-stream');
11 | const temp = remote.require('temp').track();
12 | const Store = remote.require('electron-store');
13 |
14 | import EditorStore from './EditorStore';
15 |
16 | import {IRecentProject, IProjectFile, ProjectFileType} from '../types';
17 |
18 | const store = new Store();
19 |
20 | export class ProjectStore {
21 |
22 | recentProjects: IRecentProject[] = [];
23 |
24 | @observable projectInitialized = false;
25 | @observable projectPath: string = null;
26 | @observable projectTempPath: string = null;
27 | @observable projectName: string = null;
28 | @observable projectFiles: IProjectFile[] = [];
29 |
30 | constructor() {
31 | const projects = store.get('recent-projects') || [];
32 |
33 | // Make sure that recentProjects exists on disc
34 | this.recentProjects = projects.filter((p: IRecentProject) => {
35 | return fs.existsSync(p.path);
36 | });
37 | }
38 |
39 | createProject(projectType: string, projectPath: string, cb?: Function) {
40 | const projectTemplate: string = projectType === 'coffee' ?
41 | path.join(remote.app.getAppPath(), './dist/templates/project-coffeescript') :
42 | path.join(remote.app.getAppPath(), './dist/templates/project-javascript');
43 |
44 | const modulesTemplate: string = path.join(remote.app.getAppPath(), './dist/templates/modules');
45 |
46 | const output = fs.createWriteStream(projectPath);
47 | const archive = archiver('zip');
48 |
49 | output.on('close', () => {
50 | console.log('Project Created!');
51 | trackEvent('Project', 'Created', projectType);
52 | if (cb) cb();
53 | this.initializeProject(projectPath);
54 | });
55 |
56 | archive.pipe(output);
57 | archive.directory(projectTemplate, false);
58 | archive.directory(modulesTemplate, 'modules');
59 | archive.finalize();
60 | }
61 |
62 | initializeProject(projectPath: string) {
63 | projectPath = path.resolve(projectPath);
64 |
65 | if (!projectPath.endsWith('.crsl')) return;
66 |
67 | this.projectPath = projectPath;
68 | this.projectName = path.parse(projectPath).name;
69 |
70 | temp.mkdir(`carousel-${this.projectName}`, (err: Error, dirPath: string) => {
71 | fs.createReadStream(projectPath)
72 | .pipe(unzip.Extract({path: dirPath}))
73 | .on('close', () => {
74 |
75 | this.traverseFiles(dirPath, (files: IProjectFile[]) => {
76 |
77 | this.buildModels(files);
78 |
79 | this.projectFiles = files;
80 | this.projectTempPath = dirPath;
81 |
82 | ipcRenderer.send('start-preview', this.projectTempPath);
83 |
84 | this.projectInitialized = true;
85 | trackEvent('Project', 'Initialized', this.fileFormat);
86 |
87 | this.addToRecentProjects({
88 | path: this.projectPath,
89 | name: this.projectName,
90 | lastOpen: Date.now()
91 | });
92 |
93 | EditorStore.selectFile(this.mainFile);
94 | });
95 | });
96 | });
97 | }
98 |
99 | traverseFiles(p: string, cb?: Function) {
100 | const walker = walk.walk(p);
101 |
102 | const files: IProjectFile[] = [];
103 |
104 | walker.on('file', (root: string, fileStats: any, next: Function) => {
105 |
106 | let type;
107 |
108 | if (fileStats.name.includes('Main.js') || fileStats.name.includes('Main.coffee')) {
109 | type = ProjectFileType.Main;
110 | } else if (fileStats.name.includes('.view.js') || fileStats.name.includes('.view.coffee')) {
111 | type = ProjectFileType.View;
112 | } else if (fileStats.name.includes('.component.js') || fileStats.name.includes('.component.coffee')) {
113 | type = ProjectFileType.Component;
114 | } else if (fileStats.name.includes('.generic.js') || fileStats.name.includes('.generic.coffee')) {
115 | type = ProjectFileType.Generic;
116 | } else if (fileStats.name.endsWith('.jpg') || fileStats.name.endsWith('.png') || fileStats.name.endsWith('.gif')) {
117 | type = ProjectFileType.Image;
118 | } else if (fileStats.name.endsWith('.JPG') || fileStats.name.endsWith('.PNG') || fileStats.name.endsWith('.GIF')) {
119 | type = ProjectFileType.Image;
120 | }
121 |
122 | if (type != null) {
123 | const projectFile: IProjectFile = {
124 | name: fileStats.name,
125 | shortName: fileStats.name.substring(0, fileStats.name.indexOf('.')),
126 | path: path.resolve(root, fileStats.name),
127 | model: null,
128 | selected: false,
129 | type: type
130 | }
131 |
132 | files.push(projectFile);
133 | }
134 |
135 | next();
136 | });
137 |
138 | walker.on('end', () => {
139 | cb(files);
140 | });
141 | }
142 |
143 | buildModels(files: IProjectFile[]) {
144 | files.map((f, i) => {
145 | if (f.name.endsWith('.js')) {
146 | f.model = monaco.editor.createModel(fs.readFileSync(f.path, 'utf8'), 'javascript');
147 | } else if (f.name.endsWith('.coffee')) {
148 | f.model = monaco.editor.createModel(fs.readFileSync(f.path, 'utf8'), 'coffeescript');
149 | }
150 | });
151 | }
152 |
153 | saveFile(file: IProjectFile, cb?: Function) {
154 | fs.writeFile(file.path, file.model.getValue(), (err: Error) => {
155 | if (err) return console.error(err);
156 |
157 | console.log('File Saved!');
158 | trackEvent('Project File', 'Saved', file.path);
159 |
160 | if (cb) cb();
161 | });
162 | }
163 |
164 | saveProject(cb?: Function) {
165 | this.saveFile(EditorStore.selectedFile, () => {
166 | const output = fs.createWriteStream(this.projectPath);
167 | const archive = archiver('zip');
168 |
169 | output.on('close', () => {
170 | console.log('Project Saved!');
171 | trackEvent('Project', 'Saved', this.fileFormat);
172 | if (cb) cb();
173 | });
174 |
175 | archive.pipe(output);
176 | archive.directory(this.projectTempPath, false);
177 | archive.finalize();
178 | });
179 | }
180 |
181 | createNewFile(fileType: ProjectFileType, fileName: string, cb?: Function) {
182 | let filePath: string;
183 | let type: string;
184 |
185 | switch(fileType) {
186 | case ProjectFileType.View:
187 | filePath = `views/${fileName}.view.`;
188 | type = 'view';
189 | break;
190 | case ProjectFileType.Component:
191 | filePath = `components/${fileName}.component.`;
192 | type = 'component';
193 | break;
194 | case ProjectFileType.Generic:
195 | filePath = `generic/${fileName}.generic.`;
196 | type = 'generic';
197 | break;
198 | default:
199 | filePath = `generic/${fileName}.generic.`;
200 | type = 'generic';
201 | }
202 |
203 | filePath += this.fileFormat;
204 |
205 | fs.writeFile(path.resolve(this.projectTempPath, filePath), '', (err: Error) => {
206 | if (err) return console.error(err);
207 |
208 | const file: IProjectFile = {
209 | name: fileName + '.' + type + '.' + this.fileFormat,
210 | shortName: fileName,
211 | path: path.resolve(this.projectTempPath, filePath),
212 | model: null,
213 | selected: false,
214 | type: fileType
215 | };
216 |
217 | if (this.fileFormat === 'js') {
218 | file.model = monaco.editor.createModel('', 'javascript');
219 | } else if (this.fileFormat === 'coffee') {
220 | file.model = monaco.editor.createModel('', 'coffeescript');
221 | }
222 |
223 | this.projectFiles.push(file);
224 |
225 | trackEvent('Project File', 'Created', file.path);
226 |
227 | if (cb) cb(path.resolve(this.projectTempPath, filePath));
228 | });
229 | }
230 |
231 | addToRecentProjects(project: IRecentProject) {
232 | let existingProject = this.recentProjects.find(p => p.path === project.path);
233 |
234 | if (existingProject) {
235 | const existingIndex = this.recentProjects.indexOf(existingProject);
236 | this.recentProjects[existingIndex] = project;
237 |
238 | this.recentProjects.sort((a: IRecentProject, b: IRecentProject) => {
239 | return b.lastOpen - a.lastOpen;
240 | });
241 | } else {
242 | this.recentProjects.unshift(project);
243 | }
244 |
245 | store.set('recent-projects', this.recentProjects);
246 | }
247 |
248 | get fileFormat() {
249 | const mainFilePath = this.mainFile.path;
250 | return mainFilePath.slice(mainFilePath.lastIndexOf('.') + 1, mainFilePath.length);
251 | }
252 |
253 | get mainFile() {
254 | return this.projectFiles.filter(f => f.type === ProjectFileType.Main)[0];
255 | }
256 |
257 | get viewFiles() {
258 | return this.projectFiles.filter(f => f.type === ProjectFileType.View);
259 | }
260 |
261 | get componentFiles() {
262 | return this.projectFiles.filter(f => f.type === ProjectFileType.Component);
263 | }
264 |
265 | get imageFiles() {
266 | return this.projectFiles.filter(f => f.type === ProjectFileType.Image);
267 | }
268 |
269 | get genericFiles() {
270 | return this.projectFiles.filter(f => f.type === ProjectFileType.Generic);
271 | }
272 | }
273 |
274 | const projectStore = new ProjectStore();
275 | export default projectStore;
--------------------------------------------------------------------------------
/src/stores/index.ts:
--------------------------------------------------------------------------------
1 | import ProjectStore from './ProjectStore';
2 | import EditorStore from './EditorStore';
3 | import PopupStore from './PopupStore';
4 |
5 | export {
6 | ProjectStore,
7 | EditorStore,
8 | PopupStore
9 | }
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import * as monaco from 'monaco-editor';
2 |
3 |
4 |
5 | // ----- PROJECT
6 |
7 | export interface IRecentProject {
8 | path: string;
9 | name: string;
10 | lastOpen: number;
11 | }
12 |
13 | export interface IRecentProjectsProps {
14 | ProjectStore?: any;
15 | }
16 |
17 | export enum ProjectFileType {
18 | Main,
19 | View,
20 | Component,
21 | Image,
22 | Generic
23 | }
24 |
25 | export interface IProjectFile {
26 | name: string;
27 | shortName: string;
28 | path: string;
29 | model: monaco.editor.IModel;
30 | selected: boolean;
31 | type: ProjectFileType;
32 | }
33 |
34 |
35 | // ----- EDITOR
36 |
37 | export interface IEditorProps {
38 | EditorStore?: any;
39 | ProjectStore?: any;
40 | value?: string;
41 | language?: string;
42 | uri?: monaco.Uri;
43 | options?: monaco.editor.IEditorConstructionOptions;
44 | onValueChange?(value: string): void;
45 | }
46 |
47 |
48 | // ----- SIDEBAR
49 |
50 | export interface IProjectFileCategoryHeaderProps {
51 | title: string;
52 | type: ProjectFileType;
53 | PopupStore?: any;
54 | }
55 |
56 | export interface IProjectFileItemProps {
57 | file: IProjectFile;
58 | selected: boolean;
59 | EditorStore?: any;
60 | }
61 |
62 | export interface ISidebarProps {
63 | ProjectStore?: any;
64 | EditorStore?: any;
65 | }
66 |
67 |
68 | // ----- BUTTON
69 |
70 | export interface IButtonProps {
71 | text: string;
72 | secondary?: boolean;
73 | onClick?(): void;
74 | }
75 |
76 |
77 | // ----- ICON
78 |
79 | export interface IIconProps {
80 | name: string;
81 | size?: number;
82 | color?: string;
83 | onClick?(): void;
84 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./dist",
4 | "sourceMap": true,
5 | "noImplicitAny": true,
6 | "module": "commonjs",
7 | "target": "es5",
8 | "lib": ["es2015", "dom"],
9 | "jsx": "react",
10 | "declaration": true,
11 | "declarationDir": "./dist/types",
12 | "experimentalDecorators": true
13 | },
14 | "include": [
15 | "./src/**/*"
16 | ],
17 | "exclude": [
18 | "node_modules",
19 | "dist"
20 | ]
21 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
3 |
4 | module.exports = {
5 | entry: {
6 | app: ['webpack/hot/dev-server', './src/index.tsx']
7 | },
8 | output: {
9 | filename: 'bundle.js',
10 | },
11 | mode: 'development',
12 | devtool: 'source-map',
13 | target: 'electron-renderer',
14 | resolve: {
15 | extensions: ['.ts', '.tsx', '.js', '.json']
16 | },
17 | devServer: {
18 | contentBase: './dist',
19 | port: 8000
20 | },
21 | module: {
22 | rules: [
23 | {
24 | test: /\.tsx?$/,
25 | loader: 'awesome-typescript-loader'
26 | },
27 | {
28 | test: /\.css?$/,
29 | use: ['style-loader', 'css-loader']
30 | },
31 | {
32 | test: /\.scss$/,
33 | use: [
34 | {
35 | loader: 'style-loader'
36 | },
37 | {
38 | loader: 'css-loader'
39 | },
40 | {
41 | loader: 'sass-loader'
42 | }
43 | ]
44 | }
45 | ]
46 | },
47 | plugins: [
48 | new webpack.optimize.LimitChunkCountPlugin({
49 | maxChunks: 1
50 | }),
51 | new webpack.HotModuleReplacementPlugin,
52 | new MonacoWebpackPlugin()
53 | ]
54 | }
--------------------------------------------------------------------------------