mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into extensions-api
# Conflicts: # package.json # src/common/cluster-store.ts # src/renderer/bootstrap.tsx # src/renderer/components/cluster-manager/clusters-menu.tsx # static/RELEASE_NOTES.md # webpack.dll.ts
This commit is contained in:
commit
a3dc928430
34 files changed
+273
-139
No files matched your search
@@ -5,7 +5,7 @@ import { isMac } from "../common/vars";
|
||||
import { userStore } from "../common/user-store";
|
||||
import { workspaceStore } from "../common/workspace-store";
|
||||
import { extensionStore } from "../extensions/extension-store";
|
||||
import { clusterStore, getHostedClusterId } from "../common/cluster-store";
|
||||
import { clusterStore } from "../common/cluster-store";
|
||||
import { i18nStore } from "./i18n";
|
||||
import { themeStore } from "./theme.store";
|
||||
import { App } from "./components/app";
|
||||
@@ -39,4 +39,4 @@ export async function bootstrap(App: AppComponent) {
|
||||
}
|
||||
|
||||
// run
|
||||
bootstrap(getHostedClusterId() ? App : LensApp);
|
||||
bootstrap(process.isMainFrame ? LensApp : App);
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { observable, autorun } from "mobx";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { Input } from "../../input";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
@@ -11,7 +11,15 @@ interface Props {
|
||||
|
||||
@observer
|
||||
export class ClusterHomeDirSetting extends React.Component<Props> {
|
||||
@observable directory = this.props.cluster.preferences.terminalCWD || "";
|
||||
@observable directory = "";
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this,
|
||||
autorun(() => {
|
||||
this.directory = this.props.cluster.preferences.terminalCWD || "";
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
save = () => {
|
||||
this.props.cluster.preferences.terminalCWD = this.directory;
|
||||
|
||||
@@ -28,7 +28,7 @@ export class ClusterIconSetting extends React.Component<Props> {
|
||||
try {
|
||||
if (file) {
|
||||
const buf = Buffer.from(await file.arrayBuffer());
|
||||
cluster.preferences.icon = `data:${file.type};base64, ${buf.toString('base64')}`;
|
||||
cluster.preferences.icon = `data:${file.type};base64,${buf.toString('base64')}`;
|
||||
} else {
|
||||
// this has to be done as a seperate branch (and not always) because `cluster`
|
||||
// is observable and triggers an update loop.
|
||||
@@ -73,4 +73,4 @@ export class ClusterIconSetting extends React.Component<Props> {
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { Input } from "../../input";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { observable, autorun } from "mobx";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { isRequired } from "../../input/input.validators";
|
||||
|
||||
@@ -12,7 +12,15 @@ interface Props {
|
||||
|
||||
@observer
|
||||
export class ClusterNameSetting extends React.Component<Props> {
|
||||
@observable name = this.props.cluster.preferences.clusterName || "";
|
||||
@observable name = "";
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this,
|
||||
autorun(() => {
|
||||
this.name = this.props.cluster.preferences.clusterName;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
save = () => {
|
||||
this.props.cluster.preferences.clusterName = this.name;
|
||||
|
||||
+18
-10
@@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { prometheusProviders } from "../../../../common/prometheus-providers";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { SubTitle } from "../../layout/sub-title";
|
||||
import { Select, SelectOption } from "../../select";
|
||||
import { Input } from "../../input";
|
||||
import { observable, computed } from "mobx";
|
||||
import { observable, computed, autorun } from "mobx";
|
||||
|
||||
const options: SelectOption<string>[] = [
|
||||
{ value: "", label: "Auto detect" },
|
||||
@@ -27,14 +27,22 @@ export class ClusterPrometheusSetting extends React.Component<Props> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { prometheus, prometheusProvider } = this.props.cluster.preferences;
|
||||
if (prometheus) {
|
||||
const prefix = prometheus.prefix || "";
|
||||
this.path = `${prometheus.namespace}/${prometheus.service}:${prometheus.port}${prefix}`;
|
||||
}
|
||||
if (prometheusProvider) {
|
||||
this.provider = prometheusProvider.type;
|
||||
}
|
||||
disposeOnUnmount(this,
|
||||
autorun(() => {
|
||||
const { prometheus, prometheusProvider } = this.props.cluster.preferences;
|
||||
if (prometheus) {
|
||||
const prefix = prometheus.prefix || "";
|
||||
this.path = `${prometheus.namespace}/${prometheus.service}:${prometheus.port}${prefix}`;
|
||||
} else {
|
||||
this.path = "";
|
||||
}
|
||||
if (prometheusProvider) {
|
||||
this.provider = prometheusProvider.type;
|
||||
} else {
|
||||
this.provider = "";
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
parsePrometheusPath = () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { observable, autorun } from "mobx";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { Cluster } from "../../../../main/cluster";
|
||||
import { Input } from "../../input";
|
||||
import { isUrl } from "../../input/input.validators";
|
||||
@@ -12,7 +12,15 @@ interface Props {
|
||||
|
||||
@observer
|
||||
export class ClusterProxySetting extends React.Component<Props> {
|
||||
@observable proxy = this.props.cluster.preferences.httpsProxy || "";
|
||||
@observable proxy = "";
|
||||
|
||||
componentDidMount() {
|
||||
disposeOnUnmount(this,
|
||||
autorun(() => {
|
||||
this.proxy = this.props.cluster.preferences.httpsProxy || "";
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
save = () => {
|
||||
this.props.cluster.preferences.httpsProxy = this.proxy;
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Icon } from "../icon";
|
||||
import { Input } from "../input";
|
||||
import { cssNames, prevDefault } from "../../utils";
|
||||
import { Button } from "../button";
|
||||
import { isRequired, Validator } from "../input/input.validators";
|
||||
|
||||
@observer
|
||||
export class Workspaces extends React.Component {
|
||||
@@ -41,9 +42,9 @@ export class Workspaces extends React.Component {
|
||||
|
||||
saveWorkspace = (id: WorkspaceId) => {
|
||||
const draft = toJS(this.editingWorkspaces.get(id));
|
||||
if (draft) {
|
||||
const workspace = workspaceStore.saveWorkspace(draft);
|
||||
if (workspace) {
|
||||
this.clearEditing(id);
|
||||
workspaceStore.saveWorkspace(draft);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +91,15 @@ export class Workspaces extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
onInputKeypress = (evt: React.KeyboardEvent<any>, workspaceId: WorkspaceId) => {
|
||||
if (evt.key == 'Enter') {
|
||||
// Trigget input validation
|
||||
evt.currentTarget.blur();
|
||||
evt.currentTarget.focus();
|
||||
this.saveWorkspace(workspaceId);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<WizardLayout className="Workspaces" infoPanel={this.renderInfo()}>
|
||||
@@ -102,11 +112,15 @@ export class Workspaces extends React.Component {
|
||||
const isDefault = workspaceStore.isDefault(workspaceId);
|
||||
const isEditing = this.editingWorkspaces.has(workspaceId);
|
||||
const editingWorkspace = this.editingWorkspaces.get(workspaceId);
|
||||
const className = cssNames("workspace flex gaps align-center", {
|
||||
const className = cssNames("workspace flex gaps", {
|
||||
active: isActive,
|
||||
editing: isEditing,
|
||||
default: isDefault,
|
||||
});
|
||||
const existenceValidator: Validator = {
|
||||
message: () => `Workspace '${name}' already exists`,
|
||||
validate: value => !workspaceStore.getByName(value.trim())
|
||||
}
|
||||
return (
|
||||
<div key={workspaceId} className={className}>
|
||||
{!isEditing && (
|
||||
@@ -139,23 +153,27 @@ export class Workspaces extends React.Component {
|
||||
placeholder={_i18n._(t`Name`)}
|
||||
value={editingWorkspace.name}
|
||||
onChange={v => editingWorkspace.name = v}
|
||||
onKeyPress={(e) => this.onInputKeypress(e, workspaceId)}
|
||||
validators={[isRequired, existenceValidator]}
|
||||
autoFocus
|
||||
/>
|
||||
<Input
|
||||
className="description"
|
||||
placeholder={_i18n._(t`Description`)}
|
||||
value={editingWorkspace.description}
|
||||
onChange={v => editingWorkspace.description = v}
|
||||
/>
|
||||
<Icon
|
||||
material="cancel"
|
||||
tooltip={<Trans>Cancel</Trans>}
|
||||
onClick={() => this.clearEditing(workspaceId)}
|
||||
onKeyPress={(e) => this.onInputKeypress(e, workspaceId)}
|
||||
/>
|
||||
<Icon
|
||||
material="save"
|
||||
tooltip={<Trans>Save</Trans>}
|
||||
onClick={() => this.saveWorkspace(workspaceId)}
|
||||
/>
|
||||
<Icon
|
||||
material="cancel"
|
||||
tooltip={<Trans>Cancel</Trans>}
|
||||
onClick={() => this.clearEditing(workspaceId)}
|
||||
/>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -44,8 +44,8 @@ export class App extends React.Component {
|
||||
const clusterId = getHostedClusterId();
|
||||
logger.info(`[APP]: Init dashboard, clusterId=${clusterId}, frameId=${frameId}`)
|
||||
await Terminal.preloadFonts()
|
||||
await clusterIpc.initView.invokeFromRenderer(clusterId, frameId);
|
||||
await getHostedCluster().whenInitialized;
|
||||
await clusterIpc.activate.invokeFromRenderer(clusterId, frameId);
|
||||
await getHostedCluster().whenReady; // cluster.refresh() is done at this point
|
||||
}
|
||||
|
||||
get startURL() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "./cluster-manager.scss"
|
||||
import React from "react";
|
||||
import { Redirect, Route, Switch } from "react-router";
|
||||
import { reaction } from "mobx";
|
||||
import { comparer, reaction } from "mobx";
|
||||
import { disposeOnUnmount, observer } from "mobx-react";
|
||||
import { ClustersMenu } from "./clusters-menu";
|
||||
import { BottomBar } from "./bottom-bar";
|
||||
@@ -25,11 +25,14 @@ export class ClusterManager extends React.Component {
|
||||
fireImmediately: true
|
||||
}),
|
||||
reaction(() => [
|
||||
getMatchedClusterId(), // refresh when active cluster-view changed
|
||||
hasLoadedView(getMatchedClusterId()), // refresh when cluster's webview loaded
|
||||
getMatchedCluster()?.available, // refresh on disconnect active-cluster
|
||||
getMatchedCluster()?.ready, // refresh when cluster ready-state change
|
||||
], refreshViews, {
|
||||
fireImmediately: true
|
||||
})
|
||||
fireImmediately: true,
|
||||
equals: comparer.shallow,
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export class ClusterStatus extends React.Component<Props> {
|
||||
error: res.error,
|
||||
});
|
||||
})
|
||||
if (!this.cluster.initialized || this.cluster.disconnected) {
|
||||
if (this.cluster.disconnected) {
|
||||
await this.refreshCluster();
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ export class ClusterStatus extends React.Component<Props> {
|
||||
if (!hasErrors || this.isReconnecting) {
|
||||
return (
|
||||
<>
|
||||
<CubeSpinner />
|
||||
<CubeSpinner/>
|
||||
<pre className="kube-auth-out">
|
||||
<p>{this.isReconnecting ? "Reconnecting..." : "Connecting..."}</p>
|
||||
{authOutput.map(({ data, error }, index) => {
|
||||
@@ -75,7 +75,7 @@ export class ClusterStatus extends React.Component<Props> {
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Icon material="cloud_off" className="error" />
|
||||
<Icon material="cloud_off" className="error"/>
|
||||
<h2>
|
||||
{cluster.preferences.clusterName}
|
||||
</h2>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { reaction } from "mobx";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { matchPath, RouteProps } from "react-router";
|
||||
import { buildURL, navigation } from "../../navigation";
|
||||
import { clusterStore, getHostedClusterId } from "../../../common/cluster-store";
|
||||
import { clusterStore } from "../../../common/cluster-store";
|
||||
import { clusterSettingsRoute } from "../+cluster-settings/cluster-settings.route";
|
||||
|
||||
export interface IClusterViewRouteParams {
|
||||
@@ -34,11 +34,10 @@ export function getMatchedCluster() {
|
||||
}
|
||||
|
||||
if (ipcRenderer) {
|
||||
// Refresh global menu depending on active route's type (common/cluster view)
|
||||
const isMainView = !getHostedClusterId();
|
||||
if (isMainView) {
|
||||
if (process.isMainFrame) {
|
||||
// Keep track of active cluster-id for handling IPC/menus/etc.
|
||||
reaction(() => getMatchedClusterId(), clusterId => {
|
||||
ipcRenderer.send("cluster-view:change", clusterId);
|
||||
ipcRenderer.send("cluster-view:current-id", clusterId);
|
||||
}, {
|
||||
fireImmediately: true
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observable, when } from "mobx";
|
||||
import { ClusterId, clusterStore } from "../../../common/cluster-store";
|
||||
import { ClusterId, clusterStore, getClusterFrameUrl } from "../../../common/cluster-store";
|
||||
import { getMatchedCluster } from "./cluster-view.route"
|
||||
import logger from "../../../main/logger";
|
||||
|
||||
@@ -21,29 +21,38 @@ export async function initView(clusterId: ClusterId) {
|
||||
}
|
||||
logger.info(`[LENS-VIEW]: init dashboard, clusterId=${clusterId}`)
|
||||
const cluster = clusterStore.getById(clusterId);
|
||||
await cluster.whenReady;
|
||||
const parentElem = document.getElementById("lens-views");
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.name = cluster.contextName;
|
||||
iframe.setAttribute("src", `//${clusterId}.${location.host}`)
|
||||
iframe.addEventListener("load", async () => {
|
||||
iframe.setAttribute("src", getClusterFrameUrl(clusterId))
|
||||
iframe.addEventListener("load", () => {
|
||||
logger.info(`[LENS-VIEW]: loaded from ${iframe.src}`)
|
||||
lensViews.get(clusterId).isLoaded = true;
|
||||
})
|
||||
}, { once: true });
|
||||
lensViews.set(clusterId, { clusterId, view: iframe });
|
||||
parentElem.appendChild(iframe);
|
||||
// auto-clean when cluster removed
|
||||
await autoCleanOnRemove(clusterId, iframe);
|
||||
}
|
||||
|
||||
export async function autoCleanOnRemove(clusterId: ClusterId, iframe: HTMLIFrameElement) {
|
||||
await when(() => !clusterStore.getById(clusterId));
|
||||
logger.info(`[LENS-VIEW]: remove dashboard, clusterId=${clusterId}`)
|
||||
parentElem.removeChild(iframe)
|
||||
lensViews.delete(clusterId)
|
||||
|
||||
// Keep frame in DOM to avoid possible bugs when same cluster re-created after being removed.
|
||||
// In that case for some reasons `webFrame.routingId` returns some previous frameId (usage in app.tsx)
|
||||
// Issue: https://github.com/lensapp/lens/issues/811
|
||||
iframe.dataset.meta = `${iframe.name} was removed at ${new Date().toLocaleString()}`;
|
||||
iframe.removeAttribute("src")
|
||||
iframe.removeAttribute("name")
|
||||
}
|
||||
|
||||
export function refreshViews() {
|
||||
const cluster = getMatchedCluster();
|
||||
lensViews.forEach(({ clusterId, view, isLoaded }) => {
|
||||
const isVisible = cluster && cluster.available && cluster.id === clusterId;
|
||||
view.style.display = isLoaded && isVisible ? "flex" : "none"
|
||||
const isCurrent = clusterId === cluster?.id;
|
||||
const isReady = cluster?.available && cluster?.ready;
|
||||
const isVisible = isCurrent && isLoaded && isReady;
|
||||
view.style.display = isVisible ? "flex" : "none"
|
||||
})
|
||||
}
|
||||
@@ -92,7 +92,8 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
|
||||
this.props.onOpen();
|
||||
if (!this.props.pinned) {
|
||||
if (this.elem) this.elem.addEventListener('click', this.onClickOutside);
|
||||
window.addEventListener('keydown', this.onEscapeKey);
|
||||
// Using document.body target to handle keydown event before Drawer does
|
||||
document.body.addEventListener('keydown', this.onEscapeKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +101,7 @@ export class Dialog extends React.PureComponent<DialogProps, DialogState> {
|
||||
this.props.onClose();
|
||||
if (!this.props.pinned) {
|
||||
if (this.elem) this.elem.removeEventListener('click', this.onClickOutside);
|
||||
window.removeEventListener('keydown', this.onEscapeKey);
|
||||
document.body.removeEventListener('keydown', this.onEscapeKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ export class Drawer extends React.Component<DrawerProps> {
|
||||
});
|
||||
|
||||
componentDidMount() {
|
||||
// Using window target for events to make sure they will be catched after other places (e.g. Dialog)
|
||||
window.addEventListener("mousedown", this.onMouseDown)
|
||||
window.addEventListener("click", this.onClickOutside)
|
||||
window.addEventListener("keydown", this.onEscapeKey)
|
||||
|
||||
@@ -1 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><defs><style>.cls-2{fill:#3c90ce;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-2" d="M256,496C123.67,496,16,388.33,16,256S123.67,16,256,16,496,123.67,496,256,388.33,496,256,496Zm0-470C129.17,26,26,129.18,26,256S129.17,486,256,486,486,382.82,486,256,382.83,26,256,26Z"/><path class="cls-2" d="M403.22,282l65.66-81.68A220.52,220.52,0,0,0,351.61,57.81Z"/><path class="cls-2" d="M476,256a220.86,220.86,0,0,0-4-41.5L334.74,385.3H434A219,219,0,0,0,476,256Z"/><path class="cls-2" d="M247.63,121.17,139.38,69.48A220,220,0,0,0,38.81,221Z"/><path class="cls-2" d="M140.75,184.81,37.07,234.35C36.38,241.48,36,248.69,36,256A219.13,219.13,0,0,0,91,401.4Z"/><path class="cls-2" d="M210.57,396.65l63.2,78.57a219.52,219.52,0,0,0,151.38-78.57Z"/><path class="cls-2" d="M124.21,307.38l-23.91,104A219.33,219.33,0,0,0,256,476c1.26,0,2.5-.07,3.76-.1Z"/><path class="cls-2" d="M364.52,164.42,338.67,52.12A220.21,220.21,0,0,0,151.16,62.54Z"/><path class="cls-2" d="M351.71,276.74c-.46-.11-1.12-.29-1.58-.37a45.28,45.28,0,0,0-5.2-.42,50.71,50.71,0,0,1-9.73-1.6,6.13,6.13,0,0,1-2.35-2.36l-2.19-.63a70.52,70.52,0,0,0-11.31-48.85l1.93-1.73a4.3,4.3,0,0,1,1-3.08,50.88,50.88,0,0,1,8.07-5.67,43.85,43.85,0,0,0,4.51-2.63c.35-.26.83-.67,1.2-1,2.6-2.08,3.2-5.66,1.33-8s-5.49-2.57-8.09-.49c-.38.29-.88.67-1.21,1a43.42,43.42,0,0,0-3.58,3.79,51.27,51.27,0,0,1-7.32,6.63,6.1,6.1,0,0,1-3.3.36l-2.06,1.47a71.09,71.09,0,0,0-45.06-21.77c-.05-.72-.11-2-.12-2.42a4.33,4.33,0,0,1-1.78-2.72,50.16,50.16,0,0,1,.62-9.84,45.29,45.29,0,0,0,.74-5.17c0-.43,0-1.07,0-1.54,0-3.33-2.43-6-5.43-6s-5.43,2.7-5.43,6c0,.05,0,.1,0,.15,0,.45,0,1,0,1.39a45.29,45.29,0,0,0,.74,5.17,50.87,50.87,0,0,1,.61,9.84,5.9,5.9,0,0,1-1.77,2.81l-.12,2.29a71.38,71.38,0,0,0-9.82,1.51,69.83,69.83,0,0,0-35.46,20.26c-.6-.41-1.65-1.16-2-1.39a4.32,4.32,0,0,1-3.23-.31,50.46,50.46,0,0,1-7.31-6.61,45,45,0,0,0-3.58-3.79c-.33-.29-.83-.67-1.2-1a6.42,6.42,0,0,0-3.78-1.42,5.22,5.22,0,0,0-4.33,1.91c-1.87,2.34-1.27,5.93,1.33,8l.09.06c.35.29.79.66,1.12.91a46.81,46.81,0,0,0,4.5,2.63,49.61,49.61,0,0,1,8.07,5.67,6,6,0,0,1,1.09,3.13l1.74,1.55a70.25,70.25,0,0,0-11.07,49l-2.28.66a7.61,7.61,0,0,1-2.33,2.36,51.5,51.5,0,0,1-9.73,1.6,45.38,45.38,0,0,0-5.21.41c-.41.08-1,.23-1.45.34h0l-.08,0c-3.2.78-5.26,3.72-4.6,6.61a5.75,5.75,0,0,0,7,4h.08l.1,0c.45-.1,1-.21,1.41-.32a45.65,45.65,0,0,0,4.87-1.87,51.49,51.49,0,0,1,9.46-2.78,6.07,6.07,0,0,1,3.12,1.1l2.37-.4A70.59,70.59,0,0,0,225,322.21l-1,2.37a5.46,5.46,0,0,1,.48,3.07,53.5,53.5,0,0,1-4.92,8.83,46,46,0,0,0-2.91,4.33c-.21.41-.48,1-.69,1.47a5.73,5.73,0,0,0,2.31,7.71c2.69,1.3,6-.07,7.49-3.06v0h0c.21-.42.5-1,.67-1.38a45,45,0,0,0,1.57-5c1.43-3.61,2.22-7.4,4.2-9.75a4.31,4.31,0,0,1,2.34-1.14l1.23-2.23a70.15,70.15,0,0,0,40.78,2.93,71.11,71.11,0,0,0,9.31-2.8l1.16,2.09a4.26,4.26,0,0,1,2.77,1.68,50.16,50.16,0,0,1,3.72,9.12,44.6,44.6,0,0,0,1.58,5c.18.4.47,1,.67,1.39,1.45,3,4.81,4.38,7.51,3.07s3.7-4.72,2.3-7.71c-.2-.43-.49-1.05-.7-1.46A45.8,45.8,0,0,0,302,336.4a51.18,51.18,0,0,1-4.82-8.62,4.27,4.27,0,0,1,.42-3.2,20.73,20.73,0,0,1-.9-2.19A70.66,70.66,0,0,0,328,283c.7.11,1.92.33,2.32.41a4.28,4.28,0,0,1,3-1.13,50.68,50.68,0,0,1,9.47,2.79,45.66,45.66,0,0,0,4.87,1.88c.39.1,1,.2,1.4.3l.11,0h.08a5.72,5.72,0,0,0,7-4C357,280.45,354.91,277.51,351.71,276.74Zm-51.57-55.29-23.2,16.45-.08,0a4.79,4.79,0,0,1-6.56-.88,4.71,4.71,0,0,1-1.05-2.77h0l-1.61-28.43A56.44,56.44,0,0,1,300.14,221.45Zm-43.7,31.16h8.73l5.43,6.79-2,8.46-7.84,3.77L253,267.85,251,259.39Zm-8.22-45.84a56.58,56.58,0,0,1,5.79-1l-1.61,28.47-.12.06a4.79,4.79,0,0,1-7.59,3.67l-.05,0-23.35-16.55A55.92,55.92,0,0,1,248.22,206.77Zm-35.39,25.3,21.32,19.07,0,.12a4.79,4.79,0,0,1-1.88,8.22l0,.09-27.32,7.89A55.83,55.83,0,0,1,212.83,232.07Zm28.54,50.65L230.51,309A56.27,56.27,0,0,1,208,280.73L236,276l0,.06A4.37,4.37,0,0,1,237,276a4.78,4.78,0,0,1,4.33,6.67Zm32,33.74a56.07,56.07,0,0,1-30.62-1.58L256.56,290h0a4.77,4.77,0,0,1,4-2.53A4.86,4.86,0,0,1,265,290h.1l13.82,25Q276.24,315.82,273.41,316.46Zm17.81-7.4-11-26.5,0-.05a4.78,4.78,0,0,1,2.32-6.2,4.65,4.65,0,0,1,1.83-.48,5.05,5.05,0,0,1,1.1.08l.05-.05,28.26,4.77A56,56,0,0,1,291.22,309.06Zm25.59-41.69-27.46-7.91,0-.12a4.79,4.79,0,0,1-3.4-5.68,4.7,4.7,0,0,1,1.52-2.54v-.06l21.2-19a56.92,56.92,0,0,1,8.17,35.28Z"/></g></g></svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.rect{fill:none!important;}
|
||||
</style>
|
||||
<rect class="rect" width="512" height="512"/>
|
||||
<g>
|
||||
<path d="M237,496h184.8l-42.6-181.7L237,496z"/>
|
||||
<path d="M16,392.3V496h194.2l81.2-103.7H16z"/>
|
||||
<path d="M280.1,130.3L496,235.4v-209L280.1,130.3z"/>
|
||||
<path d="M443.6,496H496V258.8l-122-59.4L443.6,496z"/>
|
||||
<path d="M469,16H200.1l-32.4,145L469,16z"/>
|
||||
<path d="M16,170v201.1h160L16,170z"/>
|
||||
<path d="M178.5,16H16v120.1l105.9,133.1L178.5,16z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 730 B |
@@ -40,18 +40,19 @@
|
||||
div.logo-text {
|
||||
position: absolute;
|
||||
left: 42px;
|
||||
top: 11.5px;
|
||||
top: 11px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-left: 2px;
|
||||
margin-top: 3px;
|
||||
margin-top: 2px;
|
||||
margin-right: 10px;
|
||||
|
||||
svg {
|
||||
--size: 28px;
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user