start/stop node
|
@ -29,4 +29,5 @@ resources.qrc
|
|||
status-react-translations/
|
||||
/.update.timestamp
|
||||
notarization.log
|
||||
status-desktop.log
|
||||
DesktopNode
|
||||
status-node.log
|
||||
|
|
9
Makefile
|
@ -34,7 +34,9 @@ BUILD_SYSTEM_DIR := vendor/nimbus-build-system
|
|||
run-macos \
|
||||
run-windows \
|
||||
status-go \
|
||||
update
|
||||
update \
|
||||
clean-status-go \
|
||||
rebuild-status-go \
|
||||
|
||||
ifeq ($(NIM_PARAMS),)
|
||||
# "variables.mk" was not included, so we update the submodules.
|
||||
|
@ -186,6 +188,11 @@ $(STATUSGO): | deps
|
|||
+ cd vendor/status-go && \
|
||||
$(MAKE) statusgo-shared-library $(HANDLE_OUTPUT)
|
||||
|
||||
clean-status-go:
|
||||
rm -f vendor/status-go/build/bin/libstatus.*
|
||||
|
||||
rebuild-status-go: clean-status-go status-go
|
||||
|
||||
FLEETS := fleets.json
|
||||
$(FLEETS):
|
||||
echo -e $(BUILD_MSG) "Getting latest $(FLEETS)"
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import NimQml, chronicles
|
||||
import ../../status/signals/types
|
||||
import ../../status/[status, node]
|
||||
import ../../status/types as status_types
|
||||
import ../../eventemitter
|
||||
import view
|
||||
|
||||
logScope:
|
||||
topics = "node"
|
||||
|
||||
type NodeController* = ref object
|
||||
status*: Status
|
||||
view*: NodeView
|
||||
variant*: QVariant
|
||||
|
||||
proc newController*(status: Status, fleetConfig: string): NodeController =
|
||||
result = NodeController()
|
||||
result.status = status
|
||||
result.view = newNodeView(status, fleetConfig)
|
||||
result.variant = newQVariant(result.view)
|
||||
|
||||
proc delete*(self: NodeController) =
|
||||
delete self.variant
|
||||
delete self.view
|
||||
|
||||
proc init*(self: NodeController) =
|
||||
self.status.events.on(SignalType.Stats.event) do (e:Args):
|
||||
self.view.setStats(StatsSignal(e).stats)
|
||||
|
||||
self.status.events.on(SignalType.NodeStarted.event) do (e:Args):
|
||||
self.view.setNodeActive(true)
|
||||
|
||||
self.status.events.on(SignalType.NodeCrashed.event) do (e:Args):
|
||||
self.view.setNodeActive(false)
|
||||
|
||||
self.status.events.on(SignalType.NodeStopped.event) do (e:Args):
|
||||
self.view.setNodeActive(false)
|
|
@ -0,0 +1,78 @@
|
|||
import NimQml, chronicles, strutils, json
|
||||
import ../../status/[status, node, types, settings]
|
||||
import ../../status/signals/types as signal_types
|
||||
import ../../status/libstatus/accounts/constants
|
||||
|
||||
logScope:
|
||||
topics = "node-view"
|
||||
|
||||
QtObject:
|
||||
type NodeView* = ref object of QObject
|
||||
status*: Status
|
||||
stats*: Stats
|
||||
fleetConfig: string
|
||||
nodeActive*: bool
|
||||
|
||||
proc setup(self: NodeView) =
|
||||
self.QObject.setup
|
||||
|
||||
proc newNodeView*(status: Status, fleetConfig: string): NodeView =
|
||||
new(result)
|
||||
result.status = status
|
||||
result.nodeActive = false
|
||||
result.fleetConfig = fleetConfig
|
||||
result.setup
|
||||
|
||||
proc delete*(self: NodeView) =
|
||||
self.QObject.delete
|
||||
|
||||
proc getDataDir(self:NodeView): string {.slot.} = DATADIR
|
||||
|
||||
QtProperty[string] dataDir:
|
||||
read = getDataDir
|
||||
|
||||
proc getNodeActive(self:NodeView): bool {.slot.} = self.nodeActive
|
||||
|
||||
proc nodeActiveChanged(self:NodeView, value:bool) {.signal.}
|
||||
|
||||
proc setNodeActive*(self:NodeView, value: bool) {.slot.} =
|
||||
self.nodeActive = value
|
||||
self.nodeActiveChanged(value)
|
||||
|
||||
QtProperty[bool] nodeActive:
|
||||
read = getNodeActive
|
||||
notify = nodeActiveChanged
|
||||
|
||||
proc getFleetConfig(self:NodeView): string {.slot.} = self.fleetConfig
|
||||
|
||||
QtProperty[string] fleetConfig:
|
||||
read = getFleetConfig
|
||||
|
||||
proc statsChanged*(self: NodeView) {.signal.}
|
||||
|
||||
proc setStats*(self: NodeView, stats: Stats) =
|
||||
self.stats = stats
|
||||
self.statsChanged()
|
||||
|
||||
proc resetStats(self: NodeView) =
|
||||
self.setStats(Stats())
|
||||
|
||||
proc uploadRate*(self: NodeView): string {.slot.} = $self.stats.uploadRate
|
||||
|
||||
QtProperty[string] uploadRate:
|
||||
read = uploadRate
|
||||
notify = statsChanged
|
||||
|
||||
proc downloadRate*(self: NodeView): string {.slot.} = $self.stats.downloadRate
|
||||
|
||||
QtProperty[string] downloadRate:
|
||||
read = downloadRate
|
||||
notify = statsChanged
|
||||
|
||||
proc startNode*(self: NodeView, jsonConfig: string) {.slot.} =
|
||||
self.status.settings.startNode(jsonConfig)
|
||||
|
||||
proc stopNode*(self: NodeView) {.slot.} =
|
||||
self.status.settings.stopNode()
|
||||
self.setNodeActive(false)
|
||||
self.resetStats()
|
|
@ -0,0 +1,5 @@
|
|||
import libstatus/accounts/constants
|
||||
|
||||
export DATADIR
|
||||
export STATUSGODIR
|
||||
export KEYSTOREDIR
|
|
@ -0,0 +1,13 @@
|
|||
import json, os, uuids, json_serialization, chronicles, strutils
|
||||
|
||||
from status_go import multiAccountGenerateAndDeriveAddresses, generateAlias, identicon, saveAccountAndLogin, login, openAccounts, getNodeConfig
|
||||
import core
|
||||
import ../utils as utils
|
||||
import ../types as types
|
||||
import accounts/constants
|
||||
import ../signals/types as signal_types
|
||||
|
||||
proc initNode*() =
|
||||
createDir(STATUSGODIR)
|
||||
createDir(KEYSTOREDIR)
|
||||
discard $status_go.initKeystore(KEYSTOREDIR)
|
|
@ -0,0 +1,57 @@
|
|||
import # std libs
|
||||
json, os, sequtils, strutils
|
||||
|
||||
import # vendor libs
|
||||
confutils
|
||||
|
||||
const sep = when defined(windows): "\\" else: "/"
|
||||
|
||||
proc defaultDataDir(): string =
|
||||
let homeDir = getHomeDir()
|
||||
let parentDir =
|
||||
if defined(development):
|
||||
parentDir(getAppDir())
|
||||
elif homeDir == "":
|
||||
getCurrentDir()
|
||||
elif defined(macosx):
|
||||
joinPath(homeDir, "Library", "Application Support")
|
||||
elif defined(windows):
|
||||
let targetDir = getEnv("LOCALAPPDATA").string
|
||||
if targetDir == "":
|
||||
joinPath(homeDir, "AppData", "Local")
|
||||
else:
|
||||
targetDir
|
||||
else:
|
||||
let targetDir = getEnv("XDG_CONFIG_HOME").string
|
||||
if targetDir == "":
|
||||
joinPath(homeDir, ".config")
|
||||
else:
|
||||
targetDir
|
||||
absolutePath(joinPath(parentDir, "DesktopNode"))
|
||||
|
||||
type StatusDesktopConfig = object
|
||||
dataDir* {.
|
||||
defaultValue: defaultDataDir()
|
||||
desc: "Desktop Node data directory"
|
||||
abbr: "d" .}: string
|
||||
|
||||
# On macOS the first time when a user gets the "App downloaded from the
|
||||
# internet" warning, and clicks the Open button, the OS passes a unique process
|
||||
# serial number (PSN) as -psn_... command-line argument, which we remove before
|
||||
# processing the arguments with nim-confutils.
|
||||
# Credit: https://github.com/bitcoin/bitcoin/blame/b6e34afe9735faf97d6be7a90fafd33ec18c0cbb/src/util/system.cpp#L383-L389
|
||||
|
||||
var cliParams = commandLineParams()
|
||||
if defined(macosx):
|
||||
cliParams.keepIf(proc(p: string): bool = not p.startsWith("-psn_"))
|
||||
|
||||
let desktopConfig = StatusDesktopConfig.load(cliParams)
|
||||
|
||||
let
|
||||
baseDir = absolutePath(expandTilde(desktopConfig.dataDir))
|
||||
DATADIR* = baseDir & sep
|
||||
STATUSGODIR* = joinPath(baseDir, "data") & sep
|
||||
KEYSTOREDIR* = joinPath(baseDir, "data", "keystore") & sep
|
||||
|
||||
createDir(DATADIR)
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import json, chronicles
|
||||
import status_go, ../utils
|
||||
|
||||
logScope:
|
||||
topics = "rpc"
|
||||
|
||||
proc callRPC*(inputJSON: string): string =
|
||||
return $status_go.callRPC(inputJSON)
|
||||
|
||||
proc callPrivateRPCRaw*(inputJSON: string): string =
|
||||
return $status_go.callPrivateRPC(inputJSON)
|
||||
|
||||
proc callPrivateRPC*(methodName: string, payload = %* []): string =
|
||||
try:
|
||||
let inputJSON = %* {
|
||||
"jsonrpc": "2.0",
|
||||
"method": methodName,
|
||||
"params": %payload
|
||||
}
|
||||
debug "callPrivateRPC", rpc_method=methodName
|
||||
let response = status_go.callPrivateRPC($inputJSON)
|
||||
result = $response
|
||||
if parseJSON(result).hasKey("error"):
|
||||
writeStackTrace()
|
||||
error "rpc response error", result, payload, methodName
|
||||
except Exception as e:
|
||||
error "error doing rpc request", methodName = methodName, exception=e.msg
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import
|
||||
json, tables, sugar, sequtils, strutils, atomics, os
|
||||
|
||||
import
|
||||
json_serialization, chronicles, uuids
|
||||
|
||||
import
|
||||
./core, ../types, ../signals/types as statusgo_types, ./accounts/constants,
|
||||
../utils
|
||||
|
||||
from status_go import nil
|
||||
|
||||
proc getWeb3ClientVersion*(): string =
|
||||
parseJson(callPrivateRPC("web3_clientVersion"))["result"].getStr
|
||||
|
||||
proc startNode*(jsonConfig: string) =
|
||||
echo status_go.startDesktopNode(jsonConfig)
|
||||
# TODO: error handling
|
||||
|
||||
proc stopNode*() =
|
||||
echo status_go.logout()
|
||||
# TODO: error handling
|
|
@ -0,0 +1,16 @@
|
|||
import libstatus/core as status
|
||||
import ../eventemitter
|
||||
|
||||
type NodeModel* = ref object
|
||||
events*: EventEmitter
|
||||
|
||||
proc newNodeModel*(): NodeModel =
|
||||
result = NodeModel()
|
||||
result.events = createEventEmitter()
|
||||
|
||||
proc delete*(self: NodeModel) =
|
||||
discard
|
||||
|
||||
proc sendRPCMessageRaw*(self: NodeModel, msg: string): string =
|
||||
echo "sending RPC message"
|
||||
status.callPrivateRPCRaw(msg)
|
|
@ -0,0 +1,25 @@
|
|||
import json, json_serialization
|
||||
|
||||
import
|
||||
sugar, sequtils, strutils, atomics
|
||||
|
||||
import libstatus/settings as libstatus_settings
|
||||
import ../eventemitter
|
||||
import signals/types
|
||||
|
||||
#TODO: temporary?
|
||||
import types as LibStatusTypes
|
||||
|
||||
type
|
||||
SettingsModel* = ref object
|
||||
events*: EventEmitter
|
||||
|
||||
proc newSettingsModel*(events: EventEmitter): SettingsModel =
|
||||
result = SettingsModel()
|
||||
result.events = events
|
||||
|
||||
proc startNode*(self: SettingsModel, jsonConfig: string) =
|
||||
libstatus_settings.startNode(jsonConfig)
|
||||
|
||||
proc stopNode*(self: SettingsModel) =
|
||||
libstatus_settings.stopNode()
|
|
@ -0,0 +1,58 @@
|
|||
import NimQml, tables, json, chronicles, strutils, json_serialization
|
||||
import ../types as status_types
|
||||
import types, stats
|
||||
import ../status
|
||||
import ../../eventemitter
|
||||
|
||||
logScope:
|
||||
topics = "signals"
|
||||
|
||||
QtObject:
|
||||
type SignalsController* = ref object of QObject
|
||||
variant*: QVariant
|
||||
status*: Status
|
||||
|
||||
proc setup(self: SignalsController) =
|
||||
self.QObject.setup
|
||||
|
||||
proc newController*(status: Status): SignalsController =
|
||||
new(result)
|
||||
result.status = status
|
||||
result.variant = newQVariant(result)
|
||||
result.setup()
|
||||
|
||||
proc delete*(self: SignalsController) =
|
||||
self.variant.delete
|
||||
self.QObject.delete
|
||||
|
||||
proc processSignal(self: SignalsController, statusSignal: string) =
|
||||
var jsonSignal: JsonNode
|
||||
try:
|
||||
jsonSignal = statusSignal.parseJson
|
||||
except:
|
||||
error "Invalid signal received", data = statusSignal
|
||||
return
|
||||
|
||||
let signalString = jsonSignal["type"].getStr
|
||||
|
||||
trace "Raw signal data", data = $jsonSignal
|
||||
|
||||
var signalType: SignalType
|
||||
|
||||
try:
|
||||
signalType = parseEnum[SignalType](signalString)
|
||||
except:
|
||||
warn "Unknown signal received", type = signalString
|
||||
signalType = SignalType.Unknown
|
||||
return
|
||||
var signal: Signal = case signalType:
|
||||
of SignalType.Stats: stats.fromEvent(jsonSignal)
|
||||
else: Signal()
|
||||
|
||||
self.status.events.emit(signalType.event, signal)
|
||||
|
||||
proc signalReceived*(self: SignalsController, signal: string) {.signal.}
|
||||
|
||||
proc receiveSignal*(self: SignalsController, signal: string) {.slot.} =
|
||||
self.processSignal(signal)
|
||||
self.signalReceived(signal)
|
|
@ -0,0 +1,13 @@
|
|||
import json
|
||||
import types
|
||||
|
||||
proc toStats(jsonMsg: JsonNode): Stats =
|
||||
result = Stats(
|
||||
uploadRate: uint64(jsonMsg{"uploadRate"}.getBiggestInt()),
|
||||
downloadRate: uint64(jsonMsg{"downloadRate"}.getBiggestInt())
|
||||
)
|
||||
|
||||
proc fromEvent*(event: JsonNode): Signal =
|
||||
var signal:StatsSignal = StatsSignal()
|
||||
signal.stats = event["event"].toStats
|
||||
result = signal
|
|
@ -0,0 +1,19 @@
|
|||
import json_serialization
|
||||
import ../types
|
||||
import ../../eventemitter
|
||||
|
||||
type Signal* = ref object of Args
|
||||
signalType* {.serializedFieldName("type").}: SignalType
|
||||
|
||||
type StatusGoError* = object
|
||||
error*: string
|
||||
|
||||
type NodeSignal* = ref object of Signal
|
||||
event*: StatusGoError
|
||||
|
||||
type Stats* = object
|
||||
uploadRate*: uint64
|
||||
downloadRate*: uint64
|
||||
|
||||
type StatsSignal* = ref object of Signal
|
||||
stats*: Stats
|
|
@ -0,0 +1,30 @@
|
|||
import libstatus/core as libstatus_core
|
||||
import types as libstatus_types
|
||||
import node, settings
|
||||
import libstatus/settings as libstatus_settings
|
||||
import ../eventemitter
|
||||
import ./tasks/task_runner_impl
|
||||
|
||||
export node, task_runner_impl, eventemitter
|
||||
|
||||
type Status* = ref object
|
||||
events*: EventEmitter
|
||||
node*: NodeModel
|
||||
tasks*: TaskRunner
|
||||
settings*: SettingsModel
|
||||
|
||||
proc newStatusInstance*(): Status =
|
||||
result = Status()
|
||||
result.tasks = newTaskRunner()
|
||||
result.events = createEventEmitter()
|
||||
result.settings = settings.newSettingsModel(result.events)
|
||||
result.node = node.newNodeModel()
|
||||
|
||||
proc initNode*(self: Status) =
|
||||
self.tasks.init()
|
||||
|
||||
proc reset*(self: Status) =
|
||||
discard
|
||||
|
||||
proc getNodeVersion*(self: Status): string =
|
||||
libstatus_settings.getWeb3ClientVersion()
|
|
@ -0,0 +1,17 @@
|
|||
import # vendor libs
|
||||
json_serialization#, stint
|
||||
|
||||
export writeValue, readValue
|
||||
|
||||
export json_serialization
|
||||
|
||||
type
|
||||
Task* = proc(arg: string): void {.gcsafe, nimcall.}
|
||||
TaskArg* = ref object of RootObj
|
||||
tptr*: ByteAddress
|
||||
|
||||
proc decode*[T](arg: string): T =
|
||||
Json.decode(arg, T, allowUnknownFields = true)
|
||||
|
||||
proc encode*[T](arg: T): string =
|
||||
arg.toJson(typeAnnotations = true)
|
|
@ -0,0 +1,43 @@
|
|||
import # std libs
|
||||
strformat, tables
|
||||
|
||||
import # vendor libs
|
||||
chronicles
|
||||
|
||||
import # status-desktop libs
|
||||
./marathon/worker, ./marathon/common as marathon_common
|
||||
export marathon_common
|
||||
|
||||
logScope:
|
||||
topics = "marathon"
|
||||
|
||||
type
|
||||
Marathon* = ref object
|
||||
workers: Table[string, MarathonWorker]
|
||||
|
||||
proc start*[T: MarathonTaskArg](self: MarathonWorker, arg: T) =
|
||||
self.chanSendToWorker.sendSync(arg.encode.safe)
|
||||
|
||||
proc newMarathon*(): Marathon =
|
||||
new(result)
|
||||
result.workers = initTable[string, MarathonWorker]()
|
||||
|
||||
proc registerWorker*(self: Marathon, worker: MarathonWorker) =
|
||||
self.workers[worker.name] = worker # overwrite if exists
|
||||
|
||||
proc `[]`*(self: Marathon, name: string): MarathonWorker =
|
||||
if not self.workers.contains(name):
|
||||
raise newException(ValueError, &"""Worker '{name}' is not registered. Use 'registerWorker("{name}", {name}Worker)' to register the worker first.""")
|
||||
self.workers[name]
|
||||
|
||||
proc init*(self: Marathon) =
|
||||
for worker in self.workers.values:
|
||||
worker.init()
|
||||
|
||||
proc teardown*(self: Marathon) =
|
||||
for worker in self.workers.values:
|
||||
worker.teardown()
|
||||
|
||||
proc onLoggedIn*(self: Marathon) =
|
||||
for worker in self.workers.values:
|
||||
worker.onLoggedIn()
|
|
@ -0,0 +1,6 @@
|
|||
import # status-desktop libs
|
||||
../qt
|
||||
|
||||
type
|
||||
MarathonTaskArg* = ref object of QObjectTaskArg
|
||||
`method`*: string
|
|
@ -0,0 +1,49 @@
|
|||
import # std libs
|
||||
json
|
||||
|
||||
import # vendor libs
|
||||
chronicles, chronos, json_serialization, task_runner
|
||||
|
||||
import # status-desktop libs
|
||||
../common
|
||||
|
||||
export
|
||||
chronos, common, json_serialization
|
||||
|
||||
logScope:
|
||||
topics = "task-marathon-worker"
|
||||
|
||||
type
|
||||
WorkerThreadArg* = object # of RootObj
|
||||
chanSendToMain*: AsyncChannel[ThreadSafeString]
|
||||
chanRecvFromMain*: AsyncChannel[ThreadSafeString]
|
||||
vptr*: ByteAddress
|
||||
MarathonWorker* = ref object of RootObj
|
||||
chanSendToWorker*: AsyncChannel[ThreadSafeString]
|
||||
chanRecvFromWorker*: AsyncChannel[ThreadSafeString]
|
||||
thread*: Thread[WorkerThreadArg]
|
||||
vptr*: ByteAddress
|
||||
|
||||
method name*(self: MarathonWorker): string {.base.} =
|
||||
# override this base method
|
||||
raise newException(CatchableError, "Method without implementation override")
|
||||
|
||||
method init*(self: MarathonWorker) {.base.} =
|
||||
# override this base method
|
||||
raise newException(CatchableError, "Method without implementation override")
|
||||
|
||||
method teardown*(self: MarathonWorker) {.base.} =
|
||||
# override this base method
|
||||
raise newException(CatchableError, "Method without implementation override")
|
||||
|
||||
method onLoggedIn*(self: MarathonWorker) {.base.} =
|
||||
# override this base method
|
||||
raise newException(CatchableError, "Method without implementation override")
|
||||
|
||||
method worker(arg: WorkerThreadArg) {.async, base, gcsafe, nimcall.} =
|
||||
# override this base method
|
||||
raise newException(CatchableError, "Method without implementation override")
|
||||
|
||||
method workerThread(arg: WorkerThreadArg) {.thread, base, gcsafe, nimcall.} =
|
||||
# override this base method
|
||||
raise newException(CatchableError, "Method without implementation override")
|
|
@ -0,0 +1,16 @@
|
|||
import # vendor libs
|
||||
NimQml, json_serialization
|
||||
|
||||
import # status-desktop libs
|
||||
./common
|
||||
|
||||
type
|
||||
QObjectTaskArg* = ref object of TaskArg
|
||||
vptr*: ByteAddress
|
||||
slot*: string
|
||||
|
||||
proc finish*[T](arg: QObjectTaskArg, payload: T) =
|
||||
signal_handler(cast[pointer](arg.vptr), Json.encode(payload), arg.slot)
|
||||
|
||||
proc finish*(arg: QObjectTaskArg, payload: string) =
|
||||
signal_handler(cast[pointer](arg.vptr), payload, arg.slot)
|
|
@ -0,0 +1,28 @@
|
|||
import # vendor libs
|
||||
chronicles, task_runner
|
||||
|
||||
import # status-desktop libs
|
||||
./marathon, ./threadpool
|
||||
|
||||
export marathon, task_runner, threadpool
|
||||
|
||||
logScope:
|
||||
topics = "task-runner"
|
||||
|
||||
type
|
||||
TaskRunner* = ref object
|
||||
threadpool*: ThreadPool
|
||||
marathon*: Marathon
|
||||
|
||||
proc newTaskRunner*(): TaskRunner =
|
||||
new(result)
|
||||
result.threadpool = newThreadPool()
|
||||
result.marathon = newMarathon()
|
||||
|
||||
proc init*(self: TaskRunner) =
|
||||
self.threadpool.init()
|
||||
self.marathon.init()
|
||||
|
||||
proc teardown*(self: TaskRunner) =
|
||||
self.threadpool.teardown()
|
||||
self.marathon.teardown()
|
|
@ -0,0 +1,257 @@
|
|||
import # std libs
|
||||
atomics, json, sequtils, tables
|
||||
|
||||
import # vendor libs
|
||||
chronicles, chronos, json_serialization, task_runner
|
||||
|
||||
import # status-desktop libs
|
||||
./common
|
||||
|
||||
export
|
||||
chronos, common, json_serialization
|
||||
|
||||
logScope:
|
||||
topics = "task-threadpool"
|
||||
|
||||
type
|
||||
ThreadPool* = ref object
|
||||
chanRecvFromPool: AsyncChannel[ThreadSafeString]
|
||||
chanSendToPool: AsyncChannel[ThreadSafeString]
|
||||
thread: Thread[PoolThreadArg]
|
||||
size: int
|
||||
running*: Atomic[bool]
|
||||
PoolThreadArg = object
|
||||
chanSendToMain: AsyncChannel[ThreadSafeString]
|
||||
chanRecvFromMain: AsyncChannel[ThreadSafeString]
|
||||
size: int
|
||||
TaskThreadArg = object
|
||||
id: int
|
||||
chanRecvFromPool: AsyncChannel[ThreadSafeString]
|
||||
chanSendToPool: AsyncChannel[ThreadSafeString]
|
||||
ThreadNotification = object
|
||||
id: int
|
||||
notice: string
|
||||
|
||||
# forward declarations
|
||||
proc poolThread(arg: PoolThreadArg) {.thread.}
|
||||
|
||||
const MaxThreadPoolSize = 16
|
||||
|
||||
proc newThreadPool*(size: int = MaxThreadPoolSize): ThreadPool =
|
||||
new(result)
|
||||
result.chanRecvFromPool = newAsyncChannel[ThreadSafeString](-1)
|
||||
result.chanSendToPool = newAsyncChannel[ThreadSafeString](-1)
|
||||
result.thread = Thread[PoolThreadArg]()
|
||||
result.size = size
|
||||
result.running.store(false)
|
||||
|
||||
proc init*(self: ThreadPool) =
|
||||
self.chanRecvFromPool.open()
|
||||
self.chanSendToPool.open()
|
||||
let arg = PoolThreadArg(
|
||||
chanSendToMain: self.chanRecvFromPool,
|
||||
chanRecvFromMain: self.chanSendToPool,
|
||||
size: self.size
|
||||
)
|
||||
createThread(self.thread, poolThread, arg)
|
||||
# block until we receive "ready"
|
||||
discard $(self.chanRecvFromPool.recvSync())
|
||||
|
||||
proc teardown*(self: ThreadPool) =
|
||||
self.running.store(false)
|
||||
self.chanSendToPool.sendSync("shutdown".safe)
|
||||
self.chanRecvFromPool.close()
|
||||
self.chanSendToPool.close()
|
||||
trace "[threadpool] waiting for the control thread to stop"
|
||||
joinThread(self.thread)
|
||||
|
||||
proc start*[T: TaskArg](self: Threadpool, arg: T) =
|
||||
self.chanSendToPool.sendSync(arg.encode.safe)
|
||||
self.running.store(true)
|
||||
|
||||
proc runner(arg: TaskThreadArg) {.async.} =
|
||||
arg.chanRecvFromPool.open()
|
||||
arg.chanSendToPool.open()
|
||||
|
||||
let noticeToPool = ThreadNotification(id: arg.id, notice: "ready")
|
||||
trace "[threadpool task thread] sending 'ready'", threadid=arg.id
|
||||
await arg.chanSendToPool.send(noticeToPool.encode.safe)
|
||||
|
||||
while true:
|
||||
trace "[threadpool task thread] waiting for message"
|
||||
let received = $(await arg.chanRecvFromPool.recv())
|
||||
|
||||
if received == "shutdown":
|
||||
trace "[threadpool task thread] received 'shutdown'"
|
||||
break
|
||||
|
||||
let
|
||||
parsed = parseJson(received)
|
||||
messageType = parsed{"$type"}.getStr
|
||||
trace "[threadpool task thread] initiating task", messageType=messageType,
|
||||
threadid=arg.id
|
||||
|
||||
try:
|
||||
let task = cast[Task](parsed{"tptr"}.getInt)
|
||||
try:
|
||||
task(received)
|
||||
except Exception as e:
|
||||
error "[threadpool task thread] exception", error=e.msg
|
||||
except Exception as e:
|
||||
error "[threadpool task thread] unknown message", message=received
|
||||
|
||||
let noticeToPool = ThreadNotification(id: arg.id, notice: "done")
|
||||
trace "[threadpool task thread] sending 'done' notice to pool",
|
||||
threadid=arg.id
|
||||
await arg.chanSendToPool.send(noticeToPool.encode.safe)
|
||||
|
||||
arg.chanRecvFromPool.close()
|
||||
arg.chanSendToPool.close()
|
||||
|
||||
proc taskThread(arg: TaskThreadArg) {.thread.} =
|
||||
waitFor runner(arg)
|
||||
|
||||
proc pool(arg: PoolThreadArg) {.async.} =
|
||||
let
|
||||
chanSendToMain = arg.chanSendToMain
|
||||
chanRecvFromMainOrTask = arg.chanRecvFromMain
|
||||
var threadsBusy = newTable[int, tuple[thr: Thread[TaskThreadArg],
|
||||
chanSendToTask: AsyncChannel[ThreadSafeString]]]()
|
||||
var threadsIdle = newSeq[tuple[id: int, thr: Thread[TaskThreadArg],
|
||||
chanSendToTask: AsyncChannel[ThreadSafeString]]](arg.size)
|
||||
var taskQueue: seq[string] = @[] # FIFO queue
|
||||
var allReady = 0
|
||||
chanSendToMain.open()
|
||||
chanRecvFromMainOrTask.open()
|
||||
|
||||
trace "[threadpool] sending 'ready' to main thread"
|
||||
await chanSendToMain.send("ready".safe)
|
||||
|
||||
for i in 0..<arg.size:
|
||||
let id = i + 1
|
||||
let chanSendToTask = newAsyncChannel[ThreadSafeString](-1)
|
||||
chanSendToTask.open()
|
||||
trace "[threadpool] adding to threadsIdle", threadid=id
|
||||
threadsIdle[i].id = id
|
||||
createThread(
|
||||
threadsIdle[i].thr,
|
||||
taskThread,
|
||||
TaskThreadArg(id: id, chanRecvFromPool: chanSendToTask,
|
||||
chanSendToPool: chanRecvFromMainOrTask
|
||||
)
|
||||
)
|
||||
threadsIdle[i].chanSendToTask = chanSendToTask
|
||||
|
||||
# when task received and number of busy threads == MaxThreadPoolSize,
|
||||
# then put the task in a queue
|
||||
|
||||
# when task received and number of busy threads < MaxThreadPoolSize, pop
|
||||
# a thread from threadsIdle, track that thread in threadsBusy, and run
|
||||
# task in that thread
|
||||
|
||||
# if "done" received from a thread, remove thread from threadsBusy, and
|
||||
# push thread into threadsIdle
|
||||
|
||||
while true:
|
||||
trace "[threadpool] waiting for message"
|
||||
var task = $(await chanRecvFromMainOrTask.recv())
|
||||
|
||||
if task == "shutdown":
|
||||
trace "[threadpool] sending 'shutdown' to all task threads"
|
||||
for tpl in threadsIdle:
|
||||
await tpl.chanSendToTask.send("shutdown".safe)
|
||||
for tpl in threadsBusy.values:
|
||||
await tpl.chanSendToTask.send("shutdown".safe)
|
||||
break
|
||||
|
||||
let
|
||||
jsonNode = parseJson(task)
|
||||
messageType = jsonNode{"$type"}.getStr
|
||||
trace "[threadpool] determined message type", messageType=messageType
|
||||
|
||||
case messageType
|
||||
of "ThreadNotification":
|
||||
try:
|
||||
let notification = decode[ThreadNotification](task)
|
||||
trace "[threadpool] received notification",
|
||||
notice=notification.notice, threadid=notification.id
|
||||
|
||||
if notification.notice == "ready":
|
||||
trace "[threadpool] received 'ready' from a task thread"
|
||||
allReady = allReady + 1
|
||||
|
||||
elif notification.notice == "done":
|
||||
let tpl = threadsBusy[notification.id]
|
||||
trace "[threadpool] adding to threadsIdle",
|
||||
newlength=(threadsIdle.len + 1)
|
||||
threadsIdle.add (notification.id, tpl.thr, tpl.chanSendToTask)
|
||||
trace "[threadpool] removing from threadsBusy",
|
||||
newlength=(threadsBusy.len - 1), threadid=notification.id
|
||||
threadsBusy.del notification.id
|
||||
|
||||
if taskQueue.len > 0:
|
||||
trace "[threadpool] removing from taskQueue",
|
||||
newlength=(taskQueue.len - 1)
|
||||
task = taskQueue[0]
|
||||
taskQueue.delete 0, 0
|
||||
|
||||
trace "[threadpool] removing from threadsIdle",
|
||||
newlength=(threadsIdle.len - 1)
|
||||
let tpl = threadsIdle[0]
|
||||
threadsIdle.delete 0, 0
|
||||
trace "[threadpool] adding to threadsBusy",
|
||||
newlength=(threadsBusy.len + 1), threadid=tpl.id
|
||||
threadsBusy.add tpl.id, (tpl.thr, tpl.chanSendToTask)
|
||||
await tpl.chanSendToTask.send(task.safe)
|
||||
|
||||
else:
|
||||
error "[threadpool] unknown notification", notice=notification.notice
|
||||
except Exception as e:
|
||||
warn "[threadpool] unknown error in thread notification", message=task, error=e.msg
|
||||
|
||||
else: # must be a request to do task work
|
||||
if allReady < arg.size or threadsBusy.len == arg.size:
|
||||
# add to queue
|
||||
trace "[threadpool] adding to taskQueue",
|
||||
newlength=(taskQueue.len + 1)
|
||||
taskQueue.add task
|
||||
|
||||
# do we have available threads in the threadpool?
|
||||
elif threadsBusy.len < arg.size:
|
||||
# check if we have tasks waiting on queue
|
||||
if taskQueue.len > 0:
|
||||
# remove first element from the task queue
|
||||
trace "[threadpool] adding to taskQueue",
|
||||
newlength=(taskQueue.len + 1)
|
||||
taskQueue.add task
|
||||
trace "[threadpool] removing from taskQueue",
|
||||
newlength=(taskQueue.len - 1)
|
||||
task = taskQueue[0]
|
||||
taskQueue.delete 0, 0
|
||||
|
||||
trace "[threadpool] removing from threadsIdle",
|
||||
newlength=(threadsIdle.len - 1)
|
||||
let tpl = threadsIdle[0]
|
||||
threadsIdle.delete 0, 0
|
||||
trace "[threadpool] adding to threadsBusy",
|
||||
newlength=(threadsBusy.len + 1), threadid=tpl.id
|
||||
threadsBusy.add tpl.id, (tpl.thr, tpl.chanSendToTask)
|
||||
await tpl.chanSendToTask.send(task.safe)
|
||||
|
||||
var allTaskThreads: seq[Thread[TaskThreadArg]] = @[]
|
||||
|
||||
for tpl in threadsIdle:
|
||||
tpl.chanSendToTask.close()
|
||||
allTaskThreads.add tpl.thr
|
||||
for tpl in threadsBusy.values:
|
||||
tpl.chanSendToTask.close()
|
||||
allTaskThreads.add tpl.thr
|
||||
|
||||
chanSendToMain.close()
|
||||
chanRecvFromMainOrTask.close()
|
||||
|
||||
trace "[threadpool] waiting for all task threads to stop"
|
||||
joinThreads(allTaskThreads)
|
||||
|
||||
proc poolThread(arg: PoolThreadArg) {.thread.} =
|
||||
waitFor pool(arg)
|
|
@ -0,0 +1,70 @@
|
|||
import json, options, typetraits, tables, sequtils, strutils
|
||||
import json_serialization, stint
|
||||
import libstatus/accounts/constants
|
||||
import ../eventemitter
|
||||
|
||||
type SignalType* {.pure.} = enum
|
||||
Message = "messages.new"
|
||||
Wallet = "wallet"
|
||||
NodeReady = "node.ready"
|
||||
NodeCrashed = "node.crashed"
|
||||
NodeStarted = "node.started"
|
||||
NodeStopped = "node.stopped"
|
||||
NodeLogin = "node.login"
|
||||
EnvelopeSent = "envelope.sent"
|
||||
EnvelopeExpired = "envelope.expired"
|
||||
MailserverRequestCompleted = "mailserver.request.completed"
|
||||
MailserverRequestExpired = "mailserver.request.expired"
|
||||
DiscoveryStarted = "discovery.started"
|
||||
DiscoveryStopped = "discovery.stopped"
|
||||
DiscoverySummary = "discovery.summary"
|
||||
SubscriptionsData = "subscriptions.data"
|
||||
SubscriptionsError = "subscriptions.error"
|
||||
WhisperFilterAdded = "whisper.filter.added"
|
||||
CommunityFound = "community.found"
|
||||
Stats = "stats"
|
||||
Unknown
|
||||
|
||||
proc event*(self:SignalType):string =
|
||||
result = "signal:" & $self
|
||||
|
||||
type RpcError* = ref object
|
||||
code*: int
|
||||
message*: string
|
||||
|
||||
type
|
||||
RpcResponse* = ref object
|
||||
jsonrpc*: string
|
||||
result*: string
|
||||
id*: int
|
||||
error*: RpcError
|
||||
# TODO: replace all RpcResponse and RpcResponseTyped occurances with a generic
|
||||
# form of RpcReponse. IOW, rename RpceResponseTyped*[T] to RpcResponse*[T] and
|
||||
# remove RpcResponse.
|
||||
RpcResponseTyped*[T] = object
|
||||
jsonrpc*: string
|
||||
result*: T
|
||||
id*: int
|
||||
error*: RpcError
|
||||
|
||||
type
|
||||
StatusGoException* = object of CatchableError
|
||||
|
||||
type
|
||||
RpcException* = object of CatchableError
|
||||
|
||||
|
||||
proc `%`*(stuint256: Stuint[256]): JsonNode =
|
||||
newJString($stuint256)
|
||||
|
||||
proc readValue*(reader: var JsonReader, value: var Stuint[256])
|
||||
{.raises: [IOError, SerializationError, Defect].} =
|
||||
try:
|
||||
let strVal = reader.readValue(string)
|
||||
value = strVal.parse(Stuint[256])
|
||||
except:
|
||||
try:
|
||||
let intVal = reader.readValue(int)
|
||||
value = intVal.stuint(256)
|
||||
except:
|
||||
raise newException(SerializationError, "Expected string or int representation of Stuint[256]")
|
|
@ -0,0 +1,112 @@
|
|||
import json, random, strutils, strformat, tables, chronicles, unicode, times
|
||||
from sugar import `=>`, `->`
|
||||
import stint
|
||||
from times import getTime, toUnix, nanosecond
|
||||
|
||||
proc prefix*(methodName: string, isExt:bool = true): string =
|
||||
result = "waku"
|
||||
result = result & (if isExt: "ext_" else: "_")
|
||||
result = result & methodName
|
||||
|
||||
proc handleRPCErrors*(response: string) =
|
||||
let parsedReponse = parseJson(response)
|
||||
if (parsedReponse.hasKey("error")):
|
||||
raise newException(ValueError, parsedReponse["error"]["message"].str)
|
||||
|
||||
proc toStUInt*[bits: static[int]](flt: float, T: typedesc[StUint[bits]]): T =
|
||||
var stringValue = fmt"{flt:<.0f}"
|
||||
stringValue.removeSuffix('.')
|
||||
if (flt >= 0):
|
||||
result = parse($stringValue, StUint[bits])
|
||||
else:
|
||||
result = parse("0", StUint[bits])
|
||||
|
||||
proc toUInt256*(flt: float): UInt256 =
|
||||
toStUInt(flt, StUInt[256])
|
||||
|
||||
proc toUInt64*(flt: float): StUInt[64] =
|
||||
toStUInt(flt, StUInt[64])
|
||||
|
||||
proc eth2Wei*(eth: float, decimals: int = 18): UInt256 =
|
||||
let weiValue = eth * parseFloat(alignLeft("1", decimals + 1, '0'))
|
||||
weiValue.toUInt256
|
||||
|
||||
proc gwei2Wei*(gwei: float): UInt256 =
|
||||
eth2Wei(gwei, 9)
|
||||
|
||||
proc wei2Eth*(input: Stuint[256], decimals: int = 18): string =
|
||||
var one_eth = u256(10).pow(decimals) # fromHex(Stuint[256], "DE0B6B3A7640000")
|
||||
|
||||
var (eth, remainder) = divmod(input, one_eth)
|
||||
let leading_zeros = "0".repeat(($one_eth).len - ($remainder).len - 1)
|
||||
|
||||
fmt"{eth}.{leading_zeros}{remainder}"
|
||||
|
||||
proc wei2Eth*(input: string, decimals: int): string =
|
||||
try:
|
||||
var input256: Stuint[256]
|
||||
if input.contains("e+"): # we have a js string BN, ie 1e+21
|
||||
let
|
||||
inputSplit = input.split("e+")
|
||||
whole = inputSplit[0].u256
|
||||
remainder = u256(10).pow(inputSplit[1].parseInt)
|
||||
input256 = whole * remainder
|
||||
else:
|
||||
input256 = input.u256
|
||||
result = wei2Eth(input256, decimals)
|
||||
except Exception as e:
|
||||
error "Error parsing this wei value", input, msg=e.msg
|
||||
result = "0"
|
||||
|
||||
proc first*(jArray: JsonNode, fieldName, id: string): JsonNode =
|
||||
if jArray == nil:
|
||||
return nil
|
||||
if jArray.kind != JArray:
|
||||
raise newException(ValueError, "Parameter 'jArray' is a " & $jArray.kind & ", but must be a JArray")
|
||||
for child in jArray.getElems:
|
||||
if child{fieldName}.getStr.toLower == id.toLower:
|
||||
return child
|
||||
|
||||
proc any*(jArray: JsonNode, fieldName, id: string): bool =
|
||||
if jArray == nil:
|
||||
return false
|
||||
result = false
|
||||
for child in jArray.getElems:
|
||||
if child{fieldName}.getStr.toLower == id.toLower:
|
||||
return true
|
||||
|
||||
proc isEmpty*(a: JsonNode): bool =
|
||||
case a.kind:
|
||||
of JObject: return a.fields.len == 0
|
||||
of JArray: return a.elems.len == 0
|
||||
of JString: return a.str == ""
|
||||
of JNull: return true
|
||||
else:
|
||||
return false
|
||||
|
||||
proc find*[T](s: seq[T], pred: proc(x: T): bool {.closure.}): T {.inline.} =
|
||||
let results = s.filter(pred)
|
||||
if results.len == 0:
|
||||
return default(type(T))
|
||||
result = results[0]
|
||||
|
||||
proc find*[T](s: seq[T], pred: proc(x: T): bool {.closure.}, found: var bool): T {.inline.} =
|
||||
let results = s.filter(pred)
|
||||
if results.len == 0:
|
||||
found = false
|
||||
return default(type(T))
|
||||
result = results[0]
|
||||
found = true
|
||||
|
||||
proc isUnique*[T](key: T, existingKeys: var seq[T]): bool =
|
||||
# If the key doesn't exist in the existingKeys seq, add it and return true.
|
||||
# Otherwise, the key already existed, so return false.
|
||||
# Can be used to deduplicate sequences with `deduplicate[T]`.
|
||||
if not existingKeys.contains(key):
|
||||
existingKeys.add key
|
||||
return true
|
||||
return false
|
||||
|
||||
proc deduplicate*[T](txs: var seq[T], key: (T) -> string) =
|
||||
var existingKeys: seq[string] = @[]
|
||||
txs.keepIf(tx => tx.key().isUnique(existingKeys))
|
|
@ -1,12 +1,9 @@
|
|||
import NimQml, chronicles, os, strformat
|
||||
|
||||
#import app/node/core as node
|
||||
#import app/utilsView/core as utilsView
|
||||
#import status/signals/core as signals
|
||||
#import status/types
|
||||
#import status/constants
|
||||
import app/node/core as node
|
||||
import status/signals/core as signals
|
||||
import status_go
|
||||
#import status/status as statuslib
|
||||
import status/status as statuslib
|
||||
import ./eventemitter
|
||||
|
||||
var signalsQObjPointer: pointer
|
||||
|
@ -28,9 +25,9 @@ proc mainProc() =
|
|||
|
||||
let
|
||||
fleetConfig = readFile(joinPath(getAppDir(), fleets))
|
||||
# status = statuslib.newStatusInstance(fleetConfig)
|
||||
status = statuslib.newStatusInstance()
|
||||
|
||||
#status.initNode()
|
||||
status.initNode()
|
||||
|
||||
enableHDPI()
|
||||
initializeOpenGL()
|
||||
|
@ -85,23 +82,19 @@ proc mainProc() =
|
|||
app.installEventFilter(dockShowAppEvent)
|
||||
app.installEventFilter(osThemeEvent)
|
||||
|
||||
# let signalController = signals.newController(status)
|
||||
#defer:
|
||||
# signalsQObjPointer = nil
|
||||
#signalController.delete()
|
||||
let signalController = signals.newController(status)
|
||||
defer:
|
||||
signalsQObjPointer = nil
|
||||
signalController.delete()
|
||||
|
||||
# We need this global variable in order to be able to access the application
|
||||
# from the non-closure callback passed to `libstatus.setSignalEventCallback`
|
||||
#signalsQObjPointer = cast[pointer](signalController.vptr)
|
||||
signalsQObjPointer = cast[pointer](signalController.vptr)
|
||||
|
||||
|
||||
# var node = node.newController(status, netAccMgr)
|
||||
#defer: node.delete()
|
||||
#engine.setRootContextProperty("nodeModel", node.variant)
|
||||
|
||||
#var utilsController = utilsView.newController(status)
|
||||
#defer: utilsController.delete()
|
||||
#engine.setRootContextProperty("utilsModel", utilsController.variant)
|
||||
var node = node.newController(status, fleetConfig)
|
||||
defer: node.delete()
|
||||
engine.setRootContextProperty("nodeModel", node.variant)
|
||||
node.init()
|
||||
|
||||
proc changeLanguage(locale: string) =
|
||||
if (locale == currentLanguageCode):
|
||||
|
@ -110,29 +103,12 @@ proc mainProc() =
|
|||
let shouldRetranslate = not defined(linux)
|
||||
engine.setTranslationPackage(joinPath(i18nPath, fmt"qml_{locale}.qm"), shouldRetranslate)
|
||||
|
||||
|
||||
|
||||
# status.tasks.marathon.onLoggedIn()
|
||||
|
||||
|
||||
|
||||
# this should be the last defer in the scope
|
||||
defer:
|
||||
info "Status app is shutting down..."
|
||||
#status.tasks.teardown()
|
||||
status.tasks.teardown()
|
||||
|
||||
|
||||
#initControllers()
|
||||
|
||||
# Handle node.stopped signal when user has logged out
|
||||
# status.events.once("nodeStopped") do(a: Args):
|
||||
# TODO: remove this once accounts are not tracked in the AccountsModel
|
||||
# status.reset()
|
||||
|
||||
# 2. Re-init controllers that don't require a running node
|
||||
# initControllers()
|
||||
|
||||
# engine.setRootContextProperty("signals", signalController.variant)
|
||||
engine.setRootContextProperty("signals", signalController.variant)
|
||||
|
||||
var prValue = newQVariant(if defined(production): true else: false)
|
||||
engine.setRootContextProperty("production", prValue)
|
||||
|
@ -148,9 +124,8 @@ proc mainProc() =
|
|||
# it will be passed as a regular C function to libstatus. This means that
|
||||
# we cannot capture any local variables here (we must rely on globals)
|
||||
var callback: SignalCallback = proc(p0: cstring) {.cdecl.} =
|
||||
discard
|
||||
# if signalsQObjPointer != nil:
|
||||
#signal_handler(signalsQObjPointer, p0, "receiveSignal")
|
||||
if signalsQObjPointer != nil:
|
||||
signal_handler(signalsQObjPointer, p0, "receiveSignal")
|
||||
|
||||
status_go.setSignalEventCallback(callback)
|
||||
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import Qt.labs.platform 1.1
|
||||
import QtQml.StateMachine 1.14 as DSM
|
||||
import Qt.labs.settings 1.0
|
||||
import QtQuick.Window 2.12
|
||||
import QtQml 2.13
|
||||
import QtQuick.Window 2.0
|
||||
import QtQuick.Controls.Universal 2.12
|
||||
|
||||
import DotherSide 0.1
|
||||
|
||||
import "../shared"
|
||||
import "../shared/status"
|
||||
import "../imports"
|
||||
|
||||
Column {
|
||||
id: generalColumn
|
||||
|
||||
FleetsModal {
|
||||
id: fleetModal
|
||||
}
|
||||
|
||||
StatusSectionHeadline {
|
||||
//% "Bloom filter level"
|
||||
text: qsTrId("bloom-filter-level")
|
||||
topPadding: Style.current.bigPadding
|
||||
bottomPadding: Style.current.padding
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: 11
|
||||
|
||||
ButtonGroup {
|
||||
id: bloomGroup
|
||||
}
|
||||
|
||||
BloomSelectorButton {
|
||||
id: btnBloomLight
|
||||
buttonGroup: bloomGroup
|
||||
enabled: !nodeModel.nodeActive
|
||||
checkedByDefault: appSettings.bloomLevel == "light"
|
||||
//% "Light Node"
|
||||
btnText: qsTrId("light-node")
|
||||
onToggled: {
|
||||
if (appSettings.bloomLevel != "light") {
|
||||
appSettings.bloomLevel = "light";
|
||||
} else {
|
||||
btnBloomLight.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BloomSelectorButton {
|
||||
id: btnBloomNormal
|
||||
enabled: !nodeModel.nodeActive
|
||||
buttonGroup: bloomGroup
|
||||
checkedByDefault: appSettings.bloomLevel == "normal"
|
||||
//% "Normal"
|
||||
btnText: qsTrId("normal")
|
||||
onToggled: {
|
||||
if (appSettings.bloomLevel != "normal") {
|
||||
appSettings.bloomLevel = "normal";
|
||||
} else {
|
||||
btnBloomNormal.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BloomSelectorButton {
|
||||
id: btnBloomFull
|
||||
enabled: !nodeModel.nodeActive
|
||||
buttonGroup: bloomGroup
|
||||
checkedByDefault: appSettings.bloomLevel == "full"
|
||||
//% "Full Node"
|
||||
btnText: qsTrId("full-node")
|
||||
onToggled: {
|
||||
if (appSettings.bloomLevel != "full") {
|
||||
appSettings.bloomLevel = "full";
|
||||
} else {
|
||||
btnBloomFull.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rate {}
|
||||
|
||||
StatusSettingsLineButton {
|
||||
//% "Fleet"
|
||||
text: qsTrId("fleet")
|
||||
currentValue: appSettings.fleet
|
||||
isEnabled: !nodeModel.nodeActive
|
||||
onClicked: {
|
||||
fleetModal.open()
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: nodeModel
|
||||
onNodeActiveChanged: {
|
||||
startNodeBtn.enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
StatusSettingsLineButton {
|
||||
id: startNodeBtn
|
||||
text: qsTr("Start Node")
|
||||
isSwitch: true
|
||||
switchChecked: nodeModel.nodeActive
|
||||
onClicked: {
|
||||
enabled = false
|
||||
if(switchChecked){
|
||||
nodeModel.stopNode();
|
||||
return
|
||||
}
|
||||
|
||||
let configJSON = {
|
||||
"EnableNTPSync": true,
|
||||
"KeyStoreDir": appSettings.dataDir + "/keystore",
|
||||
"NetworkId": appSettings.networkId,
|
||||
"LogEnabled": appSettings.LogEnabled,
|
||||
"LogFile": appSettings.LogFile,
|
||||
"LogLevel": appSettings.logLevel,
|
||||
"ListenAddr": "0.0.0.0:30303", // TODO: Add setting
|
||||
"HTTPEnabled": true, // TODO: Add setting
|
||||
"HTTPHost": "0.0.0.0", // TODO: Add setting
|
||||
"DataDir": appSettings.dataDir,
|
||||
"HTTPPort": 8545, // TODO: Add setting
|
||||
"APIModules": "eth,web3,admin", // TODO: Add setting
|
||||
"RegisterTopics": ["whispermail"],
|
||||
"NodeKey": appSettings.nodeKey,
|
||||
"WakuConfig": {
|
||||
"Enabled": !appSettings.useWakuV2,
|
||||
"DataDir": "./waku",
|
||||
"BloomFilterMode": appSettings.bloomLevel == "normal",
|
||||
"LightClient": false,
|
||||
"MinimumPoW": 0.001,
|
||||
"FullNode": appSettings.bloomLevel == "full"
|
||||
},
|
||||
"WakuV2Config": {
|
||||
"Enabled": appSettings.useWakuV2,
|
||||
"Host": "0.0.0.0", // TODO: Add setting
|
||||
"Port": 0 // TODO: Add setting
|
||||
},
|
||||
"RequireTopics": {
|
||||
"whisper": {
|
||||
"Max": 2,
|
||||
"Min": 2
|
||||
}
|
||||
},
|
||||
"NoDiscovery": false,
|
||||
"Rendezvous": false,
|
||||
"ClusterConfig": {
|
||||
"Enabled": true,
|
||||
"Fleet": appSettings.fleet,
|
||||
"RendezvousNodes": [],
|
||||
"BootNodes": [
|
||||
// TODO: Add setting
|
||||
"enode://6e6554fb3034b211398fcd0f0082cbb6bd13619e1a7e76ba66e1809aaa0c5f1ac53c9ae79cf2fd4a7bacb10d12010899b370c75fed19b991d9c0cdd02891abad@47.75.99.169:443",
|
||||
"enode://436cc6f674928fdc9a9f7990f2944002b685d1c37f025c1be425185b5b1f0900feaf1ccc2a6130268f9901be4a7d252f37302c8335a2c1a62736e9232691cc3a@178.128.138.128:443",
|
||||
"enode://32ff6d88760b0947a3dee54ceff4d8d7f0b4c023c6dad34568615fcae89e26cc2753f28f12485a4116c977be937a72665116596265aa0736b53d46b27446296a@34.70.75.208:443",
|
||||
"enode://23d0740b11919358625d79d4cac7d50a34d79e9c69e16831c5c70573757a1f5d7d884510bc595d7ee4da3c1508adf87bbc9e9260d804ef03f8c1e37f2fb2fc69@47.52.106.107:443",
|
||||
"enode://5395aab7833f1ecb671b59bf0521cf20224fe8162fc3d2675de4ee4d5636a75ec32d13268fc184df8d1ddfa803943906882da62a4df42d4fccf6d17808156a87@178.128.140.188:443",
|
||||
"enode://5405c509df683c962e7c9470b251bb679dd6978f82d5b469f1f6c64d11d50fbd5dd9f7801c6ad51f3b20a5f6c7ffe248cc9ab223f8bcbaeaf14bb1c0ef295fd0@35.223.215.156:443",
|
||||
"enode://b957e51f41e4abab8382e1ea7229e88c6e18f34672694c6eae389eac22dab8655622bbd4a08192c321416b9becffaab11c8e2b7a5d0813b922aa128b82990dab@47.75.222.178:443",
|
||||
"enode://66ba15600cda86009689354c3a77bdf1a97f4f4fb3ab50ffe34dbc904fac561040496828397be18d9744c75881ffc6ac53729ddbd2cdbdadc5f45c400e2622f7@178.128.141.87:443",
|
||||
"enode://182ed5d658d1a1a4382c9e9f7c9e5d8d9fec9db4c71ae346b9e23e1a589116aeffb3342299bdd00e0ab98dbf804f7b2d8ae564ed18da9f45650b444aed79d509@34.68.132.118:443",
|
||||
"enode://8bebe73ddf7cf09e77602c7d04c93a73f455b51f24ae0d572917a4792f1dec0bb4c562759b8830cc3615a658d38c1a4a38597a1d7ae3ba35111479fc42d65dec@47.75.85.212:443",
|
||||
"enode://4ea35352702027984a13274f241a56a47854a7fd4b3ba674a596cff917d3c825506431cf149f9f2312a293bb7c2b1cca55db742027090916d01529fe0729643b@134.209.136.79:443",
|
||||
"enode://fbeddac99d396b91d59f2c63a3cb5fc7e0f8a9f7ce6fe5f2eed5e787a0154161b7173a6a73124a4275ef338b8966dc70a611e9ae2192f0f2340395661fad81c0@34.67.230.193:443",
|
||||
"enode://ac3948b2c0786ada7d17b80cf869cf59b1909ea3accd45944aae35bf864cc069126da8b82dfef4ddf23f1d6d6b44b1565c4cf81c8b98022253c6aea1a89d3ce2@47.75.88.12:443",
|
||||
"enode://ce559a37a9c344d7109bd4907802dd690008381d51f658c43056ec36ac043338bd92f1ac6043e645b64953b06f27202d679756a9c7cf62fdefa01b2e6ac5098e@134.209.136.123:443",
|
||||
"enode://c07aa0deea3b7056c5d45a85bca42f0d8d3b1404eeb9577610f386e0a4744a0e7b2845ae328efc4aa4b28075af838b59b5b3985bffddeec0090b3b7669abc1f3@35.226.92.155:443",
|
||||
"enode://385579fc5b14e04d5b04af7eee835d426d3d40ccf11f99dbd95340405f37cf3bbbf830b3eb8f70924be0c2909790120682c9c3e791646e2d5413e7801545d353@47.244.221.249:443",
|
||||
"enode://4e0a8db9b73403c9339a2077e911851750fc955db1fc1e09f81a4a56725946884dd5e4d11258eac961f9078a393c45bcab78dd0e3bc74e37ce773b3471d2e29c@134.209.136.101:443",
|
||||
"enode://0624b4a90063923c5cc27d12624b6a49a86dfb3623fcb106801217fdbab95f7617b83fa2468b9ae3de593ff6c1cf556ccf9bc705bfae9cb4625999765127b423@35.222.158.246:443",
|
||||
"enode://b77bffc29e2592f30180311dd81204ab845e5f78953b5ba0587c6631be9c0862963dea5eb64c90617cf0efd75308e22a42e30bc4eb3cd1bbddbd1da38ff6483e@47.75.10.177:443",
|
||||
"enode://a8bddfa24e1e92a82609b390766faa56cf7a5eef85b22a2b51e79b333c8aaeec84f7b4267e432edd1cf45b63a3ad0fc7d6c3a16f046aa6bc07ebe50e80b63b8c@178.128.141.249:443",
|
||||
"enode://a5fe9c82ad1ffb16ae60cb5d4ffe746b9de4c5fbf20911992b7dd651b1c08ba17dd2c0b27ee6b03162c52d92f219961cc3eb14286aca8a90b75cf425826c3bd8@104.154.230.58:443",
|
||||
"enode://cf5f7a7e64e3b306d1bc16073fba45be3344cb6695b0b616ccc2da66ea35b9f35b3b231c6cf335fdfaba523519659a440752fc2e061d1e5bc4ef33864aac2f19@47.75.221.196:443",
|
||||
"enode://887cbd92d95afc2c5f1e227356314a53d3d18855880ac0509e0c0870362aee03939d4074e6ad31365915af41d34320b5094bfcc12a67c381788cd7298d06c875@178.128.141.0:443",
|
||||
"enode://282e009967f9f132a5c2dd366a76319f0d22d60d0c51f7e99795a1e40f213c2705a2c10e4cc6f3890319f59da1a535b8835ed9b9c4b57c3aad342bf312fd7379@35.223.240.17:443",
|
||||
"enode://13d63a1f85ccdcbd2fb6861b9bd9d03f94bdba973608951f7c36e5df5114c91de2b8194d71288f24bfd17908c48468e89dd8f0fb8ccc2b2dedae84acdf65f62a@47.244.210.80:443",
|
||||
"enode://2b01955d7e11e29dce07343b456e4e96c081760022d1652b1c4b641eaf320e3747871870fa682e9e9cfb85b819ce94ed2fee1ac458904d54fd0b97d33ba2c4a4@134.209.136.112:443",
|
||||
"enode://b706a60572634760f18a27dd407b2b3582f7e065110dae10e3998498f1ae3f29ba04db198460d83ed6d2bfb254bb06b29aab3c91415d75d3b869cd0037f3853c@35.239.5.162:443",
|
||||
"enode://32915c8841faaef21a6b75ab6ed7c2b6f0790eb177ad0f4ea6d731bacc19b938624d220d937ebd95e0f6596b7232bbb672905ee12601747a12ee71a15bfdf31c@47.75.59.11:443",
|
||||
"enode://0d9d65fcd5592df33ed4507ce862b9c748b6dbd1ea3a1deb94e3750052760b4850aa527265bbaf357021d64d5cc53c02b410458e732fafc5b53f257944247760@178.128.141.42:443",
|
||||
"enode://e87f1d8093d304c3a9d6f1165b85d6b374f1c0cc907d39c0879eb67f0a39d779be7a85cbd52920b6f53a94da43099c58837034afa6a7be4b099bfcd79ad13999@35.238.106.101:443"
|
||||
],
|
||||
"TrustedMailServers": [
|
||||
// TODO: Add setting
|
||||
"enode://606ae04a71e5db868a722c77a21c8244ae38f1bd6e81687cc6cfe88a3063fa1c245692232f64f45bd5408fed5133eab8ed78049332b04f9c110eac7f71c1b429@47.75.247.214:443",
|
||||
"enode://c42f368a23fa98ee546fd247220759062323249ef657d26d357a777443aec04db1b29a3a22ef3e7c548e18493ddaf51a31b0aed6079bd6ebe5ae838fcfaf3a49@178.128.142.54:443",
|
||||
"enode://ee2b53b0ace9692167a410514bca3024695dbf0e1a68e1dff9716da620efb195f04a4b9e873fb9b74ac84de801106c465b8e2b6c4f0d93b8749d1578bfcaf03e@104.197.238.144:443",
|
||||
"enode://2c8de3cbb27a3d30cbb5b3e003bc722b126f5aef82e2052aaef032ca94e0c7ad219e533ba88c70585ebd802de206693255335b100307645ab5170e88620d2a81@47.244.221.14:443",
|
||||
"enode://7aa648d6e855950b2e3d3bf220c496e0cae4adfddef3e1e6062e6b177aec93bc6cdcf1282cb40d1656932ebfdd565729da440368d7c4da7dbd4d004b1ac02bf8@178.128.142.26:443",
|
||||
"enode://30211cbd81c25f07b03a0196d56e6ce4604bb13db773ff1c0ea2253547fafd6c06eae6ad3533e2ba39d59564cfbdbb5e2ce7c137a5ebb85e99dcfc7a75f99f55@23.236.58.92:443",
|
||||
"enode://e85f1d4209f2f99da801af18db8716e584a28ad0bdc47fbdcd8f26af74dbd97fc279144680553ec7cd9092afe683ddea1e0f9fc571ebcb4b1d857c03a088853d@47.244.129.82:443",
|
||||
"enode://8a64b3c349a2e0ef4a32ea49609ed6eb3364be1110253c20adc17a3cebbc39a219e5d3e13b151c0eee5d8e0f9a8ba2cd026014e67b41a4ab7d1d5dd67ca27427@178.128.142.94:443",
|
||||
"enode://44160e22e8b42bd32a06c1532165fa9e096eebedd7fa6d6e5f8bbef0440bc4a4591fe3651be68193a7ec029021cdb496cfe1d7f9f1dc69eb99226e6f39a7a5d4@35.225.221.245:443"
|
||||
],
|
||||
"PushNotificationsServers": [],
|
||||
"StaticNodes": [
|
||||
// TODO: Add setting
|
||||
"enode://b77bffc29e2592f30180311dd81204ab845e5f78953b5ba0587c6631be9c0862963dea5eb64c90617cf0efd75308e22a42e30bc4eb3cd1bbddbd1da38ff6483e@47.75.10.177:443",
|
||||
"enode://a8bddfa24e1e92a82609b390766faa56cf7a5eef85b22a2b51e79b333c8aaeec84f7b4267e432edd1cf45b63a3ad0fc7d6c3a16f046aa6bc07ebe50e80b63b8c@178.128.141.249:443"
|
||||
]
|
||||
}
|
||||
}
|
||||
nodeModel.startNode(JSON.stringify(configJSON))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import "../imports"
|
||||
import "../shared"
|
||||
import "../shared/status"
|
||||
|
||||
|
||||
Rectangle {
|
||||
property var buttonGroup
|
||||
//% "TODO"
|
||||
property string btnText: qsTrId("todo")
|
||||
property bool hovered: false
|
||||
property bool checkedByDefault: false
|
||||
property bool enabled: true
|
||||
|
||||
signal checked()
|
||||
signal toggled(bool checked)
|
||||
|
||||
function click(){
|
||||
radioBtn.toggle()
|
||||
}
|
||||
|
||||
id: root
|
||||
border.color: hovered || radioBtn.checked ? (enabled ? Style.current.primary : Style.current.border ): Style.current.border
|
||||
border.width: 1
|
||||
color: Style.current.transparent
|
||||
width: 150
|
||||
height: 100
|
||||
clip: true
|
||||
radius: Style.current.radius
|
||||
|
||||
StatusRadioButton {
|
||||
id: radioBtn
|
||||
ButtonGroup.group: buttonGroup
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 14
|
||||
enabled: root.enabled
|
||||
checked: root.checkedByDefault
|
||||
onCheckedChanged: {
|
||||
if (checked) {
|
||||
root.checked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: txt
|
||||
text: btnText
|
||||
font.pixelSize: 15
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: radioBtn.bottom
|
||||
anchors.topMargin: 6
|
||||
}
|
||||
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: root.hovered = true
|
||||
onExited: root.hovered = false
|
||||
onClicked: {
|
||||
if (!root.enabled) return;
|
||||
radioBtn.toggle()
|
||||
root.toggled(radioBtn.checked)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import "../imports"
|
||||
import "../shared"
|
||||
import "../shared/status"
|
||||
|
||||
StatusRadioButtonRow {
|
||||
property string fleetName: ""
|
||||
property string newFleet: ""
|
||||
text: fleetName
|
||||
buttonGroup: fleetSettings
|
||||
checked: appSettings.fleet === text
|
||||
onRadioCheckedChanged: {
|
||||
if (checked) {
|
||||
if (appSettings.fleet === fleetName) return;
|
||||
newFleet = fleetName;
|
||||
openPopup(confirmDialogComponent)
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: confirmDialogComponent
|
||||
ConfirmationDialog {
|
||||
//% "Warning!"
|
||||
title: qsTrId("close-app-title")
|
||||
//% "Change fleet to %1"
|
||||
confirmationText: qsTrId("change-fleet-to--1").arg(newFleet)
|
||||
onConfirmButtonClicked: {
|
||||
appSettings.fleet = newFleet
|
||||
}
|
||||
onClosed: {
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import "../imports"
|
||||
import "../shared"
|
||||
import "../shared/status"
|
||||
|
||||
ModalPopup {
|
||||
id: popup
|
||||
//% "Fleet"
|
||||
title: qsTrId("fleet")
|
||||
|
||||
property string newFleet: "";
|
||||
height: 340
|
||||
|
||||
Column {
|
||||
id: column
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
anchors.leftMargin: Style.current.padding
|
||||
|
||||
spacing: 0
|
||||
|
||||
ButtonGroup { id: fleetSettings }
|
||||
|
||||
FleetRadioSelector {
|
||||
fleetName: Constants.eth_prod
|
||||
}
|
||||
|
||||
FleetRadioSelector {
|
||||
fleetName: Constants.eth_staging
|
||||
}
|
||||
|
||||
FleetRadioSelector {
|
||||
fleetName: Constants.eth_test
|
||||
}
|
||||
|
||||
FleetRadioSelector {
|
||||
fleetName: Constants.waku_prod
|
||||
}
|
||||
|
||||
FleetRadioSelector {
|
||||
fleetName: Constants.waku_test
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import "../imports"
|
||||
import "../shared"
|
||||
import "../shared/status"
|
||||
|
||||
|
||||
Column {
|
||||
spacing: 0
|
||||
StatusSectionHeadline {
|
||||
text: qsTr("Bandwidth")
|
||||
topPadding: Style.current.bigPadding
|
||||
bottomPadding: Style.current.padding
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: 10
|
||||
StyledText {
|
||||
text: qsTr("Upload")
|
||||
width: 80
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 140
|
||||
height: 44
|
||||
Input {
|
||||
id: uploadRate
|
||||
text: Math.round(parseInt(nodeModel.uploadRate, 10) / 1024 * 100) / 100
|
||||
width: parent.width
|
||||
readOnly: true
|
||||
customHeight: 44
|
||||
placeholderText: "..."
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
StyledText {
|
||||
color: Style.current.secondaryText
|
||||
text: qsTr("Kb/s")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: uploadRate.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
font.pixelSize: 15
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Download")
|
||||
width: 80
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 140
|
||||
height: 44
|
||||
Input {
|
||||
id: downloadRate
|
||||
text: Math.round(parseInt(nodeModel.downloadRate, 10) / 1024 * 100) / 100
|
||||
width: parent.width
|
||||
readOnly: true
|
||||
customHeight: 44
|
||||
placeholderText: "..."
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
StyledText {
|
||||
color: Style.current.secondaryText
|
||||
text: qsTr("Kb/s")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: downloadRate.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
font.pixelSize: 15
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="12" height="7" viewBox="0 0 12 7" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.46967 0.46967C0.762563 0.176777 1.23744 0.176777 1.53033 0.46967L5.64645 4.58579C5.84171 4.78105 6.15829 4.78105 6.35355 4.58579L10.4697 0.46967C10.7626 0.176777 11.2374 0.176777 11.5303 0.46967C11.8232 0.762563 11.8232 1.23744 11.5303 1.53033L6.88388 6.17678C6.39573 6.66493 5.60427 6.66493 5.11612 6.17678L0.46967 1.53033C0.176777 1.23744 0.176777 0.762563 0.46967 0.46967Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 545 B |
After Width: | Height: | Size: 493 B |
After Width: | Height: | Size: 481 B |
After Width: | Height: | Size: 340 B |
After Width: | Height: | Size: 299 B |
After Width: | Height: | Size: 222 B |
After Width: | Height: | Size: 214 B |
|
@ -1,7 +0,0 @@
|
|||
pragma Singleton
|
||||
|
||||
import QtQuick 2.13
|
||||
|
||||
QtObject {
|
||||
property int currentMenuTab: 0
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
module Style
|
||||
singleton Style 1.0 ./Style.qml
|
||||
singleton Constants 1.0 ./Constants.qml
|
||||
singleton Utils 1.0 ./Utils.qml
|
89
ui/main.qml
|
@ -12,36 +12,59 @@ import QtQuick.Controls.Universal 2.12
|
|||
import DotherSide 0.1
|
||||
|
||||
import "./shared"
|
||||
import "./shared/status"
|
||||
import "./imports"
|
||||
import "./app"
|
||||
|
||||
StatusWindow {
|
||||
property bool popupOpened: false
|
||||
|
||||
function openPopup(popupComponent, params = {}) {
|
||||
const popup = popupComponent.createObject(applicationWindow, params);
|
||||
popup.open()
|
||||
}
|
||||
|
||||
Universal.theme: Universal.System
|
||||
|
||||
function genHexString(len) {
|
||||
const hex = '0123456789ABCDEF';
|
||||
let output = '';
|
||||
for (let i = 0; i < len; ++i) {
|
||||
output += hex.charAt(Math.floor(Math.random() * hex.length));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
Settings {
|
||||
id: globalSettings
|
||||
category: "global"
|
||||
fileName: profileModel.settings.globalSettingsFile
|
||||
id: appSettings
|
||||
fileName: nodeModel.dataDir + "/qt/settings"
|
||||
property string locale: "en"
|
||||
property int theme: 2
|
||||
|
||||
Component.onCompleted: {
|
||||
profileModel.changeLocale(locale)
|
||||
}
|
||||
property int networkId: 1
|
||||
property bool logEnabled: true
|
||||
property string logFile: "geth.log"
|
||||
property string logLevel: "INFO"
|
||||
property string dataDir: nodeModel.dataDir
|
||||
property string fleet: Constants.eth_prod
|
||||
property string nodeKey: genHexString(64)
|
||||
property string bloomLevel: "full"
|
||||
property bool useWakuV2: false
|
||||
}
|
||||
|
||||
id: applicationWindow
|
||||
objectName: "mainWindow"
|
||||
minimumWidth: 900
|
||||
minimumHeight: 600
|
||||
width: 1232
|
||||
height: 770
|
||||
width: 500
|
||||
height: 400
|
||||
minimumWidth: width
|
||||
minimumHeight: height
|
||||
maximumWidth: width
|
||||
maximumHeight: height
|
||||
color: Style.current.background
|
||||
title: {
|
||||
// Set application settings
|
||||
//% "Status Desktop"
|
||||
Qt.application.name = qsTrId("status-desktop")
|
||||
Qt.application.name = "Status Node"
|
||||
Qt.application.organization = "Status"
|
||||
Qt.application.domain = "status.im"
|
||||
return Qt.application.name
|
||||
|
@ -52,18 +75,8 @@ StatusWindow {
|
|||
Connections {
|
||||
target: applicationWindow
|
||||
onClosing: {
|
||||
/*if (loader.sourceComponent == login) {
|
||||
applicationWindow.visible = false
|
||||
close.accepted = false
|
||||
}
|
||||
else if (loader.sourceComponent == app) {
|
||||
if (loader.item.appSettings.quitOnClose) {
|
||||
close.accepted = true
|
||||
} else {
|
||||
applicationWindow.visible = false
|
||||
close.accepted = false
|
||||
}
|
||||
}*/
|
||||
applicationWindow.visible = false
|
||||
close.accepted = false
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
|
@ -86,11 +99,11 @@ StatusWindow {
|
|||
}
|
||||
|
||||
function changeThemeFromOutside() {
|
||||
Style.changeTheme(globalSettings.theme, systemPalette.isCurrentSystemThemeDark())
|
||||
Style.changeTheme(appSettings.theme, systemPalette.isCurrentSystemThemeDark())
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
Style.changeTheme(globalSettings.theme, systemPalette.isCurrentSystemThemeDark())
|
||||
Style.changeTheme(appSettings.theme, systemPalette.isCurrentSystemThemeDark())
|
||||
setX(Qt.application.screens[0].width / 2 - width / 2);
|
||||
setY(Qt.application.screens[0].height / 2 - height / 2);
|
||||
|
||||
|
@ -149,11 +162,12 @@ StatusWindow {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
anchors.fill: parent
|
||||
property var appSettings
|
||||
AppLayout {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.rightMargin: Style.current.padding
|
||||
}
|
||||
|
||||
MacTrafficLights {
|
||||
|
@ -164,25 +178,12 @@ StatusWindow {
|
|||
visible: Qt.platform.os === "osx" && !applicationWindow.isFullScreen
|
||||
|
||||
onClose: {
|
||||
if (loader.sourceComponent == login) {
|
||||
Qt.quit();
|
||||
}
|
||||
else if (loader.sourceComponent == app) {
|
||||
if (loader.item.appSettings.quitOnClose) {
|
||||
Qt.quit();
|
||||
} else {
|
||||
applicationWindow.visible = false;
|
||||
}
|
||||
}
|
||||
applicationWindow.visible = false;
|
||||
}
|
||||
|
||||
onMinimised: {
|
||||
applicationWindow.showMinimized()
|
||||
}
|
||||
|
||||
onMaximized: {
|
||||
applicationWindow.toggleFullScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import "../imports"
|
||||
import "../shared/status"
|
||||
import "./"
|
||||
|
||||
ModalPopup {
|
||||
id: confirmationDialog
|
||||
|
||||
property Popup parentPopup
|
||||
property string btnType: "warn"
|
||||
property bool showCancelButton: false
|
||||
property alias checkbox: checkbox
|
||||
|
||||
|
||||
height: 186
|
||||
width: 400
|
||||
//% "Confirm your action"
|
||||
title: qsTrId("confirm-your-action")
|
||||
|
||||
//% "Confirm"
|
||||
property string confirmButtonLabel: qsTrId("close-app-button")
|
||||
//% "Cancel"
|
||||
//% "Cancel"
|
||||
property string cancelButtonLabel: qsTrId("browsing-cancel")
|
||||
//% "Are you sure you want to this?"
|
||||
property string confirmationText: qsTrId("are-you-sure-you-want-to-this-")
|
||||
|
||||
property var value
|
||||
|
||||
signal confirmButtonClicked()
|
||||
signal cancelButtonClicked()
|
||||
|
||||
property var executeConfirm
|
||||
property var executeCancel
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
StyledText {
|
||||
id: innerText
|
||||
text: confirmationDialog.confirmationText
|
||||
font.pixelSize: 15
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
StatusCheckBox {
|
||||
id: checkbox
|
||||
visible: false
|
||||
anchors.top: innerText.bottom
|
||||
anchors.topMargin: Style.current.halfPadding
|
||||
Layout.preferredWidth: parent.width
|
||||
//% "Do not show this again"
|
||||
text: qsTrId("do-not-show-this-again")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
footer: Item {
|
||||
id: footerContainer
|
||||
width: parent.width
|
||||
height: confirmButton.height//children[0].height
|
||||
|
||||
StatusButton {
|
||||
id: confirmButton
|
||||
type: confirmationDialog.btnType
|
||||
anchors.right: cancelButton.visible ? cancelButton.left : parent.right
|
||||
anchors.rightMargin: cancelButton.visible ? Style.current.smallPadding : 0
|
||||
text: confirmationDialog.confirmButtonLabel
|
||||
anchors.bottom: parent.bottom
|
||||
onClicked: {
|
||||
if (executeConfirm && typeof executeConfirm === "function") {
|
||||
executeConfirm()
|
||||
}
|
||||
|
||||
confirmationDialog.confirmButtonClicked()
|
||||
}
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
id: cancelButton
|
||||
anchors.right: parent.right
|
||||
visible: showCancelButton
|
||||
anchors.rightMargin: Style.current.smallPadding
|
||||
text: confirmationDialog.cancelButtonLabel
|
||||
anchors.bottom: parent.bottom
|
||||
onClicked: {
|
||||
if (executeCancel && typeof executeCancel === "function") {
|
||||
executeCancel()
|
||||
}
|
||||
confirmationDialog.cancelButtonClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtGraphicalEffects 1.13
|
||||
import "../imports"
|
||||
import "../shared/status"
|
||||
|
||||
Rectangle {
|
||||
id: copyToClipboardButton
|
||||
height: 32
|
||||
width: 32
|
||||
radius: 8
|
||||
color: Style.current.transparent
|
||||
property var onClick: function() {}
|
||||
property string textToCopy: ""
|
||||
property bool tooltipUnder: false
|
||||
|
||||
Image {
|
||||
width: 20
|
||||
height: 20
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
source: "./img/copy-to-clipboard-icon.svg"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: parent
|
||||
antialiasing: true
|
||||
source: parent
|
||||
color: Style.current.primary
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onExited: {
|
||||
parent.color = Style.current.transparent
|
||||
}
|
||||
onEntered:{
|
||||
parent.color = Style.current.backgroundHover
|
||||
}
|
||||
onPressed: {
|
||||
parent.color = Style.current.backgroundHover
|
||||
if (!toolTip.visible) {
|
||||
toolTip.visible = true
|
||||
}
|
||||
}
|
||||
onReleased: {
|
||||
parent.color = Style.current.backgroundHover
|
||||
}
|
||||
onClicked: {
|
||||
if (textToCopy) {
|
||||
chatsModel.copyToClipboard(textToCopy)
|
||||
}
|
||||
onClick()
|
||||
}
|
||||
}
|
||||
|
||||
StatusToolTip {
|
||||
id: toolTip
|
||||
//% "Copied!"
|
||||
text: qsTrId("copied-")
|
||||
orientation: tooltipUnder ? "bottom" : "top"
|
||||
}
|
||||
|
||||
Timer {
|
||||
id:hideTimer
|
||||
interval: 2000
|
||||
running: toolTip.visible
|
||||
onTriggered: {
|
||||
toolTip.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import "../imports"
|
||||
import "../shared/status"
|
||||
import "."
|
||||
|
||||
Item {
|
||||
property alias textField: inputValue
|
||||
property string placeholderText: "My placeholder"
|
||||
property string placeholderTextColor: Style.current.secondaryText
|
||||
property alias text: inputValue.text
|
||||
property alias maxLength: inputValue.maximumLength
|
||||
property string validationError: ""
|
||||
property alias validationErrorAlignment: validationErrorText.horizontalAlignment
|
||||
property int validationErrorTopMargin: 1
|
||||
property color validationErrorColor: Style.current.danger
|
||||
property string label: ""
|
||||
readonly property bool hasLabel: label !== ""
|
||||
property color bgColor: Style.current.inputBackground
|
||||
property url icon: ""
|
||||
property int iconHeight: 24
|
||||
property int iconWidth: 24
|
||||
property bool copyToClipboard: false
|
||||
property string textToCopy
|
||||
property bool pasteFromClipboard: false
|
||||
property bool readOnly: false
|
||||
|
||||
readonly property bool hasIcon: icon.toString() !== ""
|
||||
readonly property var forceActiveFocus: function () {
|
||||
inputValue.forceActiveFocus(Qt.MouseFocusReason)
|
||||
}
|
||||
readonly property int labelMargin: 7
|
||||
property int customHeight: 44
|
||||
property int fontPixelSize: 15
|
||||
property alias validator: inputValue.validator
|
||||
signal editingFinished(string inputValue)
|
||||
signal textEdited(string inputValue)
|
||||
|
||||
id: inputBox
|
||||
implicitHeight: inputRectangle.height + (hasLabel ? inputLabel.height + labelMargin : 0) + (!!validationError ? (validationErrorText.height + validationErrorTopMargin) : 0)
|
||||
height: implicitHeight
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
|
||||
function resetInternal() {
|
||||
inputValue.text = ""
|
||||
validationError = ""
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: inputLabel
|
||||
text: inputBox.label
|
||||
font.weight: Font.Medium
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
font.pixelSize: 13
|
||||
color: Style.current.textColor
|
||||
}
|
||||
|
||||
Item {
|
||||
id: inputField
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
height: customHeight
|
||||
anchors.top: inputBox.hasLabel ? inputLabel.bottom : parent.top
|
||||
anchors.topMargin: inputBox.hasLabel ? inputBox.labelMargin : 0
|
||||
|
||||
StyledTextField {
|
||||
id: inputValue
|
||||
visible: !inputBox.isTextArea && !inputBox.isSelect
|
||||
placeholderText: inputBox.placeholderText
|
||||
placeholderTextColor: inputBox.placeholderTextColor
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.right: clipboardButtonLoader.active ? clipboardButtonLoader.left : parent.right
|
||||
anchors.rightMargin: parent.rightMargin
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
leftPadding: inputBox.hasIcon ? iconWidth + 20 : Style.current.padding
|
||||
selectByMouse: true
|
||||
font.pixelSize: fontPixelSize
|
||||
readOnly: inputBox.readOnly
|
||||
background: Rectangle {
|
||||
id: inputRectangle
|
||||
anchors.fill: parent
|
||||
color: bgColor
|
||||
radius: Style.current.radius
|
||||
border.width: (!!validationError || inputValue.focus) ? 1 : 0
|
||||
border.color: {
|
||||
if (!!validationError) {
|
||||
return validationErrorColor
|
||||
}
|
||||
if (!inputBox.readOnly && inputValue.focus) {
|
||||
return Style.current.inputBorderFocus
|
||||
}
|
||||
return Style.current.transparent
|
||||
}
|
||||
}
|
||||
onEditingFinished: inputBox.editingFinished(inputBox.text)
|
||||
onTextEdited: inputBox.textEdited(inputBox.text)
|
||||
}
|
||||
|
||||
SVGImage {
|
||||
id: iconImg
|
||||
sourceSize.height: iconHeight
|
||||
sourceSize.width: iconWidth
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.smallPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: inputBox.icon
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: clipboardButtonLoader
|
||||
active: inputBox.copyToClipboard || inputBox.pasteFromClipboard
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
sourceComponent: Component {
|
||||
Item {
|
||||
width: copyBtn.width
|
||||
height: copyBtn.height
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
}
|
||||
|
||||
StatusButton {
|
||||
property bool copied: false
|
||||
id: copyBtn
|
||||
text: {
|
||||
if (copied) {
|
||||
return inputBox.copyToClipboard ?
|
||||
//% "Copied"
|
||||
qsTrId("sharing-copied-to-clipboard") :
|
||||
//% "Pasted"
|
||||
qsTrId("pasted")
|
||||
}
|
||||
return inputBox.copyToClipboard ?
|
||||
//% "Copy"
|
||||
qsTrId("copy-to-clipboard") :
|
||||
//% "Paste"
|
||||
qsTrId("paste")
|
||||
|
||||
}
|
||||
height: 28
|
||||
font.pixelSize: 12
|
||||
borderColor: Style.current.blue
|
||||
showBorder: true
|
||||
onClicked: {
|
||||
if (inputBox.copyToClipboard) {
|
||||
chatsModel.copyToClipboard(inputBox.textToCopy ? inputBox.textToCopy : inputValue.text)
|
||||
} else {
|
||||
if (inputValue.canPaste) {
|
||||
inputValue.paste()
|
||||
}
|
||||
}
|
||||
|
||||
copyBtn.copied = true
|
||||
timer.setTimeout(function() {
|
||||
copyBtn.copied = false
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
visible: !!validationError
|
||||
id: validationErrorText
|
||||
text: validationError
|
||||
anchors.top: inputField.bottom
|
||||
anchors.topMargin: validationErrorTopMargin
|
||||
anchors.right: inputField.right
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
font.pixelSize: 12
|
||||
height: 16
|
||||
color: validationErrorColor
|
||||
wrapMode: TextEdit.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
/*##^##
|
||||
Designer {
|
||||
D{i:0;formeditorColor:"#c0c0c0";formeditorZoom:1.25}
|
||||
}
|
||||
##^##*/
|
|
@ -0,0 +1,156 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import QtGraphicalEffects 1.13
|
||||
import "../imports"
|
||||
|
||||
Popup {
|
||||
property string title
|
||||
property bool noTopMargin: false
|
||||
property bool displayCloseButton: true
|
||||
default property alias content: popupContent.children
|
||||
property alias contentWrapper: popupContent
|
||||
property alias header: headerContent.children
|
||||
|
||||
id: popup
|
||||
modal: true
|
||||
property alias footer: footerContent.children
|
||||
|
||||
Overlay.modal: Rectangle {
|
||||
color: Qt.rgba(0, 0, 0, 0.4)
|
||||
}
|
||||
closePolicy: displayCloseButton? Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
: Popup.NoAutoClose
|
||||
parent: Overlay.overlay
|
||||
x: Math.round(((parent ? parent.width : 0) - width) / 2)
|
||||
y: Math.round(((parent ? parent.height : 0) - height) / 2)
|
||||
width: 480
|
||||
height: 510 // TODO find a way to make this dynamic
|
||||
background: Rectangle {
|
||||
color: Style.current.background
|
||||
radius: 8
|
||||
}
|
||||
onOpened: {
|
||||
popupOpened = true
|
||||
}
|
||||
onClosed: {
|
||||
popupOpened = false
|
||||
}
|
||||
padding: 0
|
||||
contentItem: Item {
|
||||
|
||||
Item {
|
||||
id: headerContent
|
||||
height: {
|
||||
const count = children.length
|
||||
let h = 0
|
||||
for (let i = 0; i < count; i++) {
|
||||
h += children[i] ? children[i].height : 0
|
||||
}
|
||||
return h
|
||||
}
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
anchors.topMargin: popup.noTopMargin ? 0 : Style.current.padding
|
||||
anchors.bottomMargin: Style.current.padding
|
||||
anchors.rightMargin: Style.current.padding
|
||||
anchors.leftMargin: Style.current.padding
|
||||
|
||||
StyledText {
|
||||
text: title
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
font.bold: true
|
||||
font.pixelSize: 17
|
||||
height: visible ? 24 : 0
|
||||
visible: !!title
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: closeButton
|
||||
property bool hovered: false
|
||||
visible: displayCloseButton
|
||||
height: 32
|
||||
width: 32
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 12
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 12
|
||||
radius: 8
|
||||
color: hovered ? Style.current.backgroundHover : Style.current.transparent
|
||||
|
||||
SVGImage {
|
||||
id: closeModalImg
|
||||
source: "./img/close.svg"
|
||||
width: 11
|
||||
height: 11
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: closeModalImg
|
||||
source: closeModalImg
|
||||
color: Style.current.textColor
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeModalMouseArea
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onExited: {
|
||||
closeButton.hovered = false
|
||||
}
|
||||
onEntered: {
|
||||
closeButton.hovered = true
|
||||
}
|
||||
onClicked: {
|
||||
popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Separator {
|
||||
id: separator
|
||||
anchors.top: headerContent.bottom
|
||||
anchors.topMargin: visible ? Style.current.padding : 0
|
||||
visible: title.length > 0
|
||||
}
|
||||
|
||||
Item {
|
||||
id: popupContent
|
||||
anchors.top: separator.bottom
|
||||
anchors.topMargin: Style.current.padding
|
||||
anchors.bottom: separator2.top
|
||||
anchors.bottomMargin: Style.current.padding
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
}
|
||||
|
||||
Separator {
|
||||
id: separator2
|
||||
visible: footerContent.visible && footerContent.height > 0
|
||||
anchors.bottom: footerContent.top
|
||||
anchors.bottomMargin: visible ? Style.current.padding : 0
|
||||
}
|
||||
|
||||
Item {
|
||||
id: footerContent
|
||||
visible: children.length > 0
|
||||
height: visible ? children[0] && children[0].height : 0
|
||||
width: parent.width
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: visible ? Style.current.padding : 0
|
||||
anchors.rightMargin: visible ? Style.current.padding : 0
|
||||
anchors.leftMargin: visible ? Style.current.padding : 0
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtGraphicalEffects 1.13
|
||||
import "../imports"
|
||||
import "../shared"
|
||||
|
||||
Menu {
|
||||
// This is to add icons to submenu items. QML doesn't have a way to add icons to those sadly so this is a workaround
|
||||
property var subMenuIcons: []
|
||||
property int paddingSize: 8
|
||||
property bool hasArrow: true
|
||||
closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnReleaseOutside | Popup.CloseOnEscape
|
||||
id: popupMenu
|
||||
topPadding: paddingSize
|
||||
bottomPadding: paddingSize
|
||||
|
||||
property string overrideTextColor: ""
|
||||
|
||||
delegate: MenuItem {
|
||||
property color textColor: popupMenu.overrideTextColor !== "" ? popupMenu.overrideTextColor : (this.action.icon.color.toString() !== "#00000000" ? this.action.icon.color : Style.current.textColor)
|
||||
property color hoverColor: popupMenuItem.action.icon.color === Style.current.danger ? Style.current.buttonWarnBackgroundColor : Style.current.backgroundHover
|
||||
property int subMenuIndex: {
|
||||
if (!this.subMenu) {
|
||||
return -1
|
||||
}
|
||||
|
||||
let child;
|
||||
let index = 0;
|
||||
for (let i = 0; i < popupMenu.count; i++) {
|
||||
child = popupMenu.itemAt(i)
|
||||
if (child.subMenu) {
|
||||
if (child === this) {
|
||||
return index
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
enabled: {
|
||||
if (this.subMenu) {
|
||||
return this.subMenu.enabled
|
||||
}
|
||||
return this.action.enabled
|
||||
}
|
||||
action: Action{} // Meant to be overwritten
|
||||
id: popupMenuItem
|
||||
implicitWidth: 200
|
||||
implicitHeight: 34
|
||||
font.pixelSize: 13
|
||||
font.weight: checked ? Font.Medium : Font.Normal
|
||||
icon.color: popupMenuItem.action.icon.color != "#00000000" ? popupMenuItem.action.icon.color : Style.current.blue
|
||||
icon.source: this.subMenu ? subMenuIcons[subMenuIndex].source : popupMenuItem.action.icon.source
|
||||
icon.width: this.subMenu ? subMenuIcons[subMenuIndex].width : popupMenuItem.action.icon.width
|
||||
icon.height: this.subMenu ? subMenuIcons[subMenuIndex].height : popupMenuItem.action.icon.height
|
||||
visible: enabled
|
||||
height: visible ? popupMenuItem.implicitHeight : 0
|
||||
|
||||
arrow: SVGImage {
|
||||
source: "../app/img/caret.svg"
|
||||
rotation: -90
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 12
|
||||
width: 9
|
||||
fillMode: Image.PreserveAspectFit
|
||||
visible: popupMenuItem.subMenu && popupMenuItem.subMenu.enabled
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: parent
|
||||
source: parent
|
||||
color: popupMenuItem.textColor
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME the icons looks very pixelated on Linux for some reason. Using smooth, mipmap, etc doesn't fix it
|
||||
indicator: Item {
|
||||
visible: !!popupMenuItem.icon.source.toString()
|
||||
width: !isNaN(popupMenuItem.icon.width) ? popupMenuItem.icon.width : 25
|
||||
height: !isNaN(popupMenuItem.icon.height) ? popupMenuItem.icon.height : 25
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Image {
|
||||
id: menuIcon
|
||||
source: popupMenuItem.icon.source
|
||||
visible: false
|
||||
width: parent.width
|
||||
height: parent.width
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: menuIcon
|
||||
source: menuIcon
|
||||
smooth: true
|
||||
color: (popupMenuItem.action.icon.color != "#00000000" ? popupMenuItem.action.icon.color : Style.current.primaryMenuItemHover)
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: StyledText {
|
||||
anchors.left: popupMenuItem.indicator.right
|
||||
anchors.leftMargin: popupMenu.paddingSize
|
||||
text: popupMenuItem.text
|
||||
font: popupMenuItem.font
|
||||
color: popupMenuItem.textColor
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
opacity: enabled ? 1.0 : 0.3
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 220
|
||||
implicitHeight: enabled ? 24 : 0
|
||||
color: popupMenuItem.hovered ? popupMenuItem.hoverColor : "transparent"
|
||||
}
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
}
|
||||
}
|
||||
|
||||
background: Item {
|
||||
id: bgPopupMenu
|
||||
implicitWidth: 220
|
||||
|
||||
Rectangle {
|
||||
id: bgPopupMenuContent
|
||||
implicitWidth: bgPopupMenu.width
|
||||
implicitHeight: bgPopupMenu.height
|
||||
color: Style.current.modalBackground
|
||||
radius: 8
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow{
|
||||
width: bgPopupMenuContent.width
|
||||
height: bgPopupMenuContent.height
|
||||
x: bgPopupMenuContent.x
|
||||
visible: bgPopupMenuContent.visible
|
||||
source: bgPopupMenuContent
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 4
|
||||
radius: 12
|
||||
samples: 25
|
||||
spread: 0.2
|
||||
color: "#22000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*##^##
|
||||
Designer {
|
||||
D{i:0;autoSize:true;height:480;width:640}
|
||||
}
|
||||
##^##*/
|
|
@ -0,0 +1,83 @@
|
|||
import QtQuick 2.13
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../imports"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
property alias source: roundedIconImage.source
|
||||
default property alias content: content.children
|
||||
property alias icon: roundedIconImage
|
||||
property bool rotates: false
|
||||
signal clicked
|
||||
width: 36
|
||||
height: 36
|
||||
property alias iconWidth: roundedIconImage.width
|
||||
property alias iconHeight: roundedIconImage.height
|
||||
property alias rotation: roundedIconImage.rotation
|
||||
property color iconColor: Style.current.transparent
|
||||
|
||||
color: Style.current.blue
|
||||
radius: width / 2
|
||||
|
||||
Item {
|
||||
id: iconContainer
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: roundedIconImage.width
|
||||
height: roundedIconImage.height
|
||||
|
||||
SVGImage {
|
||||
id: roundedIconImage
|
||||
width: 12
|
||||
height: 12
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../img/new_chat.svg"
|
||||
}
|
||||
ColorOverlay {
|
||||
anchors.fill: roundedIconImage
|
||||
source: roundedIconImage
|
||||
color: root.iconColor
|
||||
rotation: roundedIconImage.rotation
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: rotates
|
||||
sourceComponent: rotatorComponent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: rotatorComponent
|
||||
RotationAnimator {
|
||||
target: iconContainer
|
||||
from: 0;
|
||||
to: 360;
|
||||
duration: 1200
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
id: content
|
||||
anchors.left: iconContainer.right
|
||||
anchors.leftMargin: 6 + (root.width - iconContainer.width)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*##^##
|
||||
Designer {
|
||||
D{i:0;formeditorZoom:1.75}
|
||||
}
|
||||
##^##*/
|
|
@ -0,0 +1,42 @@
|
|||
import QtQuick 2.12
|
||||
import QtGraphicalEffects 1.0
|
||||
import "../imports"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
signal clicked
|
||||
property bool noMouseArea: false
|
||||
property bool noHover: false
|
||||
property alias showLoadingIndicator: imgStickerPackThumb.showLoadingIndicator
|
||||
property alias source: imgStickerPackThumb.source
|
||||
property alias fillMode: imgStickerPackThumb.fillMode
|
||||
|
||||
radius: width / 2
|
||||
|
||||
width: 24
|
||||
height: 24
|
||||
color: Style.current.background
|
||||
|
||||
// apply rounded corners mask
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Rectangle {
|
||||
x: root.x; y: root.y
|
||||
width: root.width
|
||||
height: root.height
|
||||
radius: root.radius
|
||||
}
|
||||
}
|
||||
|
||||
ImageLoader {
|
||||
id: imgStickerPackThumb
|
||||
noMouseArea: root.noMouseArea
|
||||
noHover: root.noHover
|
||||
opacity: 1
|
||||
smooth: false
|
||||
radius: root.radius
|
||||
anchors.fill: parent
|
||||
source: "https://ipfs.infura.io/ipfs/" + thumbnail
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import QtQuick 2.13
|
||||
|
||||
Image {
|
||||
sourceSize.width: width || undefined
|
||||
sourceSize.height: height || undefined
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
antialiasing: true
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import QtQuick 2.13
|
||||
import "../imports"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property color color: Style.current.separator
|
||||
width: parent.width
|
||||
height: root.visible ? 1 : 0
|
||||
anchors.topMargin: Style.current.padding
|
||||
Rectangle {
|
||||
id: separator
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: root.color
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import QtQuick 2.13
|
||||
import QtGraphicalEffects 1.13
|
||||
import "../imports"
|
||||
|
||||
Item {
|
||||
property int iconMargin: Style.current.padding
|
||||
property alias icon: icon
|
||||
readonly property int separatorWidth: (parent.width / 2) - (icon.height / 2) - iconMargin
|
||||
width: parent.width
|
||||
height: icon.height
|
||||
|
||||
|
||||
Separator {
|
||||
id: separatorLeft
|
||||
width: separatorWidth
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.topMargin: undefined
|
||||
}
|
||||
|
||||
SVGImage {
|
||||
id: icon
|
||||
height: 14
|
||||
width: 18
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: "../app/img/arrow-right.svg"
|
||||
rotation: 90
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: parent
|
||||
source: parent
|
||||
color: Style.current.textColor
|
||||
antialiasing: true
|
||||
}
|
||||
}
|
||||
|
||||
Separator {
|
||||
id: separatorRight
|
||||
width: separatorWidth
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.topMargin: undefined
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQml 2.14
|
||||
import "../imports"
|
||||
|
||||
Button {
|
||||
property string label: "My button"
|
||||
property color btnColor: Style.current.secondaryBackground
|
||||
property color btnBorderColor: "transparent"
|
||||
property int btnBorderWidth: 0
|
||||
property color textColor: Style.current.blue
|
||||
property int textSize: 15
|
||||
property bool disabled: false
|
||||
|
||||
id: btnStyled
|
||||
width: txtBtnLabel.width + 2 * Style.current.padding
|
||||
height: 44
|
||||
enabled: !disabled
|
||||
|
||||
background: Rectangle {
|
||||
color: disabled ? Style.current.grey :
|
||||
hovered ? Qt.darker(btnStyled.btnColor, 1.1) : btnStyled.btnColor
|
||||
radius: Style.current.radius
|
||||
anchors.fill: parent
|
||||
border.color: btnBorderColor
|
||||
border.width: btnBorderWidth
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: txtBtnLabel
|
||||
color: btnStyled.disabled ? Style.current.darkGrey : btnStyled.textColor
|
||||
font.pixelSize: btnStyled.textSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: btnStyled.label
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
parent.onClicked()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import QtQuick 2.13
|
||||
import "../imports"
|
||||
|
||||
Text {
|
||||
font.family: Style.current.fontRegular.name
|
||||
color: Style.current.textColor
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import QtQuick 2.13
|
||||
import "../imports"
|
||||
|
||||
TextEdit {
|
||||
font.family: Style.current.fontRegular.name
|
||||
color: Style.current.textColor
|
||||
selectedTextColor: Style.current.textColor
|
||||
selectionColor: Style.current.primarySelectionColor
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import "../imports"
|
||||
|
||||
TextField {
|
||||
font.family: Style.current.fontRegular.name
|
||||
color: readOnly ? Style.current.secondaryText : Style.current.textColor
|
||||
selectByMouse: !readOnly
|
||||
selectedTextColor: Style.current.textColor
|
||||
selectionColor: Style.current.primarySelectionColor
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import "../imports"
|
||||
|
||||
Button {
|
||||
id: root
|
||||
property alias label: txtBtnLabel.text
|
||||
|
||||
width: txtBtnLabel.width + 2 * 12
|
||||
height: txtBtnLabel.height + 2 * 6
|
||||
|
||||
background: Rectangle {
|
||||
color: Style.current.backgroundTertiary
|
||||
radius: 6
|
||||
anchors.fill: parent
|
||||
border.color: Style.current.borderTertiary
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: txtBtnLabel
|
||||
color: Style.current.textColorTertiary
|
||||
font.pixelSize: 12
|
||||
height: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
//% "Paste"
|
||||
text: qsTrId("paste")
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouse
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
parent.clicked()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
import "../imports"
|
||||
|
||||
Item {
|
||||
property string text: "My Text"
|
||||
property string label: "My Label"
|
||||
property string fontFamily: Style.current.fontRegular.name
|
||||
property string textToCopy: ""
|
||||
property alias value: textItem
|
||||
property bool wrap: false
|
||||
|
||||
id: infoText
|
||||
implicitHeight: this.childrenRect.height
|
||||
width: parent.width
|
||||
|
||||
StyledText {
|
||||
id: inputLabel
|
||||
text: infoText.label
|
||||
font.weight: Font.Medium
|
||||
font.pixelSize: 13
|
||||
color: Style.current.secondaryText
|
||||
}
|
||||
|
||||
StyledTextEdit {
|
||||
id: textItem
|
||||
text: infoText.text
|
||||
selectByMouse: true
|
||||
font.family: fontFamily
|
||||
readOnly: true
|
||||
anchors.top: inputLabel.bottom
|
||||
anchors.topMargin: 4
|
||||
font.pixelSize: 15
|
||||
wrapMode: infoText.wrap ? Text.WordWrap : Text.NoWrap
|
||||
anchors.left: parent.left
|
||||
anchors.right: infoText.wrap ? parent.right : undefined
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: !!infoText.textToCopy
|
||||
sourceComponent: copyComponent
|
||||
anchors.verticalCenter: textItem.verticalCenter
|
||||
anchors.left: textItem.right
|
||||
anchors.leftMargin: Style.current.smallPadding
|
||||
}
|
||||
|
||||
Component {
|
||||
id: copyComponent
|
||||
CopyToClipBoardButton {
|
||||
textToCopy: infoText.textToCopy
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*##^##
|
||||
Designer {
|
||||
D{i:0;formeditorColor:"#ffffff";formeditorZoom:1.25}
|
||||
}
|
||||
##^##*/
|
|
@ -0,0 +1,15 @@
|
|||
import QtQuick 2.13
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
function setTimeout(cb, delayTime) {
|
||||
timer.interval = delayTime;
|
||||
timer.repeat = false;
|
||||
timer.triggered.connect(cb);
|
||||
timer.triggered.connect(function release () {
|
||||
timer.triggered.disconnect(cb); // This is important
|
||||
timer.triggered.disconnect(release); // This is important as well
|
||||
});
|
||||
timer.start();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.4697 11.5303C10.7626 11.8232 11.2374 11.8232 11.5303 11.5303C11.8232 11.2374 11.8232 10.7626 11.5303 10.4697L7.41421 6.35355C7.21895 6.15829 7.21895 5.84171 7.41421 5.64645L11.5303 1.53033C11.8232 1.23744 11.8232 0.762564 11.5303 0.46967C11.2374 0.176777 10.7626 0.176777 10.4697 0.46967L6.35355 4.58579C6.15829 4.78105 5.84171 4.78105 5.64645 4.58579L1.53033 0.46967C1.23744 0.176777 0.762563 0.176777 0.46967 0.46967C0.176777 0.762563 0.176777 1.23744 0.46967 1.53033L4.58579 5.64645C4.78105 5.84171 4.78105 6.15829 4.58579 6.35355L0.46967 10.4697C0.176777 10.7626 0.176777 11.2374 0.46967 11.5303C0.762563 11.8232 1.23744 11.8232 1.53033 11.5303L5.64645 7.41421C5.84171 7.21895 6.15829 7.21895 6.35355 7.41421L10.4697 11.5303Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 862 B |
|
@ -0,0 +1,25 @@
|
|||
StyledButton 1.0 StyledButton.qml
|
||||
RoundedIcon 1.0 RoundedIcon.qml
|
||||
ModalPopup 1.0 ModalPopup.qml
|
||||
PopupMenu 1.0 PopupMenu.qml
|
||||
Separator 1.0 Separator.qml
|
||||
StatusTabButton 1.0 StatusTabButton.qml
|
||||
TextWithLabel 1.0 TextWithLabel.qml
|
||||
Input 1.0 Input.qml
|
||||
SearchBox 1.0 SearchBox.qml
|
||||
Select 1.0 Select.qml
|
||||
StyledTextArea 1.0 StyledTextArea.qml
|
||||
StyledText 1.0 StyledText.qml
|
||||
StyledTextField 1.0 StyledTextField.qml
|
||||
StyledTextEdit 1.0 StyledTextEdit.qml
|
||||
Identicon 1.0 Identicon.qml
|
||||
RoundedImage 1.0 RoundedImage.qml
|
||||
SplitViewHandle 1.0 SplitViewHandle.qml
|
||||
CopyToClipBoardButton 1.0 CopyToClipBoardButton.qml
|
||||
NotificationWindow 1.0 NotificationWindow.qml
|
||||
BlockContactConfirmationDialog 1.0 BlockContactConfirmationDialog.qml
|
||||
ConfirmationDialog 1.0 ConfirmationDialog.qml
|
||||
Timer 1.0 Timer.qml
|
||||
TransactionSigner 1.0 TransactionSigner.qml
|
||||
GlossaryEntry 1.0 GlossaryEntry.qml
|
||||
GlossaryLetter 1.0 GlossaryLetter.qml
|
|
@ -0,0 +1,141 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQml 2.14
|
||||
import QtGraphicalEffects 1.13
|
||||
import "../../imports"
|
||||
import "../../shared"
|
||||
import "./core"
|
||||
|
||||
Button {
|
||||
property string type: "primary"
|
||||
property string size: "large"
|
||||
property string state: "default"
|
||||
property color color: type === "warn" ? Style.current.danger : Style.current.buttonForegroundColor
|
||||
property color bgColor: type === "warn" ? Style.current.buttonWarnBackgroundColor : Style.current.buttonBackgroundColor
|
||||
property color borderColor: color
|
||||
property color hoveredBorderColor: color
|
||||
property bool forceBgColorOnHover: false
|
||||
property int borderRadius: Style.current.radius
|
||||
property color bgHoverColor: {
|
||||
if (type === "warn") {
|
||||
if (showBorder) {
|
||||
return Style.current.buttonOutlineHoveredWarnBackgroundColor
|
||||
}
|
||||
return Style.current.buttonHoveredWarnBackgroundColor
|
||||
}
|
||||
return Style.current.buttonBackgroundColorHover
|
||||
}
|
||||
property bool disableColorOverlay: false
|
||||
property bool showBorder: false
|
||||
property int iconRotation: 0
|
||||
|
||||
id: control
|
||||
font.pixelSize: size === "small" ? 13 : 15
|
||||
font.family: Style.current.fontRegular.name
|
||||
font.weight: Font.Medium
|
||||
implicitHeight: flat ? 32 : (size === "small" ? 38 : 44)
|
||||
implicitWidth: buttonLabel.implicitWidth + (flat ? 3* Style.current.halfPadding : 2 * Style.current.padding) +
|
||||
(iconLoader.active ? iconLoader.width : 0)
|
||||
enabled: state === "default"
|
||||
|
||||
contentItem: Item {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Loader {
|
||||
id: iconLoader
|
||||
active: !!control.icon && !!control.icon.source.toString()
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.halfPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
sourceComponent: SVGImage {
|
||||
id: iconImg
|
||||
source: control.icon.source
|
||||
height: control.icon.height
|
||||
width: control.icon.width
|
||||
fillMode: Image.PreserveAspectFit
|
||||
rotation: control.iconRotation
|
||||
|
||||
ColorOverlay {
|
||||
enabled: !control.disableColorOverlay
|
||||
anchors.fill: iconImg
|
||||
source: iconImg
|
||||
color: control.disableColorOverlay ? "transparent" : buttonLabel.color
|
||||
antialiasing: true
|
||||
smooth: true
|
||||
rotation: control.iconRotation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: buttonLabel
|
||||
text: control.text
|
||||
font: control.font
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: iconLoader.active ? undefined : parent.right
|
||||
anchors.left: iconLoader.active ? iconLoader.right : parent.left
|
||||
anchors.leftMargin: iconLoader.active ? Style.current.smallPadding : 0
|
||||
color: {
|
||||
if (!enabled) {
|
||||
return Style.current.buttonDisabledForegroundColor
|
||||
} else if (type !== "warn" && (hovered || highlighted)) {
|
||||
return control.color !== Style.current.buttonForegroundColor ?
|
||||
control.color : Style.current.blue
|
||||
}
|
||||
return control.color
|
||||
}
|
||||
visible: !loadingIndicator.active
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loadingIndicator
|
||||
active: control.state === "pending"
|
||||
sourceComponent: StatusLoadingIndicator {}
|
||||
height: loadingIndicator.visible ?
|
||||
control.size === "large" ?
|
||||
23 : 17
|
||||
: 0
|
||||
width: loadingIndicator.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
radius: borderRadius
|
||||
anchors.fill: parent
|
||||
border.width: flat || showBorder ? 1 : 0
|
||||
border.color: {
|
||||
if (hovered) {
|
||||
return control.hoveredBorderColor !== control.borderColor ? control.hoveredBorderColor : control.borderColor
|
||||
}
|
||||
if (showBorder && enabled) {
|
||||
return control.borderColor
|
||||
}
|
||||
return Style.current.transparent
|
||||
}
|
||||
color: {
|
||||
if (flat) {
|
||||
return hovered && forceBgColorOnHover ? control.bgHoverColor : "transparent"
|
||||
}
|
||||
if (type === "secondary") {
|
||||
return hovered || control.highlighted ? control.bgColor : "transparent"
|
||||
}
|
||||
return !enabled ? (control.bgColor === Style.current.transparent ? control.bgColor : Style.current.buttonDisabledBackgroundColor) :
|
||||
(hovered ? control.bgHoverColor : control.bgColor)
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtGraphicalEffects 1.13
|
||||
import "../../imports"
|
||||
import "../../shared"
|
||||
|
||||
CheckBox {
|
||||
id: control
|
||||
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 18
|
||||
implicitHeight: 18
|
||||
x: control.leftPadding
|
||||
y: parent.height / 2 - height / 2
|
||||
radius: 3
|
||||
color: (control.down || control.checked) ? Style.current.primary : Style.current.inputBackground
|
||||
|
||||
SVGImage {
|
||||
source: "../img/checkmark.svg"
|
||||
width: 16
|
||||
height: 16
|
||||
anchors.centerIn: parent
|
||||
visible: control.down || control.checked
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: StyledText {
|
||||
text: control.text
|
||||
opacity: enabled ? 1.0 : 0.3
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
leftPadding: !!control.text ? control.indicator.width + control.spacing : control.indicator.width
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import QtQuick 2.13
|
||||
import "../../imports"
|
||||
import "../../shared"
|
||||
|
||||
StatusIconButton {
|
||||
id: moreActionsBtn
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
icon.name: "dots-icon"
|
||||
iconColor: Style.current.contextMenuButtonForegroundColor
|
||||
hoveredIconColor: Style.current.contextMenuButtonForegroundColor
|
||||
highlightedBackgroundColor: Style.current.contextMenuButtonBackgroundHoverColor
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQml 2.14
|
||||
import "../../imports"
|
||||
import "../../shared"
|
||||
|
||||
RadioButton {
|
||||
id: control
|
||||
property bool isHovered: false
|
||||
property bool enabled: true
|
||||
width: indicator.implicitWidth
|
||||
|
||||
function getColor() {
|
||||
if (!enabled) {
|
||||
return checked ? Style.current.darkGrey : Style.current.grey
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
return Style.current.blue
|
||||
}
|
||||
if (hovered || isHovered) {
|
||||
return Style.current.secondaryHover
|
||||
}
|
||||
|
||||
return Style.current.grey
|
||||
}
|
||||
|
||||
indicator: Rectangle {
|
||||
implicitWidth: 20
|
||||
implicitHeight: 20
|
||||
x: 0
|
||||
y: 6
|
||||
radius: 10
|
||||
color: control.getColor()
|
||||
|
||||
Rectangle {
|
||||
width: 12
|
||||
height: 12
|
||||
radius: 6
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: control.checked ? Style.current.white : Style.current.grey
|
||||
visible: control.checked
|
||||
}
|
||||
}
|
||||
contentItem: StyledText {
|
||||
text: control.text
|
||||
color: Style.current.textColor
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: !!control.text ? control.indicator.width + control.spacing : control.indicator.width
|
||||
font.pixelSize: 15
|
||||
font.family: Style.current.fontRegular.name
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import "../../imports"
|
||||
import ".."
|
||||
import "."
|
||||
|
||||
Rectangle {
|
||||
property alias text: textElement.text
|
||||
property var buttonGroup
|
||||
property bool checked: false
|
||||
property bool isHovered: false
|
||||
signal radioCheckedChanged(checked: bool)
|
||||
|
||||
id: root
|
||||
height: 52
|
||||
color: isHovered ? Style.current.backgroundHover : Style.current.transparent
|
||||
radius: Style.current.radius
|
||||
border.width: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -Style.current.padding
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: -Style.current.padding
|
||||
|
||||
|
||||
StyledText {
|
||||
id: textElement
|
||||
text: ""
|
||||
font.pixelSize: 15
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.padding
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onEntered: root.isHovered = true
|
||||
onExited: root.isHovered = false
|
||||
onClicked: {
|
||||
radioButton.checked = true
|
||||
}
|
||||
}
|
||||
|
||||
StatusRadioButton {
|
||||
id: radioButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
ButtonGroup.group: root.buttonGroup
|
||||
rightPadding: 0
|
||||
checked: root.checked
|
||||
onCheckedChanged: root.radioCheckedChanged(checked)
|
||||
MouseArea {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
onEntered: root.isHovered = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtGraphicalEffects 1.13
|
||||
import QtQml 2.14
|
||||
import "../../imports"
|
||||
import "../../shared"
|
||||
import "./core"
|
||||
|
||||
RoundButton {
|
||||
property string type: "primary"
|
||||
property string size: "large"
|
||||
property int pressedIconRotation: 0
|
||||
property alias iconX: iconImg.x
|
||||
id: control
|
||||
|
||||
font.pixelSize: 15
|
||||
font.weight: Font.Medium
|
||||
|
||||
implicitWidth: {
|
||||
switch(size) {
|
||||
case "large":
|
||||
return 44
|
||||
case "medium":
|
||||
return 40
|
||||
case "small":
|
||||
return 32
|
||||
default:
|
||||
return 44
|
||||
}
|
||||
}
|
||||
implicitHeight: implicitWidth
|
||||
enabled: state === "default" || state === "pressed"
|
||||
rotation: 0
|
||||
state: "default"
|
||||
states: [
|
||||
State {
|
||||
name: "default"
|
||||
PropertyChanges {
|
||||
target: iconColorOverlay
|
||||
visible: true
|
||||
rotation: 0
|
||||
}
|
||||
PropertyChanges {
|
||||
target: loadingIndicator
|
||||
active: false
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "pressed"
|
||||
PropertyChanges {
|
||||
target: iconColorOverlay
|
||||
rotation: control.pressedIconRotation
|
||||
visible: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: loadingIndicator
|
||||
active: false
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "pending"
|
||||
PropertyChanges {
|
||||
target: loadingIndicator
|
||||
active: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: iconColorOverlay
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "default"
|
||||
to: "pressed"
|
||||
|
||||
RotationAnimation {
|
||||
duration: 150
|
||||
direction: RotationAnimation.Clockwise
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
},
|
||||
|
||||
Transition {
|
||||
from: "pressed"
|
||||
to: "default"
|
||||
RotationAnimation {
|
||||
duration: 150
|
||||
direction: RotationAnimation.Counterclockwise
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
icon.height: {
|
||||
switch(size) {
|
||||
case "large":
|
||||
return 20
|
||||
case "medium":
|
||||
return 14
|
||||
case "small":
|
||||
return 12
|
||||
default:
|
||||
return 20
|
||||
}
|
||||
}
|
||||
icon.width: {
|
||||
switch(size) {
|
||||
case "large":
|
||||
return 20
|
||||
case "medium":
|
||||
return 14
|
||||
case "small":
|
||||
return 12
|
||||
default:
|
||||
return 20
|
||||
}
|
||||
}
|
||||
icon.color: type === "secondary" ?
|
||||
!enabled ?
|
||||
Style.current.roundedButtonSecondaryDisabledForegroundColor :
|
||||
Style.current.roundedButtonSecondaryForegroundColor
|
||||
:
|
||||
!enabled ?
|
||||
Style.current.roundedButtonDisabledForegroundColor :
|
||||
Style.current.roundedButtonForegroundColor
|
||||
|
||||
onIconChanged: {
|
||||
icon.source = icon.name ? "../../app/img/" + icon.name + ".svg" : ""
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
opacity: hovered && size === "large" && type !== "secondary" ? 0.2 : 1
|
||||
color: {
|
||||
if (size === "medium" || size === "small" || type === "secondary") {
|
||||
return !enabled ? Style.current.roundedButtonSecondaryDisabledBackgroundColor :
|
||||
hovered ? (control.type === "warn" ? Style.current.red : Style.current.roundedButtonSecondaryHoveredBackgroundColor) :
|
||||
(control.type === "warn" ? Style.current.lightRed : Style.current.roundedButtonSecondaryBackgroundColor)
|
||||
}
|
||||
return !enabled ?
|
||||
Style.current.roundedButtonDisabledBackgroundColor :
|
||||
hovered ? (control.type === "warn" ? Style.current.red : Style.current.buttonHoveredBackgroundColor) :
|
||||
(control.type === "warn" ? Style.current.lightRed : Style.current.roundedButtonBackgroundColor)
|
||||
}
|
||||
radius: parent.width / 2
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
anchors.fill: parent
|
||||
|
||||
SVGImage {
|
||||
id: iconImg
|
||||
visible: false
|
||||
source: control.icon.source
|
||||
height: control.icon.height
|
||||
width: control.icon.width
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
Component {
|
||||
id: loadingComponent
|
||||
StatusLoadingIndicator {
|
||||
color: control.size === "medium" || control.size === "small" ?
|
||||
Style.current.roundedButtonSecondaryDisabledForegroundColor :
|
||||
Style.current.roundedButtonDisabledForegroundColor
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: loadingIndicator
|
||||
sourceComponent: loadingComponent
|
||||
height: size === "small" ? 14 : 18
|
||||
width: loadingIndicator.height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
ColorOverlay {
|
||||
id: iconColorOverlay
|
||||
anchors.fill: iconImg
|
||||
source: iconImg
|
||||
color: {
|
||||
if (type === "secondary") {
|
||||
return !control.enabled ?
|
||||
Style.current.roundedButtonSecondaryDisabledForegroundColor :
|
||||
(control.type === "warn" ? Style.current.danger : Style.current.roundedButtonSecondaryForegroundColor)
|
||||
}
|
||||
return !control.enabled ?
|
||||
Style.current.roundedButtonDisabledForegroundColor :
|
||||
(control.type === "warn" ? Style.current.danger : Style.current.roundedButtonForegroundColor)
|
||||
}
|
||||
antialiasing: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import "../../imports"
|
||||
import "../../shared"
|
||||
|
||||
StyledText {
|
||||
font.pixelSize: 15
|
||||
color: Style.current.secondaryText
|
||||
anchors.topMargin: 38
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
import QtQuick 2.13
|
||||
import QtGraphicalEffects 1.12
|
||||
import "../../imports"
|
||||
import ".."
|
||||
|
||||
Rectangle {
|
||||
property string text
|
||||
property bool isSwitch: false
|
||||
property bool switchChecked: false
|
||||
property string currentValue
|
||||
property bool isBadge: false
|
||||
property string badgeText: "1"
|
||||
property int badgeRadius: 9
|
||||
property bool isEnabled: true
|
||||
signal clicked(bool checked)
|
||||
property bool isHovered: false
|
||||
property int badgeSize: 18
|
||||
property url iconSource
|
||||
|
||||
id: root
|
||||
implicitHeight: 52
|
||||
color: isHovered ? Style.current.backgroundHover : Style.current.transparent
|
||||
radius: Style.current.radius
|
||||
border.width: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: -Style.current.padding
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: -Style.current.padding
|
||||
|
||||
RoundedIcon {
|
||||
id: pinImage
|
||||
visible: !!root.iconSource.toString()
|
||||
source: root.iconSource
|
||||
iconColor: Style.current.primary
|
||||
color: Style.current.secondaryBackground
|
||||
width: 40
|
||||
height: 40
|
||||
iconWidth: 24
|
||||
iconHeight: 24
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: textItem
|
||||
anchors.left: pinImage.visible ? pinImage.right : parent.left
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.text
|
||||
font.pixelSize: 15
|
||||
color: !root.isEnabled ? Style.current.secondaryText : Style.current.textColor
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: valueText
|
||||
visible: !!root.currentValue
|
||||
text: root.currentValue
|
||||
elide: Text.ElideRight
|
||||
font.pixelSize: 15
|
||||
horizontalAlignment: Text.AlignRight
|
||||
color: Style.current.secondaryText
|
||||
anchors.left: textItem.right
|
||||
anchors.leftMargin: Style.current.padding
|
||||
anchors.right: root.isSwitch ? switchItem.left : caret.left
|
||||
anchors.rightMargin: Style.current.padding
|
||||
anchors.verticalCenter: textItem.verticalCenter
|
||||
|
||||
}
|
||||
|
||||
StatusSwitch {
|
||||
id: switchItem
|
||||
enabled: root.isEnabled
|
||||
visible: root.isSwitch
|
||||
checked: root.switchChecked
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
anchors.verticalCenter: textItem.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: badge
|
||||
visible: root.isBadge & !root.isSwitch
|
||||
anchors.right: root.isSwitch ? switchItem.left : caret.left
|
||||
anchors.rightMargin: Style.current.padding
|
||||
anchors.verticalCenter: textItem.verticalCenter
|
||||
radius: root.badgeRadius
|
||||
color: Style.current.blue
|
||||
width: root.badgeSize
|
||||
height: root.badgeSize
|
||||
Text {
|
||||
font.pixelSize: 12
|
||||
color: Style.current.white
|
||||
anchors.centerIn: parent
|
||||
text: root.badgeText
|
||||
}
|
||||
}
|
||||
|
||||
SVGImage {
|
||||
id: caret
|
||||
visible: !root.isSwitch
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Style.current.padding
|
||||
anchors.verticalCenter: textItem.verticalCenter
|
||||
source: "../../app/img/caret.svg"
|
||||
width: 13
|
||||
height: 7
|
||||
rotation: -90
|
||||
ColorOverlay {
|
||||
anchors.fill: caret
|
||||
source: caret
|
||||
color: Style.current.secondaryText
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.isEnabled
|
||||
hoverEnabled: true
|
||||
onEntered: root.isHovered = true
|
||||
onExited: root.isHovered = false
|
||||
onClicked: {
|
||||
root.clicked(!root.switchChecked)
|
||||
}
|
||||
cursorShape: isEnabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtGraphicalEffects 1.13
|
||||
import "../../imports"
|
||||
import "../../shared"
|
||||
|
||||
Switch {
|
||||
id: control
|
||||
|
||||
indicator: Rectangle {
|
||||
id: oval
|
||||
implicitWidth: 52
|
||||
implicitHeight: 28
|
||||
x: control.leftPadding
|
||||
y: parent.height / 2 - height / 2
|
||||
radius: 14
|
||||
color: control.checked ? Style.current.primary : Style.current.inputBackground
|
||||
|
||||
Rectangle {
|
||||
id: circle
|
||||
y: 4
|
||||
width: 20
|
||||
height: 20
|
||||
radius: 10
|
||||
color: Style.current.white
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
visible: true
|
||||
verticalOffset: 1
|
||||
fast: true
|
||||
cached: true
|
||||
color: "#22000000"
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "on"
|
||||
when: control.checked
|
||||
PropertyChanges { target: circle; x: oval.width - circle.width - 4 }
|
||||
},
|
||||
State {
|
||||
name: "off"
|
||||
when: !control.checked
|
||||
PropertyChanges { target: circle; x: 4 }
|
||||
}
|
||||
]
|
||||
|
||||
transitions: Transition {
|
||||
reversible: true
|
||||
NumberAnimation { properties: "x"; easing.type: Easing.Linear; duration: 120; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: StyledText {
|
||||
text: control.text
|
||||
opacity: enabled ? 1.0 : 0.3
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
leftPadding: !!control.text ? control.indicator.width + control.spacing : control.indicator.width
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import QtQuick 2.13
|
||||
import QtGraphicalEffects 1.13
|
||||
|
||||
Image {
|
||||
property string icon: ""
|
||||
property color color
|
||||
|
||||
id: root
|
||||
width: 24
|
||||
height: 24
|
||||
sourceSize.width: width
|
||||
sourceSize.height: height
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
onIconChanged: {
|
||||
if (icon !== "") {
|
||||
source = "../assets/img/icons/" + icon + ".svg";
|
||||
}
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
visible: root.color !== undefined
|
||||
anchors.fill: root
|
||||
source: root
|
||||
color: root.color
|
||||
antialiasing: true
|
||||
smooth: true
|
||||
rotation: root.rotation
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import QtQuick 2.13
|
||||
import "."
|
||||
|
||||
StatusIcon {
|
||||
id: root
|
||||
icon: "loading"
|
||||
height: 17
|
||||
width: 17
|
||||
RotationAnimator {
|
||||
target: root;
|
||||
from: 0;
|
||||
to: 360;
|
||||
duration: 1200
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
StatusButton 1.0 StatusButton.qml
|
||||
StatusChatCommandButton 1.0 StatusChatCommandButton.qml
|
||||
StatusChatCommandPopup 1.0 StatusChatCommandPopup.qml
|
||||
StatusChatInput 1.0 StatusChatInput.qml
|
||||
StatusCategoryButton 1.0 StatusCategoryButton.qml
|
||||
StatusEmojiPopup 1.0 StatusEmojiPopup.qml
|
||||
StatusEmojiSection 1.0 StatusEmojiSection.qml
|
||||
StatusGifPopup 1.0 StatusGifPopup.qml
|
||||
StatusGifColumn 1.0 StatusGifColumn.qml
|
||||
StatusIconButton 1.0 StatusIconButton.qml
|
||||
StatusImageIdenticon 1.0 StatusImageIdenticon.qml
|
||||
StatusLetterIdenticon 1.0 StatusLetterIdenticon.qml
|
||||
StatusRadioButton 1.0 StatusRadioButton.qml
|
||||
StatusRoundButton 1.0 StatusRoundButton.qml
|
||||
StatusSectionHeadline 1.0 StatusSectionHeadline.qml
|
||||
StatusSectionMenuItem 1.0 StatusSectionMenuItem.qml
|
||||
StatusSlider 1.0 StatusSlider.qml
|
||||
StatusStickerButton 1.0 StatusStickerButton.qml
|
||||
StatusStickerList 1.0 StatusStickerList.qml
|
||||
StatusStickerMarket 1.0 StatusStickerMarket.qml
|
||||
StatusStickerPackDetails 1.0 StatusStickerPackDetails.qml
|
||||
StatusStickerPackPurchaseModal 1.0 StatusStickerPackPurchaseModal.qml
|
||||
StatusStickersPopup 1.0 StatusStickersPopup.qml
|
||||
StatusToolTip 1.0 StatusToolTip.qml
|
|
@ -0,0 +1 @@
|
|||
Subproject commit ab4ba1cbfdccdb8c0398894ffc25169bc61faeed
|
|
@ -1 +1 @@
|
|||
Subproject commit 2b6e50491786ae0d61a97f99edda27b70364838a
|
||||
Subproject commit 5d20a34714d1e4df286eb423e5447adc955bcffa
|
|
@ -1 +1 @@
|
|||
Subproject commit 0e8c7eef73427c78c413637c74ba8c1031fdb20c
|
||||
Subproject commit 83c1e3c84b02665838fd4b3aea91f4e80fc887fb
|