Commit 1e1c7e6f authored by Antoor's avatar Antoor

Merge pull request #28 from antoor/v1.1-dev

Update to v1.1.1
parents 4d6d559c f31ec40c
......@@ -4,7 +4,16 @@
## 2016/03
### /23
### /26
1. 文件管理双击:size < 100kb ? 编辑 : 下载
2. 调整 Custom 方式数据库部分代码 // 2-4:感谢[@Medicean](https://github.com/Medicean)
3. 添加 Shells 目录, 用于存放 shell 样本代码
4. 添加 `custom.jsp` 服务端样本代码
### /24
1. 文件管理双击文件进行编辑 //size < 100kb
### /23 (v1.1.0)
1. 优化数据处理截断算法
### /22
......@@ -34,10 +43,8 @@
# 待做事项
* 数据高级搜索功能
* 数据库配置编辑功能
* 数据发包代理功能
* 在线检测/下载/安装更新
* 虚拟终端复制粘贴tab补全
* 文件管理双击文件进行编辑 //size < 1024kb
* 插件模块 //实时编写插件执行、UI以及各种操作API设计
* 扩展模块 //用于扩展一些高级的功能,懒人必备
* 代码重构
......
......@@ -5,10 +5,11 @@ const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
// 导入模块
const Cache = require('./modules/cache');
const Update = require('./modules/update');
const Menubar = require('./modules/menubar');
const Request = require('./modules/request');
const Database = require('./modules/database');
const Cache = require('./modules/cache');
// electron.crashReporter.start();
......@@ -59,4 +60,7 @@ app
// 初始化缓存模块
new Cache(electron);
// 监听更新请求
new Update(electron);
});
\ No newline at end of file
......@@ -4,6 +4,11 @@
'use strict';
// 读取package.json信息
const info = JSON.parse(
require('fs').readFileSync(require('path').join(__dirname, '../package.json'))
);
class Menubar {
constructor(electron, app, mainWindow) {
......@@ -14,6 +19,7 @@ class Menubar {
Menu.setApplicationMenu(Menu.buildFromTemplate([]));
// 监听重载菜单事件
ipcMain.on('menubar', this.reload.bind(this));
ipcMain.on('quit', app.quit.bind(app));
this.electron = electron;
this.app = app;
......@@ -95,8 +101,11 @@ class Menubar {
click: event.sender.send.bind(event.sender, 'menubar', 'tabbar-close')
}
]
}, {
// 调试
}
];
// 调试菜单
if (info['debug']) {
template.push({
label: LANG['debug']['title'],
submenu: [
{
......@@ -109,51 +118,49 @@ class Menubar {
click: this.mainWindow.webContents.toggleDevTools.bind(this.mainWindow.webContents)
}
]
}
];
// OSX主菜单
// if (process.platform === 'darwin') {
template.unshift({
label: LANG['main']['title'],
submenu: [
{
label: LANG['main']['about'],
accelerator: 'Shift+CmdOrCtrl+I',
click: event.sender.send.bind(event.sender, 'menubar', 'settings-about')
}, {
label: LANG['main']['language'],
accelerator: 'Shift+CmdOrCtrl+L',
click: event.sender.send.bind(event.sender, 'menubar', 'settings-language')
}, {
label: LANG['main']['aproxy'],
accelerator: 'Shift+CmdOrCtrl+A',
click: event.sender.send.bind(event.sender, 'menubar', 'settings-aproxy')
}, {
label: LANG['main']['update'],
accelerator: 'Shift+CmdOrCtrl+U',
click: event.sender.send.bind(event.sender, 'menubar', 'settings-update')
}, {
type: 'separator'
}, {
label: LANG['main']['settings'],
accelerator: 'Shift+CmdOrCtrl+S',
click: event.sender.send.bind(event.sender, 'menubar', 'settings')
}, {
type: 'separator'
}, {
label: LANG['main']['plugin'],
accelerator: 'Shift+CmdOrCtrl+P',
click: event.sender.send.bind(event.sender, 'menubar', 'plugin')
}, {
type: 'separator'
}, {
label: LANG['main']['quit'],
accelerator: 'Command+Q',
click: this.app.quit.bind(this.app)
},
]
});
// };
};
// 主菜单
template.unshift({
label: LANG['main']['title'],
submenu: [
{
label: LANG['main']['about'],
accelerator: 'Shift+CmdOrCtrl+I',
click: event.sender.send.bind(event.sender, 'menubar', 'settings-about')
}, {
label: LANG['main']['language'],
accelerator: 'Shift+CmdOrCtrl+L',
click: event.sender.send.bind(event.sender, 'menubar', 'settings-language')
}, {
label: LANG['main']['aproxy'],
accelerator: 'Shift+CmdOrCtrl+A',
click: event.sender.send.bind(event.sender, 'menubar', 'settings-aproxy')
}, {
label: LANG['main']['update'],
accelerator: 'Shift+CmdOrCtrl+U',
click: event.sender.send.bind(event.sender, 'menubar', 'settings-update')
}, {
type: 'separator'
}, {
label: LANG['main']['settings'],
accelerator: 'Shift+CmdOrCtrl+S',
click: event.sender.send.bind(event.sender, 'menubar', 'settings')
}, {
type: 'separator'
}, {
label: LANG['main']['plugin'],
accelerator: 'Shift+CmdOrCtrl+P',
click: event.sender.send.bind(event.sender, 'menubar', 'plugin')
}, {
type: 'separator'
}, {
label: LANG['main']['quit'],
accelerator: 'Command+Q',
click: this.app.quit.bind(this.app)
},
]
});
// 更新菜单栏
this.Menu.setApplicationMenu(this.Menu.buildFromTemplate(template));
}
......
//
// 程序更新模块
//
/* 更新流程:
-------
1. 获取远程github上的package.json信息
2. 和本地版本进行判断,不一致则提示更新
3. 下载用户选择的更新源文件到临时目录`.antSword-{now}`
4. 替换程序中的`resources/app.asar`文件
5. 提示用户手动重启,关闭应用
*/
'use strict';
const os = require('os'),
fs = require('fs'),
path = require('path'),
unzip = require('extract-zip'),
crypto = require('crypto'),
nugget = require('nugget'),
logger = require('log4js').getLogger('Update'),
superagent = require('superagent');
class Update {
constructor(electron) {
const ipcMain = electron.ipcMain;
this.info = {};
ipcMain
.on('update-check', (event, arg) => {
this.check(arg['local_ver'], (hasUpdate, retVal) => {
logger.debug('check-result', hasUpdate, retVal);
event.sender.send('update-check', {
hasUpdate: hasUpdate,
retVal: retVal
});
});
})
.on('update-download', (event, source) => {
logger.debug('update-download', source);
const info = this.info['update'];
const downloadUrl = info['sources'][source];
this.download(downloadUrl, info['md5'], (done, retVal) => {
event.sender.send('update-download', {
done: done,
retVal: retVal
});
});
});
}
// 检查是否有更新
// 参数{localVer: 本地版本号, callback: 回调函数(是否有更新, 是?更新信息:错误信息)}
check(localVer, callback) {
logger.debug('check', localVer);
superagent
.get('https://raw.githubusercontent.com/antoor/antSword/master/package.json')
.timeout(9527)
.end((err, res) => {
if (err) { return callback(false, err.toString()) };
try {
const info = JSON.parse(res.text);
this.info = info;
callback(info['version'] !== localVer, info);
} catch (e) {
return callback(false, e.toString());
}
});
}
// 下载更新
// 参数{downloadUrl: 下载地址, md5: 校验MD5, callback: 回调(成功?(true, null):(false, err))}
download(downloadUrl, md5, callback) {
// 创建临时文件
const tmpDir = os.tmpDir();
const fileName = '.antSword-' + (+new Date);
const tmpFileName = path.join(tmpDir, fileName);
// 当前目录环境
const curDir = path.join(__dirname, '../../');
// 开始下载文件
nugget(
downloadUrl,
{
target: fileName,
dir: tmpDir,
resume: true,
verbose: true,
strictSSL: downloadUrl.startsWith('https')
},
(err) => {
if (err) { return callback(false, err.toString()) };
// 校验MD5
const _md5 = crypto.createHash('md5').update(fs.readFileSync(tmpFileName)).digest('hex');
if (_md5 !== md5) { return callback(false, { type: 'md5', err: _md5 }) };
// ZIP解压
unzip(tmpFileName, {
dir: tmpDir
}, (e) => {
if (e) { return (callback(false, { type: 'unzip', err: e })) };
// 删除旧asar
// fs.unlinkSync(path.join(curDir, 'app.asar'));
// 移动新asar
fs.rename(
path.join(tmpDir, 'antSword.update'),
path.join(curDir, 'app.asar'),
(_e) => {
_e ? callback(false, _e.toString()) : callback(true);
}
);
});
}
);
}
}
module.exports = Update;
\ No newline at end of file
{
"name": "antsword",
"version": "1.1.0",
"version": "1.1.1",
"description": "中国蚁剑是一款跨平台的开源网站管理工具",
"main": "app.js",
"dependencies": {
......@@ -8,11 +8,13 @@
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-stage-0": "^6.5.0",
"electron-prebuilt": "^0.36.10",
"electron-prebuilt": "^0.37.2",
"extract-zip": "^1.5.0",
"iconv-lite": "^0.4.13",
"lib-qqwry": "^0.0.5",
"log4js": "^0.6.29",
"nedb": "^1.5.1",
"nugget": "^2.0.0",
"superagent": "^1.6.1",
"superagent-proxy": "^1.0.0",
"webpack": "^1.12.14"
......@@ -27,6 +29,12 @@
"type": "git",
"url": "https://github.com/antoor/antSword"
},
"debug": true,
"update": {
"md5": "",
"logs": "",
"sources": {}
},
"bugs": {
"url": "https://github.com/antoor/antSword/issues"
},
......
## Shell-Scripts
> 此目录用于存放一些示例的服务端脚本文件,仅供参考。
\ No newline at end of file
This diff is collapsed.
......@@ -7,6 +7,8 @@
'use strict';
const fs = global.require('fs');
const path = global.require('path');
const electron = global.require('electron');
const remote = electron.remote;
const ipcRenderer = electron.ipcRenderer;
......@@ -57,6 +59,7 @@ ipcRenderer.send('aproxy', {
antSword['ipcRenderer'] = ipcRenderer;
antSword['CacheManager'] = CacheManager;
antSword['menubar'] = new Menubar();
antSword['package'] = JSON.parse(fs.readFileSync(path.join(global.__dirname, '../package.json')));
// 加载模块列表
// antSword['tabbar'] = new dhtmlXTabBar(document.getElementById('container'));
......
//
//
// 默认代码模板
//
//
// @params
// :encode SHELL编码
// :conn 数据库连接字符串
// :sql 执行SQL语句
//
// :db 数据库名
// :table 表名
module.exports = {
show_databases: {
......@@ -16,12 +17,15 @@ module.exports = {
show_tables: {
_: 'O',
'z0': '#{encode}',
'z1': '#{conn}'
'z1': '#{conn}',
'z2': '#{db}'
},
show_columns: {
_: 'P',
'z0': '#{encode}',
'z1': '#{conn}'
'z1': '#{conn}',
'z2': '#{db}',
'z3': '#{table}'
},
query: {
_: 'Q',
......@@ -29,4 +33,4 @@ module.exports = {
'z1': '#{conn}',
'z2': '#{sql}'
}
}
\ No newline at end of file
}
......@@ -403,8 +403,33 @@ module.exports = {
},
update: {
title: 'Check update',
current: 'Current version',
toolbar: {
check: 'Check'
},
check: {
ing: 'Check for updates..',
fail: (err) => `Check for update failed!<br/>${err}`,
none: (ver) => `After examination, no update![v${ver}]`,
found: (ver) => `Found a new version [v${ver}]`
},
prompt: {
btns: {
ok: 'Update',
no: 'Cancel'
},
title: 'Update to version',
changelog: 'Change Logs: ',
sources: 'Download source: ',
fail: {
md5: 'File MD5 value check failed!',
unzip: (err) => `Unzip the file failed! [${err}]`
}
},
message: {
ing: 'Downloading..',
fail: (err) => `Update failed! [${err}]`,
success: 'Update success! Please manually restart the application later!'
}
},
aproxy: {
......
......@@ -404,8 +404,33 @@ module.exports = {
},
update: {
title: '检查更新',
current: '当前版本',
toolbar: {
check: '检查'
},
check: {
ing: '检查更新中。。',
fail: (err) => `检查更新失败!<br/>${err}`,
none: (ver) => `检查完毕,暂无更新!【v${ver}】`,
found: (ver) => `发现新版本【v${ver}】`
},
prompt: {
btns: {
ok: '更新',
no: '取消'
},
title: '版本更新',
changelog: '更新日志:',
sources: '更新来源:',
fail: {
md5: '文件MD5值校验失败!',
unzip: (err) => `解压文件失败!【${err}】`
}
},
message: {
ing: '努力更新中。。',
fail: (err) => `更新失败!【${err}】`,
success: '更新成功!请稍后手动重启应用!'
}
},
aproxy: {
......
//
//
// 数据库驱动::ASP
// 支持数据库:access,sqlserver,mysql
//
//
class ASP {
......@@ -9,9 +9,9 @@ class ASP {
this.opt = opt;
this.core = this.opt.core;
this.manager = this.opt.super;
//
//
// * 数据库驱动列表
//
//
this.conns = {
'mysql': 'com.mysql.jdbc.Driver\r\njdbc:mysql://localhost/test?user=root&password=123456',
'sqlserver': 'com.microsoft.sqlserver.jdbc.SQLServerDriver\r\njdbc:sqlserver://127.0.0.1:1433;databaseName=test;user=sa;password=123456',
......@@ -71,10 +71,11 @@ class ASP {
// 生成查询SQL语句
case 'column':
let _co = arr[1].split(':');
const db = new Buffer(_co[1], 'base64').toString();
const table = new Buffer(_co[2], 'base64').toString();
const column = new Buffer(_co[3], 'base64').toString();
const sql = `SELECT TOP 20 [${column}] FROM [${table}] ORDER BY 1 DESC;`;
const sql = `SELECT ${column} FROM ${db}.${table} ORDER BY 1 DESC;`;
this.manager.query.editor.session.setValue(sql);
break;
}
......@@ -253,7 +254,7 @@ class ASP {
{
conn: conf['conn'],
encode: this.manager.opt.encode,
dbname: ['access', 'microsoft_jet_oledb_4_0'].indexOf(conf['type']) > -1 ? conf['conn'].match(/[\w]+.mdb$/) : 'database'
db: ['access', 'microsoft_jet_oledb_4_0'].indexOf(conf['type']) > -1 ? conf['conn'].match(/[\w]+.mdb$/) : 'database'
}, (ret) => {
const arr = ret.split('\t');
if (arr.length === 1 && ret === '') {
......@@ -293,7 +294,7 @@ class ASP {
{
conn: conf['conn'],
encode: this.manager.opt.encode,
dbname: db
db: db
}, (ret) => {
const arr = ret.split('\t');
const _db = new Buffer(db).toString('base64');
......@@ -329,7 +330,8 @@ class ASP {
{
conn: conf['conn'],
encode: this.manager.opt.encode,
table: conf['type'] === 'oracle' ? `SELECT * FROM (SELECT A.*,ROWNUM N FROM ${table} A) WHERE N=1` : `SELECT TOP 1 * FROM ${table}`
db: db,
table: table
}, (ret) => {
const arr = ret.split('\t');
const _db = new Buffer(db).toString('base64');
......@@ -352,8 +354,8 @@ class ASP {
// 更新编辑器SQL语句
this.manager.query.editor.session.setValue(
conf['type'] === 'oracle'
? `SELECT * FROM (SELECT A.*,ROWNUM N FROM ${table} A ORDER BY 1 DESC) WHERE N>0 AND N<=20`
: `SELECT TOP 20 * FROM ${table} ORDER BY 1 DESC;`);
? `SELECT * FROM (SELECT A.*,ROWNUM N FROM ${db}.${table} A ORDER BY 1 DESC) WHERE N>0 AND N<=20`
: `SELECT * FROM ${db}.${table} ORDER BY 1 DESC LIMIT 0,20;`);
this.manager.list.layout.progressOff();
});
}
......@@ -454,4 +456,4 @@ class ASP {
}
module.exports = ASP;
\ No newline at end of file
module.exports = ASP;
......@@ -346,12 +346,14 @@ class Files {
bmenu.hide();
});
// 双击::列出数据&&查看/编辑/下载文件(支持查看程序(png|jpg|gif..)则查看
//支持编辑文件(php,js,txt..)则启动编辑器,如果是二进制或压缩等文件(exe,dll,zip,rar..)则下载)
// 双击文件
// :如果size < 100kb,则进行编辑,否则进行下载
grid.attachEvent('onRowDblClicked', (id, lid, event) => {
const fname = grid.getRowAttribute(id, 'fname');
const fsize = grid.getRowAttribute(id, 'fsize');
if (!fname.endsWith('/')) {
// grid.callEvent('onRightClick', [id, lid, event]);
// 双击编辑size < 100kb 文件
fsize <= 100 * 1024 ? manager.editFile(fname) : manager.downloadFile(fname, fsize);
}else{
self.gotoPath(fname);
}
......
......@@ -3,6 +3,7 @@
//
const LANG = antSword['language']['settings']['update'];
const LANG_T = antSword['language']['toastr'];
class Update {
constructor(sidebar) {
......@@ -15,19 +16,152 @@ class Update {
// toolbar
const toolbar = cell.attachToolbar();
toolbar.loadStruct([
{ id: 'check', type: 'button', text: LANG['toolbar']['check'], disabled: true, icon: 'check-square-o' },
{ type: 'separator' }
{
id: 'check',
type: 'button',
// 调试或者windows平台不支持更新
disabled: antSword['package']['debug'] || process.platform === 'win',
text: LANG['toolbar']['check'], icon: 'check-square-o'
}, { type: 'separator' }
]);
// toolbar点击事件
toolbar.attachEvent('onClick', (id) => {
switch(id) {
case 'check':
this.checkUpdate();
break;
}
});
// status
cell.attachHTMLString(`
${LANG['current']}: ${antSword['package']['version']}
`);
this.cell = cell;
}
// 检查更新
checkUpdate() {
this.cell.progressOn();
toastr.info(LANG['check']['ing'], LANG_T['info']);
// 后台检查更新
antSword['ipcRenderer']
.on('update-check', (event, ret) => {
this.cell.progressOff();
const info = ret['retVal'];
// 木有更新
if (!ret['hasUpdate']) {
return typeof info === 'string'
? toastr.error(LANG['check']['fail'](info), LANG_T['error'])
: toastr.info(LANG['check']['none'](info['version']), LANG_T['info']);
}
// 发现更新
toastr.success(LANG['check']['found'](info['version']), LANG_T['success']);
// 更新来源html
let sources_html = `<select id="ant-update-source">`;
for (let s in info['update']['sources']) {
sources_html += `<option value="${s}">${s}</option>`;
}
sources_html += `</select>`;
// 提示更新
layer.open({
type: 1,
shift: 2,
skin: 'ant-update',
btn: [LANG['prompt']['btns']['ok'], LANG['prompt']['btns']['no']],
closeBtn: 0,
title: `<i class="fa fa-cloud-download"></i> ${LANG['prompt']['title']}[v${info['version']}]`,
content: `
<strong>${LANG['prompt']['changelog']}</strong>
<ol>
<li>${info['update']['logs'].split('\n').join('</li><li>')}
</ol>
<strong>${LANG['prompt']['sources']}</strong>${sources_html}
`,
yes: () => {
// 获取更新选择地址
const download_source = $('#ant-update-source').val();
// 开始更新
// 更新动画
this.updateLoading();
// 通知后台
antSword['ipcRenderer']
.on('update-download', (event, ret) => {
// 下载失败
console.log(ret);
if (!ret['done']) {
if (typeof ret['retVal'] === 'object') {
switch(ret['retVal']['type']) {
case 'md5':
this.updateFail(LANG['prompt']['fail']['md5']);
break;
case 'unzip':
this.updateFail(LANG['prompt']['fail']['unzip'](ret['retVal']['err']));
break;
default:
this.updateFail(ret['retVal']);
}
} else {
this.updateFail(ret['retVal']);
}
return;
}
this.updateSuccess();
})
.send('update-download', download_source);
}
});
})
.send('update-check', {
local_ver: antSword['package']['version']
});
}
// 更新动画
updateLoading() {
// 删除按钮
$('.layui-layer-btn').remove();
// 加载动画
$('.layui-layer-content').html(`
<div class="pacman">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<p align="center"><strong>${LANG['message']['ing']}</strong></p>
`);
}
// 更新失败提示界面
updateFail(tip) {
$('.layui-layer-content').html(`
<div align="center" style="color: red">
<i class="fa fa-times-circle update-icon" />
<p><strong>${LANG['message']['fail'](tip)}</strong></p>
</div>
`);
toastr.error(LANG['message']['fail'](tip), LANG_T['error']);
setTimeout(layer.closeAll, 1024 * 5);
}
当前版本:1.0.0
<br/>
暂不支持在线更新!
<br />
请访问<strong style="color:#0099FF">https://github.com/antoor/antSword</strong>获取最新版本!
// 更新成功提示界面
updateSuccess() {
$('.layui-layer-content').html(`
<div align="center" style="color: green">
<i class="fa fa-check-circle update-icon" />
<p><strong>${LANG['message']['success']}</strong></p>
</div>
`);
toastr.success(LANG['message']['success'], LANG_T['success']);
setTimeout(() => {
antSword['ipcRenderer'].send('quit');
}, 1024 * 3);
}
}
......
......@@ -21,4 +21,146 @@ html, body, #container, #loading {
width: auto !important;
padding: 0 5px;
background-color: #666 !important;
}
\ No newline at end of file
}
/*update*/
.ant-update {
font-family: sans-serif;
font-size: 14px;
}
.ant-update > .layui-layer-content {
padding: 10px 10px;
min-width: 300px;
}
.ant-update > .layui-layer-content > h3 {
margin: 0;
}
.ant-update > .layui-layer-content > ol {
color: #666;
}
.update-icon {
font-size: 100px;
text-align: center;
}
/*update-loading*/
@-webkit-keyframes rotate_pacman_half_up {
0% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg); }
50% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); }
100% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg); } }
@keyframes rotate_pacman_half_up {
0% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg); }
50% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); }
100% {
-webkit-transform: rotate(270deg);
transform: rotate(270deg); } }
@-webkit-keyframes rotate_pacman_half_down {
0% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg); }
50% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg); } }
@keyframes rotate_pacman_half_down {
0% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg); }
50% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(90deg);
transform: rotate(90deg); } }
@-webkit-keyframes pacman-balls {
75% {
opacity: 0.7; }
100% {
-webkit-transform: translate(-100px, -6.25px);
transform: translate(-100px, -6.25px); } }
@keyframes pacman-balls {
75% {
opacity: 0.7; }
100% {
-webkit-transform: translate(-100px, -6.25px);
transform: translate(-100px, -6.25px); } }
.pacman {
position: relative;
margin-left: 30%;
}
.pacman > div:nth-child(2) {
-webkit-animation: pacman-balls 1s 0s infinite linear;
animation: pacman-balls 1s 0s infinite linear; }
.pacman > div:nth-child(3) {
-webkit-animation: pacman-balls 1s 0.33s infinite linear;
animation: pacman-balls 1s 0.33s infinite linear; }
.pacman > div:nth-child(4) {
-webkit-animation: pacman-balls 1s 0.66s infinite linear;
animation: pacman-balls 1s 0.66s infinite linear; }
.pacman > div:nth-child(5) {
-webkit-animation: pacman-balls 1s 0.99s infinite linear;
animation: pacman-balls 1s 0.99s infinite linear; }
.pacman > div:first-of-type {
width: 0px;
height: 0px;
border-right: 25px solid transparent;
border-top: 25px solid #666;
border-left: 25px solid #666;
border-bottom: 25px solid #666;
border-radius: 25px;
-webkit-animation: rotate_pacman_half_up 0.5s 0s infinite;
animation: rotate_pacman_half_up 0.5s 0s infinite; }
.pacman > div:nth-child(2) {
width: 0px;
height: 0px;
border-right: 25px solid transparent;
border-top: 25px solid #666;
border-left: 25px solid #666;
border-bottom: 25px solid #666;
border-radius: 25px;
-webkit-animation: rotate_pacman_half_down 0.5s 0s infinite;
animation: rotate_pacman_half_down 0.5s 0s infinite;
margin-top: -50px; }
.pacman > div:nth-child(3), .pacman > div:nth-child(4), .pacman > div:nth-child(5), .pacman > div:nth-child(6) {
background-color: #666;
width: 15px;
height: 15px;
border-radius: 100%;
margin: 2px;
width: 10px;
height: 10px;
position: absolute;
-webkit-transform: translate(0, -6.25px);
-ms-transform: translate(0, -6.25px);
transform: translate(0, -6.25px);
top: 25px;
left: 100px; }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment