aiot-toolkit 2.0.2-beta.1 → 2.0.2-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,22 @@
1
- import ParamType from '@aiot-toolkit/commander/lib/interface/IParam';
1
+ import { IParam } from '@aiot-toolkit/commander';
2
2
  import { IStartOptions } from '@aiot-toolkit/emulator';
3
3
  import UxBuilder from '../builder/UxBuilder';
4
4
  import IStarter from './IStarter';
5
+ /**
6
+ * UxStarter
7
+ * ux快应用启动器
8
+ */
5
9
  declare class UxStarter extends IStarter<IStartOptions> {
10
+ private ws;
6
11
  builder: UxBuilder;
7
- params: ParamType[];
12
+ params: IParam[];
8
13
  start(projectPath: string, options: any): Promise<void>;
14
+ /**
15
+ * 检查选择的模拟器在当前环境下是否可以使用
16
+ * 下面的几种情况会导致模拟器不可用:1. 模拟器绑定的Vela镜像不存在;2. Vela4.0缺少coredump.core或者vela_data.bin文件
17
+ * @param avdName 模拟器名称
18
+ * @returns {boolean}
19
+ */
20
+ isAvailableEmulator(avdName: string): boolean;
9
21
  }
10
22
  export default UxStarter;
@@ -1,27 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -35,10 +12,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
13
  };
37
14
  Object.defineProperty(exports, "__esModule", { value: true });
38
- const JavascriptDefaultCompileOption_1 = __importStar(require("@aiot-toolkit/aiotpack/lib/compiler/javascript/JavascriptDefaultCompileOption"));
15
+ const aiotpack_1 = require("@aiot-toolkit/aiotpack");
39
16
  const emulator_1 = require("@aiot-toolkit/emulator");
40
17
  const constants_1 = require("@aiot-toolkit/emulator/lib/static/constants");
41
- const ColorConsole_1 = __importDefault(require("@aiot-toolkit/shared-utils/lib/ColorConsole"));
18
+ const index_1 = require("@aiot-toolkit/emulator/lib/utils/index");
19
+ const shared_utils_1 = require("@aiot-toolkit/shared-utils");
42
20
  const prompts_1 = require("@inquirer/prompts");
43
21
  const os_1 = __importDefault(require("os"));
44
22
  const path_1 = __importDefault(require("path"));
@@ -46,10 +24,23 @@ const portfinder_1 = __importDefault(require("portfinder"));
46
24
  const UxBuilder_1 = __importDefault(require("../builder/UxBuilder"));
47
25
  const VelaAvdUtils_1 = __importDefault(require("../utils/VelaAvdUtils"));
48
26
  const IStarter_1 = __importDefault(require("./IStarter"));
27
+ const fs_extra_1 = __importDefault(require("fs-extra"));
28
+ const ws_1 = __importDefault(require("ws"));
29
+ /**
30
+ * UxStarter
31
+ * ux快应用启动器
32
+ */
49
33
  class UxStarter extends IStarter_1.default {
50
34
  constructor() {
51
35
  super(...arguments);
52
- this.builder = new UxBuilder_1.default();
36
+ this.builder = new UxBuilder_1.default({
37
+ onBuildSuccess: () => {
38
+ var _a;
39
+ (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify({
40
+ type: 'restart'
41
+ }));
42
+ }
43
+ });
53
44
  this.params = [
54
45
  {
55
46
  name: 'disableNSH',
@@ -74,49 +65,89 @@ class UxStarter extends IStarter_1.default {
74
65
  }
75
66
  start(projectPath, options) {
76
67
  return __awaiter(this, void 0, void 0, function* () {
68
+ // 获取已经创建的模拟器列表
77
69
  const avdList = VelaAvdUtils_1.default.velaAvdCls.getVelaAvdList();
70
+ let avdName;
78
71
  if (avdList.length === 0) {
79
- ColorConsole_1.default.error('### goldfish start ### No vela emulator available, please create it first.');
72
+ shared_utils_1.ColorConsole.error('no vela emulator available, please create it first.');
80
73
  VelaAvdUtils_1.default.createVelaAvdByInquire();
81
74
  return;
82
75
  }
83
- const avdName = yield (0, prompts_1.select)({
84
- message: 'name of the avd to start',
85
- choices: avdList.map((item) => {
86
- return { value: item.avdName };
87
- })
88
- });
89
- let serverPort;
90
- // watch模型下开启server
91
- if (options.watch) {
92
- serverPort = yield portfinder_1.default.getPortPromise({
93
- port: JavascriptDefaultCompileOption_1.default.serverPort
76
+ else if (avdList.length === 1) {
77
+ const needtoRun = yield (0, prompts_1.confirm)({
78
+ message: 'there is only one emulator, need to Run?',
79
+ default: true
94
80
  });
95
- (0, JavascriptDefaultCompileOption_1.setServerPort)(serverPort);
81
+ if (!needtoRun) {
82
+ VelaAvdUtils_1.default.createVelaAvdByInquire();
83
+ return;
84
+ }
85
+ avdName = avdList[0].avdName;
86
+ shared_utils_1.ColorConsole.warn(`Start run by :${avdName} `);
96
87
  }
88
+ else {
89
+ avdName = yield (0, prompts_1.select)({
90
+ message: 'name of the avd to start',
91
+ choices: avdList.map((item) => {
92
+ return { value: item.avdName };
93
+ })
94
+ });
95
+ }
96
+ // 检查选择的模拟器是否符合要求
97
+ if (avdName && !this.isAvailableEmulator(avdName)) {
98
+ shared_utils_1.ColorConsole.throw(`this emulator is unavailable, please create a new emulator.`);
99
+ return;
100
+ }
101
+ let serverPort;
102
+ // watch模型下开启server
103
+ serverPort = yield portfinder_1.default.getPortPromise({
104
+ port: aiotpack_1.JavascriptDefaultCompileOption.serverPort
105
+ });
106
+ (0, aiotpack_1.setServerPort)(serverPort);
97
107
  // build
108
+ const compilerOption = this.builder.getCompilerOption(projectPath, options);
98
109
  yield this.builder.build(projectPath, options);
99
110
  // start
100
111
  const params = {
101
112
  sdkHome: path_1.default.resolve(os_1.default.homedir(), '.export'),
102
113
  avdHome: path_1.default.resolve(os_1.default.homedir(), '.android/avd'),
103
- projectPath
114
+ projectPath,
115
+ compilerOption
104
116
  };
117
+ // 寻找对应的模拟器instance
105
118
  const goldfishInstance = (0, emulator_1.findInstance)(avdName, params);
106
119
  if (!goldfishInstance)
107
120
  return;
108
- let vncPort;
121
+ let grpcPort;
122
+ // 设置vncPort
109
123
  if (options.openVNC) {
110
- vncPort = yield portfinder_1.default.getPortPromise({ port: constants_1.defaultVncPort, stopPort: constants_1.defaultVncPort + 100 });
124
+ grpcPort = yield portfinder_1.default.getPortPromise({
125
+ port: constants_1.defaultVncPort,
126
+ stopPort: constants_1.defaultVncPort + 100
127
+ });
128
+ }
129
+ // 设置adbPort
130
+ const adbPort = yield (0, index_1.getEvenPort)();
131
+ if (!adbPort) {
132
+ shared_utils_1.ColorConsole.throw(`${adbPort},5555和5585之间的端口号已全部不占用,请解决端口冲突,再启动模拟器`);
133
+ return;
134
+ }
135
+ // 设置debugPort
136
+ let debugPort;
137
+ if (options.devtool) {
138
+ debugPort = yield portfinder_1.default.getPortPromise({ port: constants_1.defaultDebugPort });
111
139
  }
112
140
  const startOptions = {
113
141
  avdName,
114
142
  devtool: options.devtool,
115
143
  disableNSH: options.disableNSH,
116
144
  serverPort,
117
- vncPort,
145
+ grpcPort,
146
+ adbPort,
147
+ debugPort
118
148
  };
119
- goldfishInstance.start(startOptions);
149
+ yield goldfishInstance.start(startOptions);
150
+ this.ws = new ws_1.default(`ws://localhost:${serverPort}`);
120
151
  // waiter
121
152
  // const startWaiter: PersistentCommand = new PersistentCommand({
122
153
  // description: 'aiot-toolkit start 的常驻命令',
@@ -135,5 +166,25 @@ class UxStarter extends IStarter_1.default {
135
166
  // startWaiter.start()
136
167
  });
137
168
  }
169
+ /**
170
+ * 检查选择的模拟器在当前环境下是否可以使用
171
+ * 下面的几种情况会导致模拟器不可用:1. 模拟器绑定的Vela镜像不存在;2. Vela4.0缺少coredump.core或者vela_data.bin文件
172
+ * @param avdName 模拟器名称
173
+ * @returns {boolean}
174
+ */
175
+ isAvailableEmulator(avdName) {
176
+ const { avdImagePath } = VelaAvdUtils_1.default.velaAvdCls.getVelaAvdInfo(avdName);
177
+ // 没有avdImagePath,即对应的configIni里没有 image.sysdir.1 字段
178
+ // 有avdImagePath,但是这个目录下没有nuttx
179
+ if (!avdImagePath || !fs_extra_1.default.existsSync(path_1.default.resolve(avdImagePath, 'nuttx'))) {
180
+ return false;
181
+ }
182
+ if (avdImagePath.includes('vela-release') &&
183
+ (!fs_extra_1.default.existsSync(path_1.default.resolve(avdImagePath, 'coredump.core')) ||
184
+ !fs_extra_1.default.existsSync(path_1.default.resolve(avdImagePath, 'vela_data.bin')))) {
185
+ return false;
186
+ }
187
+ return true;
188
+ }
138
189
  }
139
190
  exports.default = UxStarter;
@@ -1,9 +1,9 @@
1
- import ParamType from '@aiot-toolkit/commander/lib/interface/IParam';
1
+ import { IParam } from '@aiot-toolkit/commander';
2
2
  import XtsBuilder from '../builder/XtsBuilder';
3
3
  import IStarter from './IStarter';
4
4
  declare class XtsStarter extends IStarter {
5
5
  builder: XtsBuilder;
6
- params: ParamType[];
6
+ params: IParam[];
7
7
  start(projectPath: string, options: any): void;
8
8
  }
9
9
  export default XtsStarter;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 连接类型
3
+ */
4
+ declare enum LinkMode {
5
+ NULL = 0,
6
+ WIFI = 1,
7
+ ADB = 2
8
+ }
9
+ export default LinkMode;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * 连接类型
5
+ */
6
+ var LinkMode;
7
+ (function (LinkMode) {
8
+ LinkMode[LinkMode["NULL"] = 0] = "NULL";
9
+ LinkMode[LinkMode["WIFI"] = 1] = "WIFI";
10
+ LinkMode[LinkMode["ADB"] = 2] = "ADB";
11
+ })(LinkMode || (LinkMode = {}));
12
+ exports.default = LinkMode;
@@ -0,0 +1,55 @@
1
+ import { IJavascriptCompileOption } from '@aiot-toolkit/aiotpack';
2
+ import { IRouteItem, IRouter } from '@aiot-toolkit/shared-utils';
3
+ interface IPackageRouter {
4
+ projectPath: string;
5
+ options: IJavascriptCompileOption;
6
+ }
7
+ /**
8
+ * 打包相关的路由配置
9
+ * 用于开发机的httpServer
10
+ */
11
+ declare class PackageRouter implements IRouter<IPackageRouter> {
12
+ readonly param: IPackageRouter;
13
+ constructor(param: IPackageRouter);
14
+ routeList: IRouteItem<any, {}>[];
15
+ /**
16
+ * 下载 rpk 包
17
+ * @param ctx
18
+ * @param next
19
+ */
20
+ private handlerDownLoadRpk;
21
+ private handlerQrCode;
22
+ /**
23
+ * 记录设备信息
24
+ *
25
+ * 获取请求的 ip 和端口,记录到本机中,最多记录5个
26
+ *
27
+ * 文件内容的格式为:
28
+ * ```
29
+ * records: {
30
+ * "快应用项目路径":[
31
+ * {ip:"", port:""},
32
+ * {ip:"", port:""},
33
+ * {ip:"", port:""}
34
+ * ]
35
+ * }
36
+ * ```
37
+ *
38
+ *
39
+ * 此记录的作用是:当重新打包后,获取已连接的设备列表
40
+ * @param ctx
41
+ * @param next
42
+ */
43
+ private recordDevice;
44
+ private getClientFromRequest;
45
+ /**
46
+ * http 请求中,用于返回下载文件
47
+ *
48
+ * 调用此方法后,仍需调用 next()
49
+ *
50
+ * @param filePath 文件绝对路径
51
+ * @param ctx 请求上下文
52
+ */
53
+ private download;
54
+ }
55
+ export default PackageRouter;
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const shared_utils_1 = require("@aiot-toolkit/shared-utils");
16
+ const fs_extra_1 = __importDefault(require("fs-extra"));
17
+ const path_1 = __importDefault(require("path"));
18
+ const qr_image_1 = __importDefault(require("qr-image"));
19
+ const LinkMode_1 = __importDefault(require("./LinkMode"));
20
+ const CLIENT_PORT = 39517;
21
+ /**
22
+ * 打包相关的路由配置
23
+ * 用于开发机的httpServer
24
+ */
25
+ class PackageRouter {
26
+ constructor(param) {
27
+ this.param = param;
28
+ this.routeList = [
29
+ {
30
+ path: '/qrcode',
31
+ handler: (ctx, next) => {
32
+ return this.handlerQrCode(ctx, next);
33
+ }
34
+ },
35
+ {
36
+ path: '/bundle',
37
+ handler: (ctx, next) => {
38
+ this.recordDevice(ctx);
39
+ return this.handlerDownLoadRpk(ctx, next);
40
+ }
41
+ }
42
+ ];
43
+ /**
44
+ * 下载 rpk 包
45
+ * @param ctx
46
+ * @param next
47
+ */
48
+ this.handlerDownLoadRpk = (ctx, next) => {
49
+ const { projectPath, options } = this.param;
50
+ const dist = path_1.default.join(projectPath, options.releasePath);
51
+ const files = fs_extra_1.default.readdirSync(dist).filter((item) => path_1.default.extname(item) === '.rpk');
52
+ const filePath = files.length ? path_1.default.join(dist, files[0]) : '';
53
+ this.download(filePath, ctx);
54
+ return next();
55
+ };
56
+ this.handlerQrCode = (ctx, next) => {
57
+ const qrText = ctx.origin;
58
+ const image = qr_image_1.default.image(qrText, {
59
+ size: 9
60
+ });
61
+ ctx.type = 'image/png';
62
+ ctx.body = image;
63
+ return next();
64
+ };
65
+ /**
66
+ * 记录设备信息
67
+ *
68
+ * 获取请求的 ip 和端口,记录到本机中,最多记录5个
69
+ *
70
+ * 文件内容的格式为:
71
+ * ```
72
+ * records: {
73
+ * "快应用项目路径":[
74
+ * {ip:"", port:""},
75
+ * {ip:"", port:""},
76
+ * {ip:"", port:""}
77
+ * ]
78
+ * }
79
+ * ```
80
+ *
81
+ *
82
+ * 此记录的作用是:当重新打包后,获取已连接的设备列表
83
+ * @param ctx
84
+ * @param next
85
+ */
86
+ this.recordDevice = (ctx) => __awaiter(this, void 0, void 0, function* () {
87
+ const clientInfo = this.getClientFromRequest(ctx);
88
+ if (!(clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.clientIp)) {
89
+ return;
90
+ }
91
+ if (clientInfo.linkMode === LinkMode_1.default.WIFI) {
92
+ const max = 5;
93
+ const filePath = this.param.options.clientRecordPath;
94
+ const projectPath = this.param.projectPath;
95
+ const data = {
96
+ sn: clientInfo.sn,
97
+ ip: clientInfo.clientIp,
98
+ port: CLIENT_PORT
99
+ };
100
+ const json = fs_extra_1.default.readJSONSync(filePath, { throws: false }) || {};
101
+ if (!json.records) {
102
+ json.records = {};
103
+ }
104
+ if (!json.records[projectPath]) {
105
+ json.records[projectPath] = [];
106
+ }
107
+ const deviceList = json.records[projectPath];
108
+ if (deviceList.findIndex((item) => item.ip === data.ip) < 0) {
109
+ deviceList.push(data);
110
+ if (deviceList.length > max) {
111
+ deviceList.splice(0, deviceList.length - max);
112
+ }
113
+ fs_extra_1.default.ensureDirSync(path_1.default.dirname(filePath));
114
+ fs_extra_1.default.writeJSONSync(filePath, json, { spaces: 2 });
115
+ }
116
+ }
117
+ });
118
+ }
119
+ getClientFromRequest(ctx) {
120
+ const clientIp = shared_utils_1.NetworkUtil.getClientIp(ctx);
121
+ const serverIp = shared_utils_1.NetworkUtil.getIPv4IPAddress();
122
+ const sn = ctx.request.header['device-serial-number'];
123
+ let linkMode = LinkMode_1.default.NULL;
124
+ if (clientIp === '127.0.0.1' && sn) {
125
+ linkMode = LinkMode_1.default.ADB;
126
+ }
127
+ else if (clientIp !== '127.0.0.1' && clientIp !== serverIp) {
128
+ linkMode = LinkMode_1.default.WIFI;
129
+ }
130
+ return { clientIp, sn, linkMode };
131
+ }
132
+ /**
133
+ * http 请求中,用于返回下载文件
134
+ *
135
+ * 调用此方法后,仍需调用 next()
136
+ *
137
+ * @param filePath 文件绝对路径
138
+ * @param ctx 请求上下文
139
+ */
140
+ download(filePath, ctx) {
141
+ if (filePath && fs_extra_1.default.existsSync(filePath)) {
142
+ ctx.set('Content-Type', 'application/octet-stream');
143
+ ctx.set('Content-Transfer-Encoding', 'binary');
144
+ ctx.set('Content-Disposition', `attachment; filename=${path_1.default.basename(filePath)}`);
145
+ ctx.body = fs_extra_1.default.createReadStream(filePath);
146
+ }
147
+ else {
148
+ ctx.throw('404', 404);
149
+ }
150
+ }
151
+ }
152
+ exports.default = PackageRouter;
@@ -0,0 +1,167 @@
1
+ .alert-warning {
2
+ color: #8a6d3b;
3
+ background-color: #fcf8e3;
4
+ border-color: #faebcc;
5
+ text-align: left;
6
+ }
7
+
8
+ .alert {
9
+ padding: 8px;
10
+ margin: 10px auto;
11
+ width: 400px;
12
+ min-width: 350px;
13
+ border: 1px solid transparent;
14
+ border-radius: 6px;
15
+ box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
16
+ }
17
+
18
+ .alert p {
19
+ margin: 6px;
20
+ }
21
+
22
+ .panel-default {
23
+ border-color: #ddd;
24
+ }
25
+
26
+ .panel {
27
+ margin-bottom: 20px;
28
+ background-color: #fff;
29
+ border-radius: 4px;
30
+ -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
31
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
32
+ margin: 10px auto;
33
+ box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15);
34
+ width: 400px;
35
+ min-width: 350px;
36
+ padding: 8px;
37
+ }
38
+
39
+ .panel-default > .panel-heading {
40
+ color: #333;
41
+ border-color: #ddd;
42
+ text-align: left;
43
+ }
44
+
45
+ .panel-heading {
46
+ padding: 8px 15px;
47
+ border-bottom: 1px solid transparent;
48
+ border-top-left-radius: 3px;
49
+ border-top-right-radius: 3px;
50
+ }
51
+
52
+ .table {
53
+ width: 100%;
54
+ max-width: 100%;
55
+ margin: 6px;
56
+ background-color: transparent;
57
+ border-spacing: 0;
58
+ border-collapse: collapse;
59
+ }
60
+
61
+ .table td {
62
+ color: black;
63
+ text-shadow: none;
64
+ text-align: left;
65
+ }
66
+
67
+ .table td.tname {
68
+ width: 70%;
69
+ }
70
+
71
+ body {
72
+ margin: 0;
73
+ display: block;
74
+ position: absolute;
75
+ top: 0;
76
+ bottom: 0;
77
+ left: 0;
78
+ right: 0;
79
+ }
80
+
81
+ .hide {
82
+ display: none !important;
83
+ }
84
+
85
+ .qr-code-panel {
86
+ text-align: center;
87
+ display: flex;
88
+ flex-direction: column;
89
+ }
90
+
91
+ .qr-code-panel .qr-img {
92
+ margin: 10px 200px;
93
+ min-width: 350px;
94
+ }
95
+
96
+ .bd-bg {
97
+ position: relative;
98
+ padding: 40px 0;
99
+ color: black;
100
+ text-align: center;
101
+ background: #eee;
102
+ }
103
+
104
+ .updatetip-link {
105
+ padding: 0 5px;
106
+ }
107
+
108
+ table.tunning tr {
109
+ padding: 8px 15px;
110
+ }
111
+
112
+ .btn {
113
+ display: inline-block;
114
+ padding: 6px 12px;
115
+ margin-bottom: 0;
116
+ font-size: 14px;
117
+ font-weight: 400;
118
+ line-height: 1.42857143;
119
+ text-align: center;
120
+ white-space: nowrap;
121
+ vertical-align: middle;
122
+ -ms-touch-action: manipulation;
123
+ touch-action: manipulation;
124
+ cursor: pointer;
125
+ -webkit-user-select: none;
126
+ -moz-user-select: none;
127
+ -ms-user-select: none;
128
+ user-select: none;
129
+ background-image: none;
130
+ border: 1px solid transparent;
131
+ border-radius: 4px;
132
+ margin-left: 5px;
133
+ }
134
+
135
+ .btn-default {
136
+ color: #333;
137
+ background-color: #fff;
138
+ border-color: #ccc;
139
+ }
140
+
141
+ .label {
142
+ display: inline;
143
+ padding: 0.7em;
144
+ font-size: 75%;
145
+ font-weight: 700;
146
+ line-height: 1;
147
+ color: #fff;
148
+ text-align: center;
149
+ text-decoration: none;
150
+ white-space: nowrap;
151
+ vertical-align: baseline;
152
+ border-radius: 0.25em;
153
+ }
154
+
155
+ .label-info {
156
+ background-color: #5bc0de;
157
+ }
158
+ .block {
159
+ width: 420px;
160
+ }
161
+ .block .block-title {
162
+ font-size: 16px;
163
+ margin: 0.5em 0;
164
+ }
165
+ .block a {
166
+ color: #0055ff;
167
+ }