Commit 4e8f74c3 authored by liujiaxin's avatar liujiaxin

Initial commit

parents
Pipeline #134 failed with stages
File added
REACT_APP_AGORA_APP_ID=agora appId
REACT_APP_AGORA_LOG=true
REACT_APP_NETLESS_APP_TOKEN=netless whiteboard sdktoken
REACT_APP_NETLESS_APP_API_ENTRY=https://cloudcapiv4.herewhite.com/room?token=
REACT_APP_NETLESS_APP_JOIN_API=https://cloudcapiv4.herewhite.com/room/join?token=
REACT_APP_AGORA_RECORDING_OSS_URL=oss_url
#REACT_APP_AGORA_RECORDING_SERVICE_URL=https://api.agora.io/v1/apps/%s/cloud_recording
REACT_APP_AGORA_RECORDING_SERVICE_URL=https://apiagoraio.herewhite.com
ELECTRON_START_URL=http://localhost:3000
REACT_APP_AGORA_CUSTOMER_ID=customer_id
REACT_APP_AGORA_CUSTOMER_CERTIFICATE=customer_certificate
REACT_APP_AGORA_OSS_BUCKET_NAME=your_oss_bucket_name
REACT_APP_AGORA_OSS_BUCKET_FOLDER=your_oss_bucket_folder
REACT_APP_AGORA_OSS_BUCKET_REGION=your_bucket_region
REACT_APP_AGORA_OSS_BUCKET_KEY=your_bucket_ak
REACT_APP_AGORA_OSS_BUCKET_SECRET=your_bucket_sk
package-lock.json
yarn.lock
.env
*.*~
release/
*.local
build
src/assets/.DS_Store
!src/assets/
node_modules
# Default ignored files
/workspace.xml
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
# Agora Education Web & Electron demo
*Other Language: [简体中文](README.zh.md)*
### Preview
[web demo](https://solutions.agora.io/education/web/)
### Description
Agora Education solution is based on Agora RTM/Media sdk & netless whiteboard sdk.
For developer guideline:
|Feature|code entry|description|
| ---- | ----- | ----- |
| 1v1 | [one-to-one.tsx](./src/pages/classroom/one-to-one.tsx) | One teacher co-video with one student |
| 1v16: small-class scenario| [small-class.tsx](./src/pages/classroom/small-class.tsx) | One teacher co-video with 16 students |
| 1vN:big-class scenario | [big-class.tsx](./src/pages/classroom/big-class.tsx) | One teacher co-video with one student, student can join class with audience role, only send co-video with teacher, teacher permit co-video will become host and co-video. |
### Core SDKs
* agora-rtc-sdk (agora web sdk)
* agora-rtm-sdk (agora web sdk)
* agora-electron-sdk (official agora electron sdk)
* white-web-sdk (netless web sdk)
* ali-oss (can be replaced with your own cloud oss sdk)
* agora cloud recording (we recommend to integrate in server side)
### Frontend Tech Utilities
* typescript ^3.6.4
* react & react hooks & rxjs
* electron 5.0.8 & electron-builder
* material-ui
### Development Environment
* mac or windows
* nodejs LTS
* electron 5.0.8
### For Windows Electron Developer
* npm install electron@5.0.8 arch=ia32 manually
```
npm install electron@5.0.8 --arch=ia32 --save-dev
```
* find the `agora_electron` from package.json, replace it with below code snippet
```
"agora_electron": {
"electron_version": "5.0.8",
"prebuilt": true,
"platform": "win32"
},
```
### Setup
# Notice
#### If you are already enabled app certificate for security, please find below comment and integrate with your obtain token code snippet here.
```
WARN: IF YOU ENABLED APP CERTIFICATE, PLEASE SIGN YOUR TOKEN IN YOUR SERVER SIDE AND OBTAIN IT FROM YOUR OWN TRUSTED SERVER API
```
# obtain agora appid 和 netless sdktoken
rename .env.example to .env.local
```bash
# agora APPID obtain from dashboard.agora.io
REACT_APP_AGORA_APP_ID=Agora APPID
# true is indicate the agora sdk will enable log
REACT_APP_AGORA_LOG=true
# obtain netless whiteboard sdk from herewhite official website
REACT_APP_NETLESS_APP_TOKEN=SDKTOKEN
# whiteboard api create room entry and join room end-points
REACT_APP_NETLESS_APP_API_ENTRY=https://cloudcapiv4.herewhite.com/room?token=
REACT_APP_NETLESS_APP_JOIN_API=https://cloudcapiv4.herewhite.com/room/join?token=
# agora recording service end-point
REACT_APP_AGORA_RECORDING_SERVICE_URL=https://api.agora.io/v1/apps/%s/cloud_recording/
# oss for cloud recording storage
REACT_APP_AGORA_RECORDING_OSS_URL=云录制OSS地址
# oss parameters for whiteboard courseware
REACT_APP_AGORA_OSS_BUCKET_NAME=your_oss_bucket_name
REACT_APP_AGORA_OSS_BUCKET_FOLDER=your_oss_folder
REACT_APP_AGORA_OSS_BUCKET_REGION=your_oss_region
REACT_APP_AGORA_OSS_BUCKET_KEY=your_oss_bucket_ak
REACT_APP_AGORA_OSS_BUCKET_SECRET=your_oss_bucket_sk
```
# Build Web
#### Development build
`npm run dev`
#### Production build
`npm run build`
### Please double check your package.json,then `npm run build`
"homepage": "Your domain/website path"
# Build Electron
#### Development build
* `npm run electron`
* `Please take care, because this project using process manager to bootstrap electron main process, and render process start with create-react-app。`
#### Package Electron for Mac
npm run pack:mac
#### Package Electron for Win32
* npm i electron@5.0.8 --arch=ia32
* find and replace `agora_electron`:
```
"agora_electron": {
"electron_version": "5.0.8",
"prebuilt": true,
"platform": "win32"
},
```
* If you already did npm install, please clean node_modules & reinstall
* final step: npm run pack:win
#### FAQ
* [ISSUES](https://github.com/AgoraIO-Usecase/eEducation/issues/new)
* If you find your localhost:3000 port already exists or refused, please modify `ELECTRON_START_URL` from package.json, change to available port then run it again.
# 声网教育场景demo
*English Version: [English](README.md)*
### 在线预览
[web demo](https://solutions.agora.io/education/web/)
### 简介
Agora Edu是基于声网的音视频sdk和实时消息sdk,以及Netless的白板sdk构成
主要功能如下:
|功能概述|代码入口|功能描述|
| ---- | ----- | ----- |
|老师1v1教学授课 | [one-to-one.tsx](./src/pages/classroom/one-to-one.tsx) | 1个老师和1个学生默认连麦进入教室 |
|小班课场景:老师1v16学生教学授课| [small-class.tsx](./src/pages/classroom/small-class.tsx) | 1个老师和至多16个学生默认连麦进入教室 |
|大班课场景:老师1v多学生,默认以观众身份进入频道,举手向老师发起连麦,老师接受连麦并且统一以后,连麦互动。| [big-class.tsx](./src/pages/classroom/big-class.tsx) | 1个老师默认连麦进入教室,学生进入无限制人数 |
### 使用的SDK
* agora-rtc-sdk(web版声网sdk)
* agora-rtm-sdk(web版声网实时消息sdk)
* agora-electron-sdk(声网官方electron-sdk)
* white-web-sdk(netless官方白板sdk)
* ali-oss(可替换成你自己的oss client)
* 声网云录制 (不推荐直接在客户端集成)
### 所用技术
* typescript ^3.6.4
* react & react hooks & rxjs
* electron 5.0.8 & electron-builder
* material-ui
### 开发环境
* mac or windows
* nodejs LTS
* electron 5.0.8
### electron & node-sass 下载慢的解决方案
* mac
```
export ELECTRON_MIRROR="https://npm.taobao.org/mirrors/electron/"
export ELECTRON_CUSTOM_DIR="5.0.8"
export SASS_BINARY_SITE="https://npm.taobao.org/mirrors/node-sass/"
```
* windows
```
set ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/
set ELECTRON_CUSTOM_DIR=5.0.8
set SASS_BINARY_SITE=https://npm.taobao.org/mirrors/node-sass/
```
### electron环境注意事项
* mac 不需要修改package.json
* windows 需要找到package.json里的`agora_electron` 按照如下结构替换
```
"agora_electron": {
"electron_version": "5.0.8",
"prebuilt": true,
"platform": "win32"
},
```
(windows上推荐手动安装electron 5.0.8)
```
npm install electron@5.0.8 --arch=ia32 --save-dev
```
### 环境搭建
# 注意
#### 如果你的appid项目里启用了证书服务,请在代码里搜索以下注释寻找使用到token的地方,在这里加入获取token的业务逻辑。
```
WARN: IF YOU ENABLED APP CERTIFICATE, PLEASE SIGN YOUR TOKEN IN YOUR SERVER SIDE AND OBTAIN IT FROM YOUR OWN TRUSTED SERVER API
```
# 搭建之前先获取 agora appid和netless sdktoken
按照.env.example
修改为.env.local
```bash
# 声网的APPID 通过声网开发者管理界面获取
REACT_APP_AGORA_APP_ID=Agora APPID
# true表示开启声网前端日志
REACT_APP_AGORA_LOG=true
# 白板的sdktoken 可以通过后台获取
REACT_APP_NETLESS_APP_TOKEN=SDKTOKEN
# 白板的api 详情请参考白板官方文档的集成指南
REACT_APP_NETLESS_APP_API_ENTRY=https://cloudcapiv4.herewhite.com/room?token=
REACT_APP_NETLESS_APP_JOIN_API=https://cloudcapiv4.herewhite.com/room/join?token=
# 声网的云录制服务地址 (不推荐在前端或客户端直接集成)
REACT_APP_AGORA_RECORDING_SERVICE_URL=https://api.agora.io/v1/apps/%s/cloud_recording/
# 存放云录制OSS的CDN地址
REACT_APP_AGORA_RECORDING_OSS_URL=云录制OSS地址
# 下列OSS相关的信息不建议放在前端存储
REACT_APP_AGORA_OSS_BUCKET_NAME=你的oss名字
REACT_APP_AGORA_OSS_BUCKET_FOLDER=你的oss存储目录
REACT_APP_AGORA_OSS_BUCKET_REGION=你的oss存储节点地区
REACT_APP_AGORA_OSS_BUCKET_KEY=你的oss存储key或者存储id
REACT_APP_AGORA_OSS_BUCKET_SECRET=你的oss的存储秘钥
```
# Web发布和开发操作
#### 本地开发运行方式
`npm run dev`
#### 本地编译方式
`npm run build`
### 部署的时候需要修改package.json,然后执行npm run build
"homepage": "你的域名/路径"
# Electron版发布和开发操作
#### 本地运行
`npm run electron`
`此时会启动两个进程,一个进程使用cra的webpack编译构建render进程,electron主进程会等待webpack构建成功以后开始执行。`
#### electron mac打包方式
npm run pack:mac
等待成功运行结束时会产生一个release目录,默认会打包出一个dmg文件,正常打开更新到Application目录即可完成安装,然后可以执行程序。
#### electron win32程序打包方式(执行之前请务必确保已经正确安装--arch=ia32版本5.0.8的electron和agora-electron-sdk "platform": "win32"版)
npm run pack:win
等待成功运行结束时会产生一个release目录,默认会打包出一个安装程序,请使用windows管理员身份打开,即可完成安装,然后可以执行程序。
#### FAQ
* [问题反馈](https://github.com/AgoraIO-Usecase/eEducation/issues/new)
* 关于electron启动时发现localhost:3000端口被占用问题解决方案,可以在package.json里找到ELECTRON_START_URL=http://localhost:3000 修改成你本地可以使用的端口号
const electron = require('electron');
const ipcMain = electron.ipcMain;
// workaround for resizable issue in mac os
const platform = require('os').platform();
const process = require('process');
// Module to control application life.
const app = electron.app;
const globalShortcut = electron.globalShortcut;
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow;
const path = require('path');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
frame: false,
width: 700,
height: 500,
center: true,
resizable: false,
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, './preload')
}
});
const startUrl = process.env.ELECTRON_START_URL ||
`file://${path.resolve(
__dirname,
'../../app.asar/build'
)}/index.html`;
mainWindow.center();
// and load the index.html of the app.
mainWindow.loadURL(startUrl);
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
mainWindow.setMenu(null);
ipcMain.on('resize-window', (event, reply) => {
if (platform === 'darwin') {
}
if (platform === 'win32') {
if (reply.width === 700) {
if (mainWindow.isFullScreen()) {
mainWindow.setResizable(true);
mainWindow.setFullScreen(false);
mainWindow.setResizable(false);
}
}
}
mainWindow.setContentSize(reply.width, reply.height, false);
mainWindow.center();
});
ipcMain.on('minimum', () => {
mainWindow.minimize();
});
ipcMain.on('maximum', () => {
if (platform === 'win32') {
const fullscreen = mainWindow.isFullScreen();
if (fullscreen) {
mainWindow.setResizable(true);
mainWindow.setFullScreen(false);
mainWindow.setResizable(false);
} else {
mainWindow.setResizable(true);
mainWindow.setFullScreen(true);
mainWindow.setResizable(false);
}
}
if (platform === 'darwin') {
const fullscreen = mainWindow.isFullScreen();
mainWindow.setFullScreen(!fullscreen);
}
});
ipcMain.on('close', () => {
app.quit();
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
app.whenReady().then(() => {
// more details: https://www.electronjs.org/docs/tutorial/keyboard-shortcuts
globalShortcut.register('Control+Shift+X', () => {
// Open the DevTools.
mainWindow.webContents.openDevTools();
})
});
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
});
app.on('activate', function () {
console.log("main process activate");
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
if (mainWindow) {
mainWindow.show();
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
\ No newline at end of file
const {ipcRenderer: ipc} = require('electron');
const AgoraRtcEngine = require('agora-electron-sdk').default;
window.ipc = ipc;
const rtcEngine = new AgoraRtcEngine();
window.rtcEngine = rtcEngine;
\ No newline at end of file
const {
override,
addBabelPlugins,
addWebpackExternals,
useBabelRc
} = require('customize-cra');
const isDev = process.env.BROWSER === 'none';
module.exports = override(
isDev && addWebpackExternals({
"agora-electron-sdk": "commonjs2 agora-electron-sdk"
}),
addBabelPlugins(
'@babel/plugin-proposal-optional-chaining'
),
useBabelRc()
)
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
</dict>
</plist>
\ No newline at end of file
{
"name": "agora-edu-web",
"version": "5.0.0",
"private": true,
"homepage": "./",
"main": "./src/index.tsx",
"agora_electron": {
"electron_version": "5.0.8",
"prebuilt": true
},
"dmg": {
"compression": "store",
"npmRebuild": false,
"title": "agora_edu",
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
]
},
"build": {
"extraMetadata": {
"main": "build/index.js"
},
"productName": "AgoraEducation",
"appId": "io.agora.education",
"files": [
"build/**/*"
],
"mac": {
"target": [
"dmg",
"zip"
],
"icon": "icons/favicon.png",
"entitlements": "entitlements.mac.plist",
"category": "io.agora.Education",
"hardenedRuntime": true,
"extendInfo": {
"NSMicrophoneUsageDescription": "AgoraEdu Classroom acquire your microphone permission",
"NSCameraUsageDescription": "AgoraEdu Classroom acquire your camera permission"
}
},
"win": {
"icon": "icons/favicon.png",
"target": [
{
"target": "nsis",
"arch": [
"ia32"
]
}
]
},
"linux": {
"icon": "icons/favicon.png",
"target": [
"deb",
"rpm",
"snap",
"AppImage"
],
"category": "Development"
},
"directories": {
"buildResources": "assets",
"output": "release"
}
},
"dependencies": {
"@material-ui/core": "^4.5.1",
"@material-ui/icons": "^4.5.1",
"@netless/react-seek-slider": "^1.0.4",
"agora-electron-sdk": "2.9.1-beta.4",
"agora-rtc-sdk": "3.0.0",
"agora-rtm-sdk": "^1.2.0",
"ali-oss": "^6.3.1",
"concurrently": "^5.0.0",
"cross-env": "^6.0.3",
"immutable": "^4.0.0-rc.12",
"js-md5": "^0.7.3",
"lodash": "^4.17.15",
"moment": "^2.24.0",
"node-sass": "^4.12.0",
"react": "^16.10.2",
"react-color": "2.17.3",
"react-dom": "^16.10.2",
"react-draggable": "^4.2.0",
"react-router-dom": "^5.1.2",
"react-scripts": "3.2.0",
"rxjs": "^6.5.3",
"typescript": "^3.6.4",
"uuid": "^3.3.3",
"white-web-sdk": "^2.5.8"
},
"scripts": {
"electron": "cross-env REACT_APP_RUNTIME_PLATFORM=electron ELECTRON_START_URL=http://localhost:3000 concurrently \"npm run electron:web\" \"wait-on http://localhost:3000 && npm run electron:client\"",
"electron:client": "cross-env electron ./app/",
"electron:web": "cross-env BROWSER=none react-app-rewired start",
"electron:copy:web": "cross-env REACT_APP_RUNTIME_PLATFORM=electron react-app-rewired build",
"electron:copy:electron": "cpx ./app/**/*.js ./build",
"electron:build": "npm run electron:copy:web && npm run electron:copy:electron",
"dev": "cross-env REACT_APP_RUNTIME_PLATFORM=web react-app-rewired start",
"build": "cross-env REACT_APP_RUNTIME_PLATFORM=web react-app-rewired build",
"test": "react-app-rewired test --env=jsdom",
"analyze": "source-map-explorer 'build/static/js/*.js'",
"eject": "react-scripts eject",
"pack:mac": "npm run electron:build && electron-builder --mac",
"pack:win": "npm run electron:build && electron-builder --win"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.7.5",
"@netless/white-audio-plugin": "1.1.4",
"@netless/white-video-plugin": "1.1.10",
"@types/ali-oss": "^6.0.4",
"@types/jest": "^24.0.19",
"@types/lodash": "^4.14.145",
"@types/node": "^12.12.22",
"@types/react": "^16.9.9",
"@types/react-color": "^3.0.1",
"@types/react-dom": "^16.9.2",
"@types/react-router-dom": "^5.1.0",
"@types/uuid": "^3.4.6",
"babel-plugin-import": "^1.13.0",
"cpx": "^1.5.0",
"customize-cra": "^0.8.0",
"devtron": "^1.4.0",
"electron": "5.0.8",
"electron-builder": "^21.2.0",
"react-app-rewired": "^2.1.4",
"source-map-support": "^0.5.16",
"wait-on": "^3.3.0"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Agora Education Demo</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
{
"short_name": "Agora Education",
"name": "Agora Education",
"icons": [
{
"src": "favicon.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
File added
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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