114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Printer Simulator
2 | ##Introduction
3 | The Printer Simulator allows app developers to test the Forge 3D Print API without requiring a physical printer.
4 | It provides all the same functionality as a "regular" 3D printer, however its 3D printing is virtual.
5 | The Printer Simulator also acts as a reference implementation for printer firmware designers.
6 |
7 | To see the Forge API documentation and make meaningful use of the Printer Simulator, sign up to the developers portal at
8 | https://developer.autodesk.com.
9 |
10 | ##Contents
11 | 1. Getting Started
12 | 2. The Printer Simulator Interface
13 | 3. Calling the Printer Simulator from an App
14 |
15 | ###1. Getting Started
16 | The Printer Simulator is a simple HTML/Javascript page. It requires no client side setup except for the HTML and associated JS and CSS files.
17 | 1. Download this entire repository.
18 | 2. Open the HTML file in Chrome or Firefox. Since the simulator runs as a straight HTML file, no server setup is required.
19 |
20 | The simulator has 3 sections:
21 |
22 | a. The LCD screen, which mimics a printer display.
23 | b. The log, which reports printer activity, debug messages sent to Forge cloud services, and the storage state of the printer (token, registration state, etc.).
24 | c. The buttons, which allow you to register a printer, perform health checks (report the printer's online/offline status), and control print jobs (resume, pause, cancel).
25 |
26 | ###2. The Printer Simulator Interface
27 |
28 |
a. Registration buttons
29 |
30 | New Token - Completely resets the printer state and issues a new printer ID. Members who had registered to use the printer need to re-register as if it were a different printer.
31 |
32 | Get Token - Returns the locally stored token. Re-registration is not required; the printer ID remains unchanged.
33 |
34 |
b. Health Check buttons
35 |
36 | Online - The Printer Simulator sends regular messages (health checks) to the Forge server notifying it of its status. Print jobs and commands can be sent to the Printer Simulator.
37 |
38 | Offline - The Printer Simulator does not communicate with the Forge server. No print jobs or commands can be sent to the Printer Simulator.
39 |
40 |
c. Print buttons
41 |
42 | Resume - Resume printing a paused print job: Only active if the Printer Simulator is "printing" a print job and the pause button was pressed. The Printer Simulator will send a status check message to the Forge server, saying it has resumed printing.
43 |
44 | Pause - Pause printing an active print job: Only active if the Printer Simulator is "printing" a print job. The Printer Simulator will send a status check message to the Forge server saying that it has paused.
45 |
46 | Cancel - Cancel printing an active print job: Only active if the Printer Simulator is "printing" a print job. The Printer Simulator will send a status check message to the Forge server saying that it has paused.
47 |
48 |
d. Local Print Job buttons
49 |
50 | Local - Start printing a virtual print job on the Printer Simulator. This print job is "locally initiated" and not sent by an app. If the printer is online it will notify the Forge server of the local print job, and apps can send commands to the Printer Simulator that affect the print job.
51 |
52 | ###3. Calling the Printer Simulator from an App
53 | All the endpoint calls shown below are documented in the Forge developer portal.
54 |
55 |
a. Authentication
56 | All endpoint calls originating from the application require an Authorization header with an access token.
57 | For a guide about obtaining an access token, see our tutorial on Generating an Access Token.
58 |
59 |
b. Testing REST endpoint calls
60 | Endpoint calls can be tested using a REST client such as Postman, so you can update the token in a single location without needing to update each endpoint.
61 |
62 |
c. Registering to use the Printer Simulator
63 | Unless you register to use the Printer Simulator you will not be able to send any jobs or commands to it from an app.
64 |
65 | 1. Click New Token to connect the printer to the Forge server and retrieve a registration code. The registration code will be displayed on the Printer Simulator's log. This registers you as the "printer owner" of the Printer Simulator.
66 | 2. Call the POST printers/register endpoint.
67 | The following example uses the Postman REST client.
68 |
69 | ```
70 | POST /api/v1/print/printers/register HTTP/1.1
71 | Host: developer.api.autodesk.com
72 | Authorization: Bearer jDG4YLSTAUlI33k79KYJ2f4Q5nAZ
73 | Cache-Control: no-cache
74 | Content-Type: application/x-www-form-urlencoded
75 |
76 | printer_name=my+makerbot®istration_code=YKNRKT
77 |
78 | Response:
79 | {
80 | "registered": true,
81 | "printer_id": 58
82 | }
83 | ```
84 | The Printer Simulator actively listens for registration events. Calling the POST printers/register endpoint will cause the Forge server to notify the Printer Simulator that a registration has taken place and a message will appear in the log:
85 | ```
86 | Received message from server:{"registration":"success","type":"primary","printer_id":58,"member_id":20711941}
87 | ```
88 |
d. Making a health check
89 | Once the printer is registered, it will start sending health check messages to the server - the Printer Simulator is configured to send a message every 60 seconds. The following message appears in the log:
90 |
91 | ```
92 | sending health check ping with auth code:WfsVvaD84sN3wQoygfNK-JqmB4pvJko5Mrl2xgUFBzM
93 | POST data:{"printer_status":"ready"}
94 | Sending POST request to: http://developer.api.autodesk.com/api/v1/print/printers/status
95 | Server response status 200
96 | ```
97 | If the printer is sending health checks regularly, it will appear as ``online``. The application can check the Printer Simulator's online/offline status by calling the GET printers/status/:printer_id endpoint:
98 |
99 | ```
100 | GET /api/v1/print/printers/status/58 HTTP/1.1
101 | Host: developer.api.autodesk.com
102 | Content-Type: application/json
103 | Authorization: Bearer jDG4YLSTAUlI33k79KYJ2f4Q5nAZ
104 | Cache-Control: no-cache
105 | Content-Type: application/x-www-form-urlencoded
106 |
107 | Response:
108 | {
109 | "last_check_in": 21000,
110 | "printer_availability": "offline",
111 | "last_reported_state":
112 | {
113 | "printer_status":"paused",
114 | "error_code":0,
115 | "error_message":"",
116 | "job_id":"121333sdsd",
117 | "job_status":"paused",
118 | "job_progress":0.5,
119 | "data":
120 | {
121 | "job_name":"Part1.stl",
122 | "job_id":"121333sdsd",
123 | "layer":6,
124 | "total_layers":12,
125 | "seconds_left":37,
126 | "temperature":28.6875
127 | }
128 | }
129 | }
130 |
131 | ```
132 |
133 | You can stop the printer from sending health checks by clicking the Offline button. Once the ``last_check_in`` value exceeds 60 seconds, the health check status automatically switches to ``Offline``.
134 |
135 | ```
136 | {
137 | "status": "Offline",
138 | "last_check_in": 61000
139 | }
140 | ```
141 |
142 |
e. Sending print jobs to the Printer Simulator
143 | Once the printer is registered, it actively listens for incoming commands.
144 | To send a print job to the Printer Simulator, use the POST printers/:printer_id/jobs endpoint. This returns a ``job_id``, which you can use to check the print job's ``status`` or pause/cancel the job.
145 |
146 | ```
147 | POST /api/v1/print/printers/58/jobs HTTP/1.1
148 | Host: developer.api.autodesk.com
149 | Authorization: Bearer jDG4YLSTAUlI33k79KYJ2f4Q5nAZ
150 | Content-Type: application/json
151 | Cache-Control: no-cache
152 |
153 | { "printable_url": "http://cdn.static.com/print/abcefg", "settings": { "FirstApproachRPM": 6, "FirstZLiftMicrons": 2000, "FirstSeparationMicronsPerSec": 5000, "FirstApproachMicronsPerSec": 5000, "FirstRotationMilliDegrees": 60000 } }
154 |
155 | Response:
156 | {
157 | "printer_id": "58",
158 | "job_id": "44964c43-176e-43a5-b36c-7694054fe028",
159 | "status": "sent"
160 | }
161 |
162 | ```
163 | The printer will start "printing" the job and display appropriate messages in the log:
164 | ```
165 | POST data:{"printer_status":"printing","progress":0.88,"job_id":"44964c43-176e-43a5-b36c-7694054fe028","job_progress":0.88,"job_status":"printing","data":{"job_status":"printing","total_layers":50,"layer":44,"seconds_left":120,"temprature":71,"job_id":"44964c43-176e-43a5-b36c-7694054fe028"}}
166 | ```
167 |
168 | Your app can view the job's status with the GET jobs/:job_id endpoint. This will return the current status of the job.
169 |
170 | ```
171 | GET /api/v1/print/jobs/44964c43-176e-43a5-b36c-7694054fe028 HTTP/1.1
172 | Host: developer.api.autodesk.com
173 | Authorization: Bearer S787KIuuBJAH43QU2FgaROqUCC8S
174 | Cache-Control: no-cache
175 |
176 | Response:
177 | {
178 | "job_id": "44964c43-176e-43a5-b36c-7694054fe028",
179 | "job_status": {
180 | "printer_status": "printing",
181 | "job_id": "44964c43-176e-43a5-b36c-7694054fe028",
182 | "job_progress": "0.62",
183 | "job_status": "printing",
184 | "data": {
185 | "job_status": "printing",
186 | "total_layers": "50",
187 | "layer": "31",
188 | "seconds_left": "380",
189 | "temperature": "71",
190 | "job_id": "6e0880a9-ce65-44c2-bfa6-e6ba44d947d1"
191 | }
192 | },
193 | "job_date_time": "April 02, 2015 07:33:37",
194 | "job_status_time": "April 02, 2015 07:34:30",
195 | "local_job": false
196 | }
197 | ```
198 |
f. Sending commands to the Printer Simulator
199 | Commands can be sent to the Printer Simulator using the POST printers/:printer_id/command endpoint.
200 | While general printer commands such as ``calibrate``, ``firmware_upgrade``, and ``log``, return dummy data, ``pause``, ``cancel``, and, ``resume`` commands actually impact the (virtual) running print job.
201 |
202 | Note that commands with a job scope (pause/resume/cancel) require a ``job_id`` parameter:
203 |
204 | ```
205 | POST /api/v1/print/printers/58/command HTTP/1.1
206 | Host: developer.api.autodesk.com
207 | Authorization: Bearer S787KIuuBJAH43QU2FgaROqUCC8S
208 | Cache-Control: no-cache
209 | Content-Type: application/x-www-form-urlencoded
210 |
211 | command=pause&job_id=44964c43-176e-43a5-b36c-7694054fe028
212 |
213 | Response:
214 | {
215 | "task_id": "bf16cf49-86d9-4c7a-8aa4-90e18eb1b689"
216 | }
217 | ```
218 | This endpoint is asynchronous and initiates a command task that runs in the background, rather than halting execution of your program. The response returns a ``task_id`` that you need to use to call the GET printers/command/:task_id endpoint, which returns the status of the issued command:
219 |
220 | ```
221 | GET /api/v1/print/printers/command/bf16cf49-86d9-4c7a-8aa4-90e18eb1b689 HTTP/1.1
222 | Host: developer.api.autodesk.com
223 | Authorization: Bearer S787KIuuBJAH43QU2FgaROqUCC8S
224 | Cache-Control: no-cache
225 | Content-Type: application/x-www-form-urlencoded
226 |
227 | Response:
228 | {
229 | "progress": "0.72",
230 | "data": {
231 | "job_status": "printing",
232 | "total_layers": "50",
233 | "layer": "36",
234 | "seconds_left": "280",
235 | "temprature": "71",
236 | "job_id": "6e0880a9-ce65-44c2-bfa6-e6ba44d947d1"
237 | },
238 | "printer_status": "printing",
239 | "job_id": "6e0880a9-ce65-44c2-bfa6-e6ba44d947d1",
240 | "job_progress": "0.72",
241 | "job_status": "printing",
242 | "status_update_time": "April 02, 2015 07:37:59",
243 | "command": "resume"
244 | }
245 | ```
246 | The layers will stop or resume processing on the Printer Simulator depending on the command sent.
247 |
248 | Note that you can also trigger pause, resume, and cancel from the Print Simulator UI. They have the same impact on updating the print job status and the running job in the simulator.
249 |
250 |
--------------------------------------------------------------------------------
/css/bootstrap-responsive.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap Responsive v2.3.2
3 | *
4 | * Copyright 2013 Twitter, Inc
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Designed and built with all the love in the world by @mdo and @fat.
9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}
10 |
--------------------------------------------------------------------------------
/js/printer.js:
--------------------------------------------------------------------------------
1 | var Printer= (function() {
2 |
3 | // Private variables and functions
4 | var env = "sandbox";
5 | var TOKEN_KEY_BASE = "com.autodesk.print.token."
6 | var TOKEN_KEY = TOKEN_KEY_BASE+env;
7 | var isOnline=false;
8 | var healthCheckTimer;
9 | //how often to send health check ping
10 | var HEALTH_CHECK_INTERVAL=30000;
11 | var PRINT_JOB_INTERVAL=1000;
12 |
13 |
14 | /**switch to these url's when Jayant enables passthrough in apigee for both /faye and printer hardware end points**/
15 | //var BASE_URL="https://api-alpha.spark.autodesk.com/api/v1";
16 | //var FAYE_URL="https://api-alpha.spark.autodesk.com/faye";
17 |
18 | var BASE_URL="http://printer-sandbox.spark.autodesk.com/api/v1";
19 | var FAYE_URL="http://printer-sandbox.spark.autodesk.com/faye";
20 |
21 | var BASE_URL_LOCAL="http://localhost:8080/api/v1";
22 | var FAYE_URL_LOCAL="http://localhost:8080/faye"
23 |
24 | var BASE_URL_ALPHA="http://printer-alpha.spark.autodesk.com/api/v1";
25 | var FAYE_URL_ALPHA="http://printer-alpha.spark.autodesk.com/faye";
26 |
27 | var BASE_URL_FORGE_ALPHA="http://printer-forge-dev.spark.autodesk.com/v1";
28 | var FAYE_URL_FORGE_ALPHA="http://printer-forge-dev.spark.autodesk.com/faye";
29 |
30 | var BASE_URL_FORGE_BETA="http://printer-forge-stage.spark.autodesk.com/v1";
31 | var FAYE_URL_FORGE_BETA="http://printer-forge-stage.spark.autodesk.com/faye";
32 |
33 | var BASE_URL_FORGE="http://printer-forge-prod.spark.autodesk.com/v1";
34 | var FAYE_URL_FORGE="http://printer-forge-prod.spark.autodesk.com/faye";
35 |
36 |
37 | var STATUS_READY="ready";
38 | var STATUS_PRINTING="printing";
39 | var STATUS_PAUSED="paused";
40 | var STATUS_RECEIVED="received";
41 | var STATUS_CANCELED="canceled";
42 | var STATUS_COMPLETED="completed";
43 | //faye client
44 | var client;
45 |
46 |
47 | var registrationSub=null;
48 | var commandSub=null;
49 |
50 | //track the total and current layers and current print command
51 | var totalLayers;
52 | var currentLayer
53 | var currentPrintCommand=null;
54 | var currentPrinterStatus=STATUS_READY;
55 | var currentJobStatus="";
56 | var printCommandTimer=null;
57 | /**
58 | * Initialize simulator
59 | */
60 | var init= function(){
61 |
62 | log("initializing printer...");
63 | //set up url if local mode
64 | var local=getQueryVariable('mode');
65 | if(local!=false&&local.toUpperCase()==='LOCAL'){
66 | log("Setting url's to local mode");
67 | BASE_URL=BASE_URL_LOCAL;
68 | FAYE_URL=FAYE_URL_LOCAL;
69 | env="local";
70 | TOKEN_KEY = TOKEN_KEY_BASE+env;
71 | }
72 | else if(local!=false&&local.toUpperCase()==='ALPHA'){
73 | log("Setting url's to alpha mode");
74 | BASE_URL=BASE_URL_ALPHA;
75 | FAYE_URL=FAYE_URL_ALPHA;
76 | env="alpha";
77 | TOKEN_KEY = TOKEN_KEY_BASE+env;
78 | }
79 | else if(local!=false&&local.toUpperCase()==='FORGE_ALPHA'){
80 | log("Setting url's to forge ALPHA mode");
81 | BASE_URL=BASE_URL_FORGE_ALPHA;
82 | FAYE_URL=FAYE_URL_FORGE_ALPHA;
83 | env="alpha";
84 | TOKEN_KEY = TOKEN_KEY_BASE+env;
85 | }
86 | else if(local!=false&&local.toUpperCase()==='FORGE_BETA'){
87 | log("Setting url's to forge BETA mode");
88 | BASE_URL=BASE_URL_FORGE_BETA;
89 | FAYE_URL=FAYE_URL_FORGE_BETA;
90 | env="beta";
91 | TOKEN_KEY = TOKEN_KEY_BASE+env;
92 | }
93 | else if(local!=false&&local.toUpperCase()==='FORGE'){
94 | log("Setting url's to forge BETA mode");
95 | BASE_URL=BASE_URL_FORGE;
96 | FAYE_URL=FAYE_URL_FORGE;
97 | env="beta";
98 | TOKEN_KEY = TOKEN_KEY_BASE+env;
99 | }
100 |
101 | //first set up faye client
102 | client = new Faye.Client(FAYE_URL,{timeout: 120,retry:3});
103 | client.disable('websocket');
104 |
105 | Logger = {
106 | incoming: function(message, callback) {
107 | console.log('incoming', message);
108 | callback(message);
109 | },
110 | outgoing: function(message, callback) {
111 | console.log('outgoing', message);
112 | callback(message);
113 | }
114 | };
115 | client.addExtension(Logger);
116 |
117 | log("checking token in local storage...");
118 | var token=getToken();
119 | if(token==null){
120 | log("No token found in local storage getting a new one...");
121 | if(!resetToken()){
122 | log("failed to initialize...could not get token");
123 | return;
124 | }
125 | }
126 | else{
127 | log("retrieved token from local storage:");
128 | log("token:"+JSON.stringify(token));
129 | log ("printer id:"+token.printer_id);
130 |
131 | if(token.registered==false){
132 | offline();
133 | lcdWrite(token.registration_code)
134 | subscribeRegistrationChannel(token.printer_id);
135 | }
136 | else{
137 | lcdWrite('REGISTERED: '+token.printer_id);
138 | subscribeCommandChannel();
139 | online();
140 | }
141 | }
142 | }
143 |
144 | /**
145 | *parse query params to check if running in local mode
146 | */
147 |
148 | var getQueryVariable=function(variable)
149 | {
150 | var query = window.location.search.substring(1);
151 | var vars = query.split("&");
152 | for (var i=0;i-1){
450 | //log("Local print job no command acknowledge required...");
451 | return;
452 | }
453 | if(acg==null){
454 | acg={};
455 | }
456 | acg.printer_status=status;
457 | acg.progress=1.0;
458 | if(currentPrintCommand!=null){
459 | acg.job_id=currentPrintCommand.task_id;
460 | acg.job_progress=currentLayer/totalLayers;
461 | acg.progress=acg.job_progress;
462 | acg.job_status=jobStatus;
463 | setPrintData(acg,jobStatus);
464 | }
465 |
466 |
467 | //now send the status to server
468 | var token=getToken();
469 | var auth_token=token.auth_token;
470 | log("sending job status auth code:"+auth_token);
471 | var api_url=BASE_URL+'/print/printers/command/'+commandToken;
472 | log("Sending POST request to: "+api_url);
473 | log("POST data:"+JSON.stringify(acg));
474 |
475 | jQuery.ajax({
476 | type: 'POST',
477 | url: api_url,
478 | headers: { 'X-Printer-Auth-Token': auth_token },
479 | data:acg,
480 | success: function(data,testStatus, xhr){
481 | var json_str=JSON.stringify(data);
482 | log("Server response status "+xhr.status);
483 | },
484 | error: function(error){
485 | var json_str=JSON.stringify(error);
486 | log("Server Error in Command Acknowledge:"+json_str);
487 | },
488 | async: true
489 | });
490 | }
491 |
492 | var setPrintData=function(acg,status){
493 | acg.data={};
494 | acg.data.job_status=status;
495 | acg.data.total_layers=totalLayers;
496 | acg.data.layer=currentLayer;
497 | acg.data.seconds_left=(totalLayers-currentLayer)*20;
498 | acg.data.temprature=71;
499 | acg.data.job_id=currentPrintCommand.task_id;
500 | return acg;
501 | }
502 |
503 | /**
504 | *clear the current print job
505 | */
506 | var clearPrintJob=function(){
507 | currentPrintCommand=null;
508 | if(printCommandTimer!=null){
509 | clearTimeout(printCommandTimer);
510 | }
511 | printCommandTimer=null;
512 | currentLayer=1;
513 | currentPrinterStatus=STATUS_READY;
514 | currentJobStatus="";
515 | }
516 |
517 | var resume =function(){
518 | if(currentPrintCommand==null||printCommandTimer==null){
519 | log("No running job to resume...")
520 | return false;
521 | }
522 | else{
523 | log("resuming job "+currentPrintCommand.task_id);
524 | lcdWrite("Resume");
525 | currentPrinterStatus=STATUS_PRINTING;
526 | currentJobStatus=STATUS_PRINTING;
527 | printCommandTimer=setTimeout(processPrintCommand,PRINT_JOB_INTERVAL);
528 | healthCheck();//send a status udpate
529 | return true;
530 |
531 | }
532 |
533 | }
534 |
535 | var cancel =function(){
536 | if(currentPrintCommand==null||printCommandTimer==null){
537 | log("No running job to cancel...");
538 | return false;
539 | }
540 | else{
541 |
542 | log("Canceling job "+currentPrintCommand.task_id);
543 | lcdWrite("Cancel");
544 | currentPrinterStatus=STATUS_READY;
545 | currentJobStatus=STATUS_CANCELED;
546 | healthCheck();//send a status udpate
547 | return true;
548 | }
549 |
550 | }
551 |
552 | var pause =function(){
553 | if(currentPrintCommand==null||printCommandTimer==null){
554 | log("No running job to pause...");
555 | return false;
556 | }
557 | else{
558 | log("pausing job "+currentPrintCommand.task_id);
559 | lcdWrite("Pause");
560 | clearTimeout(printCommandTimer);
561 | currentPrinterStatus=STATUS_PAUSED;
562 | currentJobStatus=STATUS_PAUSED;
563 | healthCheck();//send a status udpate
564 | return true;
565 | }
566 |
567 | }
568 |
569 | //show the existing token in the console
570 | var showToken =function(){
571 | var token=getToken();
572 | var token_str=JSON.stringify(token);
573 | log("Token is:"+token_str);
574 | lcdWrite(token.registration_code);
575 | }
576 |
577 | var offline=function(){
578 | isOnline=false;
579 | log("set printer state to OFFLINE");
580 | clearTimeout(healthCheckTimer);
581 | }
582 |
583 | var online=function(){
584 | isOnline=true;
585 | log("set printer state to ONLINE");
586 | healthCheck();
587 | }
588 |
589 | var healthCheck=function(){
590 | if(isOnline){
591 |
592 | var token=getToken();
593 |
594 | if(token==null||token.registered==false){
595 | log("Printer not registered, no health check required");
596 |
597 | }
598 | else{
599 | var auth_token=token.auth_token;
600 | var acg={};
601 | acg.printer_status=currentPrinterStatus;
602 | if(currentPrintCommand!=null){
603 | acg.job_id=currentPrintCommand.task_id;
604 | acg.job_progress=currentLayer/totalLayers;
605 | acg.job_status=currentJobStatus;
606 | setPrintData(acg,currentJobStatus);
607 | }
608 | log("sending health check ping with auth code:"+auth_token);
609 | log("POST data:"+JSON.stringify(acg));
610 | var api_url=BASE_URL+'/print/printers/status';
611 | log("Sending POST request to: "+api_url);
612 | jQuery.ajax({
613 | type: 'POST',
614 | url: api_url,
615 | data:acg,
616 | headers: { 'X-Printer-Auth-Token': auth_token},
617 | success: function(data,testStatus, xhr){
618 | var json_str=JSON.stringify(data);
619 | log("Server response status "+xhr.status);
620 |
621 |
622 | },
623 | error: function(error){
624 |
625 | var json_str=JSON.stringify(error);
626 | log("Server Error:"+json_str);
627 |
628 |
629 | },
630 | async: true
631 | });
632 |
633 | }
634 |
635 | if(healthCheckTimer!=null){
636 | clearTimeout(healthCheckTimer);
637 | }
638 |
639 | healthCheckTimer=setTimeout(healthCheck,HEALTH_CHECK_INTERVAL);
640 |
641 | }
642 |
643 | }
644 |
645 |
646 |
647 |
648 | //log data
649 |
650 | var log=function(data){
651 | console.log('\\n');
652 | console.log(data);
653 | var txt = $(".log");
654 | txt.val( txt.val() + "\n"+data);
655 | txt.scrollTop(txt[0].scrollHeight);
656 | }
657 |
658 | //utility function to write to lcd
659 | var lcdWrite=function(data){
660 | $(".lcd").html(data);
661 | }
662 |
663 | // Public API
664 | return {
665 | init: init,
666 | showToken:showToken,
667 | resetToken:resetToken,
668 | online:online,
669 | offline:offline,
670 | pause:pause,
671 | resume:resume,
672 | cancel:cancel,
673 | startLocalPrintJob:startLocalPrintJob
674 |
675 |
676 | };
677 | })();
678 |
679 | $( document ).ready( function() {
680 | Printer.init();
681 | $(".get-token").click(Printer.showToken);
682 | $(".new-token").click(Printer.resetToken);
683 | $(".printer-online").click(Printer.online);
684 | $(".printer-offline").click(Printer.offline);
685 |
686 | $(".resume-print").click(Printer.resume);
687 | $(".cancel-print").click(Printer.cancel);
688 | $(".pause-print").click(Printer.pause);
689 | $(".local-print").click(Printer.startLocalPrintJob);
690 | $('[data-toggle="tooltip"]').tooltip();
691 |
692 |
693 | });
694 |
--------------------------------------------------------------------------------
/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap.js by @fat & @mdo
3 | * Copyright 2013 Twitter, Inc.
4 | * http://www.apache.org/licenses/LICENSE-2.0.txt
5 | */
6 | !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('').insertBefore(e(this)).on("click",r),s.toggleClass("open")),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e.fn[this.type].defaults,r={},i;this._options&&e.each(this._options,function(e,t){n[e]!=t&&(r[e]=t)},this),i=e(t.currentTarget)[this.type](r).data(this.type);if(!i.options.delay||!i.options.delay.show)return i.show();clearTimeout(this.timeout),i.hoverState="in",this.timeout=setTimeout(function(){i.hoverState=="in"&&i.show()},i.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'