mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: SFTP supports pasting files for upload (#87)
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package app.termora
|
package app.termora
|
||||||
|
|
||||||
|
import app.termora.transport.TransportDataProviders
|
||||||
import app.termora.transport.TransportPanel
|
import app.termora.transport.TransportPanel
|
||||||
import java.beans.PropertyChangeListener
|
import java.beans.PropertyChangeListener
|
||||||
import javax.swing.Icon
|
import javax.swing.Icon
|
||||||
@@ -40,8 +41,8 @@ class SFTPTerminalTab : Disposable, TerminalTab {
|
|||||||
|
|
||||||
override fun canClose(): Boolean {
|
override fun canClose(): Boolean {
|
||||||
assertEventDispatchThread()
|
assertEventDispatchThread()
|
||||||
|
val transportManager = transportPanel.getData(TransportDataProviders.TransportManager) ?: return true
|
||||||
if (transportPanel.transportManager.getTransports().isEmpty()) {
|
if (transportManager.getTransports().isEmpty()) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.termora.transport
|
package app.termora.transport
|
||||||
|
|
||||||
import app.termora.*
|
import app.termora.*
|
||||||
|
import app.termora.actions.AnActionEvent
|
||||||
import com.formdev.flatlaf.FlatClientProperties
|
import com.formdev.flatlaf.FlatClientProperties
|
||||||
import com.formdev.flatlaf.extras.components.FlatPopupMenu
|
import com.formdev.flatlaf.extras.components.FlatPopupMenu
|
||||||
import com.formdev.flatlaf.extras.components.FlatToolBar
|
import com.formdev.flatlaf.extras.components.FlatToolBar
|
||||||
@@ -11,6 +12,7 @@ import com.formdev.flatlaf.util.SystemInfo
|
|||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.swing.Swing
|
import kotlinx.coroutines.swing.Swing
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.apache.commons.lang3.SystemUtils
|
import org.apache.commons.lang3.SystemUtils
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils
|
import org.apache.commons.lang3.exception.ExceptionUtils
|
||||||
import org.apache.sshd.sftp.client.SftpClient
|
import org.apache.sshd.sftp.client.SftpClient
|
||||||
@@ -26,10 +28,12 @@ import java.awt.datatransfer.StringSelection
|
|||||||
import java.awt.dnd.DnDConstants
|
import java.awt.dnd.DnDConstants
|
||||||
import java.awt.dnd.DropTarget
|
import java.awt.dnd.DropTarget
|
||||||
import java.awt.dnd.DropTargetDropEvent
|
import java.awt.dnd.DropTargetDropEvent
|
||||||
|
import java.awt.event.ActionEvent
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.*
|
import java.nio.file.*
|
||||||
|
import java.util.*
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
import javax.swing.table.DefaultTableCellRenderer
|
import javax.swing.table.DefaultTableCellRenderer
|
||||||
import kotlin.io.path.exists
|
import kotlin.io.path.exists
|
||||||
@@ -60,7 +64,6 @@ class FileSystemPanel(
|
|||||||
private val homeBtn = JButton(Icons.homeFolder)
|
private val homeBtn = JButton(Icons.homeFolder)
|
||||||
private val showHiddenFilesBtn = JButton(Icons.eyeClose)
|
private val showHiddenFilesBtn = JButton(Icons.eyeClose)
|
||||||
private val properties get() = Database.getDatabase().properties
|
private val properties get() = Database.getDatabase().properties
|
||||||
private var isShowHiddenFiles = false
|
|
||||||
private val showHiddenFilesKey by lazy { "termora.transport.host.${host.id}.show-hidden-files" }
|
private val showHiddenFilesKey by lazy { "termora.transport.host.${host.id}.show-hidden-files" }
|
||||||
|
|
||||||
val workdir get() = tableModel.workdir
|
val workdir get() = tableModel.workdir
|
||||||
@@ -232,39 +235,10 @@ class FileSystemPanel(
|
|||||||
if (!tableModel.isLocalFileSystem) {
|
if (!tableModel.isLocalFileSystem) {
|
||||||
table.dropTarget = object : DropTarget() {
|
table.dropTarget = object : DropTarget() {
|
||||||
override fun drop(dtde: DropTargetDropEvent) {
|
override fun drop(dtde: DropTargetDropEvent) {
|
||||||
val transportPanel = getTransportPanel() ?: return
|
|
||||||
val localFileSystemPanel = transportPanel.leftFileSystemTabbed.getFileSystemPanel(0) ?: return
|
|
||||||
|
|
||||||
dtde.acceptDrop(DnDConstants.ACTION_COPY)
|
dtde.acceptDrop(DnDConstants.ACTION_COPY)
|
||||||
val files = dtde.transferable.getTransferData(DataFlavor.javaFileListFlavor) as List<*>
|
val files = dtde.transferable.getTransferData(DataFlavor.javaFileListFlavor) as List<*>
|
||||||
if (files.isEmpty()) return
|
if (files.isEmpty()) return
|
||||||
|
copyLocalFileToFileSystem(files.filterIsInstance<File>())
|
||||||
val paths = files.filterIsInstance<File>().map { FileSystemTableModel.CacheablePath(it.toPath()) }
|
|
||||||
for (path in paths) {
|
|
||||||
if (path.isDirectory) {
|
|
||||||
Files.walk(path.path).use {
|
|
||||||
for (e in it) {
|
|
||||||
transportPanel.transport(
|
|
||||||
sourceWorkdir = path.path.parent,
|
|
||||||
targetWorkdir = workdir,
|
|
||||||
isSourceDirectory = e.isDirectory(),
|
|
||||||
sourcePath = e,
|
|
||||||
sourceHolder = localFileSystemPanel,
|
|
||||||
targetHolder = this@FileSystemPanel
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
transportPanel.transport(
|
|
||||||
sourceWorkdir = path.path.parent,
|
|
||||||
targetWorkdir = workdir,
|
|
||||||
isSourceDirectory = false,
|
|
||||||
sourcePath = path.path,
|
|
||||||
sourceHolder = localFileSystemPanel,
|
|
||||||
targetHolder = this@FileSystemPanel
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
this.defaultActions = DnDConstants.ACTION_COPY
|
this.defaultActions = DnDConstants.ACTION_COPY
|
||||||
@@ -307,6 +281,7 @@ class FileSystemPanel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 显示隐藏文件
|
||||||
showHiddenFilesBtn.addActionListener {
|
showHiddenFilesBtn.addActionListener {
|
||||||
val showHiddenFiles = tableModel.isShowHiddenFiles
|
val showHiddenFiles = tableModel.isShowHiddenFiles
|
||||||
tableModel.isShowHiddenFiles = !showHiddenFiles
|
tableModel.isShowHiddenFiles = !showHiddenFiles
|
||||||
@@ -317,6 +292,19 @@ class FileSystemPanel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果不是本地的文件系统,那么支持粘贴
|
||||||
|
if (!tableModel.isLocalFileSystem) {
|
||||||
|
table.actionMap.put("paste", object : AbstractAction() {
|
||||||
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
|
if (!toolkit.systemClipboard.isDataFlavorAvailable(DataFlavor.javaFileListFlavor)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val files = (toolkit.systemClipboard.getData(DataFlavor.javaFileListFlavor) ?: return) as List<*>
|
||||||
|
copyLocalFileToFileSystem(files.filterIsInstance<File>())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Disposer.register(this, object : Disposable {
|
Disposer.register(this, object : Disposable {
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
properties.putString(showHiddenFilesKey, "${tableModel.isShowHiddenFiles}")
|
properties.putString(showHiddenFilesKey, "${tableModel.isShowHiddenFiles}")
|
||||||
@@ -326,6 +314,40 @@ class FileSystemPanel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun copyLocalFileToFileSystem(files: List<File>) {
|
||||||
|
val event = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
||||||
|
val transportPanel = event.getData(TransportDataProviders.TransportPanel) ?: return
|
||||||
|
val leftFileSystemTabbed = event.getData(TransportDataProviders.LeftFileSystemTabbed) ?: return
|
||||||
|
val localFileSystemPanel = leftFileSystemTabbed.getFileSystemPanel(0) ?: return
|
||||||
|
|
||||||
|
val paths = files.map { FileSystemTableModel.CacheablePath(it.toPath()) }
|
||||||
|
for (path in paths) {
|
||||||
|
if (path.isDirectory) {
|
||||||
|
Files.walk(path.path).use {
|
||||||
|
for (e in it) {
|
||||||
|
transportPanel.transport(
|
||||||
|
sourceWorkdir = path.path.parent,
|
||||||
|
targetWorkdir = workdir,
|
||||||
|
isSourceDirectory = e.isDirectory(),
|
||||||
|
sourcePath = e,
|
||||||
|
sourceHolder = localFileSystemPanel,
|
||||||
|
targetHolder = this@FileSystemPanel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
transportPanel.transport(
|
||||||
|
sourceWorkdir = path.path.parent,
|
||||||
|
targetWorkdir = workdir,
|
||||||
|
isSourceDirectory = false,
|
||||||
|
sourcePath = path.path,
|
||||||
|
sourceHolder = localFileSystemPanel,
|
||||||
|
targetHolder = this@FileSystemPanel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
fun reload() {
|
fun reload() {
|
||||||
if (loadingPanel.isLoading) {
|
if (loadingPanel.isLoading) {
|
||||||
@@ -393,21 +415,21 @@ class FileSystemPanel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun canTransfer(): Boolean {
|
private fun canTransfer(): Boolean {
|
||||||
return getTransportPanel()?.getTargetFileSystemPanel(this) != null
|
val event = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
||||||
}
|
val leftFileSystemTabbed = event.getData(TransportDataProviders.LeftFileSystemTabbed) ?: return false
|
||||||
|
val rightFileSystemTabbed = event.getData(TransportDataProviders.RightFileSystemTabbed) ?: return false
|
||||||
|
|
||||||
|
val parent = SwingUtilities.getAncestorOfClass(FileSystemTabbed::class.java, this)
|
||||||
private fun getTransportPanel(): TransportPanel? {
|
if (parent == leftFileSystemTabbed) {
|
||||||
var p = this as Component?
|
return event.getData(TransportDataProviders.RightFileSystemPanel) != null
|
||||||
while (p != null) {
|
} else if (parent == rightFileSystemTabbed) {
|
||||||
if (p is TransportPanel) {
|
return event.getData(TransportDataProviders.LeftFileSystemPanel) != null
|
||||||
return p
|
|
||||||
}
|
|
||||||
p = p.parent
|
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun showContextMenu(rows: IntArray, event: MouseEvent) {
|
private fun showContextMenu(rows: IntArray, event: MouseEvent) {
|
||||||
val popupMenu = FlatPopupMenu()
|
val popupMenu = FlatPopupMenu()
|
||||||
val newMenu = JMenu(I18n.getString("termora.transport.table.contextmenu.new"))
|
val newMenu = JMenu(I18n.getString("termora.transport.table.contextmenu.new"))
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package app.termora.transport
|
||||||
|
|
||||||
|
import app.termora.terminal.DataKey
|
||||||
|
|
||||||
|
object TransportDataProviders {
|
||||||
|
val LeftFileSystemPanel = DataKey(FileSystemPanel::class)
|
||||||
|
val RightFileSystemPanel = DataKey(FileSystemPanel::class)
|
||||||
|
|
||||||
|
val LeftFileSystemTabbed = DataKey(FileSystemTabbed::class)
|
||||||
|
val RightFileSystemTabbed = DataKey(FileSystemTabbed::class)
|
||||||
|
|
||||||
|
val TransportManager = DataKey(app.termora.transport.TransportManager::class)
|
||||||
|
|
||||||
|
val TransportPanel = DataKey(app.termora.transport.TransportPanel::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -3,7 +3,9 @@ package app.termora.transport
|
|||||||
import app.termora.Disposable
|
import app.termora.Disposable
|
||||||
import app.termora.Disposer
|
import app.termora.Disposer
|
||||||
import app.termora.DynamicColor
|
import app.termora.DynamicColor
|
||||||
import app.termora.assertEventDispatchThread
|
import app.termora.actions.DataProvider
|
||||||
|
import app.termora.actions.DataProviderSupport
|
||||||
|
import app.termora.terminal.DataKey
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
@@ -18,17 +20,17 @@ import javax.swing.JSplitPane
|
|||||||
/**
|
/**
|
||||||
* 传输面板
|
* 传输面板
|
||||||
*/
|
*/
|
||||||
class TransportPanel : JPanel(BorderLayout()), Disposable {
|
class TransportPanel : JPanel(BorderLayout()), Disposable, DataProvider {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = LoggerFactory.getLogger(TransportPanel::class.java)
|
private val log = LoggerFactory.getLogger(TransportPanel::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
val transportManager = TransportManager()
|
private val dataProviderSupport = DataProviderSupport()
|
||||||
|
|
||||||
val leftFileSystemTabbed = FileSystemTabbed(transportManager, true)
|
|
||||||
val rightFileSystemTabbed = FileSystemTabbed(transportManager, false)
|
|
||||||
|
|
||||||
|
private val transportManager = TransportManager()
|
||||||
|
private val leftFileSystemTabbed = FileSystemTabbed(transportManager, true)
|
||||||
|
private val rightFileSystemTabbed = FileSystemTabbed(transportManager, false)
|
||||||
private val fileTransportPanel = FileTransportPanel(transportManager)
|
private val fileTransportPanel = FileTransportPanel(transportManager)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -43,6 +45,11 @@ class TransportPanel : JPanel(BorderLayout()), Disposable {
|
|||||||
Disposer.register(this, rightFileSystemTabbed)
|
Disposer.register(this, rightFileSystemTabbed)
|
||||||
Disposer.register(this, fileTransportPanel)
|
Disposer.register(this, fileTransportPanel)
|
||||||
|
|
||||||
|
dataProviderSupport.addData(TransportDataProviders.LeftFileSystemTabbed, leftFileSystemTabbed)
|
||||||
|
dataProviderSupport.addData(TransportDataProviders.RightFileSystemTabbed, rightFileSystemTabbed)
|
||||||
|
dataProviderSupport.addData(TransportDataProviders.TransportManager, transportManager)
|
||||||
|
dataProviderSupport.addData(TransportDataProviders.TransportPanel, this)
|
||||||
|
|
||||||
leftFileSystemTabbed.border = BorderFactory.createMatteBorder(0, 0, 0, 1, DynamicColor.BorderColor)
|
leftFileSystemTabbed.border = BorderFactory.createMatteBorder(0, 0, 0, 1, DynamicColor.BorderColor)
|
||||||
rightFileSystemTabbed.border = BorderFactory.createMatteBorder(0, 1, 0, 0, DynamicColor.BorderColor)
|
rightFileSystemTabbed.border = BorderFactory.createMatteBorder(0, 1, 0, 0, DynamicColor.BorderColor)
|
||||||
|
|
||||||
@@ -128,26 +135,6 @@ class TransportPanel : JPanel(BorderLayout()), Disposable {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getTargetFileSystemPanel(fileSystemPanel: FileSystemPanel): FileSystemPanel? {
|
|
||||||
|
|
||||||
assertEventDispatchThread()
|
|
||||||
|
|
||||||
for (i in 0 until leftFileSystemTabbed.tabCount) {
|
|
||||||
if (leftFileSystemTabbed.getFileSystemPanel(i) == fileSystemPanel) {
|
|
||||||
return rightFileSystemTabbed.getSelectedFileSystemPanel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in 0 until rightFileSystemTabbed.tabCount) {
|
|
||||||
if (rightFileSystemTabbed.getFileSystemPanel(i) == fileSystemPanel) {
|
|
||||||
return leftFileSystemTabbed.getSelectedFileSystemPanel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun transport(
|
fun transport(
|
||||||
sourceWorkdir: Path,
|
sourceWorkdir: Path,
|
||||||
targetWorkdir: Path,
|
targetWorkdir: Path,
|
||||||
@@ -191,4 +178,23 @@ class TransportPanel : JPanel(BorderLayout()), Disposable {
|
|||||||
log.info("Transport is disposed")
|
log.info("Transport is disposed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun <T : Any> getData(dataKey: DataKey<T>): T? {
|
||||||
|
if (dataKey == TransportDataProviders.LeftFileSystemPanel ||
|
||||||
|
dataKey == TransportDataProviders.RightFileSystemPanel
|
||||||
|
) {
|
||||||
|
dataProviderSupport.removeData(dataKey)
|
||||||
|
if (dataKey == TransportDataProviders.LeftFileSystemPanel) {
|
||||||
|
leftFileSystemTabbed.getSelectedFileSystemPanel()?.let {
|
||||||
|
dataProviderSupport.addData(dataKey, it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rightFileSystemTabbed.getSelectedFileSystemPanel()?.let {
|
||||||
|
dataProviderSupport.addData(dataKey, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return dataProviderSupport.getData(dataKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1 @@
|
|||||||
<svg t="1736928517310" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="877"
|
<svg t="1736928586708" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="907" width="16" height="16"><path d="M1001.58577778 482.87288889l-0.11377778-0.11377778-0.11377778-0.11377778c-41.41511111-87.26755555-91.02222222-157.80977778-148.70755555-211.62666666L794.96533333 328.81777778c49.72088889 45.73866667 92.72888889 106.60977778 129.82044445 183.06844444C830.00888889 707.92533333 695.63733333 800.99555555 512 800.99555555c-58.368 0-111.84355555-9.44355555-160.65422222-28.55822222l-62.23644445 62.23644445C355.66933333 866.75911111 429.85244445 882.91555555 512 882.91555555c218.68088889 0 381.61066667-114.34666667 489.472-341.67466666 8.76088889-18.432 8.76088889-39.82222222 0.11377778-58.368zM928.768 104.90311111l-48.24177778-48.24177778c-3.52711111-3.52711111-9.32977778-3.52711111-12.85688889 0L734.77688889 189.44C668.33066667 157.24088889 594.14755555 141.08444445 512 141.08444445c-218.68088889 0-381.61066667 114.34666667-489.472 341.67466666v0.11377778c-8.76088889 18.432-8.76088889 40.04977778 0 58.59555556 41.41511111 87.26755555 91.02222222 157.80977778 148.70755555 211.74044444L56.66133333 867.55555555c-3.52711111 3.52711111-3.52711111 9.32977778 0 12.8568889l48.24177778 48.24177777c3.52711111 3.52711111 9.32977778 3.52711111 12.85688889 0l811.008-811.008c3.52711111-3.41333333 3.52711111-9.216 0-12.74311111zM383.31733333 540.89955555c-2.16177778-9.32977778-3.29955555-19.00088889-3.29955555-28.89955555 0-70.42844445 57.00266667-127.43111111 127.43111111-127.43111111 9.89866667 0 19.68355555 1.13777778 28.89955556 3.29955556L383.31733333 540.89955555z m209.92-209.92C567.18222222 318.69155555 538.16888889 311.75111111 507.44888889 311.75111111c-110.592 0-200.24888889 89.65688889-200.24888889 200.24888889 0 30.72 6.94044445 59.73333333 19.22844445 85.78844445L229.03466667 695.18222222c-49.72088889-45.73866667-92.72888889-106.60977778-129.82044445-183.06844444C194.10488889 316.07466667 328.47644445 223.00444445 512 223.00444445c58.368 0 111.84355555 9.44355555 160.65422222 28.55822222l-79.41688889 79.41688888z" p-id="908" fill="#CED0D6"></path><path d="M507.44888889 639.43111111c-7.28177778 0-14.44977778-0.56888889-21.39022222-1.82044444l-58.14044445 58.14044444c24.34844445 10.58133333 51.31377778 16.384 79.53066667 16.384 110.592 0 200.24888889-89.65688889 200.24888889-200.24888889 0-28.21688889-5.80266667-55.18222222-16.384-79.53066667l-58.14044445 58.14044445c1.13777778 6.94044445 1.82044445 14.10844445 1.82044445 21.39022222C634.88 582.42844445 577.87733333 639.43111111 507.44888889 639.43111111z" p-id="909" fill="#CED0D6"></path></svg>
|
||||||
width="16" height="16">
|
|
||||||
<path d="M1001.472 482.64533333C893.61066667 255.43111111 730.56711111 141.08444445 512 141.08444445c-218.68088889 0-381.61066667 114.34666667-489.472 341.67466666-8.76088889 18.432-8.76088889 40.04977778 0 58.59555556C130.38933333 768.56888889 293.43288889 882.91555555 512 882.91555555c218.68088889 0 381.61066667-114.34666667 489.472-341.67466666 8.76088889-18.432 8.76088889-39.82222222 0-58.59555556zM512 800.99555555c-183.52355555 0-317.89511111-93.07022222-412.672-288.99555555C194.10488889 316.07466667 328.47644445 223.00444445 512 223.00444445c183.52355555 0 317.89511111 93.07022222 412.672 288.99555555C830.00888889 707.92533333 695.63733333 800.99555555 512 800.99555555z"
|
|
||||||
p-id="878" fill="#CED0D6"></path>
|
|
||||||
<path d="M507.73333333 324.26666667c-103.68 0-187.73333333 84.05333333-187.73333333 187.73333333s84.05333333 187.73333333 187.73333333 187.73333333 187.73333333-84.05333333 187.73333334-187.73333333-84.05333333-187.73333333-187.73333334-187.73333333z m0 307.2c-66.02666667 0-119.46666667-53.44-119.46666666-119.46666667s53.44-119.46666667 119.46666666-119.46666667 119.46666667 53.44 119.46666667 119.46666667-53.44 119.46666667-119.46666667 119.46666667z"
|
|
||||||
p-id="879" fill="#CED0D6"></path>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.6 KiB |
Reference in New Issue
Block a user