|
|
@@ -0,0 +1,163 @@
|
|
|
+import AppKit
|
|
|
+import SwiftUI
|
|
|
+
|
|
|
+private let docsAccent = Color(red: 0.10, green: 0.52, blue: 0.96)
|
|
|
+private let docsPanelBg = Color(red: 0.07, green: 0.09, blue: 0.14)
|
|
|
+
|
|
|
+private enum DocsCreateURL {
|
|
|
+ static let newDoc = "https://docs.new"
|
|
|
+ static let templates = "https://docs.google.com/templates?type=docs"
|
|
|
+ static let homeList = "https://docs.google.com/document/u/0/"
|
|
|
+}
|
|
|
+
|
|
|
+/// Large Google Docs dashboard: find + create shortcuts (opens real Docs URLs in-app).
|
|
|
+struct DocsDesktopWidgetView: View {
|
|
|
+ let app: LauncherApp
|
|
|
+ let mode: DocsWidgetMode
|
|
|
+ var isPreview: Bool = false
|
|
|
+
|
|
|
+ @State private var findText: String = ""
|
|
|
+
|
|
|
+ var body: some View {
|
|
|
+ Group {
|
|
|
+ switch mode {
|
|
|
+ case .dashboard:
|
|
|
+ dashboardBody
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
|
|
|
+ }
|
|
|
+
|
|
|
+ private var dashboardBody: some View {
|
|
|
+ ZStack(alignment: .topLeading) {
|
|
|
+ LinearGradient(
|
|
|
+ colors: [
|
|
|
+ Color(red: 0.08, green: 0.22, blue: 0.42),
|
|
|
+ docsPanelBg,
|
|
|
+ ],
|
|
|
+ startPoint: .topLeading,
|
|
|
+ endPoint: .bottomTrailing
|
|
|
+ )
|
|
|
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
+
|
|
|
+ VStack(alignment: .leading, spacing: 12) {
|
|
|
+ headerRow
|
|
|
+ findFieldRow
|
|
|
+ createRow
|
|
|
+ Spacer(minLength: 0)
|
|
|
+ }
|
|
|
+ .padding(14)
|
|
|
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
|
+ }
|
|
|
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
|
+ .disabled(isPreview)
|
|
|
+ .opacity(isPreview ? 0.88 : 1)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var headerRow: some View {
|
|
|
+ HStack(spacing: 8) {
|
|
|
+ AppIconView(app: app, size: 30, showAppBackground: false, iconPaddingFactor: 0.08)
|
|
|
+ VStack(alignment: .leading, spacing: 2) {
|
|
|
+ Text("Google Docs")
|
|
|
+ .font(.system(size: 14, weight: .bold))
|
|
|
+ .foregroundStyle(.white.opacity(0.95))
|
|
|
+ Text("Dashboard")
|
|
|
+ .font(.system(size: 10, weight: .semibold))
|
|
|
+ .foregroundStyle(.white.opacity(0.55))
|
|
|
+ }
|
|
|
+ Spacer(minLength: 0)
|
|
|
+ Button(action: { openAbsolute(DocsCreateURL.homeList) }) {
|
|
|
+ Image(systemName: "arrow.up.right.square")
|
|
|
+ .font(.system(size: 14, weight: .semibold))
|
|
|
+ .foregroundStyle(.white.opacity(0.9))
|
|
|
+ .padding(8)
|
|
|
+ .background(Circle().fill(Color.white.opacity(0.12)))
|
|
|
+ }
|
|
|
+ .buttonStyle(.plain)
|
|
|
+ .help("Open Google Docs")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private var findFieldRow: some View {
|
|
|
+ HStack(spacing: 8) {
|
|
|
+ Image(systemName: "magnifyingglass")
|
|
|
+ .font(.system(size: 13, weight: .semibold))
|
|
|
+ .foregroundStyle(.white.opacity(0.5))
|
|
|
+ TextField("Find documents", text: $findText)
|
|
|
+ .textFieldStyle(.plain)
|
|
|
+ .font(.system(size: 13, weight: .medium))
|
|
|
+ .foregroundStyle(.white.opacity(0.95))
|
|
|
+ .onSubmit { submitFind() }
|
|
|
+ Button("Go", action: submitFind)
|
|
|
+ .font(.system(size: 11, weight: .bold))
|
|
|
+ .buttonStyle(.borderedProminent)
|
|
|
+ .tint(docsAccent)
|
|
|
+ .controlSize(.small)
|
|
|
+ }
|
|
|
+ .padding(.horizontal, 10)
|
|
|
+ .padding(.vertical, 8)
|
|
|
+ .background(RoundedRectangle(cornerRadius: 12, style: .continuous).fill(Color.white.opacity(0.1)))
|
|
|
+ }
|
|
|
+
|
|
|
+ private var createRow: some View {
|
|
|
+ VStack(alignment: .leading, spacing: 6) {
|
|
|
+ sectionLabel("Create")
|
|
|
+ HStack(spacing: 6) {
|
|
|
+ createPill(title: "Doc", systemImage: "doc.badge.plus", urlString: DocsCreateURL.newDoc, help: "New blank document (docs.new)")
|
|
|
+ createPill(title: "Templates", systemImage: "square.grid.2x2", urlString: DocsCreateURL.templates, help: "Document templates")
|
|
|
+ createPill(title: "Browse", systemImage: "folder", urlString: DocsCreateURL.homeList, help: "All documents")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func createPill(title: String, systemImage: String, urlString: String, help: String) -> some View {
|
|
|
+ Button {
|
|
|
+ openAbsolute(urlString)
|
|
|
+ } label: {
|
|
|
+ VStack(spacing: 4) {
|
|
|
+ Image(systemName: systemImage)
|
|
|
+ .font(.system(size: 16, weight: .semibold))
|
|
|
+ Text(title)
|
|
|
+ .font(.system(size: 10, weight: .bold))
|
|
|
+ }
|
|
|
+ .foregroundStyle(.white.opacity(0.92))
|
|
|
+ .frame(maxWidth: .infinity)
|
|
|
+ .padding(.vertical, 10)
|
|
|
+ .background(RoundedRectangle(cornerRadius: 12, style: .continuous).fill(Color.white.opacity(0.1)))
|
|
|
+ .overlay(
|
|
|
+ RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
|
+ .strokeBorder(Color.white.opacity(0.08), lineWidth: 1)
|
|
|
+ )
|
|
|
+ }
|
|
|
+ .buttonStyle(.plain)
|
|
|
+ .help(help)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func sectionLabel(_ text: String) -> some View {
|
|
|
+ Text(text)
|
|
|
+ .font(.system(size: 10, weight: .bold))
|
|
|
+ .foregroundStyle(.white.opacity(0.45))
|
|
|
+ .textCase(.uppercase)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func submitFind() {
|
|
|
+ NSApp.activate(ignoringOtherApps: true)
|
|
|
+ let q = findText.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
+ var c = URLComponents(string: DocsCreateURL.homeList)!
|
|
|
+ if !q.isEmpty {
|
|
|
+ c.queryItems = [URLQueryItem(name: "q", value: q)]
|
|
|
+ }
|
|
|
+ guard let url = c.url else { return }
|
|
|
+ openInApp(url)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func openAbsolute(_ urlString: String) {
|
|
|
+ guard let url = URL(string: urlString) else { return }
|
|
|
+ NSApp.activate(ignoringOtherApps: true)
|
|
|
+ openInApp(url)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func openInApp(_ url: URL) {
|
|
|
+ InAppBrowserWindowManager.shared.open(url: url, title: app.name)
|
|
|
+ }
|
|
|
+}
|