|
|
@@ -0,0 +1,163 @@
|
|
|
+import AppKit
|
|
|
+import SwiftUI
|
|
|
+
|
|
|
+private let formsAccent = Color(red: 0.45, green: 0.28, blue: 0.72)
|
|
|
+private let formsPanelBg = Color(red: 0.09, green: 0.08, blue: 0.14)
|
|
|
+
|
|
|
+/// Large Google Forms dashboard: find + create shortcuts (opens real Forms URLs in-app).
|
|
|
+struct FormsDesktopWidgetView: View {
|
|
|
+ let app: LauncherApp
|
|
|
+ let mode: FormsWidgetMode
|
|
|
+ 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.22, green: 0.14, blue: 0.38),
|
|
|
+ formsPanelBg,
|
|
|
+ ],
|
|
|
+ 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 Forms")
|
|
|
+ .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: { openFormsHome() }) {
|
|
|
+ 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 Forms")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private var findFieldRow: some View {
|
|
|
+ HStack(spacing: 8) {
|
|
|
+ Image(systemName: "magnifyingglass")
|
|
|
+ .font(.system(size: 13, weight: .semibold))
|
|
|
+ .foregroundStyle(.white.opacity(0.5))
|
|
|
+ TextField("Find forms", 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(formsAccent)
|
|
|
+ .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: "Form", systemImage: "doc.badge.plus", urlString: "https://docs.google.com/forms/create")
|
|
|
+ createPill(title: "Quiz", systemImage: "checkmark.circle.fill", urlString: "https://docs.google.com/forms/u/0/create")
|
|
|
+ createPill(title: "Survey", systemImage: "chart.bar.doc.horizontal", urlString: "https://docs.google.com/forms/u/0/create?usp=forms_home")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func createPill(title: String, systemImage: String, urlString: 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)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func sectionLabel(_ text: String) -> some View {
|
|
|
+ Text(text)
|
|
|
+ .font(.system(size: 10, weight: .bold))
|
|
|
+ .foregroundStyle(.white.opacity(0.45))
|
|
|
+ .textCase(.uppercase)
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Forms list and search live on `docs.google.com` (not Drive). `q` is passed to the Forms home search.
|
|
|
+ private func submitFind() {
|
|
|
+ NSApp.activate(ignoringOtherApps: true)
|
|
|
+ let q = findText.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
+ var c = URLComponents(string: "https://docs.google.com/forms/u/0/")!
|
|
|
+ if !q.isEmpty {
|
|
|
+ c.queryItems = [URLQueryItem(name: "q", value: q)]
|
|
|
+ }
|
|
|
+ guard let url = c.url else { return }
|
|
|
+ openInApp(url)
|
|
|
+ }
|
|
|
+
|
|
|
+ private func openFormsHome() {
|
|
|
+ NSApp.activate(ignoringOtherApps: true)
|
|
|
+ guard let url = URL(string: "https://docs.google.com/forms/u/0/") 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)
|
|
|
+ }
|
|
|
+}
|